2013-12-11 4 views
8

Вот пример игрушек для создания декоратора, который позволяет объявлять имена атрибутов, которые должны быть обязательными для частей «проверки интерфейса» по стандартным шаблонам __subclasshook__ и __instancecheck__.Зачем «__subclasshook__» быть обезврежен в метаклассе, но `__instancecheck__` не может?

Кажется, что работаю как ожидалось, когда я украшаю класс Foo. Я делаю класс Bar, не имеющий отношения к Foo, но который имеет необходимые атрибуты, и он правильно удовлетворяет isinstance(instance_of_bar, Foo) == True.

Но тогда как еще один пример, я сделать подкласс dict дополненной так что key значения будут доступны с getattr синтаксиса, а также (например, dict где d['a'] можно заменить d.a, чтобы получить тот же результат). В этом случае атрибуты являются только атрибутами экземпляра, поэтому __instancecheck__ должен работать.

Вот код. Обратите внимание, что, учитывая, что работает пример с Bar, выбор функции «monkeypatch» __subclasshook__ в класс Foo (который имеет метакласс) отлично работает. Так что не кажется, что нужно определить функцию непосредственно в определении класса метакласса.

#Using Python 2.7.3 

import abc 
def interface(*attributes): 
    def decorator(Base): 

     def checker(Other): 
      return all(hasattr(Other, a) for a in attributes) 

     def __subclasshook__(cls, Other): 
      if checker(Other): 
       return True 
      return NotImplemented 

     def __instancecheck__(cls, Other): 
      return checker(Other) 

     Base.__subclasshook__ = classmethod(__subclasshook__) 
     Base.__instancecheck__ = classmethod(__instancecheck__) 
     return Base 

    return decorator 

@interface("x", "y") 
class Foo(object): 
    __metaclass__ = abc.ABCMeta 
    def x(self): return 5 
    def y(self): return 10 

class Bar(object): 
    def x(self): return "blah" 
    def y(self): return "blah" 

class Baz(object): 
    def __init__(self): 
     self.x = "blah" 
     self.y = "blah" 

class attrdict(dict): 
    def __getattr__(self, attr): 
     return self[attr] 

f = Foo() 
b = Bar() 
z = Baz() 
t = attrdict({"x":27.5, "y":37.5}) 

print isinstance(f, Foo) 
print isinstance(b, Foo) 
print isinstance(z, Foo) 
print isinstance(t, Foo) 

Печать:

True 
True 
False 
False 

Это просто игрушка пример - я не ищу лучшие способы для реализации моего attrdict класса. Пример Bar демонстрирует работу с обезьяной __subclasshook__. В двух других примерах демонстрируется отказ __instancecheck__ для экземпляров, которые имеют простые атрибуты экземпляра для проверки. В таких случаях __instancecheck__ даже не называется.

я могу вручную проверить, что условие из моей __instancecheck__ функции выполняется экземпляром attrdict (то есть, hasattr(instance_of_attrdict, "x") является True по мере необходимости) или z.

Опять же, похоже, что это нормально для экземпляра Bar. Это говорит о том, что __subclasshook__ правильно применяется декоратором и что исправление пользовательского __metaclass__ не является проблемой. Но __instancecheck__, похоже, не вызван в процессе.

Почему можно определить __subclasshook__ за пределами определения класса метакласса и добавлено позже, но не __instancecheck__?

+0

Это на самом деле не имеет ничего общего с встроенными типами. Параметр 'Other' для' __subclasshook__' является * классом *, а не экземпляром класса. Если вы заменили 'attrdict' классом с именем' Baz', который присвоил 'self.x' и' self.y' в своем методе '__init__', вы получите точные результаты, потому что сам класс не имеет таких атрибуты, но экземпляр этого делает. – Blender

+0

Но почему бы не '__subclasshook__' (в силу' hasattr (Other, a) 'return' False', поскольку я неправильно пытаюсь использовать 'Other', как экземпляр вместо класса), просто верните' False', вместо этого из «AssertionError», который я вижу. Затем следует следующее: как я могу использовать '__subclasshook__' для определения проверки того, имеют ли экземпляры * необходимые атрибуты, которые будут считаться экземпляром, независимо от того, были ли эти атрибуты обезврежены, созданы как атрибуты класса, созданные как атрибуты экземпляра в '__init__' или что-то еще. – ely

+0

Ваш код не должен работать в первую очередь, так как 'all()' возвращает логическое значение, которое не имеет метода 'next()'. «AssertionError» просто говорит, что '__subclasshook__' ожидает, что тип возвращаемого значения будет логическим, в то время как вы возвращаете что-то еще. '__instancecheck__' - это то, что вы действительно должны реализовывать. – Blender

