@classmethod
s are implemented as a special object that gets processed using 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 at all.
The trick is to qualify the reference so the "magic" happens to bind it to the class, and move the qualified reference outside the class
definition (so Foo
is a defined name and can be referenced for binding) changing:
class Foo(object):
... rest of class ...
bar_vect = np.vectorize(bar) # Indented and unqualified, BAD
to:
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
Note that since you're using a classmethod
, I suspect you may eventually be interested in subclassing and overriding bar
. As written, you'd need to explicitly redefine bar_vect
after defining each subclass, or it would use the inherited bar_vect
, based on Foo.bar
, even if the subclass defines its own bar
classmethod
. Explicitly redefining bar_vect
each time is an option, but the other approach is to use metaclasses to implicitly define bar_vect
when a class redefines 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])
No need to define bar_vect
explicitly at all, and bar_vect
seamlessly uses the most local classes' definition of bar
available at class definition time, so unless bar
is redefined after class definition, it always works, and it works as efficiently as possible. To make it use bar
live, you'd need to resort to more extreme measures that perform dynamic lookup and (barring a cache) reconstruction of the np.vectorize
object on each use, which is suboptimal to say the least.
For completeness, a dynamic caching based solution (hat tip to Tadhg McDonald-Jensen's answer) that uses a dynamically populating cache that adds minimal overhead (and more importantly in my opinion, abstracts out the boilerplate code that's irrelevant to the work) for the case where the cache entry already exists by using a dict
subclass defining __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 don't need to (and should not) override bar_vect
(and can't accidentally access __bar_vect_registry
because it's name mangled such that only methods defined by Foo
will see it; change name to _bar_vect_registry
, one underscore, if it should be accessible to subclasses), they just override bar
and Foo
's bar_vect
will create/cache vectorized accessors when bar_vect
is first accessed on the subclass (or an instance thereof).