25. Object Oriented Programming

25.1. Object Paradigm

  • Odwzorowanie świata na obiekty i relacje między nimi

25.2. Classes and Objects

Code Listing 25.1. Classes and Objects
class Astronaut:
    pass


jose = Astronaut()
ivan = Astronaut()

25.3. Fields

25.3.1. Static Fields

  • Simple to use
  • Must have default values
  • Share state
Code Listing 25.2. Static Fields
class Astronaut:
    first_name = 'José'
    last_name = 'Jiménez'
    agency: str = 'NASA'
    age: int = 30


jose = Astronaut()
jose.first_name     # José

25.3.2. Dynamic Fields

  • Require __init__()
Code Listing 25.3. Dynamic fields
class Astronaut:
    def __init__(self, name, age=30, agency='NASA'):
        self.name = name
        self.agency = agency
        self.age = age


jose = Astronaut(name='José Jiménez')
ivan = Astronaut(name='Иван Иванович', agency='Roscosmos')

jose.agency     # NASA
ivan.agency     # Roscosmos

25.3.3. Static vs. Dynamic Fields

Code Listing 25.4. Static vs. Dynamic fields
class Astronaut:
    agency = 'NASA'

    def __init__(self, name):
        self.name = name


# Objects - Instances
ivan = Astronaut(name='Иван Иванович')
jose = Astronaut(name='José Jiménez')
matt = Astronaut(name='Matt Kowalski')

ivan.agency         # NASA
jose.agency         # NASA
matt.agency         # NASA
Astronaut.agency    # NASA

ivan.agency = 'Roscosmos'

ivan.agency         # Roscosmos
jose.agency         # NASA
matt.agency         # NASA
Astronaut.agency    # NASA

Astronaut.agency = 'ESA'

ivan.agency         # Roscosmos
jose.agency         # ESA
matt.agency         # ESA
Astronaut.agency    # ESA

25.3.4. __dict__ - Getting dynamic fields and values

Code Listing 25.5. __dict__ - Getting dynamic fields and values
class Astronaut:
    def __init__(self, name, agency='NASA'):
        self.name = name
        self.agency = agency


jose = Astronaut(name='José Jiménez')
ivan = Astronaut(name='Иван Иванович', agency='Roscosmos')

jose.__dict__    # {'name': 'José Jiménez', 'agency': 'NASA'}
ivan.__dict__    # {'name': 'Иван Иванович', 'agency': 'Roscosmos'}

25.4. Methods

Code Listing 25.6. Methods
class Astronaut:
    name = 'José Jiménez'

    def say_hello(self):
        print(f'My name... {self.name}')


jose = Astronaut()

jose.say_hello()
# My name... José Jiménez
Code Listing 25.7. Methods
class Astronaut:
    name = 'José Jiménez'

    def say_hello(self, text='My name...'):
        print(f'{text} {self.name}!')


jose = Astronaut()

jose.say_hello()
# My name... José Jiménez!

jose.say_hello('¡Hola')
# ¡Hola José Jiménez!

25.4.1. self - Instance as an argument

  • self - pierwszy argument do metody
  • wyjątek to metody z dekoratorem @staticmethod (o tym będzie później)
  • przy uruchomieniu funkcji nie podajemy jawnie argumentu self
Code Listing 25.8. Methods argument self
class Astronaut:
    name = 'José Jiménez'

    def say_hello(self):
        print(f'My name... {self.name}')


jose = Astronaut()

jose.say_hello()
# My name... José Jiménez

25.4.2. __init__() - Initializer Method

  • __init__() to nie konstruktor
  • Domyślny __init__() gdy niezdefiniowaliśmy własnego
  • Inicjalizacja pól klasy tylko w __init__
Code Listing 25.9. Fields added dynamically
class Astronaut:
    def __init__(self, name, age=30, agency='NASA'):
        self.name = name
        self.agency = agency
        self.age = age


jose = Astronaut(name='José Jiménez')
ivan = Astronaut(name='Иван Иванович', agency='Roscosmos')

jose.agency     # NASA
ivan.agency     # Roscosmos
Code Listing 25.10. __init__() - Initializer Method
class Server:
    def __init__(self, host, user, password=None):
        self.host = host
        self.user = user
        self.password = password


