Passing parameter to base class constructor or using instance variable? - python

All classes derived from a certain base class have to define an attribute called "path". In the sense of duck typing I could rely upon definition in the subclasses:
class Base:
pass # no "path" variable here
def Sub(Base):
def __init__(self):
self.path = "something/"
Another possiblity would be to use the base class constructor:
class Base:
def __init__(self, path):
self.path = path
def Sub(Base):
def __init__(self):
super().__init__("something/")
I use Python 3.1.
What would you prefer and why? Is there a better way?

In Python 3.0+:
I would go with a parameter to the base class's constructor like you have in the second example. As this forces classes which derive from Base to provide the necessary path property, which documents the fact that the class has such a property and that derived classes are required to provide it. Without it, you would be relying on this being stated (and read) somewhere in your class's docstrings, although it certainly does help to also state in the docstring what the particular property means.
In Python 2.6+:
I would use neither of the above; instead I would use:
class Base(object):
def __init__(self,path):
self.path=path;
class Sub(Base):
def __init__(self):
Base.__init__(self,"something/")
In other words, I would require such a parameter in the base class's constructor, because it documents the fact that all such types will have/use/need that particular parameter and that the parameter needs to be provieded. However, I would not use super() as super is somewhat fragile and dangerous in Python, and I would also make Base a new-style class by inheriting from object (or from some other new-style) class.

Related

what is the difference between super and inheritance by passing it alone as an object?

in the below example I want to know when I should use one of them for inherits? I think both are valid so, why sometimes I have to use super if the other way is workable to work with?
class User:
def __init__(self):
self._user = "User A"
pass
class UserA(User):
_user = "User B"
def __init__(self):
super().__init__()
class UserB(User):
pass
You are correct, both are valid. The difference is:
UserA: you are overwriting the __init__ method of the ancestor. This is practical if you want to add something during the initialization process. However, you still want to initialize the ancestor, and this can be done via super().__init__(), despite having overwritten the __init__ method.
UserB: you are fully using the __init__ of the ancestor you are inheriting from (by not overwriting the __init__ method). This can be used if nothing extra needs to be done during initialization.
The super() builtin returns a proxy object (temporary object of the superclass) that allows us to access methods of the base class. For example:
class Mammal(object):
def __init__(self, mammalName):
print(mammalName, 'is a warm-blooded animal.')
class Dog(Mammal):
def __init__(self):
print('Dog has four legs.')
super().__init__('Dog')
self represents the instance of the class. By using the “self” keyword we can access the attributes and methods of the class in python

Get name of class another class is instantiated inside of in Python

Is it possible to get the name of a python class in another class that was instantiated inside the first class.
Let me give you an example.
class SubClass:
top_level_name = None # name class it is instantiated in e.g. TopLevelClass
class TopLevelClass:
subclass = SubClass()
I understand that I can write the following...
class SubClass:
def __init__(self, class_name):
self.top_level_name = class_name
class TopLevelClass:
subclass = SubClass(class_name)
def __init__(self):
self.class_name = self.__class__.__name__
However, it would be nice to do it without needing to pass the class name as an argument when the class is initialized.
Is this possible, just wishful thinking, or a really bad idea for some reason I have not thought of yet.
I would suggest revisiting the design for these two classes. Having the instantiated class be aware of the calling class violates design principals of encapsulation and abstraction. It also creates a cyclic dependency (in this case only a logical dependency as only the name of the class is known). If you're using the class name as some kind of identifier for the instantiated class, you can pass in an id string in the constructor as you have in your example.

How to effectively use a base class

