如何在类属性定义中使用类方法

时间:2016-06-01 13:14:41

标签: python python-2.7 python-2.x

所有都在标题中。我想创建一个类方法和一个类属性,它们在创建类时只使用第二个定义中的第一个构造一次。

我最好的尝试,我得到一个TypeError: 'classmethod' object is not callable

这是我的代码:

import numpy as np

class Foo( object ) :

    @classmethod
    def bar( cls, x ) :
        return x+1

    bar_vect = np.vectorize( bar )

Foo.bar_vect( np.array([ 1, 2, 3 ]) )

>> TypeError: 'classmethod' object is not callable

编辑1:

'classmethod' object is not callable是一个引发同样错误的问题,但有很多变通方法。我的问题是要直截了当地说清楚如何使用@classmethod而不使用范围来访问cls

我做的另一个尝试是:

import numpy as np

class Foo( object ) :

    @classmethod
    def bar( cls, x ) :
        return x+1

    bar_vect = np.vectorize( bar )

>> NameError: name 'Foo' is not defined

3 个答案:

答案 0 :(得分:3)

@classmethod被实现为一个特殊对象,当在类上查找时,使用the descriptor protocol进行处理;在定义中,作为原始名称(不合格),它是一个特殊的classmethod对象,而不是正常的函数,并且它没有正确地绑定到类。如果你检查pure Python definition of classmethod,你会注意到它只是一个普通的对象,它实现__init__(用于构造)和__get__(用于描述符查找),但不是__call__,意思是如果你有原始的classmethod对象,它实际上根本不是callable

诀窍是限定引用,以便“魔术”碰巧将它绑定到类,并将限定引用移到class定义之外(因此Foo是一个已定义的名称,可以是引用绑定)改变:

class Foo(object):
    ... rest of class ...
    bar_vect = np.vectorize(bar)  # Indented and unqualified, BAD

为:

class Foo(object):
    ... rest of class ...
# Must qualify both bar_vect and bar, since no longer in class definition
Foo.bar_vect = np.vectorize(Foo.bar)  # Dedented, so Foo is defined for referencing, GOOD

请注意,由于您使用的是classmethod,我怀疑您最终可能会对子类化和覆盖bar感兴趣。如上所述,您需要在定义每个子类后显式重新定义bar_vect,否则它将使用基于bar_vect的继承Foo.bar,即使子类定义了自己的bar }} classmethod。每次明确重新定义bar_vect是一种选择,但另一种方法是在类重新定义bar_vect时使用元类隐式定义bar

class BarVectorized(type):
    def __new__(cls, name, bases, namespace, **kwargs):
        newcls = type.__new__(cls, name, bases, dict(namespace))
        # Make vectorized wrapper for this class (must use new wrapper
        # even if bar unchanged, so cls in bar is correct for lookup of
        # other class attributes/methods)
        try:
            newcls.bar_vect = np.vectorize(newcls.bar)
        except AttributeError:
            pass  # Allow class w/o bar; remove try/except if class must have bar
        return newcls

class Foo(object):
    __metaclass__ = BarVectorized
    @classmethod
    def bar(cls, x): return x + 1

class Foo2(Foo):
    ADD = 2  # Hardcoded 1 is dumb, use class attribute instead!
    @classmethod
    def bar(cls, x):
        return x + cls.ADD

class Foo3(Foo2):
    ADD = 3  # Provide new class attr to change Foo2.bar behavior when called via Foo3

>>> Foo.bar_vect([1,2,3])
array([2, 3, 4])
>>> Foo2.bar_vect([1,2,3])
array([3, 4, 5])
>>> Foo3.bar_vect([1,2,3])
array([4, 5, 6])

根本不需要明确定义bar_vectbar_vect无缝地使用类定义时可用的bar的最本地类定义,因此除非bar是在类定义之后重新定义,它始终有效,并且尽可能高效地工作。为了使它实时使用bar,您需要采用更极端的措施来执行动态查找和(禁止缓存)每次使用时np.vectorize对象的重建,这不是最佳说法至少

