Отдельный вопрос, документация по super () (в Python 3.2)
говорит только о методе делегирования, и не уточняет, что
поиск атрибутов для прокси также выполняется аналогичным образом. Это
случайное упущение?
Нет, это не случайно. super()
ничего не делает для поиска атрибутов. Причина в том, что атрибуты в экземпляре не связаны с конкретным классом, они просто есть. Учтите следующее:
class A:
def __init__(self):
self.foo = 'foo set from A'
class B(A):
def __init__(self):
super().__init__()
self.bar = 'bar set from B'
class C(B):
def method(self):
self.baz = 'baz set from C'
class D(C):
def __init__(self):
super().__init__()
self.foo = 'foo set from D'
self.baz = 'baz set from D'
instance = D()
instance.method()
instance.bar = 'not set from a class at all'
Какой класс "владеет" foo
, bar
и baz
?
Если бы я хотел просмотреть instance
как экземпляр C, должен ли он иметь атрибут baz
до вызова method
? Как насчет потом?
Если я рассматриваю instance
как экземпляр A, какое значение должно иметь foo
? Должно ли bar
быть невидимым, потому что оно было добавлено только в B, или видимым, потому что для него было установлено значение вне класса?
Все эти вопросы - нонсенс в Python. Нет никакого способа спроектировать систему с семантикой Python, которая могла бы дать разумные ответы на них. __init__
даже не является особенным с точки зрения добавления атрибутов к экземплярам класса; это просто совершенно обычный метод, который вызывается как часть протокола создания экземпляра. Любой метод (или вообще код из другого класса, или вообще не из какого-либо класса) может создавать атрибуты в любом экземпляре, на который он ссылается.
Фактически, все атрибуты instance
хранятся в одном и том же месте:
>>> instance.__dict__
{'baz': 'baz set from C', 'foo': 'foo set from D', 'bar': 'not set from a class at all'}
Невозможно сказать, какие из них были изначально установлены каким классом или были в последний раз установлены каким классом, или какой-либо другой формой собственности, которую вы хотите. Конечно, нет никакого способа получить "1031 *, находящегося в тени D.foo
", как вы могли бы ожидать от C ++; они являются одним и тем же атрибутом, и любая запись в него одним классом (или из другого места) приведет к засорению значения, оставленного в нем другим классом.
Следствием этого является то, что super()
не выполняет поиск атрибутов так же, как поиск методов; это невозможно, и ни один код не может быть написан вами.
На самом деле, из-за выполнения некоторых экспериментов, ни super
, ни Delegate
Свена вообще не поддерживают прямой поиск атрибутов!
class A:
def __init__(self):
self.spoon = 1
self.fork = 2
def foo(self):
print('A.foo')
class B(A):
def foo(self):
print('B.foo')
b = B()
d = Delegate(A, b)
s = super(B, b)
Тогда оба метода работают как положено:
>>> d.foo()
A.foo
>>> s.foo()
A.foo
Но:
>>> d.fork
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
d.fork
File "/tmp/foo.py", line 6, in __getattr__
x = getattr(self._delegate_cls, name)
AttributeError: type object 'A' has no attribute 'fork'
>>> s.spoon
Traceback (most recent call last):
File "<pyshell#45>", line 1, in <module>
s.spoon
AttributeError: 'super' object has no attribute 'spoon'
Таким образом, они оба действительно работают только для вызова некоторых методов, а не для передачи в произвольный сторонний код, чтобы представить себя экземпляром класса, которому вы хотите делегировать.
К сожалению, они не ведут себя одинаково при наличии множественного наследования. Дано:
class Delegate:
def __init__(self, cls, obj):
self._delegate_cls = cls
self._delegate_obj = obj
def __getattr__(self, name):
x = getattr(self._delegate_cls, name)
if hasattr(x, "__get__"):
return x.__get__(self._delegate_obj)
return x
class A:
def foo(self):
print('A.foo')
class B:
pass
class C(B, A):
def foo(self):
print('C.foo')
c = C()
d = Delegate(B, c)
s = super(C, c)
Тогда:
>>> d.foo()
Traceback (most recent call last):
File "<pyshell#50>", line 1, in <module>
d.foo()
File "/tmp/foo.py", line 6, in __getattr__
x = getattr(self._delegate_cls, name)
AttributeError: type object 'B' has no attribute 'foo'
>>> s.foo()
A.foo
Поскольку Delegate
игнорирует полную MRO любого класса, _delegate_obj
является экземпляром, только используя MRO _delegate_cls
. Принимая во внимание, что super
делает то, что вы задали в вопросе, но поведение кажется довольно странным: он не заключает в себе экземпляр C, притворяясь, что это экземпляр B, потому что для прямых экземпляров B не определено foo
.
Вот моя попытка:
class MROSkipper:
def __init__(self, cls, obj):
self.__cls = cls
self.__obj = obj
def __getattr__(self, name):
mro = self.__obj.__class__.__mro__
i = mro.index(self.__cls)
if i == 0:
# It's at the front anyway, just behave as getattr
return getattr(self.__obj, name)
else:
# Check __dict__ not getattr, otherwise we'd find methods
# on classes we're trying to skip
try:
return self.__obj.__dict__[name]
except KeyError:
return getattr(super(mro[i - 1], self.__obj), name)
Я полагаюсь на атрибут __mro__
классов, чтобы правильно определить, с чего начать, тогда я просто использую super
. Вместо этого вы можете пройтись по цепочке MRO самостоятельно, проверив класс __dict__
s на наличие методов, если странность возврата на один шаг к использованию super
слишком велика.
Я не пытался обрабатывать необычные атрибуты; те, которые реализованы с помощью дескрипторов (включая свойства), или те магические методы, которые заглядывают за кулисы Python, которые часто начинаются с класса, а не с экземпляра напрямую. Но это ведет себя так, как вы в меру хорошо спросили (с оговоркой, изложенной в ad nauseum в первой части моего поста; поиск атрибутов таким образом не даст вам никаких других результатов, чем поиск их непосредственно в экземпляре).