【问题标题】:Use class method not instance method with the same name使用类方法而不是同名的实例方法
【发布时间】:2015-04-26 06:32:20
【问题描述】:

我有以下sn-p:

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

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

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

【问题讨论】:

  • 您是否考虑过覆盖 __getattribute__ 并检查该逻辑?
  • 哪个__getattribute__?
  • Met.__getattribute__。由于__getattribute__ 被称为 before 字典查找(如果我没记错的话)。
  • 可能是这样,但我不想对 all 方法调用添加额外检查,因为这会带来相当大的开销。

标签: python metaclass


【解决方案1】:

您必须在类型上查找方法并手动传递第一个 (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'

【讨论】:

  • get 方法只是一个名称,它在__getitem__ 附近没有任何作用。但也感谢您指出该解决方案。那么除了那个“丑陋”的电话之外,没有其他解决方案吗?我正在尝试为底层操作构建一个接口,并且我正在寻找更漂亮的东西...P.S.:您发布的链接没有找到“#”部分。
  • 我已经编辑了我的问题,并添加了我真正关心的问题(很抱歉从一开始就没有具体说明,我想让这个例子尽可能简单)。
  • @AndreiHorak:链接已修复(它最初是一个 Python 3 链接,我手动将其调整为 2,但忘记了部分标题已更改)。
  • @AndreiHorak:您更改的问题现在非常不同,但解决方案应该很清楚; type(self)[arg] 或 Klass[arg] 在 Klass.get() 方法中将转换为 Meta.__getitem__。
  • @AndreiHorak:换句话说,您重命名了方法并且问题消失了。
猜你喜欢
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-27
  • 2015-01-17
  • 2016-01-13
相关资源
最近更新 更多