08. Обектно-ориентирано програмиране II
Преговор, философски
- Абстракция
- Енкапсулация
- Модулярност
Преговор, по същество
- Всичко е обект
- Отворени обекти
- Отворени класове
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)
- Конструктура се казва __init__
- Първия аргумент на метода е инстанцията, върху която се извиква
- Атрибутите ("член-променливите") не се декларират (класовете са отворени)
- Инстанцира се с оператор ()
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())
- В методите атрибутите се достъпват през self
- Методите се извикват с обект.име_на_метод()
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
- _coords е protected метод
- Отново, методите се извикват върху self
- _ е валидно име за променлива
Извикване през класа
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())
Ако имате само едно от двете, кое предпочитате?
(верен отговор по-късно)
Сравняване на обекти
- Можете да проверите дали два обекта са равни по стойност с ==
- Можете да проверите дали две имена сочат към един и същи обект с is
- Можете да предефинирате равенството за обекти от даден клас с метода __eq__
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
Аритметични оператори
Можете да предефинирате аритметичните оператори за вашите типове.
- __add__(self, other) за self + other
- __sub__(self, other) за self - other
- __mul__(self, other) за self * other
- __truediv__(self, other) за self / other
- __floordiv__(self, other) за self // other
- __mod__(self, other) за self % other
- __lshift__(self, other) за self << other
- __rshift__(self, other) за self >> other
- __and__(self, other) за self & other
- __xor__(self, other) за self ^ other
- __or__(self, other) за self | other
Преобразуване до стандартни типове
Има методи, които може да предефинирате, за преобразования от вашия клас към стандартен тип
- __int__(self) за int(обект)
- __float__(self) за float(обект)
- __complex__(self) за complex(обект)
- __bool__(self) за bool(обект)
Колекции
Python ви предлага и оператори, с които можете да третирате вашия клас като колекция
- __len__(self) за len(обект)
- __getitem__(self, key) за обект[key]
- __setitem__(self, key, value) за обект[key] = value
- __delitem__(self, key) за del обект[key]
- __contains__(self, item) за item in обект
Обекти, които могат да бъдат извиквани като функции
Можете да предефинирате оператора две скоби ().
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
- Обекти, чието състояние не може да се промени се наричат immutable.
- Такива са tuple, int и str
- Обекти, чието състояние може да се промени се наричат mutable.
- Такива са dict, list и set.
- Код, използващ 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
- Essential complexity и accidental complexity
- Първия пример се чете "създай списък, обходи потребителите; за всеки активен, добави в списък"
- Втория се чете "дай ми ми пощите на всички потребители, които са активни"
- В първия има повече accidental complexity
- 2 > 1 (очевидно)
- 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
- 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))
- Функциите са първокласни обекти
- Методите са атрибути на класа
- Класовете са динамични
- Ето защо 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.
Атрибути
- getattr(obj, 'name') е като obj.name
- setattr(obj, 'name', value) е като obj.name = value
- 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)
Може да предефинирате "оператора точка"
- __getattr__(self, name) за object.name
- __setattr__(self, name, value) за object.name = 'Foo'
- __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)
- __getattr__ се извиква само когато в обекта няма такъв атрибут.
- Ако искате да предефинирате достъпа до атрибут винаги, метода е __getattribute__. Но за това по-натам
Обектите и питоните
Опростен модел: Всеки обект се състои от две неща
- речник, съдържащ атрибутите на обекта (достъпен в __dict__)
- връзка към класа на обекта (достъпен в __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
- Python връща object.__dict__['attr']
- Ако няма такъв, Python търси в object.__class__, ако това е функция, се връща специален обект (bound method), на който може да извикате ().
- Ако това в object.__class__ не е функция, то просто се връща
- Ако го няма там се вика object.__getattr__('attr')
Обектите и питоните (4)
- В Python има наследяване
- Всичко наследява от object
- Това преди малко е поведението на object.__getattribute__
- Можете да го предефинирате (стига да имате причина)
Наследяване
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
- В Python енкапсулацията е въпрос на добро възпитание
- Имена от типа _име са protected
- Имена от типа __име са private
- Интерпретатор(а|ът) променя имената от тип __име до _клас__име. Нарича се 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
Още въпроси?
- Пишете ни на fmi@py-bg.net
- Страница на курса: http://fmi.py-bg.net/
- Форуми на курса: http://fmi.py-bg.net/topics
- Курсът в Twitter: http://twitter.com/pyfmi
- Курсът във Facebook: http://www.facebook.com/group.php?gid=104970619536589