Multiple inheritance, super() and their correct use with arguments in Python - python-3.x

I'm trying to understand multiple inheritance in python. I think that "kinda" got it, but I'm missing a few pieces. I know that if I have two clases I can do something like:
class A():
def __init__(self,name):
self.name = name
class B(A):
def __init__(self,name):
A.__init__(self,name)
self.mm = False
self.name = name
b = B("Peter")
My problem is when I have more classes and each class has their own init arguments. At first glance, it makes like no sense to have something like this:
class A():
def __init__(self,name,arg_a1,arg_a2):
self.name = name
class B(A):
def __init__(self,name,arg_b1,arg_b2,arg_a1,arg_a2...):
A.__init__(self,name,arg_a1,arg_a2...)
self.mm = False
self.name = name
class C(B):
def __init__(self,name,arg_c1,arg_c2,arg_b1,arg_b2,arg_a1,arg_a2.........):
B.__init__(self,name,arg_b1,arg_b2,arg_a1,arg_a2...)
self.name = name
So I started to look how to do it in an efficient way and not just hardcode it. Thats when I came across with multiple inheritance and thats when my doubts started to arraise.
If I have 3 classes:
class A():
def __init__(self,name):
self.name = name
class B(A):
def __init__(self,name,*args,**kwargs):
super().__init__(*args,**kwargs)
self.mm = False
self.name = name
class C(B):
def __init__(self,a,j,*args,**kwargs):
super().__init__(*args,**kwargs)
self.a = a
self.j = j
c = C("p",1,5,name="p")
Why this give an error but adding name as an init argument does not?
In this other example, if I add another argument to A init's function the I get TypeError: __init__() got multiple values for argument 'name'.
class A():
def __init__(self,name,lastname):
self.name = name
self.lastname = lastname
class B(A):
def __init__(self,name,*args,**kwargs):
super().__init__(name,*args,**kwargs)
self.mm = False
self.name = name
class C(B):
def __init__(self,a,j,*args,**kwargs):
super().__init__(*args,**kwargs)
self.a = a
self.j = j
c = C("p",1,5,name="p")
So, after all this, several questions comes to my mind.
Why this TypeError is generated?
How can I make inheritance "smart"?
Do I always need to use *args and **kwargs with multiple inheritance?
And all this gets me to the point to the libraries I use daily. Probably some of them use this concetps (I don't know, I'm assuming). What happes when the user puts a kwarg that is not present in any class? How do python "knows" that name goes in class A and not class B or viceversa?

Related

OOP - Python - printing instance variable too when I call static method alone

Here in this code I am just calling out my static method, but it prints my instance variable too. Could you please explain the reason for that, and how to avoid them being printed?
Like below:
I am a static Method
None
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
print("I am a static Method")
p1 = Player()
print(p1.demo())
As Python docs says:
Print objects to the text stream file, separated by sep and followed
by end. sep, end, file, and flush, if present, must be given as
keyword arguments.
So you can return your message in method and then just print it:
class Player:
def __init__(self, name = None):
self.name = name # creating instance variables
#staticmethod
def demo():
return "I am a static Method"
p1 = Player()
print(p1.demo())

Decorators unexpectedly change constructor behavior in Python

