04. ООП 2

04. ООП 2

04. ООП 2

18 март 2015

ООП част 2

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

PEP 8

http://legacy.python.org/dev/peps/pep-0008/

Преговор

Кои от следните оператори не могат да бъдат предефинирани?

obj = SomeClass()

obj + 5
obj + [1,2,3,4]
"muffin" in obj
obj // 3
"Cake" is obj
obj % "Muffin"
obj += 1337
obj ** 10

Преговор (1.5)

Кои от следните оператори не могат да бъдат предефинирани?

obj > BigClass()
obj = "Sweet cakes and milkshakes"
obj.menu = "Sweet cakes and milkshakes"
obj **= 42
obj %= 3
obj.cake_count
obj[1337]
obj("Hello", "World")

Преговор (2)

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

Преговор (3)

>>> 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

Преговор (4)

Кога не е нужно метод на един клас да приема инстанция (self) като първи аргумент?

Vector

Ако искате да достъпвате компонентите на вектора с v[0], v[1] и v[2]

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

Vector

Можете да направите вектора да се държи като колекция

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)))

Vector (9)

Може и да имплементирате присвояване на индекс

class Vector:
    def __init__(self, x, y, z): ...
    def __getitem__(self, i): ...
    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, index):
        return getattr(self, ('x', 'y', 'z')[index])

    def __setitem__(self, index, value):
        return setattr(self, ('x', 'y', 'z')[index], 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. __getattr__ се извиква само когато в обекта няма такъв атрибут.
  2. Ако искате да предефинирате достъпа до атрибут винаги, методът е __getattribute__. Но за това по-натам

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

Опростен модел: Всеки обект се състои от две неща

  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)

Когато извикате object.attr

  1. Python връща object.__dict__['attr']
  2. Ако няма такъв, Python търси в object.__class__, ако това е функция, се връща специален обект (bound method), на който може да извикате ().
  3. Ако това в object.__class__ не е функция, то просто се връща
  4. Ако го няма там се вика object.__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", "Gaham")
david.greet_audience()
# Hello Sofia, I am David Gaham!

Наследяване (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

  1. Когато искате да "забъркате" множество атрибути и методи в един клас
  2. Когато искате клас, който предлага само едно поведение, и искате да използвате да го ползвате само като част от много други класове

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

Въпроси?