Using Super to break class into separate classes (and separate files) - python-3.x

I am trying to break a large class into separate subclasses, which I then intend to break into separate files by function. I thought the code below would work, but it seems I have the class/subclass logic incorrect. My example code is:
class MyParentClass():
def __init__(self, x):
self.x = x
class SubClass(MyParentClass):
def __init__(self, x):
super().__init__(x)
def test(self):
print("Test inside of SubClass")
def test2(self):
print(self.x)
z = MyParentClass("hello")
z.test()
z.test2()
The end-goal is to have:
1) MyParentClass the main class that is called.
2) Have multiple SubClasses that exist in separate files (e.g. ACL, Policy, Routes in acl.py, policy.py, and routes.py)
I don't know if this is possible, but what I envision is:
z = MyParentClass("172.16.16.1")
# would exist in acl.py
z.acl("permit any any")
# would exist in policy.py
z.policy("permit any any")
# would exist in route.py
z.route("route 0/0 next-hop 172.16.16.2")
# would exist in MyParentClass
z.save()
Thanks in advance.

MyParentClass shouldn't be a parent class, but a Registry, and have a __call__() method that is a Factory Method.

Related

Multiple inheritance problem with super()

I'm having a problem with multiple inheritance that I can't seem to figure out. Here is a very abstracted minimal example that reproduces my error (my code is much more complex than this).
class Thing(object):
def __init__(self, x=None):
self.x = x
class Mixin(object):
def __init__(self):
self.numbers = [1,2,3]
def children(self):
return [super().__init__(x=num) for num in self.numbers]
class CompositeThing(Mixin, Thing):
def __init__(self):
super().__init__()
def test(self):
for child in self.children():
print(child.x)
obj = CompositeThing()
obj.test()
Per this, I expect the children() method to return a list of Things built up from self.numbers. Instead, I get TypeError: super(type, obj): obj must be an instance or subtype of type. Incidentally, the same thing happens if I don't call the constructor and allow children to return super() 3 times (i.e., the uninstantiated superclass). Any ideas why this might be happening?
Thanks in advance!
In line 9 of your code, it looks like you are trying to call __init__ of object. I am assuming you meant to have Mixin inherit from Thing.
class Thing(object):
def __init__(self, x=None):
self.x = x
class Mixin(Thing):
def __init__(self):
self.numbers = [1,2,3]
def children(self):
return [super().__init__(x=num) for num in self.numbers] # Now calls Thing.__init__ instead of object.__init__
class CompositeThing(Mixin, Thing):
def __init__(self):
super().__init__()
def test(self):
for child in self.children():
print(child.x)
obj = CompositeThing()
obj.test()
Actually, I figured it out. There were two problems: (1) super() doesn't work as expected inside comprehensions because comprehensions in Py3 have their own scope - this was causing the TypeError I was experiencing. (2) What I was really trying to do was create a new instance of the parent, rather than calling a method from the parent. I have posted a new question for just the latter problem for clarity.

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 subclass that takes superclass as argument on instantiation?