Below, I show a simplified example of a more complicated code, but nonetheless, it fully represents the issue that I have encountered.
Part 1: this works fine, no issues:
class Animal():
def __init__(self, animal_name = "no name given"):
self.set_name(animal_name)
def get_name(self):
return self._animal_name
def set_name(self, animal_name):
self._animal_name = animal_name
class Dog(Animal):
def __init__(self, dog_breed = "no breed", dog_name = "no name given"):
self._dog_breed = dog_breed
super().__init__(dog_name)
def get_breed(self):
print(self._dog_breed)
x = Dog('Greyhound', 'Rich')
Part 2: after introducing getter & setter decorators, the code stops working:
class Animal():
def __init__(self, animal_name = "no name given"):
#THE LINE BELOW SEEMS TO CAUSE AN ISSUE
self.name(animal_name)
#property
def name(self):
return self._animal_name
#name.setter
def name(self, animal_name):
self._animal_name = animal_name
class Dog(Animal):
def __init__(self, dog_breed = "no breed", dog_name = "no name given"):
self._dog_breed = dog_breed
super().__init__(dog_name)
def get_breed(self):
print(self._dog_breed)
x = Dog('Greyhound', 'Rich')
Output: AttributeError: 'Dog' object has no attribute '_animal_name'
When I keep the decorators in Part 2 but change the constructor in the Animal class to:
class Animal():
def __init__(self, animal_name = "no name given"):
self._animal_name=animal_name
It works.
I am just curious why it doesn't work in the example above in Part 2?
Short answer:
The line
self.name(animal_name)
can be split in two parts:
tmp = self.name
tmp(animal_name)
First, self.name calls the getter and the result is treated as a function. The getter uses return self._animal_name and since the setter has never been called, the respective error occurs.
Long answer:
Let's take the following class:
class Animal:
def __init__(self, animal_name):
self.name(animal_name)
#property
def name(self):
return self._animal_name
#name.setter
def name(self, animal_name):
self._animal_name = animal_name
To understand what the line
self.name(animal_name)
actually does, you first need to understand decorators.
The code
#dec
def func(a, b, ...):
[...]
is equivalent to
def func_impl(a, b, ...):
[...]
func = dec(func_impl)
(except that you can not call func_impl directly). See, for example, PEP 318 for more information.
This means that you can write the Animal class from above without using decorators:
class Animal:
def __init__(self, animal_name):
self.name(animal_name)
def get_name(self):
return self._animal_name
name = property(get_name)
def set_name(self, animal_name):
self._animal_name = animal_name
name = name.setter(set_name)
In order to understand this code, you need to understand the builtin property, which is a class. See the python docs for detailed information.
The line name = property(get_name) creates an object of type property. When retrieving the value of the property, get_name is called.
The line name = name.setter(set_name) first calls name.setter(set_name), which creates a copy of the property, and then overwrites name with this copy. When assigning a value to the copy, set_name is called.
All in all, name is an object of type property that uses get_name as getter and set_name as setter.
How does this help?
You need to understand this: name is not a function. It is a property. It is not callable.
The problematic line
self.name(animal_name)
is actually equivalent to
self.get_name()(animal_name)
which this explains the error message: The constructor calls the getter, which tries to use return self._animal_name. But since the setter has not been called, yet, self._animal_name has not been set.

How to modify/override inherited class function in python?

I have the exact function name say_hello in both parent and inherited class. I want to specify the parameter name in the Kitten class but allows for user to specify the parameter in Cat class.
Is there a way to avoid the needs to repeat the line return ('Hello '+name) in the say_hello function in Kitten class?
Currently:
class Cat:
def __init__(self):
pass
def say_hello(name):
return ('Hello '+name)
class Kitten(Cat):
def __init__(self):
super().__init__()
def say_hello(name='Thomas'):
return ('Hello '+name)
x = Cat
print (x.say_hello("Sally"))
y = Kitten
print (y.say_hello())
Ideally:
class Cat:
def __init__(self):
pass
def say_hello(name):
return ('Hello '+name)
class Kitten(Cat):
def __init__(self):
super().__init__()
def say_hello():
return super().say_hello(name='Thomas') # Something like this, so this portion of the code doesn't need to repeat completely
The say_hello method should include self as the first parameter so that it can use the super() function to access the parent's say_hello method. You should also instantiate a class by calling it with parentheses:
class Cat:
def say_hello(self, name):
return 'Hello ' + name
class Kitten(Cat):
def say_hello(self, name='Thomas'):
return super().say_hello(name)
x = Cat()
print(x.say_hello("Sally"))
y = Kitten()
print(y.say_hello())
This outputs:
Hello Sally
Hello Thomas
i think you might miss a point about classes here which is crucial to what you want to achieve.
Let's first define a class Cat but take a close look at the differences to yours.
class Cat:
def __init__(self, name):
self.name = name
def say_hello(self):
return self.name
And now see the magic and i explain it later :
cat = Cat("Sally")
print(cat.say_hello())
>>> "Sally"
What happened in comparison to your code ?
First, your __init__ method does not take an argument. My version does and it is name. Furthermore i asign it to an instance variable self.name .
With this asignment i can access the name provided at instantiation in EVERY method which has self as it's first argument. What is instantiation ?
Well in your code you called x = Cat. With this x is an object of the class but not an Instance. You need to instantiate your class.
By doing so in my code cat = Cat("Sally") what python does is call the __init__ method of the class and return an Instance of it with everythin set/done as specified in the __init__method.
In my case i set self.name = name which makes , in the example, the value of self.name to "Sally".
So whenever i call self.name in a method , which includes self in it's parameters it becomes, after instantiation, "Sally".
Now the class Cat should be clear.
What to do now ?
Simply inherit from this class !
See:
class Cat:
def __init__(self, name):
self.name = name
def say_hello(self):
return self.name
class Kitten(Cat):
def __init__(self, name):
# This is done for convenience
super().__init__(name)
With this a Kitten-, same as a Cat-instance has a name and inherits the say_hello() mehtod to say it's name .
I hope this helps.

