如何在Python中动态添加类方法
我在用Python 3。
我知道@classmethod这个装饰器,也知道类方法可以通过实例来调用。
class HappyClass(object):
@classmethod
def say_hello():
print('hello')
HappyClass.say_hello() # hello
HappyClass().say_hello() # hello
不过,我似乎无法动态创建类方法,并且让它们可以通过实例来调用。假设我想要类似这样的东西:
class SadClass(object):
def __init__(self, *args, **kwargs):
# create a class method say_dynamic
SadClass.say_dynamic() # prints "dynamic!"
SadClass().say_dynamic() # prints "dynamic!"
我试过用cls.__dict__
(这会产生错误),也试过用setattr(cls, 'say_dynamic', blahblah)
(这只让这个方法能通过类来调用,而不能通过实例来调用)。
如果你问我为什么,我想做一个懒加载的类属性。但它不能通过实例来调用。
@classmethod
def search_url(cls):
if hasattr(cls, '_search_url'):
setattr(cls, '_search_url', reverse('%s-search' % cls._meta.model_name))
return cls._search_url
可能是因为这个属性还没有从类中被调用过……
总之,我想添加一个懒加载的类方法,能够通过实例来调用……这能以优雅(不太多行代码)的方式实现吗?
有什么想法吗?
我怎么做到的
抱歉,我的例子真的是很糟糕 :\
不过,最后我这样做了……
@classmethod
def search_url(cls):
if not hasattr(cls, '_search_url'):
setattr(cls, '_search_url', reverse('%s-search' % cls._meta.model_name))
return cls._search_url
而且setattr
确实有效,但我在测试的时候犯了个错误……
4 个回答
顺便提一下,你可以直接用一个实例属性来保存一个函数:
>>> class Test:
... pass
...
>>> t=Test()
>>> t.monkey_patch=lambda s: print(s)
>>> t.monkey_patch('Hello from the monkey patch')
Hello from the monkey patch
如果你想创建类的方法,不要在 __init__
函数里创建,因为每次创建实例的时候,这个方法都会被重新生成。不过,下面的做法是可以的:
class SadClass(object):
pass
def say_dynamic(cls):
print("dynamic")
SadClass.say_dynamic = classmethod(say_dynamic)
# or
setattr(SadClass, 'say_dynamic', classmethod(say_dynamic))
SadClass.say_dynamic() # prints "dynamic!"
SadClass().say_dynamic() # prints "dynamic!"
当然,在 __init__
方法中,self
是一个实例,而不是类:如果想把方法放到类里,你可以用一些小技巧,比如:
class SadClass(object):
def __init__(self, *args, **kwargs):
@classmethod
def say_dynamic(cls):
print("dynamic!")
setattr(self.__class__, 'say_dynamic', say_dynamic)
但这样做每次创建实例的时候又会重置这个方法,可能是多此一举。而且要注意,你的代码很可能会出错,因为你在创建任何实例之前就调用了 SadClass.say_dynamic()
,所以在类方法被添加之前就已经调用了。
另外,要注意 classmethod
会隐式地接收一个类的参数 cls
;如果你希望你的函数可以不带任何参数被调用,可以使用 staticmethod
装饰器。
你可以在任何时候给一个类添加一个函数,这种做法叫做猴子补丁(monkey-patching):
class SadClass:
pass
@classmethod
def say_dynamic(cls):
print('hello')
SadClass.say_dynamic = say_dynamic
>>> SadClass.say_dynamic()
hello
>>> SadClass().say_dynamic()
hello
注意,你使用了 classmethod
这个装饰器,但你的函数没有接受任何参数,这说明它是一个 静态 方法。你是不是想用 staticmethod
呢?
我是怎么做到的:
@classmethod
def search_url(cls):
if not hasattr(cls, '_search_url'):
setattr(cls, '_search_url', reverse('%s-search' % cls._meta.model_name))
return cls._search_url