如何在Python中动态添加类方法

16 投票
4 回答
13138 浏览
提问于 2025-04-18 11:55

我在用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 个回答

0

顺便提一下,你可以直接用一个实例属性来保存一个函数:

>>> 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
11

如果你想创建类的方法,不要在 __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 装饰器。

18

你可以在任何时候给一个类添加一个函数,这种做法叫做猴子补丁(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 呢?

-1

我是怎么做到的:

@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

撰写回答