53

Is there such a thing as a static constructor in Python?

How do I implement a static constructor in Python?

Here is my code... The __init__ doesn't fire when I call App like this. The __init__ is not a static constructor or static initializer.

App.EmailQueue.DoSomething()

I have to call it like this, which instantiates the App class every time:

App().EmailQueue.DoSomething()

Here is my class:

class App:
    def __init__(self):
        self._mailQueue = EmailQueue()

    @property
    def EmailQueue(self):
        return self._mailQueue

The problem with calling __init__ every time is that the App object gets recreated. My "real" App class is quite long.

101010
  • 14,866
  • 30
  • 95
  • 172
  • 6
    Why don't you just put that code right after defining the class? Or are you asking for lazy initialization? – SLaks Sep 13 '11 at 02:13

6 Answers6

31

There's a fundamental difference between static and dynamic languages that isn't always apparent at first.

In a static language, the class is defined at compile time and everything is all nice and set in concrete before the program ever runs.

In a dynamic language, the class is actually defined at runtime. As soon as the interpreter parses and starts executing all of those classes and def statements, the equivalent of a static constructor is being run. The class definitions are being executed at that point.

You can put any number of statements anywhere inside the class body and they are in effect a static constructor. If you want, you can place them all in a function that doesn't take self as a parameter, and call that function at the end of the class.

OmarOthman
  • 1,718
  • 2
  • 19
  • 36
Sam Corder
  • 5,374
  • 3
  • 25
  • 30
  • 7
    Have you actually tried `placing them all in a function that doesn't take self and at the end of the class call that function`? That isn't as straight-forward as it sounds because the class hasn't been created yet when that function runs. – Ethan Furman Jan 08 '16 at 17:33
25

I create a static_init decorator which calls a static_init class method if it exists.

Here is the decorator and example of how to use it to initialize a class variable on an enum class:

# pylint: disable=missing-docstring,no-member

import enum

def static_init(cls):
    if getattr(cls, "static_init", None):
        cls.static_init()
    return cls

@static_init
class SomeEnum(enum.Enum):
    VAL_A = enum.auto()
    VAL_B = enum.auto()
    VAL_C = enum.auto()
    VAL_D = enum.auto()

    @classmethod
    def static_init(cls):
        text_dict = {}
        setattr(cls, 'text_dict', text_dict)
        for value in cls:
            text_dict[value.name.lower().replace("_", " ").title()] = value

def test_static_init():
    assert SomeEnum.text_dict["Val A"] == SomeEnum.VAL_A
    assert SomeEnum.text_dict["Val B"] == SomeEnum.VAL_B
    assert SomeEnum.text_dict["Val C"] == SomeEnum.VAL_C
    assert SomeEnum.text_dict["Val D"] == SomeEnum.VAL_D
Iwan Aucamp
  • 1,469
  • 20
  • 21
24

Hint: anything that references self is going to require an instantiation of the class. You could do it like this:

class App:
    email_queue = EmailQueue()

App.email_queue.do_something()

But come on, that seems like a lot of fluff. I'm with SLaks, just initialize it outside of the class. Alternatively, you could look into the singleton pattern.

Neuron
  • 5,141
  • 5
  • 38
  • 59
Chris Eberle
  • 47,994
  • 12
  • 82
  • 119
  • 5
    This should not be the accepted answer. it does not explain how to create a static initializer, instead it suggests to create objects. – Omry Yadan Jul 15 '20 at 00:39
4

You can use the class method - see @classmethod decorator in the the code sample bellow:

class A(object):
    _some_private_static_member = None

    @classmethod
    def reset_static_data_members(cls, some_value):
        cls._some_private_static_member = some_value

A.reset_static_data_members("some static value")

However, be aware, that this is most probably something you do NOT want to do, since it modifies state of type - so it will affect all further calls to methods of that type which use/depend-on the changed static class data members. Situation can get nasty if such static class data members are accessed & set via self. in instances of such class when you will easily get unexpected behaviour.

Really correct way to do this (or the common sense way which most developers expect that behavior will be), is to make sure the static data members will be set only once - during import (the analogical way static data member initialisation behave in C++, C#, Java). Python has mechanism for that- the in-place initialisation:

class A(object):
    _some_private_static_member_0 = "value 0"
    _some_private_static_member_1 = "value 1"

Potentially you can initialise the static class members with return values from functions which abstract out (of the class) more complex value generation:

class A(object):
    _some_private_static_member_0 = some_function_0()
    _some_private_static_member_1 = some_function_1()
PeterB
  • 101
  • 3
1

You need to instantiate your App, then use that instead:

myApp = App()
myApp.EmailQueue.DoSomething()
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
0

Here's another take on the matter, expounding on the region of code after the class def and before any class functions. I enclosed in an if condition as well as a code folding region so that various editors may be capable of folding this body of code in the event you wish to hide it....

class MyClass :

    myStaticVar1 = {}
    myStaticVar2 = []

    #region STATICINIT
    if not myStaticVar1 :
       # implement arbitrary initialization logic here
       # with any helper variables you don't want cluttering up the class
       localVarDontWantInClassScope = [1,2,3]

       myStaticVar1[0] = localVarDontWantInClassScope[0]
       myStaticVar1[1] = localVarDontWantInClassScope[2]

       myStaticVar2.append("abc")

       # local var cleanup
       del localVarDontWantInClassScope
    #endregion

    def f1(self, i) :
        print( "{} {}".format( i , MyClass.myStaticVar1[i] ) )
jxramos
  • 7,356
  • 6
  • 57
  • 105