dynamic class inheritance using super - python-3.x

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.

Related

Multiple inheritance, super() and their correct use with arguments in Python

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?

Is there a way for the child function that will inherit more than one parent function to access all of the parent's methods?

I am getting an error when making a third class that inherits the first two classes' attributes. The first class's function will be go through but when accessing the second class's function I am gettting an error:
class3' object has no attribute 'othernum
Here is the code:
class class1():
def __init__(self):
self.number = 10000
def getNum(self):
return self.number
class class2():
def __init__(self):
self.othernum = 1111
def displaynum(self):
return self.othernum
class class3(class1, class2):
pass
newperson = class3()
print(newperson.getNum())
print(newperson.displaynum())
Found the answer.
class class3(class1, class2):
def __init__(self):
class1.__init__(self)
class2.__init__(self)
The answer presented by #Ishaan Sathaye is indeed correct. But be aware that there are several mechanisms for initializing base classes in a multiple inheritance hierarchy. See Calling parent class init with multiple inheritance, what's the right way?, in particular the section with heading All base classes are designed for cooperative inheritance.
So, if your 3 classes were designed for cooperative inheritance, we would have:
class class1():
def __init__(self):
super().__init__()
self.number = 10000
def getNum(self):
return self.number
class class2():
def __init__(self):
super().__init__()
self.othernum = 1111
def displaynum(self):
return self.othernum
class class3(class1, class2):
def __init__(self):
super().__init__()
newperson = class3()
print(newperson.getNum())
print(newperson.displaynum())

How to share the parent class variables to third class through the child class in python?

I have the following code:
class Parent():
def __init__(self):
self.variable1 = self.method1()
self.variable2 = self.method2()
self.variable3 = self.method3()
self.variable4 = self.method4()
#.........(for example I have 100 variables here)
def method1(self):
return 100
def method2(self):
return 200
def method3(self):
return 300
def method4(self):
return 400
class Third():
def __init__(self):
a = 1
def call(self):
value = self.variable3 + 1
return value
class Child(Parent):
def __init__(self):
super().__init__()
self.getanswer = self.method11()
def method11(self):
value_count = Third().call()
return value_count
obj = Child()
It throwed the following Error:
AttributeError: 'Third' object has no attribute 'variable3'
Here I wanted to send all the values of parent to Third Class through Child class. How can I achieve it? I know that I can pass Parent class variables seperately in directly as a parameter in the class Third as following:
value_count = Third(self.variable3).call()
and change the Third Class constructor accordingly. But I don't want to do it as my Parent class has some time taking operations to do.
Also I don't want to make class Third as child to the class Parent.
So How can I recognize the Parent class variables(variable1, variable2, variable3, variable4, ....) in Third Class through Child Class ?

Better way than pass the same argument among classes in Python

I have a question related to OOP but it should be implemented in Python.
I have a file user_inputs.py with all the user parameters.
In my main file, I have a function that is called first. This function is responsible to read all the user parameters and return a dictionary that will be used in the rest of the program.
My question is: what is the cleanest way to pass the user_parameters dictionary to all classes? I did it in 2 ways:
Method 1)
def read_user_parameters():
# code to open and read all parameters etc.
return user_parameters # returns a dictionary with all the user parameters
Class A():
def __init__(self, user_parameters):
self.user_parameters = user_parameters
Class B():
def __init__(self, user_parameters):
self.user_parameters = user_parameters
user_parameters = read_user_parameters()
object_A = A(user_parameters)
object_B = B(user_parameters)
I don't like this way because I have dozens of classes that need to pass this argument. So I thought to create a parent class with the user parameters:
Method 2)
Class User_parameters():
def __init__(self, user_parameters):
def read_user_parameters():
# code to open and read all parameters etc.
return user_parameters
Class A(User_parameters):
__init__(self, user_parameters):
super().__init__()
# self.user_parameters comes from the parent class now
Class B(User_parameters):
__init__(self, user_parameters):
super().__init__()
# self.user_parameters comes from the parent class now
object_A = A()
object_B = B()
I prefer method 2, however, when super() is initialized from Class A and Class B the function read_user_parameters() that reads the file will be called twice (multiply this by dozens of times). Is there a better solution than method 1 in which I call read_user_parameters() only once but doesn't need to pass the argument for all classes?
Thank you for your time.
Why not just have a single UserParameters class and two objects of the same class (Also class nameds are supposed to be camel-cases, not snake-cased)
#Single class for user parameters
class UserParameters:
def __init__(self, user_parameters):
self.user_parameters = user_parameters
def read_user_parameters(self):
# code to open and read all parameters etc.
return self.user_parameters
#Two objects
object_A = UserParameters("<params>")
object_B = UserParameters("<params>")

Python overriding default attribute assignment

for a specific framework i work with, i need to define object attributes as special classes, for example, instead of writing this:
class A:
def __init__(self):
self.some_int = 2
i would need to write:
class A:
def __init__(self):
self.some_int = SpecialIntWrapper(name = "some_int", value = 2)
I would like to somehow override operators/methods so that typing the first code (self.some_int = 2) will call SpecialIntWrapper behind the scenes, with the attribute name and value.
is this possible?
Basically there are two ways - via a #property decorator (preferable unless you want to affect arbitrary names)
class MyClass:
def __init__(self):
self.some_int = 2
# if you know the name of the property define it as a property - a getter
#property
def some_int(self):
return self._some_int
# and a setter
#some_int.setter
def some_int(self, value):
self._some_int = SpecialIntWrapper("some_int", value)
or overloading the __setattr__ magic method
class MyClass:
def __init__(self):
self.some_int = 2
def __setattr__(self, name, value):
# in general if you dont know the names of the properties
# beforehand you can somehow filter them here
if name == "some_int":
super().__setattr__(name, SpecialIntWrapper(name=name, value=value))
else:
# to use the setattr in a default way, just call it via super(Python 3)
super().__setattr__(name, value)
either way the some_int will be initialized to the SpecialIntWrapper instance
>>>print(MyClass().some_int)
<__main__.SpecialIntWrapper object at 0x03721810>
Something like this
class SpecialIntWrapper:
def __init__(self, name, value):
pass
class MyClass:
def __init__(self):
self.some_int = 3
def __setattr__(self, key, value):
if key == 'some_int':
self.__dict__[key] = SpecialIntWrapper(key, value)
else:
self.__dict__[key] = value
print(MyClass().some_int)
# >>> <__main__.SpecialIntWrapper object at 0x1076f1748>

Resources