Diamond inheritance in Python with different signatures - python

Here's the setup:
class Player(object):
def __init__(self, heigth):
self.heigth = heigth
print('do not forget that this should happen once!')
class Attacker(Player):
def __init__(self, heigth, goal_probability):
super().__init__(heigth)
self.goal_prob = goal_probability
def hit(self):
pass
# implementation
class Goalie(Player):
def __init__(self, heigth, save_probability=0.1):
super().__init__(heigth)
self.save_prob = save_probability
def catch(self):
pass
# implementation
class UniversalPlayer(Attacker, Goalie):
pass
up = UniversalPlayer(heigth=1.96, goal_probability=0.6)
It all works as expected: the MRO chooses Attacker first, then Goalie. I call UniversalPlayer's constructor with Attacker's __init__ signature, Goalie's constructor is called with Player's signature, it goes ok because save_probability has a default value but the problem is that I have no way of choosing save_probability, apart from setting up.save_probability after instantiating up, which I find very inelegant.
Furthermore, had Goalie not had a default value for save_probability, this code would raise an exception.
Is there a way to write UniversalPlayer so that I can choose save_probability too, or is there some fundamental problem here that cannot be worked around?

Each additional parameter to __init__ needs to have a class responsible for removing it from calls to super, so that when object.__init__ is finally called, you don't accidentally pass any arguments to it. Additionally, each method has to accept arbitrary arguments and pass them on for the next method to possibly handle.
# Player will be responsible for height
class Player(object):
def __init__(self, height, **kwargs):
super().__init__(**kwargs) # Player needs to use super too!
self.height = height
print('do not forget that this should happen once!')
# Attacker will be responsible for goal_probability
class Attacker(Player):
def __init__(self, height, goal_probability, **kwargs):
super().__init__(height, **kwargs)
self.goal_prob = goal_probability
def hit(self):
pass
# Goalie will be responsible for save_probability
class Goalie(Player):
def __init__(self, height, save_probability=0.1, **kwargs):
super().__init__(height, **kwargs)
self.save_prob = save_probability
def catch(self):
pass
# implementation
class UniversalPlayer(Attacker, Goalie):
pass
# Pass all arguments
# Life is easier if you stick to keyword arguments when using super().__init__
up = UniversalPlayer(height=1.96, goal_probability=0.6, save_probability=0.2)
Now, Attacker.__init__ is the first to be called. It uses goal_probability, then does not pass it on to other calls. It accepts save_probability via **kwargs and passes it on for Goalie.__init__ to eventually receive. Note that neither Attacker.__init__ nor Goalie.__init__ would have to explicitly include height in their argument lists; it could also be accepted via **kwargs to be eventually received by Player.__init__.

Besides the fact I'm not sure if separate classes is the best way to handle these, the issue is that your constructors can't handle unknown arguments. To allow them to use the *args, **kwargs notation.
Effectively all arguments will be passed to each __init__ and the unused ones ignored.
class Player(object):
def __init__(self, *args, **kwargs):
self.height = kwargs['height']
class Attacker(Player):
def __init__(self, goal_probability, *args, **kwargs):
super().__init__(*args, **kwargs)
self.goal_prob = goal_probability
def hit(self):
pass
# implementation
class Goalie(Player):
def __init__(self, save_probability, *args, **kwargs):
super().__init__(*args, **kwargs)
self.save_prob = save_probability
def catch(self):
pass
# implementation
class UniversalPlayer(Attacker, Goalie):
pass
up = UniversalPlayer(height=1.96, goal_probability=0.6, save_probability=0.2)

Related

Correct way of passing a self variable as argument to a mixin parent method

