OOP Casting class member to another class - python-3.x

I have parent class Ad with 2 children Direct and Adword. On the other side I have parent class Campaign with 2 children DirectCampaign and AdwordsCampaign. Campaign class contains array of corresponding Ad children instances.
class Ad:
def __init__(self, keyword, negative_keywords, url):
self.keyword = keyword
self.negative_keywords = negative_keywords
self.url = url
class Direct(Ad):
def __init__(self, keyword, negative_keywords, headline, description, url):
super().__init__(keyword, negative_keywords, url)
self.url_with_utm = url + "?"
class Adword(Ad):
def __init__(self, keyword, negative_keywords, headline1, headline2, description, url):
super().__init__(keyword, negative_keywords, url)
class Campaign():
ads = []
def __init__(self, name):
self.name = name
def add(self, ad):
self.ads.append(ad)
class DirectCampaign(Campaign):
def __init__(self, name):
super().__init__(name)
class AdwordsCampaign(Campaign):
pass
def main():
direct = DirectCampaign("adv_search")
direct.import_from_csv('direct.csv')
My primary goal is to cast DirectCampaign to AdwordsCampaign, but I cannot understand how to do it according to OOP(what class should have such method, should I call AdwordsCampaign constructor there and Adwords constructor after).
Secondary goal is DirectCampain has name attribute, how I can access it in Direct instance? Should I pass it through constructor to every Direct instance?
Any feedback for the code is welcome.

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?

Inherit instance variable from parent class

main.py
...
person = cPerson("xyz", "ozp")
...
person.set_name("somename")
...
csystem = cSystem()
...
cperson.py
class cPerson:
def __init__(self, addr, client):
self.addr = addr
self.client = client
self.name = None
def set_name(self, name):
self.name = name
csystem.py
from cperson import cPerson
class cSystem(cPerson):
def __init__(self):
print(self.name)
Can i access self.name from the parent class in this way? I get the error message:
AttributeError: 'cSystem' object has no attribute 'name'
I do not want to initialize from the csystem class, i want the current value from the instance variable set as shown in the main program.
I am not entirely sure what you want the end result to be.
In any case is this what you are looking for?
class cPerson:
name = None
def __init__(self, addr, client):
self.addr = addr
self.client = client
#classmethod
def set_name(cls, value):
cls.name = value
class cSystem(cPerson):
def __init__(self, addr, client):
super().__init__(addr, client)
print(self.name)
person = cPerson("xyz", "ozp")
person.set_name('Jake')
csystem = cSystem("xyz", "ozp")
The above code returns 'Jake'.
A class method is a method which is bound to the class and not the object of the class. They have the access to the state of the class as it takes a class parameter that points to the class and not the object instance.
Is this what you were looking for? If not can you explain the problem in a bit more detail?

Implementing a python class

I am working on a personal project and I am having trouble implementing this following part.
Implementing a Menu Class.
This class will make use of MenuItem objects.
This class represents the restaurant menu which contains 4 different categories of menu item diners can order from.
This class will have a single class(or static) variable:
Menu_Item_types: a list containing 4 strings representing the 4 possible types of menu items.: Drink, appetizer, entree, dessert.
This class will use the following instance attribute:
List item
self.menuItemDrinkList: list of all drink list
self.menuItemAppetizerList: list of all appetizer list
self.menuItemEntreeList: a list of all entree list
self.menuItemDessertList: a list of all the dessert list
Below is the menuItem object
class MenuItem:
def __init__(self, name=None, types=None, price=None, description=None):
self.name = name
self.types = types
self.price = price
self.description = description
def setName(self, name):
self.name = name
def getName(self):
return self.name
def setTypes(self, types):
self.types = types
def getTypes(self):
return self.types
def setPrice(self, price):
self.price = price
def getPrice(self):
return self.price
def setDescription(self, description):
self.description = description
def getDescription(self):
return self.description
def __str__(self):
return "{} ({}): ${}, {}".format(self.name, self.types, self.price, self.description)
If I understand you correctly you are looking for setters and getters properties. Here is the way how you do it in Python.
You can learn more about properties here:
class MenuItem:
def __init__(self, name=None, types=None, price=None, description=None):
self._name = name
self._types = types
self._price = price
self._description = description
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
#property
def types(self):
return self._types
#types.setter
def types(self, types):
self._types = types
#property
def price(self):
return self._price
#price.setter
def price(self, price):
self._price = price
#property
def description(self):
return self._description
#description.setter
def description(self, description):
self._description = description
def __str__(self):
return "{} ({}): ${}, {}".format(
self._name, self._types, self._price, self._description
)
menu_item = MenuItem("pizza", "entry", 10)
print(menu_item)
menu_item.price = 20
menu_item.description = "Delicious"
print(menu_item)
output:
pizza (entry): $10, None
pizza (entry): $20, Delicious
Please notice:
In python you don't call properties with getXxx or setXxx, you just use regular names and decoreate methods with #property and #xxx.setter
You should have #property before #setter.
Setter must start with the property name
In order to escape recursion, name your internal
attributes with _ (_name). If you will not do it you will have a
"RecursionError: maximum recursion depth exceeded in comparison"
because setter will call itself in the loop.

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>

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.

Resources