Lekcja 10 - programowanie obiektowe (notatki)

Lekcja 10 - programowanie obiektowe (notatki)

Tkinter (cd.)

Frame (czyli ramka)

Komponent, którego nie widzimy - jego główną funkcją jest zgrupowanie elementów ze sobą. Wpisujemy go jako rodzica konkretnych elementów, to umożliwia nam dokonanie zmian na większej ilości komponentów oraz wybieranie pomiędzy rozpakowywaniem packiem a gridem (w obrębie jednego rodzica używa się tylko jednego sposobu).

import tkiner as tk

okno = tk.Tk()
okno.title("Jakis tytuł")

ramka = tk.Frame(okno, padx = 10, pady=10)
#padding - odstępy pomiędzy komponentami
ramka.pack()

przycisk = tk.Button(ramka, text="To przycisk")
przycisk.pack()

ramka2 = tk.Frame(okno, padx = 10, pady=10)
ramka2.pack()
przycisk2 = tk.Button(ramka2, text="Poza ramką")
przycisk2.grid(row=0, column=0)

okno.mainloop()

Pack

Wygodny sposób rozmieszczania półautomatycznego, można podać dodatkowe parametry:

Metoda fill rozciąga dany element na szerokość jego rodzica.

okno.geometry("800x800")

pole_tekstowe = tk.Entry(ramka, width=100)
pole_tekstowe.pack()

przycisk = tk.Button(okno, text="To przycisk")
przycisk.pack(fill="x")

Metoda side przenosi komponent w odpowiednie miejsce: góra (top), dół (bottom), lewo (left) czy prawo (right).

okno.geometry("800x800")

przycisk = tk.Button(okno, text="To przycisk")
przycisk.pack(side="bottom")

Grid

Ułatwia rozmieszczenie komponentów gdy efekt jaki chcemy osiągnąć jest bardziej skomplikowany. Metoda ta w pewien sposób nakłada "niewidzialną siatkę" na okno i umożliwia wstawienie danego komponentu do odpowiedniej kratki lub kratek.

etykieta1 = tk.Label(ramka, text="etykieta1")
etykieta1.grid(row=0, column=0)

wprowadz1 = tk.Entry(ramka, width=20)
wprowadz1.grid(row=0, column=1)

etykietaxx = tk.Label(ramka, text="etykietaXX")
etykietaxx.grid(row=0, column=2)

etykieta2 = tk.Label(ramka, text="etykieta2")
etykieta2.grid(row=1, column=0)

wprowadz2 = tk.Entry(ramka, width=20)
wprowadz2.grid(row=1, column=1)

