17

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

1
  • 1
    Why the setattr with a constant 2nd argument? You could as well write cls._search_url = reverse(...). Commented Jul 2, 2014 at 20:00

4 Answers 4

20

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?

Sign up to request clarification or add additional context in comments.

2 Comments

Is it okay if you use @classmethod decorator outside of class? The code works, but I worry if is this a really pythonic way to solve the problem.
@AlexeiMarinichenko It's ok to use @classmethod outside of a class if you plan to attach the resulting function object to a class. Another matter is whether you need such monkey-patching in the first place. (You almost never do.)
11

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.

2 Comments

self should be cls in the first part of your example.
You're right, writing it inside init would make kind of put extra work unnecessarily. My example was bad, but I actually did need the cls argument in real life. Sorry about that >.<
0

As a side note, you can just use an instance attribute to hold a function:

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

Comments

-1

How I achieved it:

@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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.