I think using a base class would be very helpful for a set of classes I am defining for an application. In the (possibly incorrect) example below, I outline what I'm going for: a base class containing an attribute that I won't want to define multiple times. In this case, the base class will define the base part of a file path that each child class will then use to build out their own more specific paths.
However, it seems like I'd have to type in parent_path to the __init__ method of the children classes anyway, regardless of the use of single inheritance from the base class.
import pathlib
class BaseObject:
def __init__(self, parent_path: pathlib.Path):
self.parent_path = parent_path
class ChildObject(BaseObject):
def __init__(self, parent_path: pathlib.Path, child_path: pathlib.Path):
super(ChildObject, self).__init__()
self.full_path = parent_path.joinpath(child_path)
class ChildObject2(BaseObject):
...
class ChildObject3(BaseObject):
...
If this is the case, then is there any reason to use inheritance from a base class like this, other than to make it clearer what my implementation is trying to do?
I don't see an advantage for this implementation. As you've noted, you still have to pass the parent_path into the child instantiation. You also have to call the parent's __init__, which counteracts the one-line clarity "improvement".
For my eyes, you've already made it clear by using good attribute names. I'd switch from parent_path to base_path, so the reader doesn't look for a parent object.
Alternately, you might want to make that a class attribute of the parent: set it once, and let all the objects share it by direct reference, rather than passing in the same value for every instantiation.
Yes, it is correct that you have to provide parent_path into the __init__ call of the parent, that is super(ChildObject, self).__init__(parent_path) (you missed to provide parent_path in your example).
However, this is Python, so there is usually help so you can avoid writing boilerplate code. In this case, I would recommend to use the attrs library. With this you can even avoid writing your init classes all together.
To get a usefulness of such inheritance scheme - make your BaseObject more flexible and accept optional (keyword) arguments:
import pathlib
class BaseObject:
def __init__(self, parent_path: pathlib.Path, child_path: pathlib.Path=None):
self.parent_path = parent_path
self.full_path = parent_path.joinpath(child_path) if child_path else parent_path
class ChildObject(BaseObject):
...
class ChildObject2(BaseObject):
...
class ChildObject3(BaseObject):
...
co = ChildObject(pathlib.Path('.'), pathlib.Path('../text_files'))
print(co, vars(co))
# <__main__.ChildObject object at 0x7f1a664b49b0> {'parent_path': PosixPath('.'), 'full_path': PosixPath('../text_files')}

Using class variables from superclass to initialize class variables in subclass without super()?

I want to use a class variable in my parent class for initializing a class variable in a child class. I think I have found a solution that uses super():
class Parent:
PARENT_CLASS_VAR = 'ABC'
class Child(Parent):
CHILD_CLASS_VAR = super().PARENT_CLASS_VAR
However, I'm curious whether this is a good way to do this and whether there are other ways.
EDIT: Meanwhile, I've also thought that another valid solution is using the __init__ constructor and calling the superclass class variable using self.

Why call a base class constructor from inherited class

I have been trying to understand this use case, where we often call a base class constructor from the inherited class, is the sole purpose of doing that is to just ensure that the base class is initialized? Or, would there be other possible use cases?
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
is the sole purpose of doing that is to just ensure that the base class is initialized?
Well yes, but what do you mean, just?
Assuming your base class has a reason to exist, it must do something.
Unless it's just a convenient collection of #staticmethod functions, whatever it does might depend on its __init__ having been called, because that's how class objects work.
Even if your base class has an empty __init__ today, it's sensible to call it, in case that changes in the future.
Or, would there be other possible use cases?
The use case is to make sure that the base class part of your object is correctly initialized. Without that, you can't safely call any of its non-static methods.
In principle your base class could do something tricksy in its __init__ (starting a background thread, or registering the instance with some singleton, or ... whatever). So yes, there could be effects other than just assigning instance variables, but this is still part of initializing an object of that base class.
In C++ or Java, the compiler will require you to call the base class constructor (either by automatically inserting a zero-argument call or by giving you an error).
Python requires you to call it yourself, as it is written:
If a base class has an __init__() method, the derived class’s __init__() method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance
The reason why is a principle of object oriented design. An A "is-a" Base, which could also be written equivalently as an A "has-a" Base. Unless you specifically want to interfere with the implementation of Base, you have to allow the object to be initialized as designed. Skipping the constructor will leave the Base object improperly initialized, disappointing anyone who expects it to behave as a Base object ought to.
When overriding a method besides the constructor, it is the programmer's discretion to delegate to the base class implementation or to override it entirely. This can still lead to incorrect behavior --- several API docs I can think of pepper the documentation with "If you override this method, you should call super" for various methods.
The point of this is to initialize all the stuff the base class usually initializes. For example,
class Base:
def __init__(self, number):
print('Base.__init__')
self.number = number
class A(Base):
def __init__(self, number, string):
super().__init__(number)
self.string = string
print('A.__init__')
In this code example it's more obvious. When A calls the base constructor, the base constructor will initialize all of the stuff needed, such as self.number. This way, the rest of A's initialization function can build on top of the base initialization function without any retyping. In this example, A is building on top of Base by adding self.string on top of self.number.

Categories

Resources