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

How can one use a class method in a class attribute definition

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

尽我最大的努力,我得到了 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 来访问 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

@classmethods 被实现为一个特殊对象,完全使用 the descriptor protocol when looked up on the class; inside the definition, as a raw name (unqualified), it's a special classmethod object, not a normal function and it's not bound to the class properly. If you check the pure Python definition of classmethod, you'll note it's just a normal object that implements __init__ (for construction) and __get__ (for descriptor lookup), but not __call__, meaning that if you have the raw classmethod object, it's not actually a callable 进行处理。

诀窍是限定引用,因此 "magic" 恰好将其绑定到 class,并将限定引用移到 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,我怀疑您最终可能会对子class 和覆盖 bar 感兴趣。正如所写,您需要在定义每个子 class 之后显式地重新定义 bar_vect,否则它会使用继承的 bar_vect,基于 Foo.bar,即使子class定义了自己的barclassmethod。每次显式重新定义 bar_vect 是一种选择,但另一种方法是在 class 重新定义 bar 时使用 metaclasses 隐式定义 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 无缝地使用 classes 对 bar 的最本地定义,可在 class定义时间,因此除非在 class 定义之后重新定义 bar,否则它始终有效,并且尽可能高效地工作。要使其实时使用 bar,您需要采取更极端的措施,在每次使用时执行动态查找和(禁止缓存)重建 np.vectorize 对象,这不是最佳选择最少。

为了完整起见,基于动态缓存的解决方案(向 致敬)使用动态填充缓存,增加的开销最小(在我看来更重要的是,抽象出与工作)对于缓存条目已经存在的情况,通过使用 dict subclass 定义 __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)

Subclasses 不需要(也不应该)重写 bar_vect(并且不能意外访问 __bar_vect_registry 因为它的名称被破坏了,所以只有由定义的方法Foo 会看到它;将名称更改为 _bar_vect_registry,一个下划线,如果子 classes 应该可以访问的话),它们只是覆盖 barFoobar_vect 首次在子 class(或其实例)上访问时,bar_vect 将 create/cache 向量化访问器。

你真正的问题是你尝试在 class 之前使用 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>

这表明在class完全构建之前,方法标识符仅绑定到方法定义而不是真正的方法。

如果你想实现你想要的,你必须在 class 定义之外定义 class 变量(在最后一行之后),正如@ShadowRanger

所解释的

您对为什么这不是一个简单的解决方法感到困惑是可以理解的,让我详细说明为什么以这种方式使用 classmethod 行不通...

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

因此,当您执行 Foo.bar 时,它基本上会加载 bar class 方法并调用:

bar.__get__(None, Foo)

其中 None 表示实例(有 None 因为它在 class 本身),第二个参数表示 class,一个 class 方法不可调用,因为那样它也没有 class 来绑定它!

不仅如此,绑定它的 class 对象也不存在,直到 class 定义块结束(并且 metaclass type 实际上把它放在一起)所以最低限度是在实际定义 class 之后创建 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 )

这当然可以,但是你破坏了 subclasses 的功能,如果你想改变 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 重新创建至少一个 class,最简单的版本是每次调用 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)

这显然是不可取的,因为每次使用 x.bar_vect 时它都会调用 np.vectorize,但是您可以记录所有 classes 并且仅在新的 class 被使用:

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