etykieta3 = tk.Label(ramka, text="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
etykieta3.grid(row=0, column=0, columnspan=3)
#columnspan - dodatkowy parametr nakazujący zając 3 komórki. Odpowiednikiem scalania komórek wzdłuż kolumny jest rowspan.

wprowadz3 = tk.Entry(ramka, width=20)
wprowadz3.grid(row=3, column=1)

CustomTkinter

Zmodyfikowana biblioteka tkintera, która umożliwia nadanie naszej aplikacji bardziej nowoczesnego wyglądu.

import tkinter as tk
import qrcode
import customtkinter as ctk
from PIL import ImageTk

def generuj_qr():
    global qkod, obrazek
    dane = pole_tekstowe.get()
    qrkod = qrcode.make(dane)
    obrazek = ImageTk.PhotoImage(qrkod)
    pic_label.configure(image=obrazek)

ctk.set_appearance_mode("light")

okno = ctk.CTk()
okno.title("QR Code Maker")
okno.configure(padx=50, pady=50)

etykieta = ctk.CTkLabel(okno, text="Podaj linka: ")
etykieta.pack()

pole_tekstowe = ctk.CTkEntry(okno, width=50)
pole_tekstowe.pack()

przycisk = ctk.CTkButton(okno, text="OK", command=generuj_qr)
przycisk.pack()

pic_label = ctk.CTkLabel(okno, text="")
pic_label.pack()

okno.mainloop()

Programowanie obiektowe

Funkcje - czasowniki

Własne funkcje deklarujemy za pomocą def, według wzoru:

def nasza_nazwa_funkcji (parametry dla funkcji)
  #blok kodu
  return zwracana_wartość

Nie każda funkcja zwróci nam jakąś wartość.

Dobrze w przypadku parametrów funkcji podać jakiego typu elementu się tam spodziewamy tj. stringa, liczby całkowitej itd. Choć nie ma takiego obowiązku lepiej to robić ponieważ eliminuje to dużą część błędów (program zasygnalizuje błąd, gdy wprowadzone później dane są innego typu). Mile widziane jest też podawanie co zwróci dana funkcja (po strzałeczce), nawet jeśli nic nie zwróci (wtedy podajemy "None").

def podnies_do_szescianu(liczba:int)-> int:
  szescian = liczba * liczba * liczba
  return szescian

dwa_do_szesc = podnies_do_szescianu(2)

print(dwa_do_szesc)

Nazwy zamknięte w funkcji są niezależne od innych elementów w aplikacji, dlatego nawet jeśli nazwy zmiennych się powtarzają to się nie nadpiszą:

tekst = "Alice"

def drukuj_3razy(tekst:str):
  print(tekst*3)

drukuj_3razy("Bob")

Wydrukuje się: BobBobBob

Podobnie w tym wypadku:

zmienna1 = 1
zmienna2 = 2
zmienna3 = 55
def drukuj_cos(zmienna3)
  zmienna3 =+ 1

print(zmienna3)

Wydrukuje się 55

Jeśli chcemy wewnątrz funkcji skorzystać ze zmiennej spoza funkcji, można przekazać ją jako parametr:

ksiazka_kontaktowa = {"Stefan" : "discord:stefox", "Ala" : "234567890"}

def drukuj_kontakty(kontakty:dict):
    for imie, kontakt in kontakty.items():
        print(f"Imię:    {imie}")
        print(f"Dane kontaktowe: {kontakt}")
        print("""
              *****************
            """)

drukuj_kontakty(ksiazka_kontaktowa)

lub wciągnąć ją jako globalną zmienną:

def generuj_qr():
    global obrazek
    dane = pole_url.get()
    qrkod = qrcode.make(dane)
    obrazek = ImageTk.PhotoImage(qrkod)
    pic_label.config(image=obrazek)

Odradza się stosowanie tej metody.


Do bardziej przydatnych własnych funkcji należy czyszczenie ekranu, zgodne ze wzorem:

from os import system, name

def czysc_ekran() -> None:
  if name == "nt": #sprawdza na jakim programie działa użytkownik
    _ = system("cls") #jeśli na windowsie, wykonuje polecenie cls
  else:
    _ = system("clear") #jeśli na macu itd. clear

Klasy obiektów

Klasa to ogólny opis cech lub czynności, które dany obiekt posiada lub wykonuje, dzięki czemu można zaklasyfikować go do jakiejś wspólnej grupy. Na przykład jeśli spotykamy obiekt, który:

#Ma zespół atrybutów:
łapy: 4
ogon: 1
maja_pazury: True
ostre_zeby: True

#wykonuje czynności:
miauczy
mruczy

Możemy przyjąć, że należy do klasy kotów 😉

Do obiektów należących do jednej klasy, można zastosować jeden sposób postępowania, w ten sposób eliminuje się powtarzanie i ułatwia się pracę w zespołach (przypisanie jednej osoby do pracy nad konkretną klasą).

Deklarowanie klasy

Deklarowanie klasy przebiega zgodnie z pewnym schematem. Wpierw po słowie class podajemy nazwę klasy, zgodnie ze standardami powinien być to rzeczownik zaczynający się od dużej litery, na końcu zaś pusty nawias okrągły.

menu = {"sałatka" : 12.5, "sok" : 8.1, "burito" : 25.2,}

class Zamowienie():

Teraz trzeba zastosować funkcję, która jest wykonywana podczas tworzenia obiektu. Zgodnie ze wzorem def __init__ (parametry). Init - umożliwia przypisanie obiektowi jego indywidualnych cech. Pamiętaj, przed i po init podwójna podłoga!!! Następnie w kolejnych linijkach po wcięciu podajemy parametry, które mamy zastosować - poprzedzając je self - umożliwiające stworzenie cech indywidualnych danego obiektu:

  def __init__(self, stolik: int, potrawy:list) - > None:
    self.nr_stolika = stolik
    self.potrawy = potrawy
    self.czy_oplacone = False

W powyższym przykładzie cechami Zamówienia są numer stolika, jakie potrawy przyniesiono i czy zapłacono za posiłek.

Ponadto Zamówienie powiązane jest też z pewną funkcjonalnością w tym wypadku obliczaniem rachunku oraz przyjmowaniem zapłaty stąd kolejne funkcje przypisane do tej klasy. Takie funkcje wbudowane w daną klasę to metody, powinny one korzystać z indywidualnych cech obiektu (stąd self).

  def oblicz_rachunek(self) -> float:
    rachunek = 0
    for potrawa in self.potrawy:
      rachunek += menu[potrawa]
    return rachunek
    
  def przyjmij_oplate(self):
    self.czy_oplacone = True

Teraz już mogę wykonywać metody przypisane do tej konkretnej klasy:

zam1 = Zamowienie(1, ["sałatka", "burito"])
print(zam1.oblicz_rachunek())
print(zam1.potrawy)

Uwaga! Jeśli nie zastosujemy w metodzie self, sugeruje to, że daną metodę lepiej z klasy wyjąć.

Ćwiczenie

W ten sposób można stworzyć aplikację do gry RPG, gdzie klasą będą postacie, posiadające zestaw cech: imię, profesję, punkty życia, magii i siły.

class PostacGracza():
  def __init__(self, imie:str, profesja:str, hp:int = 100, mana:int =100, sila:int) -> None:
    self.imie = imie
    self.profesja = profesja
    self.hp = hp #punkty życia, jeśli użytkownik nie poda inaczej będzie miał 100.
    self.mana = mana #punkty magii
    self.sila = sila #punkty siły

Można przetestować działanie:

gracz1 = PostacGracza(imie="Fizban", profesja="mag", mana=200, sila=10,)

print(f"{gracz1.imie} - pkt. życia: {gracz1.hp}")
print(f"{gracz1.imie} - siła magii: {gracz1.mana}")
print(f"{gracz1.imie} - profesja: {gracz1.profesja}")

gracz2 = PostacGracza(imie="Taz", profesja="łotrzyk", sila=15,)

print(f"{gracz2.imie} - pkt. życia: {gracz2.hp}")
print(f"{gracz2.imie} - siła magii: {gracz2.mana}")
print(f"{gracz2.imie} - profesja: {gracz2.profesja}")

Stwórz metody przypisane do klasy. które wyświetlają powitanie dla danego gracza, liczą ilość obrażeń jakie dana postać może zadać w walce wręcz i odejmującą punkty życia:

def powitaj(self):
  print(f"Witaj {self.imie}.")
def zadaj_obrazenia(self) -> int:
  return self.sila * 2
def otrzymaj_obrazenia(self, ilosc_obrazen:int)
  self.hp-=ilosc_obrazen

Zadanie domowe

class Wojownik():
    def __init__(self, imie:str, hp:int = 200, mana:int = 100, sila:int = 30, zrecznosc:int = 100, wytrzymalosc:int =30,) -> None:
        self.imie = imie
        self.hp = hp
        self.mana = mana
        self.sila = sila
        self.zrecznosc = zrecznosc
        self.wytrzymalosc = wytrzymalosc

    def paruj_ciosy(self):
        print("Sparowano cios. Zredukowano obrażenia")
    
    def wyswietl_karte_postaci(self):
        print(f"""-----------------
            Karta postaci
            -----------------------
            IMIĘ {self.imie}
            RASA/KLAN/TYP  Wojownik
            -----------------------
            SIŁA         {self.sila}
            ZRĘCZNOŚĆ    {self.zrecznosc}
            WYTRZYMAŁOŚĆ {self.wytrzymalosc}
            ŻYWOTNOŚĆ    {self.hp}
            MAGIA        {self.mana}
            """)


gracz1 = Wojownik(imie="Boreasz")
gracz1.wyswietl_karte_postaci()


class Mag():
    def __init__(self, imie:str, hp:int = 200, mana:int = 400, sila:int = 10, zrecznosc:int = 100, wytrzymalosc:int =10,) -> None:
        self.imie = imie
        self.hp = hp
        self.mana = mana
        self.sila = sila
        self.zrecznosc = zrecznosc
        self.wytrzymalosc = wytrzymalosc

    def rzuc_kule_ognia(self):
        print("Rzucono kulę ognia")

    def wyswietl_karte_postaci(self):
        print(f"""-----------------
            Karta postaci
            -----------------------
            IMIĘ {self.imie}
            RASA/KLAN/TYP  Mag
            -----------------------
            SIŁA         {self.sila}
            ZRĘCZNOŚĆ    {self.zrecznosc}
            WYTRZYMAŁOŚĆ {self.wytrzymalosc}
            ŻYWOTNOŚĆ    {self.hp}
            MAGIA        {self.mana}
            """)

gracz2 = Mag(imie="Merlin")
gracz2.wyswietl_karte_postaci()