отдельный вопрос, документацию для супер() (в 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'}
Там нет никакого способа узнать, какой из них изначально были установлены какому классу, или были последний набор по какому классу, или какой-либо мерой владения, который вы хотите. Конечно, нет никакого способа получить «A.foo
, затененный 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
игнорирует полный МРО независимо от класса _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, которые часто начинаются в классе, а не непосредственно с экземпляром. Но это ведет себя так же, как вы просили умеренно хорошо (с предостережением, изложенным в рекламной паузе в первой части моего сообщения, поиск атрибутов таким образом не даст вам никаких других результатов, чем поиск их непосредственно в экземпляре).
Вы не можете убедиться, что он не действует как экземпляр класса C, потому что это то, что оно есть. Если вы действительно серьезно хотите экземпляр B(), я рекомендую вам просто сделать экземпляр B с необходимыми атрибутами. Вы можете скопировать их из C. –
@LennartRegebro: ну, я не требовал, чтобы 'b' был экземпляром' class C', и на самом деле это не так; это экземпляр совершенно другого класса ('делегат класса 'в случае решения Sven,' super super' в случае встроенного 'super()' и т. д.). В стороне, вы можете быть правы, что 'b' не может быть пресечено от предательства своей способности (косвенно) доступа к методам и атрибутам класса C'. Если да, можете ли вы привести пример того, как «Делегат (B, c)» Свена будет действовать как экземпляр класса C? – max