2. OOP Intermediate

2.1. Stringify object

  • print converts it’s arguments to str() before printing

Listing 2.42. Object without __str__() method overloaded prints their memory address
class Iris:
    def __init__(self, species):
        self.species = species


flower = Iris('setosa')

str(flower)       # <__main__.Iris object at 0x112b366d8>
print(flower)     # <__main__.Iris object at 0x112b366d8>
Listing 2.43. Objects can verbose print if __str__() method is present
class Iris:
    def __init__(self, species):
        self.species = species

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


flower = Iris('setosa')

str(flower)       # Species: setosa
print(flower)     # Species: setosa

2.2. Inheritance

2.2.1. Simple inheritance

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


class Setosa(Iris):
    pass


setosa = Setosa(
    sepal_length=5.1,
    sepal_width=3.5,
    petal_length=1.4,
    petal_width=0.2,
    species='setosa'
)

2.2.2. Multilevel Inheritance

Listing 2.44. Multilevel Inheritance
class Flower:
    kingdom = 'plantae'

class Iris(Flower):
    genus = 'iris'

class Setosa(Iris):
    species = 'setosa'

2.2.3. Multiple Inheritance

Listing 2.45. Multiple inheritance.
class JSONMixin:
    def to_json(self):
        return ...

class CSVMixin:
    def to_csv(self):
        return ...

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

2.2.4. super() - Calling object parent

Listing 2.46. 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

2.3. Fields

2.3.1. Static Fields

  • Simple to use

  • Must have default values

  • Share state

Listing 2.47. Static Fields
class Iris:
    species = 'setosa'


setosa = Iris()
versicolor = Iris()

setosa.species      # setosa
versicolor.species  # setosa
Iris.species        # setosa

2.3.2. Dynamic Fields

  • Require __init__()

Listing 2.48. Dynamic fields
class Iris:
    def __init__(self, species):
        self.species = species


setosa = Iris('setosa')
versicolor = Iris('versicolor')

setosa.species      # setosa
versicolor.species  # versicolor
Iris.species        # AttributeError: type object 'Iris' has no attribute 'species'

2.3.3. Static vs. Dynamic Fields

Listing 2.49. Static vs. Dynamic fields
class Iris:
    kingdom = 'Plantae'

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


setosa = Iris('setosa')
versicolor = Iris('versicolor')
virginica = Iris('virginica')


# Check value of field agency
setosa.kingdom       # Plantae
versicolor.kingdom   # Plantae
virginica.kingdom    # Plantae
Iris.kingdom         # Plantae


# Let's change ``kingdom`` of ``setosa`` object
setosa.kingdom = 'Flower'

setosa.kingdom       # Flower
versicolor.kingdom   # Plantae
virginica.kingdom    # Plantae
Iris.kingdom         # Plantae


# Let's change ``kingdom`` of ``Iris`` class
Iris.kingdom = 'Iris'

setosa.kingdom       # Flower
versicolor.kingdom   # Iris
virginica.kingdom    # Iris
Iris.kingdom         # Iris

2.3.4. __dict__ - Getting dynamic fields and values

Listing 2.50. __dict__ - Getting dynamic fields and values
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

flower = Iris(
    sepal_length=5.1,
    sepal_width=3.5,
    petal_length=1.4,
    petal_width=0.2,
    species='setosa'
)

flower.__dict__
# {'sepal_length': 5.1,
# 'sepal_width': 3.5,
# 'petal_length': 1.4,
# 'petal_width': 0.2,
# 'species': 'setosa'}

2.4. Initial arguments mutability and shared state

2.4.1. Bad

Listing 2.51. Initial arguments mutability and shared state
class Contact:
    def __init__(self, name, addresses=[]):
        self.name = name
        self.addresses = addresses


jose = Contact(name='Jose Jimenez')

jose.addresses.append('2101 E NASA Pkwy, Houston, TX')
print('Jose:', jose.addresses)
# [2101 E NASA Pkwy, Houston, TX]

ivan = Contact(name='Ivan Ivanovich')
print('Ivan:', ivan.addresses)
# [2101 E NASA Pkwy, Houston, TX]

2.4.2. Good

Listing 2.52. Initial arguments mutability and shared state
class Contact:
    def __init__(self, name, addresses=()):
        self.name = name
        self.addresses = list(addresses)


jose = Contact(name='Jose Jimenez')
jose.addresses.append('2101 E NASA Pkwy, Houston, TX')
print(jose.addresses)
# [2101 E NASA Pkwy, Houston, TX]

ivan = Contact(name='Ivan Ivanovich')
print(ivan.addresses)
# []

2.5. Relations

