Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a classmethod in Python dynamically

I'm using Python 3. I know about the @classmethod decorator. Also, I know that classmethods can be called from instances.

class HappyClass(object):
    @classmethod
    def say_hello():
        print('hello')
HappyClass.say_hello() # hello
HappyClass().say_hello() # hello

However, I don't seem to be able to create class methods dynamically AND let them be called from instances. Let's say I want something like

class SadClass(object):
    def __init__(self, *args, **kwargs):
        # create a class method say_dynamic

SadClass.say_dynamic() # prints "dynamic!"
SadClass().say_dynamic() # prints "dynamic!"

I've played with cls.__dict__ (which produces exceptions), and with setattr(cls, 'say_dynamic', blahblah) (which only makes the thingie callable from the class and not the instance).

If you ask me why, I wanted to make a lazy class property. But it cannot be called from instances.

@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

Maybe because the property hasn't been called from the class yet...

In summary, I want to add a lazy, class method that can be called from the instance... Can this be achieved in an elegant (nottoomanylines) way?

Any thoughts?

How I achieved it

Sorry, my examples were very bad ones :\

Anyway, in the end I did it like this...

@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

And the setattr does work, but I had made a mistake when testing it...

like image 565
jleeothon Avatar asked Jul 02 '14 18:07

jleeothon


People also ask

How do I create a dynamic code in Python?

Method 1: exec() ? Python's built-in exec() executes the Python code you pass as a string or executable object argument. This is called dynamic execution because, in contrast to normal static Python code, you can generate code and execute it at runtime. This way, you can run programmatically-created Python code.

Can a Classmethod be inherited in Python?

Yes, they can be inherited.


2 Answers

You can add a function to a class at any point, a practice known as 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

Note that you are using the classmethod decorator, but your function accepts no arguments, which indicates that it's designed to be a static method. Did you mean to use staticmethod instead?

like image 179
user4815162342 Avatar answered Oct 01 '22 12:10

user4815162342


If you want to create class methods, do not create them in the __init__ function as it is then recreated for each instance creation. However, following works:

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!"

Of course, in the __init__ method the self argument is an instance, and not the class: to put the method in the class there, you can hack something like

class SadClass(object):
    def __init__(self, *args, **kwargs):
        @classmethod
        def say_dynamic(cls):
            print("dynamic!")

        setattr(self.__class__, 'say_dynamic', say_dynamic)

But it will again reset the method for each instance creation, possibly needlessly. And notice that your code most probably fails because you are calling the SadClass.say_dynamic() before any instances are created, and thus before the class method is injected.

Also, notice that a classmethod gets the implicit class argument cls; if you do want your function to be called without any arguments, use the staticmethod decorator.