Why do attribute references act like this with Python inheritance? [duplicate]

The following seems strange.. Basically, the somedata attribute seems shared between all the classes that inherited from the_base_class.

class the_base_class:
    somedata = {}
    somedata['was_false_in_base'] = False


class subclassthing(the_base_class):
    def __init__(self):
            print self.somedata


first = subclassthing()
{'was_false_in_base': False}
first.somedata['was_false_in_base'] = True
second = subclassthing()
{'was_false_in_base': True}
>>> del first
>>> del second
>>> third = subclassthing()
{'was_false_in_base': True}

Defining self.somedata in the __init__ function is obviously the correct way to get around this (so each class has it's own somedata dict) - but when is such behavior desirable?


Asked by: Max942 | Posted: 06-10-2021






Answer 1

You are right, somedata is shared between all instances of the class and it's subclasses, because it is created at class definition time. The lines

somedata = {}
somedata['was_false_in_base'] = False

are executed when the class is defined, i.e. when the interpreter encounters the class statement - not when the instance is created (think static initializer blocks in Java). If an attribute does not exist in a class instance, the class object is checked for the attribute.

At class definition time, you can run arbritrary code, like this:

 import sys
 class Test(object):
     if sys.platform == "linux2":
         def hello(self):
              print "Hello Linux"
     else:
         def hello(self):
              print "Hello ~Linux"

On a Linux system, Test().hello() will print Hello Linux, on all other systems the other string will be printed.

In constrast, objects in __init__ are created at instantiation time and belong to the instance only (when they are assigned to self):

class Test(object):
    def __init__(self):
        self.inst_var = [1, 2, 3]

Objects defined on a class object rather than instance can be useful in many cases. For instance, you might want to cache instances of your class, so that instances with the same member values can be shared (assuming they are supposed to be immutable):

class SomeClass(object):
    __instances__ = {}

    def __new__(cls, v1, v2, v3):
        try:
            return cls.__insts__[(v1, v2, v3)]
        except KeyError:
            return cls.__insts__.setdefault(
               (v1, v2, v3), 
               object.__new__(cls, v1, v2, v3))

Mostly, I use data in class bodies in conjunction with metaclasses or generic factory methods.

Answered by: Roland319 | Posted: 07-11-2021



Answer 2

Note that part of the behavior you’re seeing is due to somedata being a dict, as opposed to a simple data type such as a bool.

For instance, see this different example which behaves differently (although very similar):

class the_base_class:
    somedata = False

class subclassthing(the_base_class):
    def __init__(self):
        print self.somedata


>>> first = subclassthing()
False
>>> first.somedata = True
>>> print first.somedata
True
>>> second = subclassthing()
False
>>> print first.somedata
True
>>> del first
>>> del second
>>> third = subclassthing()
False

The reason this example behaves differently from the one given in the question is because here first.somedata is being given a new value (the object True), whereas in the first example the dict object referenced by first.somedata (and also by the other subclass instances) is being modified.

See Torsten Marek’s comment to this answer for further clarification.

Answered by: Dexter478 | Posted: 07-11-2021



Answer 3

I think the easiest way to understand this (so that you can predict behavior) is to realize that your somedata is an attribute of the class and not the instance of that class if you define it that way.

There is really only one somedata at all times because in your example you didn't assign to that name but used it to look up a dict and then assign an item (key, value) to it. It's a gotcha that is a consequence of how the python interpreter works and can be confusing at first.

Answered by: Kelvin217 | Posted: 07-11-2021



Similar questions

python properties and inheritance

I have a base class with a property which (the get method) I want to overwrite in the subclass. My first thought was something like: class Foo(object): def _get_age(self): return 11 age = property(_get_age) class Bar(Foo): def _get_age(self): return 44 This does not work (subclass bar.age returns 11). I found a solution with an lambda expression which works:


Python inheritance - how to disable a function

In C++ you can disable a function in parent's class by declaring it as private in the child class. How can this be done in Python? I.E. How can I hide parent's function from child's public interface?


python - Single Table Inheritance in Django

Is there explicit support for Single Table Inheritance in Django? Last I heard, the feature was still under development and debate. Are there libraries/hacks I can use in the meantime to capture the basic behavior? I have a hierarchy that mixes different objects. The canonical example of a corporation structure with an Employee class, subclasses for types of employees, and a manager_id (parent_id) would be a good ...


multiple inheritance - Why can't I inherit from dict AND Exception in Python?

I got the following class : class ConstraintFailureSet(dict, Exception) : """ Container for constraint failures. It act as a constraint failure itself but can contain other constraint failures that can be accessed with a dict syntax. """ def __init__(self, **failures) : dict.__init__(self, failures) Exception.__init__(self) print isinstance(ConstraintFailureSet(...


inheritance - Python super() raises TypeError

In Python 2.5, the following code raises a TypeError: >>> class X: def a(self): print "a" >>> class Y(X): def a(self): super(Y,self).a() print "b" >>> c = Y() >>> c.a() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in a TypeError: super() argument 1 mu...


inheritance - Validating Python Arguments in Subclasses

I'm trying to validate a few python arguments. Until we get the new static typing in Python 3.0, what is the best way of going about this. Here is an example of what I am attempting: class A(object): @accepts(int, int, int) def __init__(a, b, c): pass class B(A): @accepts(int, int, int, int) def __init__(a, b, c, d): A.__init__(a, b, c) As you can s...


Inheritance and Overriding __init__ in python

I was reading 'Dive Into Python' and in the chapter on classes it gives this example: class FileInfo(UserDict): "store file metadata" def __init__(self, filename=None): UserDict.__init__(self) self["name"] = filename The author then says that if you want to override the __init__ method, you must explicitly call the parent __init__ with the ...


python - Discussion of multiple inheritance vs Composition for a project (+other things)

I am writing a python platform for the simulation of distributed sensor swarms. The idea being that the end user can write a custom Node consisting of the SensorNode behaviour (communication, logging, etc) as well as implementing a number of different sensors. The example below briefly demonstrates the concept. #prewritten class Sensor(object): def __init__(self): print "Hello from Sensor" #...


How do you alias a python class to have another name without using inheritance?

If I have a python class, how can I alias that class-name into another class-name and retain all it's methods and class members and instance members? Is this possible without using inheritance? e.g. I have a class like: class MyReallyBigClassNameWhichIHateToType: def __init__(self): <blah> [...] I'm creating an interactive console session where I don't want ...


inheritance - Python base class method call: unexpected behavior

Why does str(A()) seemingly call A.__repr__() and not dict.__str__() in the example below? class A(dict): def __repr__(self): return 'repr(A)' def __str__(self): return dict.__str__(self) class B(dict): def __str__(self): return dict.__str__(self) print 'call: repr(A) expect: repr(A) get:', repr(A()) # works print 'call: str(A)...






Still can't find your answer? Check out these communities...



PySlackers | Full Stack Python | NHS Python | Pythonist Cafe | Hacker Earth | Discord Python



top