connection = Server(
    host='localhost',
    user='admin',
    password='admin'
)

connection.__dict__
# {'host': 'localhost', 'user': 'admin', 'password': 'admin'}

25.4.3. __str__() - Stringify object

  • print converts it’s arguments to str() automatically before printing
Code Listing 25.11. Print object without __str__() method overloaded
class Astronaut:
    name = 'José Jiménez'


jose = Astronaut()

str(jose)
# <__main__.Astronaut object at 0x01E3FDF0>

print(jose)
# <__main__.Astronaut object at 0x01E3FDF0>
Code Listing 25.12. Stringify object
class Astronaut:
    name = 'José Jiménez'

    def __str__(self):
        return f'My name... {self.name}'


jose = Astronaut()

str(jose)
# My name... José Jiménez

print(jose)
# My name... José Jiménez

25.5. Inheritance

Code Listing 25.13. Inheritance
class Spaceman:
    agency = None

    def __init__(self, name):
        self.name = name

    def what_is_your_agency(self):
        return self.agency


class Astronaut(Spaceman):
    agency = 'NASA'


class Cosmonaut(Spaceman):
    agency = 'Roscosmos'



jose = Astronaut(name='José Jiménez')
jose.what_is_your_agency()       # NASA

ivan = Cosmonaut(name='Иван Иванович')
ivan.what_is_your_agency()       # Roscosmos

25.5.1. Multilevel Inheritance

Code Listing 25.14. Multilevel Inheritance
class Astronaut:
    def __init__(self, name):
        self.name = name

    def say_hallo(self):
        print(f'My name... {self.name}')


class MaleAstronaut(Astronaut):
    gender = 'male'


class Gieroj(MaleAstronaut):
    status = 'hero'


ivan = Gieroj(name='Иван Иванович')
ivan.say_hallo()        # My name... Иван Иванович
ivan.status             # hero
ivan.gender             # male

25.5.2. Multiple Inheritance

Code Listing 25.15. Multiple Inheritance
class Cosmonaut:
    agency = 'Roscosmos'


class Gieroy:
    status = 'gieroy'


class GeroySovietskogoSoyuza(Cosmonaut, Gieroy):
    def __init__(self, name):
        self.name = name

    def say_hallo(self):
        print(f'Здравствуйте... {self.name}')


ivan = GeroySovietskogoSoyuza(name='Иван Иванович')
ivan.say_hallo()        # Здравствуйте... Иван Иванович
ivan.status             # gieroy
ivan.agency             # Roscosmos

Note

The topic will be continued in Advanced OOP chapter

25.5.3. super() - Calling object parent

Code Listing 25.16. Using super() on a class
class Astronaut:
    def say_hello(self):
        print('I am an astronaut')


class FictionalAstronaut(Astronaut):
    def say_hello(self):
        print(f'My name... José Jiménez')
        super().say_hello()


jose = FictionalAstronaut()
jose.say_hello()
# My name... José Jiménez
# I am an astronaut

25.5.4. Enum

Code Listing 25.17. enum - Support for enumerations
from enum import Enum


class Color(Enum):
    RED = 1
    GREEN = '#00FF00'
    BLUE = 'blue'


print(Color.RED)
# Color.RED

print(Color.RED.name)
# RED

print(Color.RED.value)
# 1

for color in Color:
    print(color)

# Color.RED
# Color.GREEN
# Color.BLUE

Color('#00FF00')
# <Color.GREEN: '#00FF00'>

Color.RED is Color.RED
# True
Code Listing 25.18. enum - Example usage
from enum import Enum


class Color(Enum):
    RED = 'Roscosmos'
    BLUE = 'NASA'


class Moon:
    def __init__(self, explorer):
        self.color = Color(explorer)


moon = Moon(explorer='NASA')


if moon.color is Color.BLUE:
    print("That's one small step for [a] man, one giant leap for mankind.")

elif moon.color is Color.RED:
    print('Красная Луна, товарищ!')

25.6. One class per file?

  • Jeden plik - gdy klasy są małe i czytelne
  • Osobne pliki - gdy klasy są duże

25.7. More advanced topics

Note

The topic will be continued in Advanced OOP chapter