I am trying to create a wrapper class in Python with the following behaviour:
It should take as an argument an existing class from which it should inherit all methods and attributes
The wrapper class methods should be able to use Python super() to access methods of the superclass (the one passed as an argument)
Because of my second requirement I think the solution here will not suffice (and in any case I am having separate issues deepcopying some of the methods of the superclass' I am trying to inherit from).
I tried this but it's not correct...
class A:
def shout(self):
print("I AM A!")
class B:
def shout(self):
print("My name is B!")
class wrapper:
def __init__(self, super_class):
## Some inheritance thing here ##
# I initially tried this but no success...
super(super_class).__init__() # or similar?
def shout(self):
print('This is a wrapper')
super().shout()
And this is the behaviour I require...
my_wrapper = wrapper(A)
my_wrapper.shout()
# Expected output:
# > This is a wrapper
# > I AM A
my_wrapper = wrapper(B)
my_wrapper.shout()
# Expected output:
# > This is a wrapper
# > My name is B!
Is inheritance the correct approach here, if so am I sniffing in the right direction? Any help is appreciated, thanks :)
Edit for context:
I intend to build multiple wrappers so that all of my ML models have the same API. Generally, models from the same package (sklearn for example) have the same API and should be able to be wrapped by the same wrapper. In doing this I wish to modify/add functionality to the existing methods in these models whilst keeping the same method name.
If wrapper has to be a class then a composition solution would fit much better here.
Keep in mind that I turned the shout methods to staticmethod because in your example you pass the class to wrapper.shout, not an instance.
class A:
#staticmethod
def shout():
print("I AM A!")
class B:
#staticmethod
def shout():
print("My name is B!")
class wrapper:
def __init__(self, super_class):
self._super_class = super_class
def __getattr__(self, item):
try:
return self.__dict__[item].__func__
except KeyError:
return self._super_class.__dict__[item].__func__
def a_wrapper_method(self):
print('a wrapper attribute can still be used')
my_wrapper = wrapper(A)
my_wrapper.shout()
my_wrapper = wrapper(B)
my_wrapper.shout()
my_wrapper.a_wrapper_method()
Outputs
This is a wrapper
I AM A!
This is a wrapper
My name is B!
a wrapper attribute can still be used
So I went for a function in the end. My final solution:
class A:
def shout(self):
print("I AM A!")
class B:
def shout(self):
print("My name is B!")
def wrap_letter_class(to_wrap):
global letterWrapper
class letterWrapper(to_wrap):
def __init__(self):
super().__init__()
def shout(self):
print('This is a wrapper')
super().shout()
def __getstate__(self):
# Add the wrapper to global scope before pickling
global letterWrapper
letterWrapper = self.__class__
return self.__dict__
return letterWrapper()
Which produces the desired behaviour...
In [2]: wrapped = wrap_letter_class(A)
In [3]: wrapped.shout()
This is a wrapper
I AM A!
In [4]: wrapped = wrap_letter_class(B)
In [5]: wrapped.shout()
This is a wrapper
My name is B!
Something not mentioned in my initial question was that I intended to pickle my custom class, this is not possible if the class is not defined in the global scope, hence the __getstate__ and global additions.
Thanks!

super().method() vs Parent.method()

class Parent():
def __init__(self, x):
self.x = x
print('init parent')
def hithere(self):
print('hey there')
print(self.x)
class Child(Parent):
def __init__(self, x):
self.x = x
super().hithere()
child = Child(3)
Here I have a Parent class and a child class which inherets from the parent.
Why do I need super() If I can always do the same thing by replacing it with the name of the parent class im inhereting:
class Parent():
def __init__(self, x,y):
self.x = x
self.y = y
print('init parent')
def hithere(self):
print('hey there')
print(self.x)
class Child(Parent):
def __init__(self, x):
self.x = x
Parent.hithere(self)
child = Child(3)
which does the same thing.
My second question is it correct to say that abstract classes can't have atributes? If Parent was an abstract class, then whenever one of it's methods calls for self like hithere(self), I need to pass it back with super().method(self). So those attributes are actually Child's attributes, which just so happen to have the same attribut names as the parent class to be used.
This is one and the same thing, but you should you super() because:
In future, if you want to change the name of the parent class, then you don't have to change the name for every instance.
For e.g.
Parent.hithere(self)
Parent.hithere1(self)
Parent.hithere2(self)
Now if you change the name of your parent class, then you would have to change the name Parent for every instance. This would not be the case, if you would do:
super().hithere1()
super().hithere2()
super().hithere3()
I think your second question is ambiguous, but you can read more about the abstract class here.
You almost always want to use self.foo(), unless you're writing a Child.foo method that overwrites Parent.foo.
If you later write a Child.hithere method, your current code won't use it, which is frequently not what you want.

Calling classmethod multiple times in python

I am trying to create a classmethod which can be called again and again, however it only works once and stops. Here is the code:
class NewBytes(bytes):
def __init__(self, var):
self.var= var
#classmethod
def rip(cls):
return cls(var[2:])
a = b"12asd5789"
x = NewBytes(a)
print(x, x.rip(), x.rip().rip(), x.rip().rip().rip())
Here is what I got from this:
b'12asd5789' b'asd5789' b'asd5789' b'asd5789'
However, what I want to have is:
b'12asd5789' b'asd5789' b'd5789' b'789'
Thanks in advance.
Probably you don't actually want a class method, since you need access to instance state here.
class NewBytes(bytes):
def __init__(self, x):
self.x = x
def rip(self):
return type(self)(self.x[2:])
My previous answer of using self.x doesnt make sense since this is a class method (too quick to answer). I think this is a case of the XY problem, see the below example of how to use a class method.
class Test(object):
x = "hey there whats up this is a long string"
#classmethod
def TestFunction(cls):
cls.x = cls.x[3:]
print(cls.x)
print(Test().x)
Test().TestFunction()
Test().TestFunction()
Test().TestFunction()
Test().TestFunction()
Test().TestFunction()

Resources