Usar método de clase, no método de instancia con el mismo nombre

Tengo el siguiente fragmento de código:

class Meta(type): def __getattr__(self, name): pass class Klass(object): __metaclass__ = Meta def get(self, arg): pass 

Ahora, si lo hago:

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

Todo funciona como se espera (se llama el método de instancia get ).

Pero si lo hago:

 Klass.get('arg') 

de nuevo, se encuentra el método de instancia y se da una excepción, ya que se trata como un método independiente.

¿Cómo puedo hacer una llamada a Klass.get('arg') pasar por el __getattr__ definido en la metaclase? Necesito esto porque quiero hacer un proxy de todos los métodos llamados en una clase a otro objeto (esto se haría en __getattr__ ).

Tendrá que buscar el método en el tipo y pasar el primer ( self ) argumento manualmente:

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

Este problema es la razón por la que se buscan nombres de métodos especiales utilizando esta ruta ; las clases personalizadas no serían hashables ni se podrían representar si Python no lo hiciera.

Podrías hacer uso de ese hecho; en lugar de usar un método get() , use __getitem__ , sobrecargando [..] syntax de indexación, y haga que Python haga la danza type(ob).methodname(ob, *args) para usted:

 class Meta(type): def __getitem__(self, arg): pass class Klass(object): __metaclass__ = Meta def __getitem__(self, arg): pass 

y luego Klass()['arg'] y Klass['arg'] funcionan como se espera.

Sin embargo, si tiene que hacer que Klass.get() comporte de manera diferente (y la búsqueda para que Meta.__getattribute__ intercepte Meta.__getattribute__ ) tiene que manejar esto explícitamente en su método Klass.get ; se llamará con un argumento menos si se llama en la clase, puede usar eso y devolver una llamada en la clase:

 _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 ... 

También podría manejar esto en un descriptor que imite los objetos de método:

 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) 

y usa esto como un decorador:

 class Klass(object): __metaclass__ = Meta @class_and_instance_method def get(self, arg): pass 

y redirigirá las búsquedas a la metaclase si no hay una instancia para enlazar a:

 >>> 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' 

La aplicación del decorador se puede hacer en la metaclase:

 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) 

y luego puede agregar métodos a las clases usando esta metaclase sin tener que preocuparse por la delegación:

 >>> 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'