25.8. Assignments

25.8.1. Bank i Bankomaty

  1. Klient może otworzyć konto w banku
  2. Bank może mieć wiele kont dla różnych klientów
  3. Każde konto ma unikalny numer, który jest generowany przy zakładaniu
  4. Klient może odpytać o swój numer
  5. Klient może wpłacić pieniądze na swoje konto
  6. Klient może wybrać pieniądze z bankomatu
About:
  • Filename: oop_bank.py
  • Lines of code to write: 60 lines
  • Estimated time of completion: 20 min

25.8.2. Dragon (Easy)

Note

Jeżeli konieczne jest wprowadzenie nowej metody, klasy lub pól to należy to zrobić

  1. Skopiuj kod gry z listingu Code Listing 25.19.

  2. Smok ma mieć:

    • nazwę smoka
    • pozycja x na ekranie
    • pozycja y na ekranie
    • teksturę, domyślnie dragon.png
    • punkty życia, domyślnie losowy int z zakresu od 50 do 100
  3. Smok może:

    • otrzymywać obrażenia
    • zadawać komuś losowe obrażenia z przedziału od 5 do 20
    • być ustawiony w dowolne miejsce ekranu
    • być przesuwany o zadaną liczbę punktów w którymś z kierunków
  4. Przesuwając smoka, można podać tylko niektóre kierunki, np: right=30, down=50 lub up=20

  5. Przyjmij górny lewy róg ekranu za punkt (0, 0)

    • idąc w prawo dodajesz x
    • idąc w lewo odejmujesz x
    • idąc w górę odejmujesz y
    • idąc w dół dodajesz y
  6. Przy każdym obrażeniu wypisz na ekranie nazwę smoka, ilość obrażeń i pozostałe punkty życia

  7. Kiedy punkty życia smoka spadną do, lub poniżej zera:

    • ustaw status obiektu na dead
    • na ekranie ma pojawić się napis ‘Dragon is dead’
    • zmień teksturę smoka na dragon-dead.png
    • na ekranie pojawi się informacja ile złota smok wyrzucił (losowa 1-100)
    • na ekranie pojawi się informacja o pozycji gdzie smok zginął
  8. Nie można zadawać smokowi obrażeń, jeżeli już nie żyje

About:
  • Filename: oop_dragon_easy.py
  • Lines of code to write: 40 lines
  • Estimated time of completion: 75 min
Code Listing 25.19. Dragon API
class Dragon:
    pass


# Do not modify anything below!

wawelski = Dragon(name='Wawelski', position_x=50, position_y=120)

wawelski.set_position(x=10, y=20)
wawelski.move(left=10, down=20)
wawelski.move(left=10, right=15)
wawelski.move(right=15, up=5)
wawelski.move(down=5)

wawelski.take_damage(10)
wawelski.take_damage(50)
wawelski.take_damage(35)
wawelski.take_damage(20)
wawelski.take_damage(25)

25.8.3. Dragon (Medium)

Note

Jeżeli konieczne jest wprowadzenie nowej metody, klasy lub pól to należy to zrobić

  1. Zaimportuj smoka z zadania podstawowego rozszerz go przez dziedziczenie

  2. Smok nie może wyjść poza obszar ekranu (1024x768) + napis doctest

  3. Jeżeli dojdzie do granicy ekranu, to przesuwając dalej, pozycja będzie ustawiona na maks

  4. Stwórz bohatera (José Jiménez):

    • losowe punkty życia (100-150)
    • zadaje losowe obrażenia (1-15)
    • klasa postaci (domyślnie “wojownik”)
  5. Wszystkie istoty mają statusy:

    • “Pełnia życia” (zastąp status “alive”) - gdy punkty życia 100%
    • “Lekko Ranny” - gdy punkty życia 99% - 75%
    • “Poważnie ranny” - gdy punkty życia 75% - 25%
    • “Na skraju śmierci” - gdy punkty życia poniżej 25%
  6. Bohater przejmuje złoto smoka, jeżeli go zabije

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

About:
  • Filename: oop_dragon_medium.py
  • Lines of code to write: 100 lines
  • Estimated time of completion: 60 min
Hint:
  • Aby zaimportować trzeba najpierw w katalogu stworzyć pusty plik __init__.py

