python: super () - подобный прокси-объект, который запускает поиск MRO в указанном классе
Согласно документам, super(cls, obj)
возвращает
Прокси-объект, делегирующий вызовы метода родителю или родному брату класс типа cls
Я понимаю, почему super()
предлагает эту функциональность, но мне нужно что-то немного другое: мне нужно создать прокси-объект, который делегирует вызовы методов (и поиск атрибутов) самому классу cls
; и как в super
, если cls
не реализует метод / атрибут, мой прокси должен продолжать искать в порядке MRO новый Не исходный класс). Есть ли какая-нибудь функция, которую я могу написать, чтобы достичь этого?
Пример:
class X:
def act():
#...
class Y:
def act():
#...
class A(X, Y):
def act():
#...
class B(X, Y):
def act():
#...
class C(A, B):
def act():
#...
c = C()
b = some_magic_function(B, c)
# `b` needs to delegate calls to `act` to B, and look up attribute `s` in B
# I will pass `b` somewhere else, and have no control over it
Конечно, я мог бы сделать b = super(A, c)
, но это зависит от знания точной иерархии классов и того факта, что B
следует за A
в MRO. Он молча сломается, если в будущем изменится какое-либо из этих двух предположений. (Обратите внимание, что super
не делает никаких таких предположений!)
Если бы мне просто нужно было позвонить b.act()
, я мог бы использовать B.act(c)
. Но это так. передаю b
кому-то другому, и понятия не имею, что они с этим сделают. Мне нужно убедиться, что он не предаст меня и не начнет действовать как экземпляр class C
в какой-то момент.
Отдельный вопрос, документация для super()
(в Python 3.2) говорит только о делегировании метода и не уточняет, что поиск атрибутов для прокси также выполняется таким же образом. Может быть, это случайное упущение?
EDIT
Обновленный Делегатский подход работает следующим образом пример также:
class A:
def f(self):
print('A.f')
def h(self):
print('A.h')
self.f()
class B(A):
def g(self):
self.f()
print('B.g')
def f(self):
print('B.f')
def t(self):
super().h()
a_true = A()
# instance of A ends up executing A.f
a_true.h()
b = B()
a_proxy = Delegate(A, b)
# *unlike* super(), the updated `Delegate` implementation would call A.f, not B.f
a_proxy.h()
Обратите внимание, что обновленный class Delegate
ближе к тому, что я хочу, чем super()
по двум причинам:
-
super()
только проксирование для первого вызова; последующие вызовы будут происходить как обычно, так как к тому времени используется объект, а не его прокси. -
super()
не разрешает доступ к атрибутам.
Оказывается, что на более высоком уровне я был пытаясь сделать что-то, что я не должен (смотрите мои комментарии здесь).
2 ответа:
Этот класс должен охватывать наиболее распространенные случаи:
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
Используйте его так:
b = Delegate(B, c)
(с именами из вашего примера кода.)
Ограничения:
Вы не можете получить некоторые специальные атрибуты, такие как
__class__
и т.д. из класса, который вы передаете в конструктор через этот прокси. (Эти рестиции также применимы кsuper
.)Это может вести себя weired, если атрибут, который вы хотите получить, является каким-то weired дескриптор.
Edit: Если вы хотите, чтобы код в обновлении к вашему вопросу работал по желанию, вы можете использовать код foloowing:
class Delegate: def __init__(self, cls): self._delegate_cls = cls def __getattr__(self, name): x = getattr(self._delegate_cls, name) if hasattr(x, "__get__"): return x.__get__(self) return x
Это передает прокси-объект в качестве параметра
self
любому вызываемому методу, и ему вообще не нужен исходный объект, поэтому я удалил его из конструктора.Если вы также хотите, чтобы атрибуты экземпляра были доступны, вы можете использовать эту версию:
class Delegate: def __init__(self, cls, obj): self._delegate_cls = cls self._delegate_obj = obj def __getattr__(self, name): if name in vars(self._delegate_obj): return getattr(self._delegate_obj, name) x = getattr(self._delegate_cls, name) if hasattr(x, "__get__"): return x.__get__(self) return x
Отдельный вопрос, документация для 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'}
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
игнорирует полный 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 в первой части моего поста; поиск атрибутов таким образом не даст вам никаких других результатов, чем поиск их непосредственно в данном случае).