OOP

part 2

Q&A

  • What are the OOP principles?
  • What is an object?
  • What are the dunders ("magic methods")? Do you know any?
  • What is the difference between class and object attributes?
  • Can we have multiple constructors in Python?

The __dict__ attribute

Represents all writable attributes of an object.

class Cake:
    def __init__(self, flavour, calories):
        self.flavour = flavour
        self.calories = calories

carrot_cake = Cake('Carrots', 1000)
print('The dict of cake: ', carrot_cake.__dict__)
The dict of cake:  {'flavour': 'Carrots', 'calories': 1000}

Mutating the __dict__

class Cake:
    def __init__(self, flavour, calories):
        self.flavour = flavour
        self.calories = calories

carrot_cake = Cake('Carrots', 1000)
print('The dict of cake: ', carrot_cake.__dict__)
carrot_cake.__dict__['flavour'] = 'Chocolate'
carrot_cake.__dict__['fake'] = True
print('The dict of cake: ', carrot_cake.__dict__)

The dict of cake:  {'flavour': 'Carrots', 'calories': 1000}
The dict of cake:  {'flavour': 'Chocolate', 'calories': 1000, 'fake': True}

Python getters and setters

setattr

carrot_cake = Cake('Carrots', 1000)
print(carrot_cake.__dict__)

carrot_cake.owner = 'Marto'
print(carrot_cake.__dict__)

setattr(carrot_cake, 'calories', 100)
print(carrot_cake.__dict__)

setattr(carrot_cake, 'price', 10)
print(carrot_cake.__dict__)
{'flavour': 'Carrots', 'calories': 1000}
{'flavour': 'Carrots', 'calories': 1000, 'owner': 'Marto'}
{'flavour': 'Carrots', 'calories': 100, 'owner': 'Marto'}
{'flavour': 'Carrots', 'calories': 100, 'owner': 'Marto', 'price': 10}

getattr

carrot_cake = Cake('Carrots', 1000)
print(carrot_cake.__dict__)

print(getattr(carrot_cake, 'calories'))

print(getattr(carrot_cake, 'calories_new', 2000))

print(getattr(carrot_cake, 'calories_new'))
{'flavour': 'Carrots', 'calories': 1000}
1000
2000
Traceback (most recent call last):
  File "shano.py", line 18, in <module>
    print(getattr(carrot_cake, 'calories_new'))
AttributeError: 'Cake' object has no attribute 'calories_new'

hasattr

carrot_cake = Cake('Carrots', 1000)
print(carrot_cake.__dict__)

print(hasattr(carrot_cake, 'price'))

carrot_cake.price = 10000
print(hasattr(carrot_cake, 'price'))
{'flavour': 'Carrots', 'calories': 1000}
False
True

delattr

carrot_cake = Cake('Carrots', 1000)
print(carrot_cake.__dict__)

carrot_cake.price = 42
print(carrot_cake.__dict__)

del carrot_cake.price
print(carrot_cake.__dict__)

setattr(carrot_cake, 'price', 424242)
print(carrot_cake.__dict__)
delattr(carrot_cake, 'price')
print(carrot_cake.__dict__)
{'flavour': 'Carrots', 'calories': 1000}
{'flavour': 'Carrots', 'calories': 1000, 'price': 42}
{'flavour': 'Carrots', 'calories': 1000}
{'flavour': 'Carrots', 'calories': 1000, 'price': 424242}
{'flavour': 'Carrots', 'calories': 1000}

Working with private/protected

class Cake:
    price = 100
    _extras = ['E100']
    __bad_extras = ['E420']

    def __init__(self, flavour):
        self.flavour = flavour
        self.price = 555
        self._extras = ['E111']
        self.__bad_extras = ['E444']
pprint(Cake.__dict__)

{'_Cake__bad_extras': ['E420'],
 '__dict__': <attribute '__dict__' of 'Cake' objects>,
 '__doc__': None,
 '__init__': <function Cake.__init__ at 0x7fe18562f9e0>,
 '__module__': '__main__',
 '__weakref__': <attribute '__weakref__' of 'Cake' objects>,
 '_extras': ['E100'],
 'price': 100}
vanilla_cake = Cake('Vanilla')
pprint(vanilla_cake.__dict__)

{'_Cake__bad_extras': ['E444'],
 '_extras': ['E111'],
 'flavour': 'Vanilla',
 'price': 555}

Inheritance

class Worker:
    _hours = 8

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

    def hours(self):
        return self._hours

class Developer(Worker):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

    def get_working_hours(self):
        return self.hours()

marto = Developer('Marto', 40)
print(marto.get_working_hours())  # 8
print(marto.hours())  # 8
print(marto._hours)  # 8

Accessing Private attributes

class Worker:
    __salary = 1000


class Developer(Worker):
    def get_salary(self):
        return self.__salary

marto = Developer()
print(marto.get_salary())
Traceback (most recent call last):
  File "shano.py", line 10, in <module>
    print(marto.get_salary())
  File "shano.py", line 7, in get_salary
    return self.__salary
AttributeError: 'Developer' object has
  no attribute '_Developer__salary'
class Worker:
    __salary = 1000


class Developer(Worker):
    def get_salary(self):
        return self.__salary

marto = Developer()
print(marto.get_salary())
1000

Bound and Unbound methods

class Developer:
    def work(self):
        # Bound method
        print('Working...')


marto = Developer()
marto.work()
print('----------------')
Developer.work()
Working...
----------------
Traceback (most recent call last):
  File "shano.py", line 10, in <module>
    Developer.work()
TypeError: work() missing 1 required
  positional argument: 'self'
class Developer:
    def get_stats():
        # Unbound method
        print('WFH.')


marto = Developer()
Developer.get_stats()
print('-------------------')
marto.get_stats()
WFH.
-------------------
Traceback (most recent call last):
  File "shano.py", line 9, in <module>
    marto.get_stats()
TypeError: get_stats() takes 0 positional
  arguments but 1 was given

Static and class methods

class Date(object):
    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date = cls(day, month, year)
        return date

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

Python 101 9th OOP part2

By Hack Bulgaria

Python 101 9th OOP part2

  • 1,007