17. OOP Advanced

17.1. Placeholder class

DATA = {"sepal_length": 6.0, "sepal_width": 3.4, "petal_length": 4.5, "petal_width": 1.6, "species": "versicolor"},

class Iris:
    def __init__(self, sepal_length, sepal_width, petal_length, petal_width, species):
        self.sepal_length = sepal_length
        self.sepal_width = sepal_width
        self.petal_length = petal_length
        self.petal_width = petal_width
        self.species = species

iris = Iris(**DATA)
iris.species
# 'versicolor'
DATA = [
    {"sepal_length": 6.0, "sepal_width": 3.4, "petal_length": 4.5, "petal_width": 1.6, "species": "versicolor"},
    {"sepal_length": 4.9, "sepal_width": 3.1, "petal_length": 1.5, "petal_width": 0.1, "species": "setosa"},
]

class Iris:
    def __init__(self, sepal_length, sepal_width, petal_length, petal_width, species):
        self.sepal_length = sepal_length
        self.sepal_width = sepal_width
        self.petal_length = petal_length
        self.petal_width = petal_width
        self.species = species

    def __repr__(self):
        return f'{self.species}'

flowers = []

for row in DATA:
    iris = Iris(**row)
    flowers.append(iris)

print(flowers)
# ['versicolor', 'setosa']
class Contact:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)


a = Contact(first_name='Jan', last_name='Twardowski')
a.first_name          # Jan
a.last_name           # 'Twardowski'

b = Contact(last_name='Twardowski', date_of_birth='1970-01-01')
b.first_name         # AttributeError: 'Contact' object has no attribute 'first_name'
b.last_name          # 'Twardowski'
b.date_of_birth      # '1970-01-01'

17.2. Objects and instances

Listing 340. Implicit passing instance to class as self.
text = 'Jan,Twardowski'

text.split(',')                     # ['Jan', 'Twardowski']
Listing 341. Explicit passing instance to class overriding self.
text = 'Jan,Twardowski'

str.split(text, ',')                # ['Jan', 'Twardowski']
Listing 342. Passing anonymous objects as instances.
'Jan,Twardowski'.split(',')         # ['Jan', 'Twardowski']
str.split('Jan,Twardowski', ',')    # ['Jan', 'Twardowski']

17.3. What should be in the class and what not?

  • Jeżeli metoda w swoim ciele ma self i z niego korzysta to powinna być w klasie

  • Jeżeli metoda nie ma w swoim ciele self to nie powinna być w klasie

  • Jeżeli metoda nie ma w swoim ciele self ale wybitnie pasuje do klasy, to można ją tam zamieścić oraz dodać dekorator @staticmethod

17.3.1. @staticmethod

  • Using class as namespace

  • Will not pass instance as a first argument

  • self is not required

Listing 343. Functions on a high level of a module lack namespace
def add(a, b):
    return a + b

def sub(a, b):
    return a - b


add(1, 2)
sub(8, 4)
Listing 344. When add and sub are in Calculator class (namespace) they get instance (self) as a first argument. Instantiating Calculator is not needed, as of functions do not read or write to instance variables.
class Calculator:

    def add(self, a, b):
        return a + b

    def sub(self, a, b):
        return a - b


Calculator.add(10, 20)  # TypeError: add() missing 1 required positional argument: 'b'
Calculator.sub(8, 4)    # TypeError: add() missing 1 required positional argument: 'b'

calc = Calculator()
calc.add(1, 2)          # 3
calc.sub(8, 4)          # 4
Listing 345. Class Calculator is a namespace for functions. @staticmethod remove instance (self) argument to method.
class Calculator:

    @staticmethod
    def add(a, b):
        return a + b

    @staticmethod
    def sub(a, b):
        return a - b


Calculator.add(1, 2)
Calculator.sub(8, 4)

17.3.2. @classmethod

import json

class JSONSerializable:
    def to_json(self):
        return json.dumps(self.__dict__)

    @classmethod
    def from_json(cls, data):
        data = json.loads(data)
        return cls(**data)


class User:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def __str__(self):
        return f'{self.first_name} {self.last_name}'

class Guest(User, JSONSerializable):
    pass

class Admin(User, JSONSerializable):
    pass


DATA = '{"first_name": "Jan", "last_name": "Twardowski"}'

guest = Guest.from_json(DATA)
admin = Admin.from_json(DATA)

type(guest)     # <class '__main__.Guest'>
type(admin)      # <class '__main__.Admin'>

17.4. Assignments

17.4.1. Dragon (version release candidate)

../_images/dragon.gif

Figure 37. Firkraag dragon from game Baldur's Gate II: Shadows of Amn

  1. Dodaj możliwość poruszania się smoka i bohatera w 3 wymiarach

  2. Bohater może należeć do drużyny, który może składać się maks z 6 postaci (różnych klas)

  3. Żadna z istot na planszy nie może wyjść poza zakres ekranu

  4. Bohater może dodatkowo założyć ekwipunek i może być to wiele obiektów na raz

  5. Każdy z przedmiotów ma swoją nazwę, typ oraz modyfikator

    • zbroję (dodatkowe punkty obrony, np. +10%)

    • tarczę (dodatkowe punkty obrony, np. +5%)

    • miecz (dodatkowe punkty ataku, np. +5%)

  6. Zbroja i tarcza chroni przed uderzeniami obniżając ilość obrażeń o wartość obrony

  7. Miecz zwiększa ilość zadawanych obrażeń

  8. Obrażenia smoka maleją z sześcianem odległości (zianie ogniem)

  9. Bohater nie może zadawać obrażeń jak jest dalej niż 50 punktów od przeciwnika

  10. Wszystkie istoty mogą levelować a bazowe punty życia i obrażeń się zmieniają z poziomem

  11. Przeprowadź symulację walki. Kto zginie pierwszy?