class Address:
    def __init__(self, street=None, city=None, country=None):
        self.street = street
        self.city = city
        self.country = country


class Contact:
    def __init__(self, first_name, last_name, addresses=()):
        self.first_name = first_name
        self.last_name = last_name
        self.address = addresses


twardowski = Contact(first_name='Jan', last_name='Twardowski', address=[
    Address(street='Kamienica Pod św. Janem Kapistranem', city='Kraków', country='Poland'),
    Address(street='2101 E NASA Pkwy', city='Houston', country='USA'),
    Address(city='Kennedy Space Center', country='USA'),
])

2.6. More advanced topics

Note

The topic will be continued in OOP Advanced chapter

2.7. Assignments

2.7.1. Defining Classes

  • Filename: oop_iris.py

  • Lines of code to write: 15 lines

  • Estimated time of completion: 10 min

  • Input data: Listing 2.53.

Listing 2.53. Iris sample dataset
DATA = [
    ('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species'),
    (5.8, 2.7, 5.1, 1.9, 'virginica'),
    (5.1, 3.5, 1.4, 0.2, 'setosa'),
    (5.7, 2.8, 4.1, 1.3, 'versicolor'),
    (6.3, 2.9, 5.6, 1.8, 'virginica'),
    (6.4, 3.2, 4.5, 1.5, 'versicolor'),
    (4.7, 3.2, 1.3, 0.2, 'setosa'),
    (7.0, 3.2, 4.7, 1.4, 'versicolor'),
    (7.6, 3.0, 6.6, 2.1, 'virginica'),
    (4.9, 3.0, 1.4, 0.2, 'setosa'),
    (4.6, 3.1, 1.5, 0.2, 'setosa'),
]
  1. Stwórz flowers: list

  2. Stwórz klasy Virginica, Versicolor, Setosa, które będą identyczne do Iris

  3. Iterując po DATA z Listing 2.53.:

    1. Twórz obiekty klasy odpowiedniej dla nazwy gatunku (ostatni rekord każdej z krotek)

    2. Obiekt inicjalizuj danymi z pomiarów

    3. Obiekt dodaj do listy flowers

  4. Na ekranie wyświetlaj nazwę gatunku oraz sumę i średnią z pomiarów.

Dla chętnych
  1. Wynik sformatuj aby wyglądał jak tabelka:

    Species    Total   Avg
    ----------------------
     virginica  15.5  3.88
        setosa  10.2  2.55
    versicolor  13.9  3.48
     virginica  16.6  4.15
    versicolor  15.6  3.90
        setosa   9.4  2.35
    versicolor  16.3  4.07
     virginica  19.3  4.83
        setosa   9.5  2.38
        setosa   9.4  2.35
    

2.7.2. Credit Scoring

  • Filename: oop_credit_scoring.py

  • Lines of code to write: 30 lines

  • Estimated time of completion: 20 min

  1. Stwórz klasę opisującą klienta banku

  2. Stwórz klasę konto bankowe

  3. Stwórz konta walutowe, oszczędnościowe, emerytalne i bieżące

  4. Wylicz scoring kredytowy na podstawie informacji:

    • czy klient ma żonę/męża

    • czy klient ma dzieci

    • czy klient ma umowę o pracę

    • suma środków zgromadzonych na wszystkich kontach

    • wiek

  5. Przedstaw scoring

2.7.3. Basic Address Book

  • Filename: oop_addressbook_basic.py

  • Lines of code to write: 10 lines

  • Estimated time of completion: 20 min

  1. Dla danych z listingu poniżej napisz książkę adresową

    Listing 2.54. Address Book
    [
        {"first_name": "Jan", "last_name": "Twardowski", "addresses": [
            {"street": "Kamienica Pod św. Janem Kapistranem", "city": "Kraków", "post_code": "31-008", "region": "Malopołskie", "country": "Poland"}]},
    
        {"first_name": "José", "last_name": "Jiménez", "addresses": [
            {"street": "2101 E NASA Pkwy", "city": "Houston", "post_code": 77058, "region": "Texas", "country": "USA"},
            {"street": "", "city": "Kennedy Space Center", "post_code": 32899, "region": "Florida", "country": "USA"}]},
    
        {"first_name": "Mark", "last_name": "Watney", "addresses": [
            {"street": "4800 Oak Grove Dr", "city": "Pasadena", "post_code": 91109, "region": "California", "country": "USA"},
            {"street": "2825 E Ave P", "city": "Palmdale", "post_code": "93550", "region": "California", "country": "USA"}]},
    
        {"first_name": "Иван", "last_name": "Иванович", "addresses": [
            {"street": "", "city": "Космодро́м Байкону́р", "post_code": "", "region": "Кызылординская область", "country": "Қазақстан"},
            {"street": "", "city": "Звёздный городо́к", "post_code": 141160, "region": "Московская область", "country": "Россия"}]},
    
        {"first_name": "Melissa", "last_name": "Lewis", "addresses": []},
    
        {"first_name": "Alex", "last_name": "Vogel", "addresses": [
            {"street": "Linder Hoehe", "city": "Köln", "post_code": 51147, "region": "North Rhine-Westphalia", "country": "Germany"}]}
    ]
    
  2. W zadaniu mamy do czynienia z trzema klasami, wymień je.

  3. Zamodeluj problem wykorzystując trzy klasy i relacje między nimi

  4. Użytkownik może mieć wiele adresów

  5. Użytkownik może nie mieć żadnego adresu

