Множественное наследование
При создании класса можно указать более одного базового класса.
При поиске атрибутов интерпретатор обходит указанные в заголовке классы слева направо, пока не найдет совпадение.
Так как сами базовые классы могут быть наследниками других классов, получаем дерево (не в терминах графов) наследования.
Разница в поиске атрибутов:
- старые классы - поиск атрибутов сначала продолжается по направлению снизу вверх всеми возможными путями, вплоть до вершины дерева наследования, а затем слева направо.
- новые классы (в Python 3 все классы новые) - поиск в ширину (см. раздел про super() и mro()).
Пример множественного наследования для реализации __repr__
Идея: написать 1 класс, который будет реализовывать удобный __repr__ для произвольного класса и использовать его.
Класс ListInstance в файле lister.py: получим список атрибутов экземпляра.
class ListInstance(object):
def __str__(self):
return '<Instance of {}, id = {}>:\n{}'.format(
self.__class__.__name__, # имя класса реального объекта
id(self),
self.__attrnames() # список пар атрибут - значение
)
def __attrnames(self):
"""Список пар атрибут - значение объекта в виде строки с отступом"""
return '\n'.join(('\tname {} = {}'.format(attr, self.__dict__[attr]) for
attr in sorted(self.__dict__)))
Использование:
class A(object):
def __init__(self, x):
self.x = x
def foo(self):
pass
class B(A, ListInstance):
def __init__(self, x, y):
super().__init__(x)
self.y = y
def bzz(self):
pass
x = ListInstance()
print(x)
b = B(1, 2)
print(b)
напечатает:
<Instance of ListInstance, id = 4292312080>:
<Instance of B, id = 4292312240>:
name bzz = <bound method B.bzz of <__main__.B object at 0xffd77e10>>
name foo = <bound method A.foo of <__main__.B object at 0xffd77e10>>
name x = 1
name y = 2
Метод __attrnames() сделан с __, чтобы ни в каком классе не был определен метод с таким же методом (ибо он разворачивается в _имякласса__имяметода как и прочие атрибуты, начинающиеся с __ (но не оканчивающиеся на __).
Дополонительные атрибуты тоже будут напечатаны:
>>> import lister
>>> class C(lister.ListInstance): pass
...
>>> x = C()
>>> x.a = 1; x.b = 2; x.c = 3
>>> print(x)
<Instance of C, address 40961776>:
name a=1
name b=2
name c=3
Преимущества такой реализации __str__:
- реализация вывода сделана в одном месте, ее можно централизованно поменять;
- можно использовать в различных классах.
Изменим класс ListInstance (все атрибуты, вместе с наследуемыми)
Хотим, чтобы print(x) печатало все наследуемые атрибуты (кроме специальных, начинающихся и оканчивающихся __).
Для этого заменим вызов __dir__ на dir() - получим атрибуты всех классов и чтобы находили значение этого атрибута, заменим self.__dir__[attr] на getattr(self, attr), которая будет искать в дереве наследования:
class ListInstance(object):
def __str__(self):
return '<Instance of {}, id = {}>:\n{}'.format(
self.__class__.__name__, # имя класса реального объекта
id(self),
self.__attrnames() # список пар атрибут - значение
)
def __attrnames(self):
"""Список пар атрибут - значение объекта в виде строки с отступом"""
return '\n'.join(('\tname {} = {}'.format(
attr,
'<>' if attr.startswith('__') and attr.endswith('__') else getattr(self, attr)
)
for attr in sorted(dir(self))
))
получим:
<Instance of B, id = 4292312336>:
name _ListInstance__attrnames = <bound method ListInstance.__attrnamesf <__main__.B object at 0xffd77d10>>
name __class__ = <>
.... еще много-много методов вида __метод__ .....
name __str__ = <>
name bzz = <bound method B.bzz of <__main__.B object at 0xffd77e10>>
name foo = <bound method A.foo of <__main__.B object at 0xffd77e10>>
name x = 1
name y = 2
Задача
- Выводить атрибуты, разбив их по классам;
Универсальные фабрики объектов
Иногда до этапа выполнения неизвестно, какие объекты нужно создавать.
Для создания объектов "по требованию", используют шаблон проектирования фабрика.
Сделаем функцию factory, которая создает объект нужного класса с указанными параметрами:
def factory(aClass, *args): # Кортеж с переменным числом аргументов
return aClass(*args) # Вызов aClass
class Spam:
def doit(self, message):
print(message)
class Person:
def __init__(self, name, job):
self.name = name
self.job = job
object1 = factory(Spam) # Создать объект Spam
object2 = factory(Person, 'Guido', 'guru') # Создать объект Person
Можно сделать factory гибче:
def factory(aClass, *args, **kwargs): # +kwargs
return aClass(*args, **kwargs) # Вызвать aClass