Home > Back-end >  Make parent class do something "once" in Python
Make parent class do something "once" in Python

Time:11-22

class TaskInput:
    def __init__(self):
        self.cfg = my_config #### Question: How do I do this only once?

class TaskA(TaskInput):
    def __init__(self):
        pass

class TaskB (TaskInput):
    def __init__(self):
        pass
  • There are many tasks like TaskA, TaskB etc, they all are inherited from TaskInput.
  • Tasks also depend on something, let's say, a configuration which I only want to set ONCE.
  • The code has multiple Tasks classes, like TaskA, TaskB etc. They all depend on this common configuration.

One natural way would be to make this configuration a class member of TaskInput, ie, TaskInput.cfg = my_config, something that's initialized in __init__() of TaskInput.

However, if it's a member of TaskInput, it'll get executed multiple times, every time a new object of type TaskX is created as all those Tasks are inherited from TaskInput.

What's the best practice and best way to accomplish this in Python?

CodePudding user response:

Make the configuration a class attribute by defining it on the class rather than in __init__.

class TaskInput:
    cfg = my_config

It is now accessible as self.cfg on any instance of TaskInput or its children.

CodePudding user response:

I will not try to guess your need so I will assume you mean exactly what you said below, namely that you want a single initialization of a class member, but done through the creation of an instance.

a class member of TaskInput, ie, TaskInput.cfg = my_config, something that's initialized in init() of TaskInput.

This can work, but not the way you did it. in your code you never created a class attribute, anything created with self is an instance attribute belonging to a single specific task instance so that:

from copy import deepcopy

class TaskInput:
    _cfg = None # prefix with '_' to indicate it should be considered private
    
    def __init__(self, my_config=None):
        _cfg = _cfg or my_config
    
    @property
    def cfg(self):
        """ If you want to privatize it a bit more,
        make yourself a getter that returns a deep copy."""
        return deepcopy(cfg)

Now, there basically is no such thing as true privatization in python and you will never be able to entirely prevent manipulation. In the example above, any child has direct read-write access to _cfg, so it would fall on us not to use it directly and pass by its accessors (__init__() and cfg()).

There's always a way to make things more difficult, like the following, using modules.

 Project
 ├─ __init__.py
 ├─ settings.py
 ├─ module1.py
 └─ module2.py

settings.py

cfg = None

module1.py

from copy import deepcopy
import settings

class A:
    def __init__(self, cfg_=None):
        settings.cfg = settings.cfg or cfg_

    @property
    def cfg(self):
        return deepcopy(settings.cfg)

module2.py

""" The following classes won't be able to
overwrite the config without importing
from settings.py.
"""

from module1 import A

class B(A):
    pass

class C(A):
    def __init__(self):
        super().__init__("foobar")

Giving these results:

b0 = B()
b0.cfg
# > None

b1 = B({"foo1": "bar1"})
b1.cfg
# > {'foo1': 'bar1'}

b2 = B({"foo1": "bar2", "foo3": "bar3"})
b2.cfg
# > {'foo1': 'bar1'}

try:
    b2.cfg = 1234
except Exception as e:
    print(type(e), e)
# > <class 'AttributeError'> can't set attribute

b2.cfg
# > {'foo1': 'bar1'}

c = C("asdf")
c.cfg
# > {'foo1': 'bar1'}

Which can be overkill of course and removes the actual ownership of the configuration from the class

  • Related