为了完整性,一个基于动态缓存的解决方案(给Tadhg McDonald-Jensen's answer提示),它使用动态填充的缓存,增加了最小的开销(更重要的是,在我看来,抽象出与工作无关的样板代码)对于使用定义dict的{​​{1}}子类已存在缓存条目的情况:

__missing__

子类不需要(也不应该)覆盖import operator import numpy as np class ClassAttrRegistry(dict): '''Dictionary keyed by classes which returns optionally wrapped cached attributes''' __slots__ = '_wrapper', '_attrgetter' def __init__(self, attr, wrapperfunc=lambda x: x): self._wrapper = wrapperfunc self._attrgetter = operator.attrgetter(attr) def __missing__(self, cls): self[cls] = wrapped = self._wrapper(self._attrgetter(cls)) return wrapped class Foo(object): @classmethod def bar(cls, x): return x + 1 # Dunder prefix makes cache private to Foo methods; if subclass overrides bar_vect, # assumed it's more complex than "vectorized bar"; cache should not be used __bar_vect_registry = ClassAttrRegistry('bar', np.vectorize) @classmethod def bar_vect(cls, x): # Get cached vectorized bar (creating if needed) then call it return cls.__bar_vect_registry[cls](x) (并且不能意外访问bar_vect,因为它的名称被破坏,只有__bar_vect_registry定义的方法才能看到它;将名称更改为Foo,一个下划线,如果它应该可供子类访问),它们只是覆盖_bar_vect_registry,而bar的{​​{1}}将创建/缓存矢量化访问器首先在子类(或其实例)上访问Foo

答案 1 :(得分:2)

您对为什么这不是一件容易的事情感到困惑是可以理解的,让我详细说明为什么以这种方式使用classmethod是行不通的......

classmethod的工作方式是创建一个描述符,当一个对象作为对象的属性被检索时,该对象实现__get__

所以,当你执行Foo.bar时,它基本上会加载bar类方法并调用:

bar.__get__(None, Foo)

None表示实例(因为它在类本身上有None)而第二个参数表示类,classmethod不可调用,因为它也没有类来绑定它!

class定义块结束(并且元类type实际将它组合在一起)之前,不仅这个而且绑定它的类对象也不存在,所以最小的是创建在实际定义类之后bar_vect

class Foo( object ):
    a = 1 #lets use an example that actually uses the class
    @classmethod
    def bar( cls, x ):
        return x+cls.a

Foo.bar_vect = np.vectorize( Foo.bar )

这可以肯定,但是你打破了子类的功能,如果你想改变a怎么办?

class Subfoo(Foo):
    a = 3 #this will have no effect on 

assert Subfoo.bar_vect(np.array([ 1, 2, 3 ])) == np.array([ 4, 5, 6 ])
#this SHOULD work but doesn't because you bound bar_Vect to just Foo
#subclasses mean nothing to your class method

在这种情况下使其工作的唯一方法是为每个子类重新创建np.vectorize至少一个,最简单的版本就是每次调用bar_vect时都这样做:

class Foo( object ):
    a = 1
    @classmethod
    def bar( cls, x ):
        return x+cls.a
    @classmethod
    def bar_vect(cls,arg):
        return np.vectorize(cls.bar)(arg)

这显然是不受欢迎的,因为每次使用np.vectorize时它都会调用x.bar_vect,但是你可以记录所有类,只有在使用新类时才能记录它:

_bar_vect_registry = {}
@classmethod
def bar_vect(cls,arg):
    try:
        return cls._bar_vect_registry[cls](arg)
    except KeyError:
        cls._bar_vect_registry[cls] = np.vectorize(cls.bar)
        return cls._bar_vect_registry[cls](arg)

答案 2 :(得分:1)

你真正的问题是,如果完全构造了,你会尝试在类之前使用bar,因此你没有得到预期的对象。

这是一个简化的例子:

class Foo:
    @classmethod
    def bar(cls, x):
        print ('bar called in', cls, 'with', x)
    barv = str(bar)

print(str(Foo.bar))
print(Foo.barv)

给出:

<bound method Foo.bar of <class '__main__.Foo'>>
<classmethod object at 0x00000000035B0320>

这表明直到类完全构造,方法标识符只绑定到方法定义而不绑定到实际方法。

如果你想达到你想要的,你必须在类定义之外定义类变量(在最后一行之后),如@ShadowRanger所述