05. ООП 2

05. ООП 2

05. ООП 2

16 март 2016

ООП част 2

Обектно-ориентирано програмиране част втора

Какво характеризира ООП?

Абстракция

Енкапсулация

Модулярност

Какво не е наред?

class Muffin:
    def __init__(color, taste, calories):
        self.color = color
        self.taste = taste
        self.calories = calories

Точно така!

>>> yum = Muffin("Green", "sweet", 420)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  TypeError: __init__() takes 3 positional arguments but 4 were given

Какво е self?

по конвенция така се кръщава първия аргумент на методите на клас

В такъв случай

Трябва ли всеки метод на един клас да приема self като първи аргумент?

Fennec

>>> class Fennec:
...     def __init__(self, name):
...         self.name = name
... 
>>> pesho = Fennec('Пешо')
>>> pesho.name
'Пешо'
>>> pesho.age
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Fennec' object has no attribute 'age'
>>> pesho.age = 2
>>> pesho.age
2

del

с ключовата дума del можем да изтриваме атрибути на обект

>>> del pesho.age
>>> pesho.age
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Fennec' object has no attribute 'age'
>>> pesho.name
'Пешо'
>>> del pesho.name
>>> pesho.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Fennec' object has no attribute 'name'

В __init__ не се случва нищо магическо

And now for something completely different…

Vector

class Vector:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

v = Vector(3, 4, 5)
print(v.x)

Не може ли малко по-удобно?

Ако си мислим за математическата абстракция вектор, тя често пъти има смисъла и на наредена енторка.

Защо тогава да не можем да я третираме като такава?

v[0] == v.x
v[1] == v.y
v[2] == v.z

__getitem__

class Vector:
    
    def __getitem__(self, i)
        return (self.x, self.y, self.z)[i]

Vector (8)

Нашия вектор може да бъде ползван като колекция

class Vector:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def __getitem__(self, i):
        return (self.x, self.y, self.z)[i]

    def __str__(self):
        return str((self.x, self.y, self.z))

    def __len__(self):
        return 3

    def __add__(self, other):
         return Vector(*map(sum, zip(self, other)))

Ами присвояване?

Какво ще се случи тук?

v = Vector(3, 4, 5)
v[0] = 8
TypeError: 'Vector' object does not support item assignment

Ами, присвояване!

class Vector:
    
    def __setitem__(self, index, value):
        if index == 0:
            self.x = value
        elif index == 1:
            self.y = value
        elif index == 2:
            self.z = value
        else:
            pass # тук можем да възбудим изключение

v = Vector(1, 2, 3)
v[1] = 10
print(v.y) # 10

Атрибути

class Spam: pass

spam = Spam()

spam.eggs = "Eggs"
print(getattr(spam, 'eggs')) # Eggs

setattr(spam, 'bacon', 'Spam, eggs and bacon')
print(spam.bacon) # Spam, eggs and bacon

Атрибути (2)

Може да дефинирате __getitem__ и __setitem__ по-компактно

class Vector:
    def __init__(self, x, y, z): ...

    def __getitem__(self, i):
        return getattr(self, ('x', 'y', 'z')[i])

    def __setitem__(self, index, value)
        return setattr(self, ('x', 'y', 'z')[i], value)

Атрибути (3)

Може да предефинирате "оператора точка"

Може да предефинираме достъпването на атрибути на нашите обекти

Атрибути (4)

__getattr__(self, name) се извиква само ако обекта няма атрибут name.

class Spam:
    def __init__(self):
        self.eggs = 'larodi'

    def __getattr__(self, name)
        return name.upper()

    def answer(self)
        return 42

spam = Spam()
print(spam.foo) # FOO
print(spam.bar) # BAR
print(spam.eggs) # larodi
print(spam.answer()) # 42

Атрибути (5)

__setattr__ се извиква, когато присвоявате стойност на атрибут на обект.

За да не изпаднете в безкрайна рекурсия, ползвайте object.__setattr__.

class Spam:
    def __setattr__(self, name, value):
        print("Setting {0} to {1}".format(name, value))
        return object.__setattr__(self, name.upper(), value + 10)

spam = Spam()
spam.foo = 42
print(spam.FOO) # 52
print(spam.foo) # грешка!

Атрибути (6)

Обектите и питоните

Можем да разглеждаме всеки обект като съвкупност от две неща:

  1. речник, съдържащ атрибутите на обекта (атрибута __dict__ на обекта)
  2. връзка към класа на обекта (атрибута __class__ на обекта)
