How to effectively use a base class - python

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')}

Related

Initializing a class derived from a base type

I am creating a class called an Environment which subclasses a dictionary. It looks something like this:
class Env(dict):
"An environment dict, containing the parent Env (or None) where created."
def __init__(self, parent=None):
self.parent = parent
# super().__init__() <-- not included
Pylint complains that:
super-init-not-called: __init__ method from base class 'dict' is not called.
What does doing super() on a dict type do? Is this something that is required to be done, and if so, why is it necessary?
After playing around with this a bit, I'm not so sure it does anything (or maybe it automatically does the super behind-the-scenes anyways). Here's an example:
class Env1(dict):
def __init__(self, parent=None):
self.parent = parent
super().__init__()
class Env2(dict):
def __init__(self, parent=None):
self.parent = parent
dir(Env1()) == dir(Env2()), len(dir(Env1))
(True, 48)
Pylint doesn't know what dict.__init__ does. It can't be sure if there's some important setup logic in that method or not. That's why it's warning you, so that you can either decide to call super().__init__ to be safe, or to silence the warning if you're confident you don't need the call.
I'm pretty sure you don't need to call dict.__init__ when you want to initialize your instances as empty dictionaries. But that may be dependent on the implementation details of the dict class you're inheriting from (which does all of its setup in the C-API equivalent __new__). Another Python implementation might do more of the setup work for its dictionaries in __init__ and then your code wouldn't work correctly.
To be safe, it's generally a good idea to call your parent class's __init__ method. This is such broad advice that it's baked into Pylint. You can ignore those warnings, and even add comments to your code that will suppress the ones that don't apply to certain parts of your code (so they don't distract you from real issues). But most of the warnings are generally good to obey, even if they don't reflect a serious bug in your current code.
Calling super() is not required, but makes sense if you want to follow OOP, specifically, the Liskov substitution principle.
From Wikipedia, the Liskov substitution principle says:
If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program.
In plain words, let S is a subclass of T. If T has a method or attribute, then S also has it. Moreover if T.some_method(arg1, arg2,...,argn) is a proper syntax, then S.some_method(arg1, arg2, ..., argn) is also a proper syntax and the output is identical. (There is more to it, but I skip it for simplicity)
What does this theory mean for our case? If dict has any attributes (except parent) declared during the init, they are lost, and the Liskov substitution principle is violated. Please check the following example.
class T:
def __init__(self):
self.t = 1
class S(T):
def __init__(self, parent=None):
self.parent = parent
s = S()
s.t
raises the error because class S does not have access to the attribute t.
Why no error is in our case? Because there are no attributes created inside __init__ in the parent class dict. Therefore, the extension works well and does not violate OOP.
To fix PyLint issue, change the code as follows:
class Env(dict):
def __init__(self, parent=None):
super().__init__() # get all parent's __init__ setup
self.parent = parent # add your attributes
It does just what the documentation teaches us: it calls the __init__ method of the parent class. This does all of the initialization behind the attributes you supposedly want to inherit from the parent.
In general, if you do not call super().__init__(), then your object has only the added parent field, plus access to methods and class attributes of the parent. This will work just fine (except for the warning) for any class that does not use initialization arguments -- or, in particular, one that does not initialize any fields on the fly.
Python built-in types do what you expect (or want), so your given use is okay.
In contrast, consider the case of extending your Env class to one called Context:
class Context(Env):
def __init__(upper, lower):
self.upper = upper
self.lower = lower
ctx = Context(7, 0)
print(ctx.upper)
print(ctx.parent)
At this last statement, you'll get a run-time fault: ctx has no attribute parent, since I never called super().__init__() in Context.__init__

How to get filename of subclass?

How to get the filename of the subclass?
Example:
base.py:
class BaseClass:
def __init__(self):
# How to get the path "./main1.py"?
main1.py:
from base import BaseClass
class MainClass1(BaseClass):
pass
Remember that self in BaseClass.__init__ is an instance of the actual class that's being initialised. Therefore, one solution, is to ask that class which module it came from, and then from the path for that module:
import importlib
class BaseClass:
def __init__(self):
m = importlib.import_module(self.__module__)
print m.__file__
I think there are probably a number of way you could end up with a module that you can't import though; this doesn't feel like the most robust solution.
If all you're trying to do is identify where the subclass came from, then probably combining the module name and class name is sufficient, since that should uniquely identify it:
class BaseClass:
def __init__(self):
print "{}.{}".format(
self.__module__,
self.__class__.__name__
)
You could do it by reaching back through the calling stack to get the global namespace of the caller of the BaseClass.__init__() method, and from that you can extract the name of the file it is in by using the value of the __file__ key in that namespace.
Here's what I mean:
base.py:
import sys
class BaseClass(object):
def __init__(self):
print('In BaseClass.__init__()')
callers_path = sys._getframe(1).f_globals['__file__']
print(' callers_path:', callers_path)
main1.py:
from base import BaseClass
class MainClass1(BaseClass):
def __init(self):
super().__init__()
mainclass1 = MainClass1()
Sample output of running main1.py:
In BaseClass.__init__()
callers_path: the\path\to\main1.py
I think you're looking to the wrong mechanism for your solution. Your comments suggest that what you want is an exception handler with minimal trace-back capability. This is not something readily handled within the general class mechanism.
Rather, you should look into Python's stack inspection capabilities. Very simply, you want your __init__ method to report the file name of the calling sub-class. You can hammer this by requiring the caller to pass its own __file__ value. In automated fashion, you can dig back one stack frame and access __file__ via that context record. Note that this approach assumes that the only time you need this information is when __init__ is called is directly from a sub-class method.
Is that enough to get you to the right documentation?

Class inheritance: Access parent class arguments in a subclass?

I'm trying to wrap my head around how to utilize inheritance in some code I'm writing for an API. I have the following parent class which holds a bunch of common variables that I'd like to instantiate once, and inherit with other classes to make my code look cleaner:
class ApiCommon(object):
def __init__(self, _apikey, _serviceid=None, _vclversion=None,
_aclname=None, _aclid=None):
self.BaseApiUrl = "https://api.fastly.com"
self.APIKey = _apikey
self.headers = {'Fastly-Key': self.APIKey}
self.ServiceID = _serviceid
self.VCLVersion = _vclversion
self.ACLName = _aclname
self.ACLid = _aclid
self.Data = None
self.IP = None
self.CIDR = None
self.fullurl = None
self.r = None
self.jsonresp = None
self.ACLcomment = None
self.ACLentryid = None
And I am inheriting it in another class below, like so in a lib file called lib/security.py:
from apicommon import ApiCommon
class EdgeAclControl(ApiCommon):
def __init__(self):
super(EdgeAclControl, self).__init__()
...
def somemethodhere(self):
return 'stuff'
When I instantiate an object for ApiCommon(object), I can't access the methods in EdgeAclControl(ApiCommon). Example of what I'm trying which isn't working:
from lib import security
gza = security.ApiCommon(_aclname='pytest', _apikey='mykey',
_serviceid='stuffhere', _vclversion=5)
gza.somemethodhere()
How would I instantiate ApiCommon and have access to the methods in EdgeAclControl?
Your current code appears to be trying to use inheritance backwards. When you create an instance of ApiCommon, it will only get the methods defined in that base class. If you want to get methods from a subclass, you need to create an instance of the subclass instead.
So the first fix you need to make is to change gza = security.ApiCommon(...) to gza = EdgeAclControl(...) (though depending on how you're doing your imports, you might need to prefix the class name with a module).
The second issue is that your EdgeAclControl class doesn't take the arguments that its base class needs. Your current code doesn't pass any arguments to super(...).__init__, which doesn't work since the _apikey parameter is required. You could repeat all the arguments again in the subclass, but a lot of the time it's easier to use variable-argument syntax instead.
I suggest that you change EdgeAclControl.__init__ to accept *args and/or **kwargs and pass on those variable arguments when it calls its parent's __init__ method using super. That would look like this:
def __init__(self, *args, **kwargs):
super(EdgeAclControl, self).__init__(*args, **kwargs)
Note that if, as in this example, you're not doing anything other than calling the parent __init__ method in the derived __init__ method, you could get the same effect by just deleting the derived version entirely!
It's likely that your real code does something in EdgeAclControl.__init__, so you may need to keep it in some form. Note that it can take arguments normally in addition to the *args and **kwargs. Just remember to pass on the extra arguments, if necessary, when calling the base class.
May I ask why you have to instantiate an ApiCommon object? I don't see any point of doing so.
If you insist doing that, you have to add methods in superclass and then subclass may override theses methods. But you still couldn't access methods of EdgeAclControl from ApiCommon object

Passing parameter to base class constructor or using instance variable?

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.

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 correct parameters.
What if that FileInfo class had more than one ancestor class?
Do I have to explicitly call all of the ancestor classes' __init__ methods?
Also, do I have to do this to any other method I want to override?
The book is a bit dated with respect to subclass-superclass calling. It's also a little dated with respect to subclassing built-in classes.
It looks like this nowadays:
class FileInfo(dict):
"""store file metadata"""
def __init__(self, filename=None):
super(FileInfo, self).__init__()
self["name"] = filename
Note the following:
We can directly subclass built-in classes, like dict, list, tuple, etc.
The super function handles tracking down this class's superclasses and calling functions in them appropriately.
In each class that you need to inherit from, you can run a loop of each class that needs init'd upon initiation of the child class...an example that can copied might be better understood...
class Female_Grandparent:
def __init__(self):
self.grandma_name = 'Grandma'
class Male_Grandparent:
def __init__(self):
self.grandpa_name = 'Grandpa'
class Parent(Female_Grandparent, Male_Grandparent):
def __init__(self):
Female_Grandparent.__init__(self)
Male_Grandparent.__init__(self)
self.parent_name = 'Parent Class'
class Child(Parent):
def __init__(self):
Parent.__init__(self)
#---------------------------------------------------------------------------------------#
for cls in Parent.__bases__: # This block grabs the classes of the child
cls.__init__(self) # class (which is named 'Parent' in this case),
# and iterates through them, initiating each one.
# The result is that each parent, of each child,
# is automatically handled upon initiation of the
# dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#
g = Female_Grandparent()
print g.grandma_name
p = Parent()
print p.grandma_name
child = Child()
print child.grandma_name
You don't really have to call the __init__ methods of the base class(es), but you usually want to do it because the base classes will do some important initializations there that are needed for rest of the classes methods to work.
For other methods it depends on your intentions. If you just want to add something to the base classes behavior you will want to call the base classes method additionally to your own code. If you want to fundamentally change the behavior, you might not call the base class' method and implement all the functionality directly in the derived class.
If the FileInfo class has more than one ancestor class then you should definitely call all of their __init__() functions. You should also do the same for the __del__() function, which is a destructor.
Yes, you must call __init__ for each parent class. The same goes for functions, if you are overriding a function that exists in both parents.

Categories

Resources