所有都在标题中。我想创建一个类方法和一个类属性,它们在创建类时只使用第二个定义中的第一个构造一次。
我最好的尝试,我得到一个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
'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
答案 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_vect
,bar_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所述