class Spam: pass

spam = Spam()
spam.foo = 1
spam.bar = 2
print(spam.__dict__) # {'foo': 1, 'bar': 2}
print(spam.__class__) # <class '__main__.Spam'>
print(spam.__class__ is Spam) # True

Обектите и питоните (2)

Функциите и променливите дефинирани в тялото на класа са атрибути на класа.

class Spam:
    def foo(self):
        return 1

    bar = 42

print(Spam.foo) # <function foo at 0x0c4f3b4b3>
print(Spam.bar) # 42

Обектите и питоните (3)

Когато срещне кода baba.attr python прави следните неща:

  1. Връща стойността на baba.__dict__['attr']
  2. Ако не намери, търси baba.__class__.attr
    • ако не е функция се връща директно
    • ако е функция, се връща bound method
  3. Ако го няма и там се вика baba.__getattr__('attr')

Обектите и питоните (4)

  1. В Python има наследяване
  2. Всичко наследява от object
  3. Предния слайд описва какво прави object.__getattribute__
  4. Можете да го предефинирате (стига да имате причина)

Познайте дали имате причина?

Наследяване

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

    def name(self):
        return self.first_name + " " + self.last_name

class Star(Person):
    def greet_audience(self):
        print("Hello Sofia, I am {0}!".format(self.name()))

david = Star("David", "Gahan")
david.greet_audience()
# Hello Sofia, I am David Gahan!

Наследяване (2)

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

    def name(self):
        return "{0} {1}".format(self.first_name, self.last_name)

class Japanese(Person):
    def name(self):
        return "{0} {1}".format(self.last_name, self.first_name)

print(Person("Edward", "Murdsone").name()) # Edward Murdstone
print(Japanese("Yukihiro", "Matsumoto").name()) # Matsumoto Yukihiro

Наследяване (3)

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

    def name(self)
        return "{0} {1}".format(self.first_name, self.last_name)

class Doctor(Person):
    def name(self):
        return "{0}, M.D.".format(Person.name(self))

print(Doctor("Gregory", "House").name()) # Gregory House, M.D.

Множествено наследяване

class Spam:
    def spam(self): return "spam"

class Eggs:
    def eggs(self): return "eggs"

class CheeseShop(Spam, Eggs):
    def food(self):
        return self.spam() + " and " + self.eggs()

Mixins

Mixins (2)

Гледайте на миксините като резервни части които не можете да ползвате сами по себе си, но можете да сглобите нещо от тях

class Screen: # ...
class RadioTransmitter: # ...
class GSMTransmitter(RadioTransmitter): # ...
class Input: # ...
class MultiTouchInput(Input): # ...
class ButtonInput(Input): # ...
class MassStorage: # ...
class ProcessingUnit: # ...

class Phone(ProcessingUnit, Screen, GSMTransmitter,
            MultiTouchInput, ButtonInput, MassStorage): # ...

class Tablet(ProcessingUnit, Screen, RadioTransmitter,
             MultiTouchInput, MassStorage): # ...

Mixins (3)

class Serializable: # ...
class NetworkSupport: # ...

private и protected

  1. В Python енкапсулацията е въпрос на добро възпитание
  2. Имена от типа _име са protected
  3. Имена от типа __име са private
  4. Интерпретатора променя имената от тип __име до _клас__име. Нарича се name mangling и създава ефект, подобен на този в C++/Java.
class Spam:
    def __init__(self):
        self.__var = 42

print(dir(Spam())) # ['_Spam__var', '__class__', ...]

private и protected (2)

class Base:
    def __init__(self, name, age):
        self.__name = name
        self._age = age
    def report_base(self):
        print("Base:", self.__name, self._age)

class Derived(Base):
    def __init__(self, name, age, derived_name):
        Base.__init__(self, name, age)
        self.__name = derived_name
        self._age = 33
    def report_derived(self):
        print("Derived:", self.__name, self._age)

derived = Derived("John", 0, "Doe")
print(derived.report_base()) # Base: John 33
print(derived.report_derived()) # Derived: Doe 33
print(derived._Base__name, derived._Derived__name, sep=', ') # John, Doe

isinstance и issubclass

print(isinstance(3, int)) # True
print(isinstance(4.5, int)) # False

print(issubclass(int, object)) # True
print(issubclass(float, int)) # False

Въпроси?