dynamic class inheritance using super

I'm trying to dynamically create a class using type() and assign an __init__ constructor which calls super().__init__(...); however, when super() gets called I receive the following error:
TypeError: super(type, obj): obj must be an instance or subtype of type
Here is my code:
class Item():
def __init__(self, name, description, cost, **kwargs):
self.name = name
self.description = description
self.cost = cost
self.kwargs = kwargs
class ItemBase(Item):
def __init__(self, name, description, cost):
super().__init__(name, description, cost)
def __constructor__(self, n, d, c):
super().__init__(name=n, description=d, cost=c)
item = type('Item1', (ItemBase,), {'__init__':__constructor__})
item_instance = item('MyName', 'MyDescription', 'MyCost')
Why is super() inside the __constructor__ method not understanding the object parameter; and how do I fix it?
Solution 1: Using cls = type('ClassName', ...)
Note the solution of sadmicrowave creates an infinite loop if the dynamically-created class gets inherited as self.__class__ will correspond to the child class.
An alternative way which do not have this issue is to assigns __init__ after creating the class, such as the class can be linked explicitly through closure. Example:
# Base class
class A():
def __init__(self):
print('A')
# Dynamically created class
B = type('B', (A,), {})
def __init__(self):
print('B')
super(B, self).__init__()
B.__init__ = __init__
# Child class
class C(B):
def __init__(self):
print('C')
super().__init__()
C() # print C, B, A
Solution 2: Using MyClass.__name__ = 'ClassName'
An alternative way to dynamically create class is to define a class inside the function, then reassign the __name__ and __qualname__ attributes:
class A:
def __init__(self):
print(A.__name__)
def make_class(name, base):
class Child(base):
def __init__(self):
print(Child.__name__)
super().__init__()
Child.__name__ = name
Child.__qualname__ = name
return Child
B = make_class('B', A)
class C(B):
def __init__(self):
print(C.__name__)
super().__init__()
C() # Display C B A
Here is how I solved the issue. I reference the type() method to dynamically instantiate a class with variable references as such:
def __constructor__(self, n, d, c, h):
# initialize super of class type
super(self.__class__, self).__init__(name=n, description=d, cost=c, hp=h)
# create the object class dynamically, utilizing __constructor__ for __init__ method
item = type(item_name, (eval("{}.{}".format(name,row[1].value)),), {'__init__':__constructor__})
# add new object to the global _objects object to be used throughout the world
self._objects[ item_name ] = item(row[0].value, row[2].value, row[3].value, row[4].value)
There may be a better way to accomplish this, but I needed a fix and this is what I came up with... use it if you can.

Name Error python, a text adventure game

So i have a problem, that i don't quite understand why it's happening. I get a (Name Error global variable "value" is not defined) when it should be on my weapons class.
from items import *
class weapons(Item):
def __init__(self, name, attack_damage, lifesteal = 0):
super(weapons,self).__init__(name, value, quantity=1)
self.attack_damage = attack_damage
self.lifesteal = lifesteal
Here is the class that weapons is getting it from that already has value defined.
class Item(object):
def __init__(self, name, value, quantity=1):
self.name = name
self.raw = name.replace(" ","").lower()
self.quantity = quantity
self.value = value
self.netValue = quantity * value
def recalc(self):
self.netValue = self.quantity * self.value
I already have a piece of code similar to this that is working, but for some reason this value error is happening. I'm just going to include it.
from character import*
class player(character):
def __init__(self,name,hp,maxhp,attack_damage,ability_power):
super(player,self).__init__(name, hp, maxhp)
self.attack_damage = attack_damage
self.ability_power = ability_power
and the class that player is getting its stuff from
class character(object):
def __init__(self,name,hp,maxhp):
self.name = name
self.hp = hp
self.maxhp = maxhp
def attack(self,other):
pass
as you can see i did it here and this piece of code works when i call a player.
You need to add the value argument to the __init__ constructor of the weapons class.
super needs a parameter value but you did not pass it into the init

Resources