2012-01-12 4 views
4

Согласно документации, super(cls, obj) возвращаетпитон: супер() - как прокси объект, который начинает поиск MRO в указанном классе

прокси-объект, который метод делегатов призывает родителей или родственного класса типа ЦБС

Я понимаю, почему super() предлагает эту функциональность, но мне нужно что-то немного иначе: мне нужно создать прокси-объект, который вызывает делегирует методы (и приписывать Lookups) к классу 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() по двум причинам:

  1. super() только он проксирует для первого вызова; последующие вызовы будут выполняться как обычно, так как к тому моменту используется объект, а не его прокси.

  2. super() не разрешает доступ к атрибутам.

Таким образом, мой вопрос по заданию имеет (почти) идеальный ответ на Python.

Оказалось, что на более высоком уровне я пытался сделать что-то, чего я не должен (see my comments here).

+0

Вы не можете убедиться, что он не действует как экземпляр класса C, потому что это то, что оно есть. Если вы действительно серьезно хотите экземпляр B(), я рекомендую вам просто сделать экземпляр B с необходимыми атрибутами. Вы можете скопировать их из C. –

+0

@LennartRegebro: ну, я не требовал, чтобы 'b' был экземпляром' class C', и на самом деле это не так; это экземпляр совершенно другого класса ('делегат класса 'в случае решения Sven,' super super' в случае встроенного 'super()' и т. д.). В стороне, вы можете быть правы, что 'b' не может быть пресечено от предательства своей способности (косвенно) доступа к методам и атрибутам класса C'. Если да, можете ли вы привести пример того, как «Делегат (B, c)» Свена будет действовать как экземпляр класса C? – max

ответ

3

Этот класс должен охватывать наиболее распространенные случаи:

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) 

(с именами из вашего кода примера.)

Ограничения:

  1. Вы не можете получить некоторые специальные атрибуты, такие как __class__ и т. Д. Из класса, который вы передаете в конструкторе через этот прокси. (Эти перестройки также относятся к super.)

  2. Это может привести к потере веса, если атрибут, который вы хотите получить, - это какой-то прослойка дескриптора.

Редактировать: Если вы хотите, чтобы код в обновление к вашему вопросу, чтобы работать по желанию, вы можете использовать код 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 
+0

Я только что обновил вопрос, так как получается, что я фактически передаю 'b' кому-то другому. Это означает, что мое решение 'functools' больше не работает. Кажется, что то, что вы делаете, точно соответствует моим потребностям, позвольте мне немного проверить его. Я предполагаю, что просто говорю «b = Делегировать (B, c)»? – max

+0

Это не ведет себя как 'super' при наличии множественного наследования. Он делегирует MRO '_delegate_cls', независимо от того, где он находится в полном MRO' _delegate_obj'. Фактически он делегирует '_delegate_cls', независимо от того, является ли это ** ** полным MRO' _delegate_obj', что интересно. :) Но учитывая, что OP явно говорил о множественном наследовании и MRO, это может быть нежелательным. Кроме того, он не поддерживает поиск атрибутов (также «супер» не замечает вас). – Ben

+0

Ах да, нам нужно проверить, что cls находится в MRO, а также если метод не найден в cls, начните поиск со следующего класса в MRO AFTER cls – max

2

отдельный вопрос, документацию для супер() (в 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, которые часто начинаются в классе, а не непосредственно с экземпляром. Но это ведет себя так же, как вы просили умеренно хорошо (с предостережением, изложенным в рекламной паузе в первой части моего сообщения, поиск атрибутов таким образом не даст вам никаких других результатов, чем поиск их непосредственно в экземпляре).

+0

'super' и' Delegate' не обрабатывают атрибуты экземпляра, потому что это не имеет большого смысла для этого. Тем не менее, оба они * корректно обрабатывают атрибуты класса. –

+0

@SvenMarnach Он предназначен для заявленной цели OP для объединения экземпляра, который реализует методы, начинающиеся дальше в MRO, а затем переходит к другому коду, который не знает, что он получает обертку. Часто, но не всегда, другой код будет манипулировать объектом исключительно с помощью методов, поэтому вы также захотите получить атрибуты. – Ben

+0

Правильно, понял. –