08. Обектно-ориентирано програмиране II

Преговор, философски

  1. Абстракция
  2. Енкапсулация
  3. Модулярност

Преговор, по същество

  1. Всичко е обект
  2. Отворени обекти
  3. Отворени класове

Vector (2)

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

           spam = Vector(1.0, 2.0, 3.0)
           print(spam.x)
    
  1. Конструктура се казва __init__
  2. Първия аргумент на метода е инстанцията, върху която се извиква
  3. Атрибутите ("член-променливите") не се декларират (класовете са отворени)
  4. Инстанцира се с оператор ()

Vector (3)

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

               def length(self)
                   return (self.x * self.x + self.y * self.y + self.z * self.z) ** 0.5

               spam = Vector(1.0, 2.0, 3.0)
               print(spam.length())
    
  1. В методите атрибутите се достъпват през self
  2. Методите се извикват с обект.име_на_метод()

Vector (4)

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

               def _coords(self)
                   return (self.x, self.y, self.z)

               def length(self)
                   return sum(_ ** 2 for _ in self._coords()) ** 0.5
    
  1. _coords е protected метод
  2. Отново, методите се извикват върху self
  3. _ е валидно име за променлива

Извикване през класа

           v1 = Vector(1.0, 2.0, 3.0)
           v2 = Vector(4.0, 5.0, 6.0)
           v3 = Vector(7.0, 8.0, 9.0)

           print(Vector.length(v1))
           print(Vector.length(v2))
           print(map(Vector.length, [v1, v2, v3]))
    

Vector (5)

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

Vector (6)

           class Vector
               def __init__(self, x, y, z): ...
               def length(self): ...
               def normalized(self)
                   return Vector(self.x / self.length(),
                                 self.y / self.length(), self.z / self.length())
    

normalize vs normalized

           class Vector
               def normalize(self):
                   length = self.length()
                   self.x /= length
                   self.y /= length
                   self.z /= length

               def normalized(self)
                   return Vector(self.x / self.length(), self.y / self.length(), self.z / self.length())
    

Ако имате само едно от двете, кое предпочитате?

(верен отговор по-късно)

Сравняване на обекти

        class Vector
            def __init__(self, x, y, z):
                self._coords = map(float, [x, y, z])
            def __eq__(self, other)
                return all([a == b for a, b in zip(self._coords, other._coords)])
    

По подразбиране, __eq__ е имплементирана с is

Аритметични оператори

Можете да предефинирате аритметичните оператори за вашите типове.

Преобразуване до стандартни типове

Има методи, които може да предефинирате, за преобразования от вашия клас към стандартен тип

Колекции

Python ви предлага и оператори, с които можете да третирате вашия клас като колекция

Обекти, които могат да бъдат извиквани като функции

Можете да предефинирате оператора две скоби ().

        class Stamp
            def __init__(self, name): self.name = name
            def __call__(self, something)
                print("{0} was stamped by {1}".format(something, self.name))

        >>> stamp = Stamp("The government")
        >>> stamp("That thing there")
        That thing there was stamped by The government
    

Статични методи

При статичните методи положението е малко странно

        class Person
            people = []

            @staticmethod
            def register(name)
                Person.people.append(name)
                print(len(Person.people), "people are registered now")

        >>> Person.register("Mityo the Python")
        1 people are registered now
        >>> Person.register("Pooh")
        2 people are registered now
    

Класови методи

В Python има "класови" методи, които вземат класът на който са извикани като първи аргумент. Понякога е полезно при наследяване

        class Something

            @classmethod
            def greet(cls, someone)
                print(someone, "was greeted from", cls)

        >>> Something.greet("Mityo")
        Mityo was greeted from <class '__main__.Something'>
    

mutable срещу immutable

  1. Обекти, чието състояние не може да се промени се наричат immutable.
  2. Такива са tuple, int и str
  3. Обекти, чието състояние може да се промени се наричат mutable.
  4. Такива са dict, list и set.
  5. Код, използващ immutable обекти обикновено е по-лесно разбираем от код, използващ mutable обекти.

Нещо подобно

Искаме да пишем на всички активни потребители.

        emails = []
        for user in User.all()
            if user.active():
                emails.append(user.email)
        # или
        emails = [user.email for user in User.all() if user.active()]
    

Кое и защо?

Fred Brooks

  1. Essential complexity и accidental complexity
  2. Първия пример се чете "създай списък, обходи потребителите; за всеки активен, добави в списък"
  3. Втория се чете "дай ми ми пощите на всички потребители, които са активни"
  4. В първия има повече accidental complexity
  5. 2 > 1 (очевидно)
  6. mutable векторите имат излишен accidental complexity

Vector (7)

        class Vector
            def __init__(self, x, y, z): ...
            def __add__(self, other)
                return Vector(self.x + other.x, self.y + other.y, self.z + other.z)

        spam = Vector(1.0, 2.0, 3.0)
        eggs = Vector(4.0, 5.0, 6.0)
        breakfast = spam + eggs
    
  1. self + other се предефинира с __add__(self, other)

Vector (8)

        class Vector
            def __init__(self, x, y, z): ...
            def _coords(self): ...
            def __add__(self, other)
                return Vector(*map(sum, zip(self._coords(), other._coords())))

        spam = Vector(1.0, 2.0, 3.0)
        eggs = Vector(4.0, 5.0, 6.0)
        breakfast = spam + eggs
    

По-хакерско, но спорно дали по-четимо

Vector (9)

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

        def addition(a, b)
            return Vector(a.x + b.x, a.y + b.y, a.z + b.z)

        Vector.__add__ = addition

        print(Vector(1.0, 2.0, 3.0) + Vector(4.0, 5.0, 6.0))
    
  1. Функциите са първокласни обекти
  2. Методите са атрибути на класа
  3. Класовете са динамични
  4. Ето защо self е явен

Vector (10)

Ако искате да достъпвате компонентите на вектора с 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 (11)

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

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

            def __len__(self)
                return 3

            def length(self)
                return sum(_ ** 2 for _ in self) ** 0.5

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

Vector (12)

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

        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
    

Разбира се, по-добре вектора да е immutable.

Атрибути

  1. getattr(obj, 'name') е като obj.name
  2. setattr(obj, 'name', value) е като obj.name = value
  3. delattr(obj, 'name') е като del obj.name
        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)

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

  1. __getattr__(self, name) за object.name
  2. __setattr__(self, name, value) за object.name = 'Foo'
  3. __delattr__(self, name) за del object.name

Атрибути (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()
    

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

Методи се търсят в широчина (breath-first)

        class A
            def spam(self): return "A.spam"
        class B(A)
            pass
        class C(A)
            def spam(self): return "C.spam"
        class D(B, C)
            pass

        print(D().spam()) # C.spam
    

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

Да, в широчина

        class A
            def spam(self): return "A.spam"
        class B(A)
            def spam(self): return "B.spam"
        class C(A)
            def spam(self): return "C.spam"
        class D(B, C)
            pass

        print(D().spam()) # B.spam
    

24 карата

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

(но има и изключения)

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
    

Още въпроси?