使用类方法而不是具有相同名称的实例方法 [英] Use class method not instance method with the same name
问题描述
我有以下代码段:
class Meta(type):
def __getattr__(self, name):
pass
class Klass(object):
__metaclass__ = Meta
def get(self, arg):
pass
现在,如果我这样做了:
Now, if I do:
kls = Klass()
kls.get('arg')
一切正常(调用实例方法get
).
everything works as expected (the instance method get
is called).
但如果我这样做:
Klass.get('arg')
再次找到实例方法并给出异常,因为它被视为未绑定方法.
again the instance method is found and an exception is given, since it is treated as an unbound method.
如何通过元类中定义的__getattr__
调用Klass.get('arg')
?我需要这样做是因为我想将类上调用的所有方法代理到另一个对象(这将在__getattr__
中完成).
How can I make a call to Klass.get('arg')
go through the __getattr__
defined in the metaclass? I need this because I want to proxy all methods called on a class to another object (this would be done in __getattr__
).
推荐答案
您必须在类型上查找方法,然后手动传递第一个(self
)参数:
You'll have to look up the method on the type and pass in the first (self
) argument manually:
type(Klass).get(Klass, 'arg')
此问题正是特殊方法名称的原因使用此路径进行查找;如果Python不这样做,则自定义类本身将不可哈希或不可表示.
This problem is the very reason that special method names are looked up using this path; custom classes would not be hashable or representable themselves if Python didn't do this.
您可以利用这一事实;而不是使用get()
方法,请使用__getitem__
,重载[..]
索引语法,然后让Python为您执行type(ob).methodname(ob, *args)
跳舞:
You could make use of that fact; rather than use a get()
method, use __getitem__
, overloading [..]
indexing syntax, and have Python do the type(ob).methodname(ob, *args)
dance for you:
class Meta(type):
def __getitem__(self, arg):
pass
class Klass(object):
__metaclass__ = Meta
def __getitem__(self, arg):
pass
,然后Klass()['arg']
和Klass['arg']
会按预期工作.
and then Klass()['arg']
and Klass['arg']
work as expected.
但是,如果必须让Klass.get()
的行为有所不同(并且查找该内容以Meta.__getattribute__
进行拦截),则必须在Klass.get
方法中显式处理此问题.如果在类上调用它,则将减少一个参数的调用,您可以利用该参数并返回对该类的调用:
However, if you have to have Klass.get()
behave differently (and the lookup for this to be intercepted by Meta.__getattribute__
) you have to explicitly handle this in your Klass.get
method; it'll be called with one argument less if called on the class, you could make use of that and return a call on the class:
_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 ...
您还可以在模仿方法对象的描述符中进行处理:
You could also handle this in a descriptor that mimics method objects:
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
,并且如果没有实例绑定到,它将把查找重定向到元类:
and it'll redirect look-ups to the metaclass if there is no instance to bind to:
>>> 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'
应用装饰器可以在元类中完成:
Applying the decorator can be done in the metaclass:
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)
然后您可以使用此元类将方法添加到类中,而不必担心委派:
and you can then add methods to classes using this metaclass without having to worry about delegation:
>>> 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'
这篇关于使用类方法而不是具有相同名称的实例方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!