44

I would like to create a class in Python that manages above all static members. These members should be initiliazed during definition of the class already. Due to the fact that there will be the requirement to reinitialize the static members later on I would put this code into a classmethod.

My question: How can I call this classmethod from inside the class?

class Test():
    # static member
    x = None
    # HERE I WOULD LOVE TO CALL SOMEHOW static_init!

    # initialize static member in classmethod, so that it can be 
    #reinitialized later on again    
    @classmethod
    def static_init(cls):
        cls.x = 10

Any help is appreciated!

Thanks in advance, Volker

Volker
  • 453
  • 1
  • 4
  • 6
  • Ignore my brain burp! Any reason you want to do it this way? – Jon Clements Dec 16 '12 at 10:50
  • Well, important for me is that I'm able to to this init again later on. Of course that doesn't make sense with just x=10, but in the real appl. I will load some XML documents that can be changed during runtime of the application. – Volker Dec 16 '12 at 10:56

6 Answers6

39

At the time that x=10 is executed in your example, not only does the class not exist, but the classmethod doesn't exist either.

Execution in Python goes top to bottom. If x=10 is above the classmethod, there is no way you can access the classmethod at that point, because it hasn't been defined yet.

Even if you could run the classmethod, it wouldn't matter, because the class doesn't exist yet, so the classmethod couldn't refer to it. The class is not created until after the entire class block runs, so while you're inside the class block, there's no class.

If you want to factor out some class initialization so you can re-run it later in the way you describe, use a class decorator. The class decorator runs after the class is created, so it can call the classmethod just fine.

>>> def deco(cls):
...     cls.initStuff()
...     return cls
>>> @deco
... class Foo(object):
...     x = 10
...     
...     @classmethod
...     def initStuff(cls):
...         cls.x = 88
>>> Foo.x
88
>>> Foo.x = 10
>>> Foo.x
10
>>> Foo.initStuff() # reinitialize
>>> Foo.x
88
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • 1
    wow, that's an elegant solution! I've started with a module-level function to solve my problem, but this one is a general approach! – Volker Dec 16 '12 at 11:40
  • 1
    @user1907681 I'm not convinced you want to be going this route, but then I don't know your exact problem, so can't really be sure... But, this is definitely the most Pythonic way of achieving this (with good explanation)... +1 – Jon Clements Dec 16 '12 at 11:42
  • Better to call initStuff from __init__ rather than relying on the creator to call it. That solves the original poster's '# HERE I WOULD LOVE TO CALL SOMEHOW static_init!' requirement. – steveayre Dec 07 '16 at 09:37
  • 4
    @steveayre: Calling it from `__init__` would mean it is called for every instance. He wants it to be called once for the whole class. – BrenBarn Dec 07 '16 at 18:44
5

You call a class method by appending the class name likewise:

class.method

In your code something like this should suffice:

Test.static_init()

You could also do this:

static_init(Test)

To call it inside your class, have your code do this:

Test.static_init()

My working code:

class Test(object):

    @classmethod
    def static_method(cls):
        print("Hello")

    def another_method(self):
        Test.static_method()

and Test().another_method() returns Hello

NlightNFotis
  • 9,559
  • 5
  • 43
  • 66
  • 2
    Unfortunatley that just seem to work from OUTSIDE the class. I've just tried: class Test(): # static member x = None Test.static_init() # initialize static member in classmethod, so that it can be #reinitialized later on again @classmethod def static_init(cls): cls.x = 10 if __name__ == "__main__": print(Test.x) Result: Test.static_init() NameError: name 'Test' is not defined# – Volker Dec 16 '12 at 10:54
  • 1
    When I try to use Test.static_init() or static_init(Test) from INSIDE the class and before the classmethod is defined I get an error: NameError: name 'static_init' is not defined – Volker Dec 16 '12 at 11:01
  • @user1907681 Yeah dude, it's because python follows a top down approach. This means that if you try to do `Test.static_init()` before you actually `def`ine it then `static_init()` is not known to the rest of your program, so it is normal to get a `Name Error`. – NlightNFotis Dec 16 '12 at 11:07
  • Well, I've tried also to put the code AFTER the defintion of the classmethod. But I've got the same NameError: class Test(): # static member x = None # initialize static member in classmethod, so that it can be #reinitialized later on again @classmethod def static_init(cls): cls.x = 10 Test.static_init() – Volker Dec 16 '12 at 11:11
  • Downrated because this was clearly not what the original poster asked for. – AdamC May 03 '23 at 16:52