I have to model a warrior and the different kinds of attacks he can perform. The idea is to use mixins to contain the attack logic. I have my classes defined in the following way:
class Warrior:
def __init__(self, energy):
self.energy = energy
class TemplarKnight(Warrior, HandToHandCombatMixin):
pass
class CombatMixin:
def __init__(self):
self.attacks_cost = {}
def attack(self, attacker, attack_cost):
if attacker.energy < attack_cost:
print('Not enough energy to attack')
else:
attacker.energy -= attack_cost
print('Attack!')
class HandToHandCombatMixin(CombatMixin):
def __init__(self):
super().__init__()
self.attacks_cost['sword_spin'] = 10
def sword_spin(self, attacker):
return self.attack(attacker, self.attacks_cost['sword_spin'])
But the problem comes when I try to test this setup. When I do
class TestTemplarKnight(unittest.TestCase):
def setUp(self):
self.templar = TemplarKnight(energy=100)
def test_templar_knight_can_sword_spin(self):
self.templar.sword_spin(self.warrior)
self.assertEquals(self.templar.energy, 90)
I get
def sword_spin(self, attacker):
return self.attack(
> attacker, self.attacks_cost['sword_spin'])
E AttributeError: 'TemplarKnight' object has no attribute 'attacks_cost'
It seems that Python thinks that the parameter self.attacks_cost (when calling self.attack() inside the sword_spin() method of the HandToHandCombatMixin class) belongs to the TemplarKnight class instead of the HandToHandCombatMixin.
How should I have written this code to make Python look for self.attacks_cost inside HandToHandCombatMixin?
To use super correctly, all the classes involved need to use it. Right now, Warrior.__init__ is called first, but it doesn't use super, so HandToHandCombatMixin.__init__ is never called.
Make the following additions:
class Warrior:
def __init__(self, energy, **kwargs):
super().__init__(**kwargs)
self.energy = energy
class TemplarKnight(Warrior, HandToHandCombatMixin):
pass
class CombatMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.attacks_cost = {}
def attack(self, attacker, attack_cost):
if attacker.energy < attack_cost:
print('Not enough energy to attack')
else:
attacker.energy -= attack_cost
print('Attack!')
class HandToHandCombatMixin(CombatMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.attacks_cost['sword_spin'] = 10
def sword_spin(self, attacker):
return self.attack(attacker, self.attacks_cost['sword_spin'])
Now when you instantiate TemplarKnight, you'll guarantee that all the __init__ methods are called, and in the correct order. Eventually, once of the calls to super() will cause object.__init__ to be called, at which point the chain finally ends. If you are correctly handling the keyword arguments, **kwargs will be empty by the time that happens.

Python inheritance structure and arguments

I am trying to design a class structure that allows the user to define their own class that overloads predefined methods in other classes. In this case the user would create the C class to overload the "function" method in D. The user created C class has common logic for other user created classes A and B so they inherit from C to overload "function" but also inherit from D to use D's other methods. The issue I am having is how to pass "value" from A and B to D and ignore passing it to C. What I currently have written will produce an error as C does not have "value" as an argument.
I know that I can add "value" (or *args) to C's init method and the super call but I don't want to have to know what inputs other classes need in order to add new classes to A and B. Also, if I swap the order of C and D I won't get an error but then I don't use C's overloaded "function". Is there an obvious way around this?
class D(SomethingElse):
def __init__(self, value, **kwargs):
super(D, self).__init__(**kwargs)
self.value = value
def function(self):
return self.value
def other_method(self):
pass
class C(object):
def __init__(self):
super(C, self).__init__()
def function(self):
return self.value*2
class B(C, D):
def __init__(self, value, **kwargs):
super(B, self).__init__(value, **kwargs)
class A(C, D):
def __init__(self, value, **kwargs):
super(A, self).__init__(value, **kwargs)
a = A(3)
print(a.function())
>>> 6
Essentially, there are two things you need to do to make your __init__ methods play nice with multiple inheritance in Python:
Always take a **kwargs parameter, and always call super().__init__(**kwargs), even if you think you are the base class. Just because your superclass is object doesn't mean you are last (before object) in the method resolution order.
Don't pass your parent class's __init__ arguments explicitly; only pass them via **kwargs. Your parent class isn't necessarily the next one after you in the method resolution order, so positional arguments might be passed to the wrong other __init__ method.
This is called "co-operative subclassing". Let's try with your example code:
class D:
def __init__(self, value, **kwargs):
self.value = value
super().__init__(**kwargs)
def function(self):
return self.value
class C:
# add **kwargs parameter
def __init__(self, **kwargs):
# pass kwargs to super().__init__
super().__init__(**kwargs)
def function(self):
return self.value * 2
class B(C, D):
# don't take parent class's value arg explicitly
def __init__(self, **kwargs):
# pass value arg via kwargs
super().__init__(**kwargs)
class A(C, D):
# don't take parent class's value arg explicitly
def __init__(self, **kwargs):
# pass value arg via kwargs
super().__init__(**kwargs)
Demo:
>>> a = A(value=3)
>>> a.value
3
>>> a.function()
6
Note that value must be passed to the A constructor as a keyword argument, not as a positional argument. It's also recommended to set self.value = value before calling super().__init__.
I've also simplified class C(object): to class C:, and super(C, self) to just super() since these are equivalent in Python 3.
So I'm trying to understand the point of A AND B. I'm guessing that maybe you want to mix in the superclass behavior and sometimes have local behavior. So suppose A is just mixing together behaviors, and B has some local behavior and state.
If you don't need your own state, you probably don't need an __init__. So for A and C just omit __init__.
class SomethingElse(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class D(SomethingElse):
def __init__(self, value, *args, **kwargs):
super(D, self).__init__(*args, **kwargs)
self.value = value
def function(self):
return self.value
def other_method(self):
return self.__dict__
class C(object):
#def __init__(self):
# super(C, self).__init__()
def function(self):
return self.value*2
class B(C, D):
def __init__(self, value, bstate, *args, **kwargs):
super(B, self).__init__(value, *args, **kwargs)
self.bstate = bstate
def __repr__(self):
return (self.__class__.__name__ + ' ' +
self.bstate + ' ' + str(self.other_method()))
class A(C, D):
pass
a = A(3)
b = B(21, 'extra')
a.function()
6
b.function()
42
repr(a)
'<xx.A object at 0x107cf5e10>'
repr(b)
"B extra {'args': (), 'bstate': 'extra', 'value': 21, 'kwargs': {}}"
I've kept python2 syntax assuming you might still be using it, but as another answer points out, python3 simplifies super() syntax, and you really should be using python3 now.
If you swap C and D you are changing the python method resolution order, and that will indeed change the method to which a call to A.function resolves.

super().__init__(....) in mixins fails with `Too many arguments for "__init__" of "object" `

When I create a mixin class that extends the logic of __init__, the regular thing to do is:
class ExtraValuemixin:
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# some extra initialization
self._extra_value = 1
def retrieve_extra_value(self):
return self._extra_value
However this looks wrong to mypy, as it says:
Too many arguments for "__init__" of "object"
I get it, there's no *args or **kwargs in the object's constructor signature; but this is a mixin, and it relies on its childen's constructors. Ho do I make mypy understand this?
Full example:
class ExtraValuemixin:
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# some extra initialization
self._extra_value = 1
def retrieve_extra_value(self):
return self._extra_value
class ParentObj:
def __init__(self, value):
self.value = value
class ChildObj(ExtraValuemixin, ParentObj):
pass
obj = ChildObj(value=5)
print(obj.retrieve_extra_value())
super().__init__(...)
Calls the __init__ function of it's parent class.
Defining a class with
class XXX:
(Omitting the hierarchal parent), leaves the parent as the default: our friend and beloved object class.
And we know the source of that class looks like:
class object:
def __init__():
Meaning, that it accepts no arguments. You essentially called this __init__ with two arguments. Hence, your exact error of:
Too many arguments for "__init__" of "object"

Python instance Decorator

I would like to decorate certain instance functions with a decorator from a "parent" instance, is there a way that I can use the instance to decorate the functions.
Here is the thought on what I need to do;
class Foo(object):
def __init__(self):
pass
def set_configuration(self, function):
def config(*args, **kwargs):
# NOTE: this function needs to use instance variables.
print 'foo ' + function()
return config()
class Bar(object):
def __init__(self, parent):
self.parent = parent
#self.parent.set_configuration
def set_config_2(self)
return 'bar'
foo = Foo()
foo.bar = Bar(foo)
foo.bar.set_config_2
EDIT:
Ok guys here is the actual issue, I have a device that i need to interact with. So a device may have several levels to it ie a device a
has multiple interfaces and an interface may have multiple vlans attached. So the idea is that if I want to change a vlan on an interface, instead of building a full command I would like to allow the parent class to handle the building of it's level of the command. So I would like to just call the "change vlan" function and it will send it's part of the command to the next level to be wrapped and sent up the chain till it hits the device level and the full command is sent to the device.
class Device(object):
def __init__(self):
self.interfaces = list()
self.ssh = ssh('blah')
def set_device(self, function):
self.ssh.command('setup commands')
self.ssh.command(wrapped command here)
self.ssh.command('exit commands')
class Interface(object):
def __init__(self, name, parent):
self.name
self.parent
self.vlan = Vlan('name')
def set_interface(self):
return self.name
class Vlan(object):
def __init__(self, name, parent):
self.name = name
self.parent = parent
def set_vlan(self):
return self.name
I hope this makes more sense. if not please let me know.
No, you cannot use decorators here, because at definition time of Bar, parent is not known.
Simply use set_configuration with a argument:
class Foo(object):
def __init__(self):
pass
def set_configuration(self, function):
def config(*args, **kwargs):
# NOTE: this function needs to use instance variables.
print 'foo ' + function()
return config
class Bar(object):
def __init__(self, parent):
self.parent = parent
def set_config_2(self, args)
def inner_function():
return 'bar'
return self.parent.set_configuration(inner_function)(args)
foo = Foo()
foo.bar = Bar(foo)
foo.bar.set_config_2(123)
Python is a dynamic language so many things are possible. I'm making no comment about whether this is a good thing to do or not - and I really can't understand the purpose of your logic.
To make this possible you will need dynamically create the set_config_2 in Bar.__init__ as parent is unknown at the class definition time:
from types import MethodType
class Foo(object):
def __init__(self):
pass
def set_configuration(self, f):
def config(inst, *args, **kwargs):
print('foo', f(inst, *args, **kwargs))
return config
class Bar(object):
def __init__(self, parent):
self.parent = parent
#self.parent.set_configuration
def set_config_2(inst):
return 'bar'
self.set_config_2 = MethodType(set_config_2, self)
foo = Foo()
foo.bar = Bar(foo)
foo.bar.set_config_2()
Output:
foo bar
This is desperately ugly and there must be a better way of doing what you are attempting. Perhaps you can ask a different question explaining what you are trying to achieve.
Your decorator does not have to use instance methods, since that's the wrapping function config who needs them. Therefore, the decorator does not have to be a method. For example:
def set_configuration(func):
#functools.wraps(func) # copy function's metadata
def wrapper(self, *args, **kwargs):
# do whatever you want to fetch the config data
return 'foo' + func(self, *args, **kwargs)
return wrapper
That said, there likely is a more straightforward and explicit way, depending on what exactly you want.
I'm pretty sure you can do this without making the decorator an instance. Here are a couple ideas.
Invert the hierarchy
It seems to me like the hierarchy you have is backwards. My understanding:
Device is only providing the ssh instance
The common method you want to call is something the VLAN defines
The setup and exit commands are constants
By making the hierarchy go the other way, you can define the "change VLAN" method to access stuff from the lower levels that it needs.
class Device(object):
def __init__(self):
self.ssh = ssh('blah')
class Interface(object):
def __init__(self, name, device):
self.name
self.device = device
class Vlan(object):
def __init__(self, name, change_command, interface):
self.name = name
# How you actually store this command is completely up to you.
# You might want to shove it in an abstract method
# and subclass Vlan, but the point is make it part of the
# Vlan somehow.
self.change_command = change_command
self.interface = interface
def change_vlan(self):
ssh = self.interface.device.ssh
ssh.command('setup commands')
ssh.command(self.change_command)
ssh.command('exit commands')
device1 = Device()
device2 = Device()
interface1 = Interface('i1', device1)
interface2 = Interface('i2', device1)
interface3 = Interface('i3', device2)
vlans = [
Vlan('v1', 'change 1', interface1)
Vlan('v2', 'change 2', interface1)
Vlan('v3', 'change 3', interface2)
Vlan('v4', 'change 4', interface3)
]
This might not show exactly what you want to do, but hopefully it demonstrates how you can set this up with the hierarchy going the other way.
Make The decorator accept a Device
Alternatively, if you still think decorating is a better option, you can make the decorate accept the instances you need.
def ssh_command(device, function):
def execute_ssh_command(*args, **kwargs):
device.ssh.command('setup commands')
device.ssh.command(wrapped command here)
device.ssh.command('exit commands')
# Note: no parentheses. You're returning the function itself
return execute_ssh_command
class Interface(object):
def __init__(self, name, parent):
self.name
self.parent
self.vlan = Vlan('name')
#ssh_command
def set_interface(self):
return self.name
Note you'll need to make a separate subclass per whatever thing uses the decorator.

How to pass an augment in super() correctly

My class takes an augment called resource:
> AClass(resource="123")
Class:
class AClass(Base):
def __init__(self, resource):
super(AClass, self).__init__(self)
Which will be set in the Base class it extends from.
class BaseHTTP(object):
def __init__(self, resource, data=None):
self.resource = resource
In Python 2.7 what should I be doing to make sure the base class gets these arguments, is this OK...
super(Get, self).__init__(self, resource)
You should not pass self to super(...).__init__, as super(...).__init__ returns the bound method:
class AClass(Base):
def __init__(self, resource):
super(AClass, self).__init__(resource)
Also, I'm not sure what Get is. Usually super's first argument should be the class from which it is called -- in this case, AClass.
Bonus trivia: super(...).__new__, in contrast, returns the staticmethod, since __new__ is a staticmethod. So for __new__, self must still be passed:
super(...).__new__(self, ...)
In addition to #unutbu's excellent answer, let me point out the canonical use of this idiom.
class Parent(object):
def __init__(self, name, species, gender):
self.name = name
self.color = color
self.species = species
self.gender = gender
self.children = []
def make_kid(self, partner, child_name):
if self.gender == "F":
return Child(mom=self, dad=partner, name=child_name,
species=self.species, gender=random.choice(["M", "F"]))
else:
return Child(mom=partner, dad=self, name=child_name,
species=self.species, gender=random.choice(["M", "F"]))
class Child(Parent):
def __init__(self, mom=None, dad=None, *args, **kwargs):
# a list of arguments we care about as a Child, followed by
# *args, **kwargs that other classes further up the MRO may need
self.mom = mom
self.dad = dad
# strip out the arguments we deal with here as a Child
super(Child, self).__init__(*args, **kwargs)
# then send the rest of them to the parent object
man = Parent("Adam","Human","M")
woman = Parent("Eve","Human","F")
child = man.make_kid(woman, "Junior")
I usually use:
class AClass(Base):
def __init__(self, resource):
Base.__init__(self, resource)
But #unutbu solution is better, I think.
Hope this helps.

Categories

Resources