Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python subclass inheritance

I am trying to build some classes that inherit from a parent class, which contains subclasses that inherit from other parent classes. But when I change attributes in the subclasses in any children, the change affects all child classes. I am looking to avoid having to create instances, as I am using that feature later.

The code below boils down the problem. The final line shows the unexpected result.

class SubclsParent(object):
    a = "Hello"

class Parent(object):
    class Subcls(SubclsParent):
        pass

class Child1(Parent):
    pass

class Child2(Parent):
    pass

Child1.Subcls.a # Returns "Hello"
Child2.Subcls.a # Returns "Hello"
Child1.Subcls.a = "Goodbye"
Child1.Subcls.a # Returns "Goodbye"
Child2.Subcls.a # Returns "Goodbye" / Should still return "Hello"!
like image 588
Conor Avatar asked Dec 09 '22 10:12

Conor


2 Answers

The behaviour you are seeing is exactly what you should expect. When you define a class

>>> class Foo(object): pass
...

you can modify that class -- not instances of it, the class itself -- because the class is just another object, stored in the variable Foo. So, for instance, you can get and set attributes of the class:

>>> Foo.a = 1
>>> Foo.a
1

In other words, the class keyword creates a new type of object and binds the specified name to that object.


Now, if you define a class inside another class (which is a weird thing to do, by the way), that is equivalent to defining a local variable inside the class body. And you know what defining local variables inside the body of a class does: it sets them as class attributes. In other words, variables defined locally are stored on the class object and not on individual instances. Thus,

>>> class Foo(object):
...     class Bar(object): pass
...

defines a class Foo with one class attribute, Bar, which happens itself to be a class. There is no subclassing going on here, though -- the classes Foo and Bar are entirely independent. (The behaviour you have achieved could be replicated as follows:

>>> class Foo(object):
...     class Bar(object): pass
...
>>> class Foo(object): pass
...
>>> class Bar(object): pass
...
>>> Foo.Bar = Bar

.)

So you are always modifying the same variable! Of course you will change the values you see; you have changed them yourself!


Your problem seems to be that you are somewhat confused between instance and class attributes, which are not the same thing at all.

A class attribute is a variable which is defined over the whole class. That is, any instance of the class will share the same variable. For instance, most methods are class attributes, since you want to call the same methods on every instance (usually). You can also use class attributes for things like global counters (how many times have you instantiated this class?) and other properties which should be shared amongst instances.

An instance attribute is a variable peculiar to an instance of the class. That is, each instance has a different copy of the variable, with possibly-different contents. This is where you store the data in classes -- if you have a Page class, say, you would like the contents attribute to be stored per-instance, since different Pages will of course need different contents.

In your example, you want Child1.Subcls.a and Child2.Subcls.a to be different variables. Naturally, then, they should depend on the instance!


This may be a bit of a leap of faith, but are you trying to implement Java-style interfaces in Python? In other words, are you trying to specify what properties and methods a class should have, without actually defining those properties?

This used to be considered something of a non-Pythonic thing to do, since the prevailing consensus was that you should allow the classes to do whatever they want and catch the exceptions which arise when they don't define a needed property or method. However, recently people have realised that interfaces are actually sometimes a good thing, and new functionality was added to Python to allow this: abstract base classes.

like image 182
Katriel Avatar answered Jan 06 '23 15:01

Katriel


Try this

class SubclsParent(object):
    def __init__(self):
         self.a = "Hello"

When you define SubclsParent.a directly on the class, you are defining it as static.

like image 29
beer_monk Avatar answered Jan 06 '23 15:01

beer_monk