使用 class 方法而不是同名的实例方法
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
现在,如果我这样做:
kls = Klass()
kls.get('arg')
一切正常(实例方法 get
被调用)。
但如果我这样做:
Klass.get('arg')
再次找到实例方法并给出异常,因为它被视为未绑定方法。
如何通过元class 中定义的__getattr__
调用Klass.get('arg')
?我需要这个,因为我想将在 class 上调用的所有方法代理到另一个对象(这将在 __getattr__
中完成)。
您必须在类型上查找方法并手动传递第一个 (self
) 参数:
type(Klass).get(Klass, 'arg')
这个问题正是special method names are looked up using this path的原因;如果 Python 不这样做,自定义 classes 本身将不可哈希或不可表示。
你可以利用这个事实;不要使用 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
方法中明确处理它;如果在 class 上调用它,它会少一个参数,你可以利用它和 return 在 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 ...
您也可以在模仿方法对象的 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:
>>> 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'
可以在 meta 中应用装饰器class:
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)
然后您可以使用此元class向 classes 添加方法,而不必担心委派:
>>> 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'
我有以下片段:
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')
再次找到实例方法并给出异常,因为它被视为未绑定方法。
如何通过元class 中定义的__getattr__
调用Klass.get('arg')
?我需要这个,因为我想将在 class 上调用的所有方法代理到另一个对象(这将在 __getattr__
中完成)。
您必须在类型上查找方法并手动传递第一个 (self
) 参数:
type(Klass).get(Klass, 'arg')
这个问题正是special method names are looked up using this path的原因;如果 Python 不这样做,自定义 classes 本身将不可哈希或不可表示。
你可以利用这个事实;不要使用 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
方法中明确处理它;如果在 class 上调用它,它会少一个参数,你可以利用它和 return 在 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 ...
您也可以在模仿方法对象的 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:
>>> 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'
可以在 meta 中应用装饰器class:
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)
然后您可以使用此元class向 classes 添加方法,而不必担心委派:
>>> 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'