一切都在标题中。我想创建一个类方法和一个类属性,它们都只在创建类时构造一次,使用第二个定义中的第一个。
尽我最大的努力,我得到了一个 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
最佳答案
@classmethod
被实现为使用 the descriptor protocol 处理的特殊对象当抬头看课时;在定义中,作为一个原始名称(非限定),它是一个特殊的 classmethod
对象,而不是一个普通的函数,并且它没有正确地绑定(bind)到类。如果您检查 pure Python definition of classmethod
,你会注意到它只是一个普通对象,它实现了 __init__
(用于构造)和 __get__
(用于描述符查找),但没有实现 __call__
,这意味着如果你有原始的 classmethod
对象,它实际上不是 callable完全没有。
诀窍是限定引用,这样“魔法”恰好将它绑定(bind)到类,并将限定引用移到 class
定义之外(所以 Foo
是一个已定义的名称,可以为绑定(bind)引用)更改:
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
时使用元类隐式定义 bar_vect
:
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 致敬)使用动态填充的缓存来增加最小的开销(在我看来更重要的是,抽象出与工作无关的样板代码)用于通过使用定义 __missing__
的 dict
子类已经存在缓存条目的情况:
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
和 Foo
的 bar_vect
将在首次访问子类(或其实例)上的 bar_vect
时创建/缓存矢量化访问器。
关于python - 如何在类属性定义中使用类方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37569798/