2015-02-24 4 views
1

я следующий фрагмент кода:Использование метода класса не метод экземпляра с тем же именем

class Meta(type): 
    def __getattr__(self, name): 
     pass 

class Klass(object): 
    __metaclass__ = Meta 

    def get(self, arg): 
     pass 

Теперь, если я:

kls = Klass() 
kls.get('arg') 

все работает, как ожидалось (метод экземпляра get называется).

Но если я:

Klass.get('arg') 

снова метод экземпляра найден и исключение дается, поскольку она рассматривается в качестве несвязанного метода.

Как я могу позвонить по телефону Klass.get('arg') через __getattr__, определенный в метаклассе? Мне нужно это, потому что я хочу проксировать все методы, вызываемые классом, на другой объект (это будет сделано в __getattr__).

+0

Вы думали о переопределении '__getattribute__' и выполнении некоторой проверки в этой логике? –

+0

Какой '__getattribute__'? – linkyndy

+0

'Met .__ getattribute__'. Поскольку '__getattribute__' вызывается * перед * dict lookup (если я не ошибаюсь). –

ответ

5

Вы должны искать метод от типа и передать в первом (self) аргумент вручную:

type(Klass).get(Klass, 'arg') 

Эта проблема является очень причина, по которой special method names are looked up using this path; пользовательские классы не будут хешировать или представлять себя, если Python этого не сделает.

Вы можете использовать этот факт ; а не использовать метод get(), используйте __getitem__, перегрузку [..] синтаксиса индексации, и есть Python сделать type(ob).methodname(ob, *args) танец для вас:

class Meta(type): 
    def __getitem__(self, arg): 
     pass 

class Klass(object): 
    __metaclass__ = Meta 

    def __getitem__(self, arg): 
     pass 

, а затем Klass()['arg'] и Klass['arg'] работы, как ожидалось.

Однако, если вы должны иметь Klass.get() себя по-разному (и поиск для этого, чтобы быть перехвачены Meta.__getattribute__) вы должны явно справиться с этим в вашем методе Klass.get; она будет вызвана с одним аргументом меньше, если называется на классе, вы могли бы использовать это и возвращаете вызов класса:

_sentinel = object() 

class Klass(object): 
    __metaclass__ = Meta 

    def get(self, arg=_sentinel): 
     if arg=_sentinel: 
      if isinstance(self, Klass): 
       raise TypeError("get() missing 1 required positional argument: 'arg'") 
      return type(Klass).get(Klass, self) 
     # handle the instance case ... 

Вы также мог бы справиться с этим в descriptor что метод подражает объекты:

class class_and_instance_method(object): 
    def __init__(self, func): 
     self.func = func 
    def __get__(self, instance, cls=None): 
     if instance is None: 
      # return the metaclass method, bound to the class 
      type_ = type(cls) 
      return getattr(type_, self.func.__name__).__get__(cls, type_) 
     return self.func.__get__(instance, cls) 

и использовать это в качестве декоратора:

class Klass(object): 
    __metaclass__ = Meta 

    @class_and_instance_method 
    def get(self, arg): 
     pass 

, и он будет перенаправлять внешний вид окна в метакласса если нет ни одного случая, чтобы связываться с:

>>> class Meta(type): 
...  def __getattr__(self, name): 
...   print 'Meta.{} look-up'.format(name) 
...   return lambda arg: arg 
... 
>>> class Klass(object): 
...  __metaclass__ = Meta 
...  @class_and_instance_method 
...  def get(self, arg): 
...   print 'Klass().get() called' 
...   return 'You requested {}'.format(arg) 
... 
>>> Klass().get('foo') 
Klass().get() called 
'You requested foo' 
>>> Klass.get('foo') 
Meta.get look-up 
'foo' 

Применение декоратора может быть сделана в метаклассе:

class Meta(type): 
    def __new__(mcls, name, bases, body): 
     for name, value in body.iteritems(): 
      if name in proxied_methods and callable(value): 
       body[name] = class_and_instance_method(value) 
     return super(Meta, mcls).__new__(mcls, name, bases, body) 

, а затем вы можете добавить методы в классы с помощью этого метакласса без необходимости беспокоиться о делегировании:

>>> proxied_methods = ('get',) 
>>> class Meta(type): 
...  def __new__(mcls, name, bases, body): 
...   for name, value in body.iteritems(): 
...    if name in proxied_methods and callable(value): 
...     body[name] = class_and_instance_method(value) 
...   return super(Meta, mcls).__new__(mcls, name, bases, body) 
...  def __getattr__(self, name): 
...   print 'Meta.{} look-up'.format(name) 
...   return lambda arg: arg 
... 
>>> class Klass(object): 
...  __metaclass__ = Meta 
...  def get(self, arg): 
...   print 'Klass().get() called' 
...   return 'You requested {}'.format(arg) 
... 
>>> Klass.get('foo') 
Meta.get look-up 
'foo' 
>>> Klass().get('foo') 
Klass().get() called 
'You requested foo' 
+0

Метод 'get' был просто именем, он ничего не делает рядом с' __getitem__'. Но спасибо за то, что вы указали это решение. Так нет ли другого решения для этого, но этот «уродливый» звонок? Я пытаюсь создать интерфейс для базовых операций, и я искал что-то красивое ... P.S .: Ссылка, которую вы опубликовали, не находит часть «#». – linkyndy

+0

Я отредактировал свой вопрос, добавил к нему свою фактическую заботу (извините за то, что не был конкретным с самого начала, хотел сделать пример как можно проще). – linkyndy

+0

@AndreiHorak: ссылка исправлена ​​(она была первоначально ссылкой Python 3, и я вручную скорректировал ее на 2, но забыл, что название раздела изменилось). –

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