Kihagyás

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.

  1. 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)
  1. 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.

Alap felállás - statikus adattagok

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ó).

asd

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é.

asd

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

  1. 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

  2. Valósíts meg egy tetszőleges kártyajátékot (pl.: Black jack), Poker, stb.

Kapcsolódó linkek


Utolsó frissítés: 2024-02-20 13:44:13