25.8.4. Dragon (Advanced)

  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ą lewelować a bazowe punty życia i obrażeń się zmieniają z poziomem

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

About:
  • Filename: oop_dragon_advanced.py
  • Lines of code to write: 50 lines
  • Estimated time of completion: 30 min

25.8.5. Address Book (Easy)

  1. Dla danych z Code Listing 25.20. napisz książkę adresową
  2. Wszystkie dane w książce muszą być reprezentowane przez klasy.
  3. Klasy powinny wykorzystywać domyślne argumenty w __init__.
  4. Użytkownik może mieć wiele adresów.
  5. Użytkownik może nie mieć żadnego adresu
About:
  • Filename: oop_addressbook_easy.py
  • Lines of code to write: 10 lines
  • Estimated time of completion: 20 min
The whys and wherefores:
 
  • myślenie obiektowe i odwzorowanie struktury w programie
  • praca z obiektami
  • zagnieżdżanie obiektów
  • rzutowanie obiektu na stringa oraz jego reprezentacja (które i kiedy użyć)
Code Listing 25.20. Address Book
addressbook = [
    {'first_name': 'José', 'last_name': 'Jiménez'},
    {'first_name': 'Иван', 'last_name': 'Иванович', 'addresses': []},
    {'first_name': 'Matt', 'last_name': 'Kowalski', 'addresses': [
        {'street': '2101 E NASA Pkwy', 'city': 'Houston'},
        {'city': 'Kennedy Space Center'},
        {'street': '4800 Oak Grove Dr', 'city': 'Pasadena'},
        {'street': '2825 E Ave P', 'city': 'Palmdale'}
    ]}
]

25.8.6. Points and Vectors

Przekształć swój kod z przykładu z modułu “Matematyka” tak żeby wykorzystywał klasy.

Zadanie 0:

Napisz klasę ObiektGraficzny, która implementuje “wirtualną” funkcję plot(). Niech domyślnie ta funkcja podnosi NotImplementedError (podpowiedź: raise NotImplementedError).

Zadanie 1:

Napisz klasę Punkt, która dziedziczy po ObiektGraficzny, która będzie miała “ukryte” pola _x, _y. Konstruktor tej klasy ma przyjmować współrzędne x oraz y jako argumenty. Napisz obsługę pól ukrytych _x oraz _y jako @property tej klasy (obsługiwane jako x oraz y). Dopisz implementacje metod __str__ oraz __repr__. Zaimplementuj metodę plot(kolor), która wyrysuje ten punkt na aktualnie aktywnym wykresie. Kolor domyślnie powinien przyjmować wartość 'black'.

Dopisz do tej klasy metodę statyczną, która zwróci losowy punkt w podobny sposób jak funkcja random_point(center, std) zwracała obiekt dwuelementowy.

Zadanie 2:

Dopisz do tej klasy dwie metody, które pozwolą obliczyć odległość między dwoma punktami. Jedna z tych metod niech będzie metodą statyczną, która przyjmuje dwa punkty jako argumenty, a zwraca odległość między nimi (przykładowe wywołanie tej metody: Punkt.oblicz_odleglosc_miedzy_punktami(punkt_A, punkt_B)). Druga z tych metod niech będzie zwykłą metodą klasy, która przyjmie jeden punkt jako argument oraz obliczy odległość od tego punktu do punktu na którym jest wykonywana (punkt_A.oblicz_odleglosc_do(punkt_B)).

Zadanie 3:

Napisz kod, który wykorzystując klasę zaimplementowaną w przykładzie powyżej, wygeneruje listę losowych punktów wokół punktów A i B. Wyrysuj te punkty na wykresie, podobnie jak w przykładzie z modułu “Matematyka”.

Zadanie 4:

Napisz kod, który zaklasyfikuje te losowo wygenerowane punkty do punktów A oraz B na podstawie odległości. W tym celu wykorzystaj napisane metody do obliczania odległości między punktami. Po klasyfikacji wyrysuj te punkty na wykresie, podobnie jak w przykładzie z modułu “Matematyka”.

About:
  • Filename: oop-vector.py
  • Lines of code to write: 20 lines
  • Estimated time of completion: 30 min