4

You can't call a classmethod in the class definition because the class hasn't been fully defined yet, so there's nothing to pass the method as its first cls argument...a classic chicken-and-egg problem. However you can work around this limitation by overloading the __new__() method in a metaclass, and calling the classmethod from there after the class has been created as illustrated below:

class Test(object):
    # nested metaclass definition
    class __metaclass__(type):
        def __new__(mcl, classname, bases, classdict):
            cls = type.__new__(mcl, classname, bases, classdict)  # creates class
            cls.static_init()  # call the classmethod
            return cls

    x = None

    @classmethod
    def static_init(cls):  # called by metaclass when class is defined
        print("Hello")
        cls.x = 10

print Test.x

Output:

Hello
10
martineau
  • 119,623
  • 25
  • 170
  • 301
2

After re-reading your question carefully this time I can think of two solutions. The first one is to apply the Borg design pattern. The second one is to discard the class method and use a module level function instead. This appears to solve your problem:

def _test_static_init(value):
    return value, value * 2

class Test:
    x, y = _test_static_init(20)

if __name__ == "__main__":
    print Test.x, Test.y

Old, incorrect answer:

Here's an example, I hope it helps:

class Test:
    x = None

    @classmethod
    def set_x_class(cls, value):
        Test.x = value

    def set_x_self(self):
        self.__class__.set_x_class(10)

if __name__ == "__main__":
    obj = Test()
    print Test.x
    obj.set_x_self()
    print Test.x
    obj.__class__.set_x_class(15)
    print Test.x

Anyway, NlightNFotis's answer is a better one: use the class name when accessing the class methods. It makes your code less obscure.

ZalewaPL
  • 1,104
  • 1
  • 6
  • 14
  • Unfortunately that doesn't solve my problem. I know how to access the classmethod from OUTSIDE of the class. But I would like to have something similar to a static initializor in Java which calls a static method that can be called again later on. – Volker Dec 16 '12 at 11:15
  • Look into Borg design pattern. – ZalewaPL Dec 16 '12 at 11:19
  • Yeah, the Borg design pattern or another Singleton pattern solution will solve my problem, too. – Volker Dec 16 '12 at 11:43
  • 'It makes your code less obscure' It also breaks subclassing to override the classmethod. – steveayre Dec 07 '16 at 09:36
0

This seems like a reasonable solution:

from __future__ import annotations
from typing import ClassVar, Dict
import abc
import string


class Cipher(abc.ABC):
    @abc.abstractmethod
    def encrypt(self, plaintext: str) -> str:
        pass

    @abc.abstractmethod
    def decrypt(self, ciphertext: str) -> str:
        pass


class RotateCipher(Cipher, abc.ABC):
    @staticmethod
    def rotate(n: int) -> str:
        return string.ascii_uppercase[n:] + string.ascii_uppercase[:n]


class VigenereCipher(RotateCipher):
    _TABLE: ClassVar[Dict[str, str]] = dict({(chr(i + ord("A")), RotateCipher.rotate(i)) for i in range(26)})

    def encrypt(self, plaintext: str) -> str:
        pass

    def decrypt(self, plaintext: str) -> str:
        pass


vc = VigenereCipher()

The method is now a static method of the cipher, nothing outside the classes is referenced. You could opt to name RotateCipher _RotateCipher instead, if you don't want people using it by itself.

Note: I removed the Final, as I ran this on 3.7, but after reading the documentation on Final, I don't think it would affect the solution? Also added an import for string which the question was missing. And finally added an implementation for the abstract methods, alternatively, could have let VigenereCipher inherit from abc.ABC as well.

Grismar
  • 27,561
  • 4
  • 31
  • 54
0

If your classmethod is not used very often do a lazy evaluation

class A() {
   # this does not work: x=A.initMe()

   @classmethod
   def initMe(cls) {
     if not hasattr(cls,"x"):
        # your code her
        cls.x=# your result
        pass

   @classmethod
   def f1(cls) {
      # needs initMe
      cls.initMe()
      # more code using cls.x
   }

}
Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186