2. gyakorlat¶
Warning
Figyelem! A héten kiadásra kerül az első beadandó! Bővebb információ CooSpace-en.
A gyakorlat anyaga¶
Ismétlés - *args és **kwargs¶
A tetszőleges számú felsorolt, illetve a tetszőleges számú nevesített paraméterek megadására szolgálhatnak az egy- vagy kétcsillagos paraméterek Pythonban.
Az alapprobléma, hogy egy függvénynek tetszőleges számú paramétert szeretnénk átadni. Egyik megoldás lehet erre a listák használata, ám akkor egy listában kell átadni az argumentumokat (tehát egy darab argumentum lesz, ami lista típusú).
def add_simple(a, b, c):
return a + b + c
def add_args_default(a, b=0, c=0):
return a + b + c
def add_list(simple_list):
return sum(simple_list)
Ezek megoldására használhatjuk az egy- vagy kétcsillagos paramétereket. Egy csillag esetén a paraméter tetszőleges számú, de csak felsorolt paramétert tartalmazhat, ez esetben a függvényen belül tuple-ként használhatjuk, két csillag esetén pedig dictként használhatjuk a paramétert a függvény törzsén belül.
def add_args(*args):
print("type:", type(args))
return sum(args)
def card(**data):
print("type:", type(data))
for key, value in data.items():
# print("{} is {}".format(key, value))
print(f"{key.title()}: {value}")
A teljes példaprogram, függvényhívással:
def add_simple(a, b, c):
return a + b + c
# def add_simple(a: int, b: int, c: int) -> int:
# return a + b + c
def add_list(simple_list):
return sum(simple_list)
# def add_list(simple_list: list) -> int:
# return sum(simple_list)
def add_args_default(a, b=0, c=0):
return a + b + c
def add_args(*args):
print("type:", type(args))
return sum(args)
def card(**data):
print("type:", type(data))
for key, value in data.items():
# print("{} is {}".format(key, value))
print(f"{key.title()}: {value}")
if __name__ == '__main__':
print("Simple add")
print(add_simple(1, 2, 4))
print(add_simple(1, 2.5, 4))
print("Add (list)")
# print(add_list(1, 2, 3))
print(add_list([1, 2, 3]))
print("Add (simple, default)")
print(add_args(1))
print(add_args(1, 2))
print(add_args(1, 2, 3))
print("Add (*args)")
print(add_args(1, 2, 3, 4, 5))
card(name="John Rambo", phone="+0036705554433", address="N/A", email="info@rambo.com")
Öröklődés Pythonban¶
Egyszerű öröklődés¶
- object öröklődés
- propertyk (ismétlés)
- ős inicializálása
- isinstance
- issubclass
class Cake(object):
def __init__(self, slices=10, flavour='chocolate'):
self._slices = slices
self._flavour = flavour
@property
def slices(self):
print('# Accessed to Cake#_slices via slices property')
return self._slices
@slices.setter
def slices(self, new_slices):
if new_slices > 0:
print('# New number of slices is ok, updated')
self._slices = new_slices
else:
print('# Cannot update number of slices, invalid value was', new_slices)
def __str__(self):
return '[Cake, slices=%d, flavour=%s]' % (self._slices, self._flavour)
# Egyszeru oroklodes, oroklunk ugyan, de se nem egeszitjuk ki, se nem veszunk
# fel uj metodusokat, adattagokat. Van ez igy...
class RottenCake(Cake):
pass
class BirthdayCake(Cake):
def __init__(self, slices=10, flavour='chocolate', years=20):
# os konstruktoranak inicializalasa
# https://docs.python.org/3/library/functions.html#super
# https://stackoverflow.com/a/33469090/5738367
# super(BirthdayCake, self).__init__(slices, flavour)
# Python 3 felett megtehetjuk csak igy:
super().__init__(slices, flavour)
self.years = years
def __str__(self): # override
return 'BirthdayCake[years=%d, %s]' % (
self.years, super(BirthdayCake, self).__str__())
def main():
choco = Cake(slices=16)
print(choco)
print(choco.slices) # hozzaferes a propertyhez
choco.slices = 10 # valtoztatunk a propertyn keresztul
print(choco)
choco.slices = -10 # invalid modositas a property erteken
print(choco)
print()
rotten = RottenCake(flavour='bad', slices=8)
print('Rotten cake', rotten)
del rotten # torolhetjuk is oket, elvesztjuk a referenciat
# print(rotten) # hibas sor a fenti torles miatt
print()
banana = BirthdayCake(flavour='banana', years=24)
print(banana)
print('birthday cake is cake?', isinstance(banana, Cake))
print('type of banana is subclass of Cake?', issubclass(type(banana), Cake))
banana.slices = -10 # oroklodes
if __name__ == '__main__':
main()
Method Resolution Order¶
class A:
def __init__(self):
print('A')
super().__init__()
class B(A):
def __init__(self):
print('B')
super().__init__()
class X:
def __init__(self):
print('X')
super().__init__()
class Forward(B, X):
def __init__(self):
print('Forward')
super().__init__()
class Backward(X, B):
def __init__(self):
print('Backward')
super().__init__()
if __name__ == '__main__':
print("Create Backward")
Backward()
print()
print("Create Forward")
Forward()
Többszörös öröklődés¶
- MRO + super
- Ős metódusának hívása (azonos nevű függvények?)
- kwargs
class Rectangle:
def __init__(self, length, width, **kwargs):
print("> Rectangle init STARTED")
self.length = length
self.width = width
super().__init__(**kwargs) # Comment this
print("! Rectangle init finished (no base)")
def area(self):
print("Rectangle area called")
return self.length * self.width
def perimeter(self):
return 2 * self.length + 2 * self.width
class Square(Rectangle):
def __init__(self, length, **kwargs):
print("> Square init STARTED")
super().__init__(length=length, width=length, **kwargs)
print("Square init finished")
class Triangle:
def __init__(self, base, height, **kwargs):
print("> Triangle init STARTED")
self.base = base
self.height = height
# super().__init__(**kwargs)
print("! Triangle init finished (no base)")
def triangle_area(self):
return 0.5 * self.base * self.height
# def area(self):
# print("Triangle area called")
# return 0.5 * self.base * self.height
class Pyramid(Square, Triangle):
def __init__(self, base, slant_height, **kwargs):
self.base = base
self.slant_height = slant_height
kwargs["height"] = slant_height
kwargs["length"] = base
super().__init__(base=base, **kwargs)
print("RightPyramid init finished")
def area(self):
base_area = super().area()
# triangle_area = Triangle.area(self)
triangle_area = super().triangle_area()
return triangle_area * 4 + base_area
def print_mro(self):
print(Pyramid.__mro__)
if __name__ == '__main__':
pyramid = Pyramid(10, 10)
print(pyramid.area())
pyramid.print_mro()
Statikus és osztály-specifikus¶
Az osztályon belül deklarált változók alapvetően statikus változók lesznek (tehát minden példány egy tényleges adaton osztozik).
class Car(object):
wheels = 4 # Minden autonak negy kereke van
_count = 0
def __init__(self, brand='Toyota', model='Supra 1978'):
self.brand = brand # objektum valtozo
self.model = model # objektum valtozo
Car._count += 1 # hivatkozas statikus valtozora
A statikus változókon túl használhatunk statikus metódusokat is, azonban Pythonban kétféle "statikus" metódus is létezik, mindegyiket dekorátor segítségével érhetünk el.
- A hagyományos statikus metódus. Ez más nyelvekből is ismert lehet, eléggé egyszerű eset: itt nincs
self
paraméter, amilyen paramétereket beírunk a metódus fejlécébe, azokkal a paraméterekkel is kell meghívni a metódust. A metódus fejléce felé@staticmethod
dekorátort kell helyeznünk.
class Car(object):
@staticmethod
def print_count(placeholder='Number of cars: %d'):
print(placeholder % Car._count)
- Class method készítése. Ebben az esetben a függvényünk kap egy explicit első paramétert, ami bármilyen nevű lehet, de általában itt
cls
nevet használunk. Az osztálymetódusok első paraméterként nem az objektumot kapják meg, hanem az aktuális osztályt. Ez azért lehet jó, mert leszármazott osztály esetén a leszármazott osztályt kapjuk meg paraméterként.
class Car(object):
@classmethod
def print_number_of_cars(cls, placeholder='Number of cars: %d'):
print(placeholder % Car._count)
print('Class of method:', str(cls.__name__))
Ez alapvetően elsőre buta ötletnek tűnhet, ám gyakran jól jöhet, amikor egy statikus (pontosabban class) metódus példányosítja az adott osztályt.
Ennek a megoldásnak egy nagyon gyakori alkalmazási módszere az alábbi: van egy __init__
függvényünk, ami várhat például két szöveget paraméterként, mindkettőnek van alapértelmezett értéke.
Mi pedig szeretnénk inicializálni az osztályunkat egy paraméterrel (ami például egy JSON-ből érkező dict
vagy éppen CSV-beli sor.
Ebben az esetben az eredeti inicializáló függvényünkbe vagy nagyon komplex, spagetti logikát készítünk, vagy pedig készítünk egy másik konstruktor függvényt.
Ez utóbbira Pythonban nincs lehetőség, de például van lehetőségünk egy alkalmas class method használatával, ami az öröklődési hierarchiával is működhet (amennyiben jól csináljuk meg).
Az ilyen metódusok gyakran from_primitive
, from_json
, és hasonló néven szerepelnek a kódban, alternatív konstruktorként is gondolhatunk rájuk.
Bővebben: https://stackoverflow.com/a/1950927/5738367
@classmethod
def from_primitive(cls, primitive_str):
# kapunk egy szoveget, amiben a kocsi markaja es tipusa el van valasztva
# mondjuk pontosvesszovel, es abbol akarunk peldanyt letrehozni.
splitted = primitive_str.split(';')
if len(splitted) == 2:
# Letrehozunk egy peldanyt
instance = cls()
instance.brand = splitted[0]
instance.model = splitted[1]
return instance
else:
print('Nem sikerult jol megadni; itt egy auto')
return cls()
Ez az öröklődési láncban is működni fog, leszármazott típus esetén olyan típusú objektum jön létre, nem pedig ős.
Természetesen a feldolgozás, és flexibilitás megoldása már a programozó dolga, de egy jól megírt, korábban látott **kwargs
paraméterezésű inicializáló függvény segíthet.
class Car(object):
# Minden olyan valtozo, ami osztaly szinten van megosztva
# Azaz az adott osztaly minden peldanyara egy es ugyanaz valtozo lesz
# A peldanyok osztoznak rajta
wheels = 4 # Minden autonak negy kereke van
_count = 0
def __init__(self, brand='Toyota', model='Supra 1978'):
self.brand = brand
self.model = model
Car._count += 1
# Itt nincs elso self parameter, sem semmi egyeb. Amit parameternek kiirom
# Azt varom, nincs trukkozes, staightforward.
@staticmethod
def print_count(placeholder='Number of cars: %d'):
print(placeholder % Car._count)
# Hasonlo, mint a staticmethod, de kicsit megis mas lesz
# A classmethod megkapja implicit modon az osztalyt, mint parametert
# Persze hasznalhatjuk ugy, ahogy eddig, de a trukkozes majd mindjart jon
@classmethod
def print_number_of_cars(cls, placeholder='Number of cars: %d'):
print(placeholder % Car._count)
print('Class of method:', str(cls.__name__))
# A trukk pedig a kovetkezo: mivel a konstruktorunk ket szoveget var
# ezert, ha egy szoveggel hivom meg, azt fogja hinni, hogy az az auto tipusa
# pedig siman lehet, hogy nem. Es itt jon a classmethod segitsegul
# Jellemzoen alternativ konstruktorokra szoktuk oket hasznalni
# Na azt hogy? Hat igy
# https://stackoverflow.com/a/1950927/5738367
@classmethod
def from_primitive(cls, primitive_str):
# kapunk egy szoveget, amiben a kocsi markaja es tipusa el van valasztva
# mondjuk pontosvesszovel, es abbol akarunk peldanyt letrehozni.
splitted = primitive_str.split(';')
if len(splitted) == 2:
# Letrehozunk egy peldanyt
instance = cls()
instance.brand = splitted[0]
instance.model = splitted[1]
return instance
else:
print('Nem sikerult jol megadni; itt egy auto')
return cls()
class FlyingCar(Car):
pass
def main():
lada = Car('Lada', '2107')
niva = Car('Lada', 'Niva')
supra = Car()
print('Lada 2107 wheels:', lada.wheels)
print('Lada Niva wheels:', niva.wheels)
print('Supra wheels:', supra.wheels)
print('Car class wheel attribute', Car.wheels)
print("---")
niva.wheels = 8 # Csak a peldanyban szereplo ertek valtozik meg
print('-- Niva has 8 wheels now')
print('Lada 2107 wheels:', lada.wheels)
print('Lada Niva wheels:', niva.wheels)
print('Supra wheels:', supra.wheels)
print('Car class wheel attribute', Car.wheels)
print("---")
Car.wheels = 7
print('-- Every car has 7 wheels now')
print('Lada 2107 wheels:', lada.wheels)
print('Lada Niva wheels:', niva.wheels) # Except Niva
print('Supra wheels:', supra.wheels)
print('Car class wheel attribute', Car.wheels)
print("---")
# Ez a metodus sokkal inkabb az osztalyhoz tartozik, mintsem
# egyes peldanyokhoz, hiszen a peldanyok adatai nem szuksegesek
# a fuggveny futtatasahoz, csak minden peldanyban elerheto osztalyvaltozot
# hasznal es a fuggveny parametereit.
lada.print_count() # ne
niva.print_count() # ne
Car.print_count('We have %d cars.')
lada.print_number_of_cars()
flying = FlyingCar()
flying.print_number_of_cars()
corolla = Car.from_primitive('Toyota;Corolla 2005')
print(type(corolla))
print('Added model:', corolla.model)
flying_corolla = FlyingCar.from_primitive('Flyota;Corolla 2400')
print(type(flying_corolla))
print('Added model:', flying_corolla.model)
if __name__ == '__main__':
main()
Statikus adattagok - vagy mi?¶
Akkor most mi történt a fenti kódban?
Létrehoztunk egy Car
osztályt, ami tartalmazza a wheels
és _count
statikus adattagokat.
A main
függvényben létrehoztunk egy lada
nevű, és egy niva
nevű objektumot, ez látható az alábbi képen.
Mindkét esetben a statikus adattagok az osztálybeli adatokra mutatnak.
Ezt követően, ha módosítanánk a Car
osztály wheels
statikus adattagját, módosulna az érték, és ha példányon keresztül is érnénk el, a módosított értéket látnánk.
Azonban ha módosítjuk ezt az értéket egy példányon (niva
) keresztül, akkor az nem módosítja az autóban deklarált adattagot, hanem csak és kizárólag a niva
példányhoz tartozóan módosul az érték (kvázi létrejön egy példányváltozó).
Ha ezt követően módosítjuk a Car
adattagját, az már nem lesz kihatással a niva
objektum wheels
adattagjára, ami így "átkonvertálódott" osztályszintű adattagból objektum szintűvé.
Absztrakt osztályok, metódusok¶
- importálnunk kell az abc modult (vagy annak részeit)
- abc = Abstract Base Classes
Absztrakt osztály létrehozása¶
from abc import ABC, abstractmethod
# Az ital egy altalanos fogalom, de az a baj, hogy ugyanugy letre lehet hozni,
# mint barmely mas osztalyt. Viszont a price() metodusnak igy nincs ertelme,
# es nem is lenne jo, ha letre lehetne hozni egy italt.
# Tegyuk absztraktta!
# Ehhez a Python ABC modulja fog kelleni. ABC = Abstract Base Class
# Python 3.4+ verziokban eleg csak az ABC osztalybol szarmazni, korabbi
# valtozatokban viszont be kell allitani az osztaly metaclassjat az ABCMetara
# Python 3.0+
# class Drink(metaclass=ABCMeta):
# https://stackoverflow.com/a/13646263/5738367
class Drink(ABC):
def __init__(self, dl=0.0):
"""
Drink osztály inicializáló függvénye.
:param dl: az ital űrtartalma, deciliterben.
"""
self.dl = dl
@abstractmethod
def price(self):
"""
:return: price of the drink
"""
pass
# Ez egy megvalositott metodus, amit nem kotelezo feluldefinialni.
def drink(self):
print(f"We drank {type(self).__name__}!")
self.dl = 0.0
class CarbonatedDrink(Drink):
def __init__(self, dl=0.0, bubbles=100):
super(CarbonatedDrink, self).__init__(dl)
self.bubbles = bubbles
def __str__(self):
return 'CarbonatedDrink [dl=%f, bubbles=%d]' % (self.dl, self.bubbles)
def price(self):
return self.dl * 50 + ((100 - self.bubbles) / 5)
def main():
# x = Drink()
# print('Price of x', x.price())
sth = CarbonatedDrink(3, 100)
print(sth)
print(sth.price())
sth.drink()
if __name__ == '__main__':
main()
import abc
class Drink(abc.ABC):
def __init__(self, dl=0.0):
self.dl = dl
# @abc.abstractproperty
# def price(self):
# """
# :return: price of the drink
# """
# pass
@property
@abc.abstractmethod
def price(self):
"""
:return: price of the drink
"""
pass
class CarbonatedDrink(Drink):
def __init__(self, dl=0.0, bubbles=100):
super(CarbonatedDrink, self).__init__(dl)
self.bubbles = bubbles
def __str__(self):
return 'CarbonatedDrink [dl=%f, bubbles=%d]' % (self.dl, self.bubbles)
@property
def price(self):
return self.dl * 50 + ((100 - self.bubbles) / 5)
def main():
sth = CarbonatedDrink(3, 100)
print(sth)
print(sth.price)
if __name__ == '__main__':
main()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from abc import ABC, abstractmethod
class Drink(ABC):
def __init__(self, dl=0.0):
self.dl = dl
@abstractmethod
def price(self):
"""
:return: price of the drink
"""
pass
class Wine(Drink):
def __init__(self, dl=0.0, winery='Danko', alcohol=0.12):
super().__init__(dl=dl)
self.winery = winery
self.alcohol = alcohol
print('- Wine constructor')
def price(self):
return self.dl * 250
def __str__(self):
return 'Wine[dl=%f, winery=%s, alcohol=%f]' % (self.dl,
self.winery,
self.alcohol)
class Cola(Drink):
def __init__(self, dl=0.0, brand='Coca Cola', sugar_free=False):
super().__init__(dl=dl)
self.brand = brand
self.sugar_free = sugar_free
print('- Cola constructor')
def price(self):
return self.dl * 70
def __str__(self):
return "Cola[brand=%s, dl=%f, sugar_free=%s" % (self.brand,
self.dl,
self.sugar_free)
# Diamond problem, MRO
# http://www.python-course.eu/python3_multiple_inheritance.php
# https://stackoverflow.com/a/26927718/5738367
# https://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
class MagicPotion(Cola, Wine):
def __init__(self, dl=1.0, brand='?', winery='?', sfree=False):
half = dl/2.0
Wine.__init__(self, dl=half, winery=winery, alcohol=0.08)
Cola.__init__(self, dl=half, brand=brand, sugar_free=sfree)
def __str__(self):
return "Magic Potion[cola=%s, wine=%s]" % (Cola.__str__(self),
Wine.__str__(self))
def price(self):
return 2*self.dl*125
def main():
redwine = Wine(winery='Kekfrankos', alcohol=0.11)
print(redwine)
cc = Cola(dl=4)
print(cc)
print('----')
potion = MagicPotion(5)
print(potion)
print(potion.price())
if __name__ == '__main__':
main()
Órai feladatok¶
Megjegyzés
Az órai feladatok alatt lévő feladat(sor)ok azok a feladat(sor)ok, amelyek megoldása beleszámít a zh javításért megoldható feladatok közé. Ezen feladatokból 6 darab megoldása szükséges ahhoz, hogy egy zh javíthatóvá váljon.
Kártyajáték - alapok¶
Készítsük el egy kártyajáték alapjait.
Készítsd el a Kártya
osztályt, aminek van színe (pikk, kőr, káró, treff), és van értéke (2-10, J, Q, K, A)
Valósítsd meg a __str__
függvényt, ami a kártyát szövegként reprezentálja a színével és számával (pl.: "káró 10").
Készíts hozzá egy inicializáló függvényt is.
Készítsd el a Pakli
osztályt. Készíts konstruktort, ami egy új paklit készít, mind a négy szín összes lehetséges értékével.
A paklik is legyenek szöveggé alakíthatóak, mégpedig a pakliban jelenleg benne lévő kártya számával reprezentálja a paklit.
Legyen egy keverés
metódus, ami bekeveri a paklit, ha mind az 52 kártya benne van még, különben dobjon ValueError
típusú kivételt "Megkezdett pakli nem keverhető" szöveggel.
A keveréshez használd a random
modul shuffle
metódusát.
A metódushívás lehessen láncolható (paklim.keveres().keveres()
).
Készíts egy húzás
metódust, ami egy tetszőleges kártyát visszaad és kivesz a pakliból.
Amennyiben 0 kártya van a pakliban, dobjunk ValueError
típusú kivételt egy tetszőleges szöveggel.
Házi feladatok¶
-
Valósítsd meg a színek és értékek lehetőségeit
Enum
segítségével. Bővebben az Enumokról: https://docs.python.org/3/library/enum.html -
Valósíts meg egy tetszőleges kártyajátékot (pl.: Black jack), Poker, stb.