Объектная модель питона: наследование; staticmethod, classmethod; множественное наследование; super
Содержание
План
- Наследование
- Пример
__new__, __init__
- unbound/bound method - порядок создания класса
__slots__
__getattr__, __getattribute__, __setattr__
- staticmethod, применения
- classmethod, применения
- property
- Множественное наследование
- Основная идея и применения
- Проблемы
Материалы
Заметки
Наследование
"классический" класс <-> new-style класс = подкласс встроенного типа. т.е. создаваемый класс всегда наследуется от какого-то встроенного класса, или существующего класса(ов).
При этом Old-style классы, это то, что почти не используется (в Python [3:].* - упразднены).
встроенный тип <-> созданный тип
Наследуя класс мы создаем новый тип. Тип класса наследованного от встроенного типа именуется 'type'. Класс и тип new-style классов - это одно и то же.
New-style класс наследует функции встроенного типа. Но можно добавить новые. Добавляя новые функции - заменяет встроенные функции типа. Этим всем мы многократно пользовались - например, при создании конструктора класса.
Наглядный пример про работу классов и объектов
1 >>> def a():
2 print 'ill kill your brain'
3 >>> class A(object):
4 def __init__(self):
5 self.__dict__['show'] = a
6 def show(self):
7 print 'show is going on'
8 >>> b = A()
9 >>> b.show()
10 ill kill your brain
11 >>> del b.show
12 >>> b.show()
13 show is going on
14 >>> class A(object):
15 show = a
16 def show(self):
17 print 'show is going on'
18 >>> b = A()
19 >>> b.show()
20 show is going on
21 >>> class A(object):
22 def show(self):
23 print 'show is going on'
24 show = a
25 >>> b = A()
26 >>> b.show()
27 Traceback (most recent call last):
28 File "<pyshell#151>", line 1, in <module>
29 b.show()
30 TypeError: a() takes no arguments (1 given)
__new__ , __init__
__new__ создает новый объект наследуя его от класса. Затем запускается __init__ - который как-то модифицирует пространство имен самого объекта.
unbound/bound method
Т.е. - есть функция <function b at 0x2c4f8c0> - которая сама по себе, и лежит где-то в своем месте в памяти; есть класс <class '__main__.A'>. A.b - представляет из себя некую обертку <unbound method A.b> - которая связана с <class '__main__.A'> и в нужный момент подсовывает нам функцию <function b at 0x2c4f8c0>. А есть объект a <__main__.A object at 0x2c51f50> - и a.b - это тоже некая обертка - которая в нужный момент нам подсовывает A.b - который в свою очередь...
__slots__
.__slots__ - хранит список возможных атрибутов класса, read-only, т.е. нельзя считерить типа object.__slots__ = [new_list], и дальше веселуха. Тогда при попытке придать объекту какой-либо атрибут, вылезет ошибка.
.__slots__ vs. .__dict__ - (если кто забыл, .__dict__ <=> vars() - словарь пространства имен объекта). Если внутри класса задается .__slots__ - то внутри него не создается атрибут .__dict__, а следовательно и внутри класса не создается этого атрибута.
__getattr__, __getattribute__, __setattr__
a.x = y <=>
y = a.x <=>
1 >>> class A(object):
2 def __init__(self):
3 self.x = 'x'
4 def __getattribute__(self, name):
5 a = self.__dict__(name)
6 return a
7
8
9 >>> a = A()
10 >>> a.x
11 .
12 .
13 .
14 File "<pyshell#30>", line 3, in __getattribute__
15 return self.__dict__(name)
16 RuntimeError: maximum recursion depth exceeded while calling a Python object
staticmethod, classmethod
<=> (вспоминая, что вообще то, как раз так выглядит декоратор, и понимая, что это он и есть)
staticmethod возвращает фукцию неизмененной, т.е. не добавляет туда self в качестве аргумента.
classmethod - возвращает в качестве порвого аргумента - класс. По традиции эта переменная именуется cls.
Применеие таких методов может быть в создании множественных конструкторов. Например
Но, что же делать - если мы захотим от нашего объекта С - 100500 раз последовательно наследоваться. Ну а создавать то новый объект мы хотим каждый раз текущего класса:
property
Следовательно x = property(getx, setx) - обозначает, что атрибут x - будет использовать функцию getx, когда атрибут будут пытаться "прочитать", и setx - когда пытаться присвоить.
Вообще, полный систаксис - property(fget=None, fset=None, fdel=None, doc=None).
Зачем, собственно нужно: Если изменить __setattr__- то у нас он начнет медленнее работать, притом для всех атрибудтов(а нам может надо только над одним так глумиться). А если менять __getattr__ - то придется еще и голову поломать, как добиться того, чтобы все работало.
Множественное Наследование
Ну хорошо, хорошо. Если мы можем наследоваться от одного типа/класса, почему бы нам не наследоваться от двух...нет...трех классов?!
Так же, как и в случае "простого" наследования - потомок получит все функции.
Вроде как неплохо: отсюда мы получаем применение - создавая классы-блоки, потом мы можем наследовать новый класс от них всех (или только некого набора) - получая класс, обладающий неким набором свойств. Т.е. - множественное наследование - при достаточном мастерстве может стать очень удобным подходом.
Проблемы множественного наследования
Одноимённые методы родителей:
Решить, какой из методов лучше, очевидно, нельзя. Вызывается тот, кто стоит первый. т.е.:
и
могут иметь одинаковый набор имен функций, и разный набор самих функций.
- diamond diagram
Чуть более сложная система:
class A: ^ ^ def save(self): ... / \ / \ / \ / \ class B class C: ^ ^ def save(self): ... \ / \ / \ / \ / class D
super
super(type[, object-or-type])
что делает метод super - он направляет выполнение функции к одноименной функции предка класса объекта.
При этом он может быть использован и как просто переход к функции предка, без дополнительных усложнений.
Также в нем реализован обход по mro, что позволяет разрешать проблему diamond diagram и пр.
Альтернативы
Данный способ реализации наследования основан на том, что каждый класс будет загружаться ровно один раз:
- вот такие беды с разрешением того, чью функцию использовать
+ нет рассинхронизации классов
+ ну и меньше памяти занимает само собой
Другой способ основан на том, что для каждого класса загружаются все предки вне зависимости от того, разные это классы, или нет.
Крышеснос
1 >>> class defaultdict(dict):
2 a = 1
3 __slots__ = ['a', 'default']
4 def __init__(self, default=None):
5 dict.__init__(self)
6 self.default = default
7 >>> dict(vars(defaultdict))
8 {'a': 1, '__module__': '__main__', 'default': <member 'default' of 'defaultdict' objects>, '__slots__': ['a', 'default'], '__doc__': None, '__init__': <function __init__ at 0x1fa4758>}
9 >>> defaultdict.__dict__ is defaultdict.__dict__
10 False
Контрольная работа
Напишите декоратор once, который при первом обращении к задекорированной функции вызывает её с теми аргументами, которые ей передали, и запоминает результат. При последующих обращениях к задекорированной функции он возвращает сохранённое значение вне зависимости от аргументов.
- Заполните пропущенные места: