使用类方法而不是具有相同名称的实例方法

lin*_*ndy 1 python metaclass

我有以下代码段:

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

class Klass(object):
    __metaclass__ = Meta

    def get(self, arg):
        pass
Run Code Online (Sandbox Code Playgroud)

现在,如果我这样做:

kls = Klass()
kls.get('arg')
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作(get调用实例方法).

但如果我这样做:

Klass.get('arg')
Run Code Online (Sandbox Code Playgroud)

再次找到实例方法并给出异常,因为它被视为未绑定方法.

如何调用以Klass.get('arg')通过__getattr__元类中的定义?我需要这个,因为我想将一个类上调用的所有方法代理到另一个对象(这将完成__getattr__).

Mar*_*ers 5

您必须在类型上查找方法并self手动传入first()参数:

type(Klass).get(Klass, 'arg')
Run Code Online (Sandbox Code Playgroud)

这个问题是使用此路径查找特殊方法名称的原因; 如果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
Run Code Online (Sandbox Code Playgroud)

然后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 ... 
Run Code Online (Sandbox Code Playgroud)

您还可以在模仿方法对象的描述符中处理此问题:

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)
Run Code Online (Sandbox Code Playgroud)

并将其用作装饰者:

class Klass(object):
    __metaclass__ = Meta

    @class_and_instance_method
    def get(self, arg):
        pass
Run Code Online (Sandbox Code Playgroud)

如果没有要绑定的实例,它会将查找重定向到元类:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

应用装饰器可以在元类中完成:

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)
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用此元类向类添加方法,而无需担心委派:

>>> 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'
Run Code Online (Sandbox Code Playgroud)