ответ

2

Все работает как есть. Если вы используете __metaclass__, вы переписываете процесс создания класса. Похоже, ваш monkeypatch работает для __subclasshook__, но он называется только от функции __subclasshook__ от ABCMeta. Вы можете проверить это с этим:

>>> type(Foo) 
<class 'abc.ABCMeta'> 

Чтобы быть более явным: __subclasshook__ случае работы случайно в этом примере, потому что метакласс-х __subclasscheck__ происходит отложить до класса __subclasshook__ в некоторых ситуациях.Протокол __instancecheck__ метакласса никогда не откладывает до определения класса __instancecheck__, поэтому в конечном итоге вызывается обезглавленная версия __subclasshook__, но обезопашенная версия __instancecheck__ не вызывается.

Подробнее: Если вы создаете класс с метаклассом, тип класса будет метаклассом. В этом случае ABCMeta. И определение isinstance() говорит следующее: 'isinstance (object, class-or-type-or-tuple) -> bool', что означает, что проверка экземпляра будет выполняться в данном классе, типе или кортеже классов/типы. В этом случае проверка isinstance будет выполнена на ABCMeta (ABCMeta.__instancecheck__() будет называться). Поскольку monkeypatch был применен к классу Foo, а не ABCMeta, метод __instancecheck__Foo никогда не будет работать. Но __instancecheck__ из ABCMethod вызовет метод __subclasscheck__ сам по себе, и этот второй метод попытается выполнить валидацию, выполнив метод созданного класса __subclasshook__ (в этом примере Foo).

В этом случае вы можете получить желаемое поведение, если вы перезаписать функции метакласса как это:

def interface(*attributes): 
    def decorator(Base): 

     def checker(Other): 
      return all(hasattr(Other, a) for a in attributes) 

     def __subclasshook__(cls, Other): 
      if checker(Other): 
       return True 
      return NotImplemented 

     def __instancecheck__(cls, Other): 
      return checker(Other) 

     Base.__metaclass__.__subclasshook__ = classmethod(__subclasshook__) 
     Base.__metaclass__.__instancecheck__ = classmethod(__instancecheck__) 
     return Base 

    return decorator 

И выход с обновленным кодом:

True 
True 
True 
True 

Другой подход был бы чтобы определить свой собственный класс для работы в качестве метакласса и создать вид протокола __instancecheck__, который вы ищете, чтобы он сделал. Отложить до определения класса __instancecheck__, когда определение метакласса соответствует некоторым критериям отказа. Затем установите __metaclass__ как , что класс внутри Foo и ваш существующий декоратор должен работать как есть.

Подробнее: A good post about metaclasses

+0

Это хорошее обходное решение, но не ответ на вопрос. Если бы я написал свое определение класса 'Foo' с помощью« @classmethod ... def __instancecheck __ (...) », тогда он работает. То же самое для '__subclasshook__'. Поэтому определение этих функций как classmethods * для 'Foo' себя * ** работает **. Monkeypatching их на 'Foo' также работает - для' __subclasshook__' (опять же, никогда не нужно применять его к '__metaclass__', как и вы). Итак, учитывая все это, почему одна и та же процедура не работает для '__instancecheck__'? Другими словами, между ними существует разница между ними, без каких-либо задокументированных причин ожидать этого. – ely

+0

Это не обходной путь. В вашем примере вы создаете экземпляр 'ABCMeta', а не' Foo' (вы можете увидеть его на примере 'type (Foo)'. И поскольку это экземпляр 'ABCMeta', он будет действовать как' ABCMeta ' '. Вы можете увидеть его источник здесь: [link] (http://hg.python.org/cpython/file/2.7/Lib/abc.py) Этот метод определен здесь и когда вы вызываете' isinstance ', этот метод будет выполнен. Поэтому, если вы хотите перезаписать метод, вам нужно перезаписать/обезвредить это, а не метод оригинального' Foo' (потому что он действует как 'Foo', но это не' Foo') –

+0

Я считаю, что тот факт, что мой '__subclasshook__' работает после monkeypatching, показывает, что он правильно определен как функция * метакласса *, а не' 'Foo' '. Я согласен с вами в печати' type (Foo) 'и видя 'ABCMeta' - это неудивительно для меня, это ожидается. Я не уверен, что вы думаете об этом. Даже после повторного чтения вашего ответа и комментариев мой вопрос все еще остается. – ely

 Смежные вопросы

  • Нет связанных вопросов^_^