Python **kwargs in parent and child constructor - python

If I write an inheritance relationship as follows:
class Pet(object):
def __init__(self, n):
print n
class Dog(Pet):
def __init__(self, **kwargs):
Pet.__init__(self, 5)
Then the output is 5. If, however, I wanted to do:
class Pet(object):
def __init__(self, **kwargs):
if not "n" in kwargs:
raise ValueError("Please specify the number I am to print")
print kwargs["n"]
class Dog(Pet):
def __init__(self, **kwargs):
Pet.__init__(self, kwargs)
Then I get the error TypeError: __init__() takes exactly one argument (two given)
How can I pass the extra arguments up the inheritance chain in this way?

Simply pass the arguments down as keyword arguments:
class Pet(object):
def __init__(self, **kwargs):
if not "n" in kwargs:
raise ValueError("Please specify the number I am to print")
print kwargs["n"]
class Dog(Pet):
def __init__(self, **kwargs):
Pet.__init__(self, **kwargs)
However, you should use super rather than hardcoding the superclass.
Change your definition of Dog to (Python 2.X):
class Dog(Pet):
def __init__(self, **kwargs):
super(Dog, self).__init__(**kwargs)
And in Python 3.X it's nicer, just:
class Dog(Pet):
def __init__(self, **kwargs):
super().__init__(**kwargs)

Figured it out: Use Pet.__init__(self, **kwargs)
It's called "unpacking" and it transforms the dictionary kwargs into a list of argument=value pairs.
Then **kwargs in the parent constructor is able to handle them. Just passing a dictionary would not work because **kwargs in the constructor is expecting a bunch of argument=value pairs but instead I was just passing it one value, the dictionary kwargs from the child.

Related

Extending behavior of the property decorator