The whys and wherefores
  • myślenie obiektowe i odwzorowanie struktury w programie

  • praca z obiektami

  • zagnieżdżanie obiektów

  • rzutowanie obiektu na str oraz jego reprezentacja (które i kiedy użyć)

2.7.4. Address Book from API

  • Filename: oop_addressbook_api.py

  • Lines of code to write: 15 lines

  • Estimated time of completion: 20 min

  1. API programu powinno być tak jak na Listing 2.55.

  2. Zrób tak, aby się ładnie wyświetlało zarówno dla jednego wyniku jak i dla wszystkich w książce

  3. Address ma mieć zmienną liczbę argumentów

The whys and wherefores
  • Korzystanie z .__str__()

Listing 2.55. Address Book
class AddressBook:
    pass

class Contact:
    pass

class Address:
    pass


melissa = Contact(imie='Melissa', nazwisko='Lewis')
print(melissa)
# Melissa Lewis

mark = Contact(imie='Mark', nazwisko='Watney', adresy=[Address(miasto='Houston'), Address(miasto='Cocoa Beach')])
print(mark)
# Mark Watney [Houston, Cocoa Beach]

addressbook = AddressBook([
    Contact(imie='Jan', nazwisko='Twardowski', adresy=[
        Address(ulica='2101 E NASA Pkwy', miasto='Houston', stan='Texas', kod='77058', panstwo='USA'),
        Address(ulica=None, miasto='Kennedy Space Center', kod='32899', panstwo='USA'),
        Address(ulica='4800 Oak Grove Dr', miasto='Pasadena', kod='91109', panstwo='USA'),
        Address(ulica='2825 E Ave P', miasto='Palmdale', stan='California', kod='93550', panstwo='USA'),
    ]),
    Contact(imie='José', nazwisko='Jiménez'),
    Contact(imie='Иван', nazwisko='Иванович', adresy=[]),
])


print(addressbook)
# [Jan Twardowski [Houston, Kennedy Space Center, Pasadena, Palmdale], José Jiménez, Иван Иванович]

2.7.5. Dragon (Part 2)

  • Filename: oop_dragon_2.py

  • Lines of code to write: 120 lines

  • Estimated time of completion: 60 min

  • Warning: Don’t delete code, assignment will be continued

../_images/dragon.gif

Figure 2.14. Firkraag dragon from game Baldur’s Gate II: Shadows of Amn

  1. Zaimportuj smoka z poprzedniej części zadania (“Part 1”)

  2. Wykorzystaj mechanizm dziedziczenia dla Smoka

  3. Nie modyfikuj klasy smoka z poprzedniej części

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

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

  6. Zmień smokowi punkty życia na losowy int z zakresu 100 do 150

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

    • losowe punkty życia (200-250)

    • zadaje losowe obrażenia (1-15)

    • klasa postaci (domyślnie “Warrior”)

    • Bohater może przyjmować obrażenia

    • Bohater może zginąć

    • Bohater może poruszać się po planszy

  8. Wszystkie istoty mają statusy:

    • “Full Health” - gdy punkty życia 100% (zastąp status “alive”)

    • “Injured” - gdy punkty życia 99% - 75%

    • “Badly Wounded” - gdy punkty życia 74% - 25%

    • “Near Death” - gdy punkty życia 24% - 1%

    • “Dead” - gdy punkty życia poniżej lub równe 0%

  9. Bohater przejmuje złoto smoka, jeżeli go zabije

  10. Przeprowadź walkę, tak długo aż ktoś pierwszy nie zginie

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

Hint
  • Aby zaimportować trzeba najpierw w katalogu stworzyć pusty plik __init__.py

2.7.6. Bank i Bankomaty

  • Filename: oop_bank.py

  • Lines of code to write: 60 lines

  • Estimated time of completion: 20 min

  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