I would like to extend the behavior of the builtin #property decorator. The desired usage is shown in the code below:
class A:
def __init__(self):
self.xy = 42
#my_property(some_arg="some_value")
def x(self):
return self.xy
print(A().x) # should print 42
First of all, the decorator should retain the property behavior so that no () is needed after the x. Next, I would like to be able to access the arguments a programmer passes to my decorator.
I started off with this:
class my_property(property):
def __init__(self, fn):
super().__init__(fn)
TypeError: __init__() got an unexpected keyword argument 'some_arg'
After adding **kwargs:
class my_property(property):
def __init__(self, fn, **kwargs):
super().__init__(fn)
TypeError: __init__() missing 1 required positional argument: 'fn'
OK, let's do *args instead:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
TypeError: 'my_property' object is not callable
Let's make it callable:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
def __call__(self, *args, **kwargs):
pass
No errors, but prints None instead of 42
And now I am lost. I have not even yet managed to access `some_arg="some_value" and the property behavior seems to be already gone. What is wrong and how to fix it?
It's not clear how you intent to use some_arg, but to pass a parameter to a decorator you need to have "two layers" of decorators
#my_decorator(arg)
def foo():
return
under the hood this translates to my_decorator(arg)(foo) (i.e. my_decorator(arg) must return another decorator that is called with foo). The inner decorator in this case should be your custom implementation of property
def my_property(some_arg):
class inner(object):
def __init__(self, func):
print(some_arg) # do something with some_arg
self.func = func
def __get__(self, obj, type_=None):
return self.func(obj)
return inner
Now you can use it like this:
class MyClass:
def __init__(self, x):
self.x = x
#my_property('test!')
def foo(self):
return self.x
obj = MyClass(42) # > test!
obj.foo # > 42
Read more about descriptors here

OOP and Threading subclass

When we subclass the threading class, is calling the original threading__init__ method within our new class's__init__ method, essentially just wiping the slate clean?
Or are we inheriting the original __init__method's attributes?
This is how the original __init__looks for the threading class (abridged form)
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
if kwargs is None:
kwargs = {}
self._target = target
self._name = str(name or _newname())
self._args = args
self._kwargs = kwargs
So now when I create a subclass and def my int as such:
class MyThread(threading.Thread):
def __init__(self, number):
threading.Thread.__init__(self)
self.number = number
print(number)
Does this mean I am overwriting the original threading classes
init attributes such as
group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None
and thus only have access to the one attribute number that I created within my
new init method.
If so, is there a way to still have access to the original init attributes
and just add on top of those attributes when I create my new
subclass?
Your current subclass can only instantiate a Thread with it's default arguments.
To avoid having to rewrite the arguments, a form like this can be used:
def __init__(self,
subclass_positional_arg, *args,
subclass_kw_arg=None, other_arg=None, **kwargs):
super(MyThread, self).__init__(*args, **kwargs)
# Do stuff with `subclass_positional_arg`,
# `subclass_kw_arg` and `other_arg`
And you could instantiate it like this:
MyThread(positional, subclass_kw_arg=value)
MyThread(positional)
In your specific situation, you could do one of these two things:
def __init__(self, *args, number, **kwargs):
super(MyThread, self).__init__(*args, **kwargs)
self.number = number
MyThread(number=<number>) # You could also give a default value
def __init__(self, number, *args, **kwargs):
super(MyThread, self).__init__(*args, **kwargs)
self.number = number
MyThread(<number>)
# or
MyThread(number=<number>)
# Though you could no longer give a default value
In Python 2, keyword-only arguments are not allowed. You achieve a similar effect by popping from **kwargs
def __init__(self, *args, **kwargs):
# Use `kwargs.pop('number', <default value>)` to be optional
number = kwargs.pop('number')
super(MyThread, self).__init__(*args, **kwargs)
self.number = number

Python - Base class' constructor is overriden

As explained in How does Python's super() work with multiple inheritance?, super can be used in multiple inheritance as well, as it will look for the attribute in both parents. But what attribute? The subclass already includes a super (if you look at the code below). How do I specify the attribute I want? I want Error's constructor.
class Error(object):
def __init__(self, values):
self.values = values
class AddDialog(sized_controls.SizedDialog, Error):
def __init__(self, *args, **kwargs):
Error.__init__(self, *args)
super(AddDialog, self).__init__(*args, **kwargs)
It is as easy as just trying it out:
class Error(object):
def __init__(self, values):
self.values = values
print('Error')
class SizedDialog(object):
def __init__(self, values):
self.values = values
print('SizedDialog')
class AddDialog(SizedDialog, Error):
def __init__(self, *args, **kwargs):
print('AddDialog')
Error.__init__(self, *args)
super(AddDialog, self).__init__(*args, **kwargs)
Now, super() is nothing else but going along the method resolution order (MRO) which you can get with mro():
>>> AddDialog.mro()
[__main__.AddDialog, __main__.SizedDialog, __main__.Error, object]
So, in your case you call the __init__() of Error explicitly first. Then super() will, in this specific case, find the __init__() of SizedDialog because it comes before Error in the MRO.
>>> a = AddDialog(10)
AddDialog
Error
SizedDialog
If you only use super() (no call to __init__() of Error), you get only the __init__() of SizedDialog:
class AddDialog(SizedDialog, Error):
def __init__(self, *args, **kwargs):
print('AddDialog')
super(AddDialog, self).__init__(*args, **kwargs)
>>> a = AddDialog(10)
AddDialog
SizedDialog
Finally, if you only call the __init__() of Error, it is the only __init__() that is called.
class AddDialog(SizedDialog, Error):
def __init__(self, *args, **kwargs):
print('AddDialog')
Error.__init__(self, *args)
>>> a = AddDialog(10)
AddDialog
Error
So your question:
But what attribute?
has the answer:
The one you call.
It does not matter if you hard-wire the class, as done with Error, or let super() find the appropriate parent class, i.e. the next one in the MRO.
The only difference is that super() might call the __init__()of the grandparent class if the parent class does not have an __init__().
But this is the intended behavior of super().

how do you automate setting self attributes in init of python class?

I'd like to automate unpacking init variables once and for all. My thought process is to unpack all and use setattr with the name of the variable as the attribute name.
class BaseObject(object):
def __init__(self, *args):
for arg in args:
setattr(self, argname, arg)
class Sub(BaseObject):
def __init__(self, argone, argtwo, argthree, argfour):
super(Sub, self).__init__(args*)
Then you should be able to do:
In [3]: mysubclass = Sub('hi', 'stack', 'overflow', 'peeps')
In [4]: mysubclass.argtwo
Out[4]: 'stack'
Based on the param for 'stack' having been named argtwo.
This way you'll automatically have access but you could still override like below:
class Sub(BaseObject):
def __init__(self, argone, argtwo, argthree, argfour):
super(Sub, self).__init__(arglist)
clean_arg_three = process_arg_somehow(argthree)
self.argthree = clean_arg_three
Clearly I'm stuck on how to pass the actual name of the param (argone, argtwo, etc) as a string to setattr, and also how to properly pass the args into the super init (the args of super(Sub, self).__init__(args*))
Thank you
Use kwargs instead of args
class BaseObject(object):
def __init__(self, **kwargs):
for argname, arg in kwargs.iteritems():
setattr(self, argname, arg)
class Sub(BaseObject):
def __init__(self, argone, argtwo, argthree, argfour):
super(Sub, self).__init__(argone=argone, argtwo=argtwo, argthree=argthree)
Then you can do:
s = Sub('a', 'b', 'c')
BaseObject.__init__() needs to discover the names of the arguments to Sub.__init__() somehow. The explicit approach would be to pass a dictionary of arguments to BaseObject.__init__(), as Uri Shalit suggested. Alternatively, you can use the inspect module to do this magically, with no extra code in your subclass. That comes with the usual downsides of magic ("how'd that happen?").
import inspect
class BaseObject(object):
def __init__(self):
frame = inspect.stack()[1][0]
args, _, _, values = inspect.getargvalues(frame)
for key in args:
setattr(self, key, values[key])
class Sub(BaseObject):
def __init__(self, argone, argtwo, argthree, argfour):
super(Sub, self).__init__()
This works fine as written, but if you don't define Sub.__init__(), this will grab the arguments from the wrong place (i.e., from the function where you call Sub() to create an object). You might be able to use inspect to doublecheck that the caller is an __init__() function of a subclass of BaseObject. Or you could just move this code to a separate method, e.g., set_attributes_from_my_arguments() and call that from your subclasses' __init__() methods when you want this behavior.
import inspect
class BaseObject(object):
def __init__(self, args):
del args['self']
self.__dict__.update(args)
class Sub(BaseObject):
def __init__(self, argone, argtwo, argthree, argfour):
args = inspect.getargvalues(inspect.currentframe()).locals
super(Sub, self).__init__(args)
s = Sub(1, 2, 3, 4)

Get attribute from a super class in python

I have a base class, a bunch of subclasses, and for each of these subclasses, I have another set of sub-subclasses. For example:
class BaseClass(object):
def __init__(self):
with open(config.txt) as f
self.config_array = f.readlines()
class FirstOrderSubClass(BaseClass):
def __init__(self, name):
self.name = name
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version):
self.name = name
self.version = version
super(SecondOrderSubClass, self).__init__(self.name)
# needed to access self.config_array
print self.config_array
I need to get the __init__() method of the SecondOrderSubClass to make the following assignment: self.lines = self.config_array.
EDIT: added line print self.config_array. If I run the code I get:
TypeError: __getattr__() takes exactly 1 argument (2 given)
You cannot access self.config_array until BaseClass.__init__() has run to set the attribute.
Either fix FirstOrderSubClass to also invoke the base class __init__ or call it directly.
Fixing the FirstOrderSubClass is probably the best way to do so:
class FirstOrderSubClass(BaseClass):
def __init__(self, name):
super(FirstOrderSubClass, self).__init__()
self.name = name
However, your __init__ method signatures do not match so you cannot rely on cooperative behaviour here; as soon as you add a mix-in class in the hierarchy, things can and probably will break. See *Python's super() is considered super! by Raymond Hettinger, or it's followup PyCon presentation to explain why you want your signatures to match.
Calling the BaseClass.__init__ unbound method directly (passing in self explicitly) would also work:
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version):
super(SecondOrderSubClass, self).__init__(name)
self.version = version
BaseClass.__init__(self)
Note that there is no point in assigning to self.name there if you are going to ask FirstOrderSubClass.__init__ to do the exact same thing.
The proper way to use super() is for all your methods to at least accept all the same arguments. Since object.__init__() never does, this means you need a sentinel class that does not use super(); BaseClass will do nicely here. You can use *args and **kw to capture any additional arguments and just ignore those to make cooperative subclassing work:
class BaseClass(object):
def __init__(self, *args, **kw):
with open(config.txt) as f
self.config_array = f.readlines()
class FirstOrderSubClass(BaseClass):
def __init__(self, name, *args, **kw):
super(FirstOrderSubClass, self).__init__(*args, **kw)
self.name = name
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version, *args, **kw):
super(SecondOrderSubClass, self).__init__(name, *args, **kw)
self.version = version
You have to call the FirstOrderSubClass super method:
class BaseClass(object):
def __init__(self):
with open("config.example.txt",'w') as f:
f.write("Hello world")
with open("config.example.txt") as f:
self.config_array = f.readlines()
class FirstOrderSubClass(BaseClass):
def __init__(self, name):
super(FirstOrderSubClass,self).__init__()
self.name = name
class SecondOrderSubClass(FirstOrderSubClass):
def __init__(self, name, version):
self.name = name
self.version = version
super(SecondOrderSubClass, self).__init__(self.name)
# needed to access self.config_array
grandchild = SecondOrderSubClass("peter",2.0)
print grandchild.config_array
##>>>
##['Hello world']

Categories