What is right way to create upgradable classes - python

I try to made easy upgradable classes. Idea in following: I have an instance of class "Person", which has to be converted to another class "Worker" by instantiation using object of class "Person". The main conditions:
"Person" is parent class of "Worker"
init functions are differents from each other
Now I wrote below code. But I wondering: if it is possible to change redundant properties declarations at "Worker" class to something more simple, because base class "Person" can be extended any time, and I don't want to write duplicated code.
And what is best practice to achieve same goal?
class Person:
def __init__(self, row: dict = None):
if not row:
row = {}
self.first_name = row.get('first_name', None)
self.last_name = row.get('last_name', None)
# here goes many properties
class Worker(Person):
def __init__(self, person: Person):
super().__init__()
self.position = 'unemployed'
# below code which I want to make independent from base class properties
self.first_name = person.first_name
self.last_name = person.last_name
# here goes many properties of base class
person_info = {'first_name': 'John', 'last_name': 'Doe'}
person = Person(person_info)
worker = Worker(person)
print(f'{worker.first_name} {worker.last_name} is {worker.position}')

You can do something like:
class Worker(Parent):
def __init__(self, person: Person):
vars(self).update(vars(person))
self.position = 'unemployed'
Seems pointless to call super().__init__().
Note, the above will only work with a non-slotted class.

Related

Is it possible to call function from second class with same name in multiple inheritance

Lets say we have three classes
class Father:
def __init__(self, father_name) -> None:
self.father_name = father_name
def name(self):
print(f"Father name : {self.father_name}")
class Mother:
def __init__(self, mother_name) -> None:
self.mother_name = mother_name
def name(self):
print(f"Mother name : {self.mother_name}")
class Son(Father, Mother):
def __init__(self, father_name, mother_name):
Father.__init__(self, father_name)
Mother.__init__(self, mother_name)
me = Son('Jamie', 'Jack', 'Linda')
me.name()
The output is :
Father name : Jack
How do I call name method from Mother class instead of Father class without changing the function name so the output would be :
Mother name : Linda
There is a bunch of problems in your code.
Your example is not working.
Instead of:
Father.__init__(self, father_name)
Mother.__init__(self, mother_name)
You should use:
super(Father, self).__init__(self, father_name)
super(Mother, self).__init__(self, mother_name)
(Assuming you really want to have inheritance here)
Father's name method is called because of something called MRO (method resolution order). Worth a read, but not something you should try to modify. If you only ever want to call Mother's name and not Father's, it's a simple matter of saying Son(Mother, Father) instead of Son(Father, Mother)
As mentioned by #Mark, this is not a reasonable use case for
inheritance. Child has parents, not is parents.
All in all, if you want to do it with inheritance, that's what I'd suggest:
class Son(Mother, Father):
def __init__(self, father_name, mother_name):
super(Father, self).__init__(self, father_name)
super(Mother, self).__init__(self, mother_name)
def name(self, ParentClass):
return super(ParentClass, self).name()
me = Son('Jack', 'Linda')
me.name(Mother)
me.name(Father)
But, I think a much more reasonable way is using attributes.
class Son():
def __init__(self, father_name, mother_name):
self.father = Father(father_name)
self.mother = Mother(mother_name)
me = Son('Jack', 'Linda')
me.mother.name()
me.father.name()
Or, even better, create a Son with already existing Parent objects. That way you can create multiple people with same parents.
class Son():
def __init__(self, father, mother):
self.father = father
self.mother = mother
mother = Mother('Linda')
father = Father('Jack')
me = Son(mother, father)
me.mother.name()
me.father.name()
You should not do something which looks like this as it's even naturally looks bad.
There is MRO (Method Resolution Order) concept in Python, and if you wan't to dig into multiple inheritance, you should read about that.
Answer to your question is just swap Father and Mother mixins
class Son(Mother, Father):
def __init__(self, my_name, father_name, mother_name):
self.my_name = my_name
Father.__init__(self, father_name)
Mother.__init__(self, mother_name)
Avoid using raw usage of Class.__init__ though. __init__ is not object creation, but instance fields initialization and might result in unexpected results.

How to access attribute of object from another object's method, which is one of attributes in Python?

I would like to know if it's possible, and if yes, how to access attribute(s) of a "super" class instance, when having composition implemented.
Example provided below is only to provide idea here and setup common ground on further explanations.
I want to have access to "id" attribute for an instance of MiniVan directly from object "door" (type DoorElement).
My Code
class Car:
def __init__(self, _id):
self.id = _id
class CarElement:
def __init__(self, name):
self.name = name
def get_car_id(self):
# Body which will access value of attribute "id"
return car_id
class MiniVan(Car):
def __init__(self, _id):
super(MiniVan, self).__init__(_id)
self.door = DoorElement('door')
self.engine = EngineElement('engine')
class DoorElement(CarElement):
def __init__(self, name):
super(DoorElement, self).__init__(name)
class EngineElement(CarElement):
def __init__(self, name):
super(EngineElement, self).__init__(name)
def main():
mini_van = MiniVan(123)
id_from_door = mini_van.door.get_car_id()
id_from_engine = mini_van.engine.get_car_id()
print(id_from_door) # Expected output 123
print(id_from_engine) # Expected output 123
if __name__ == '__main__':
main()
Expected:
Printed out twice "123"
What I've tried:
Passing required attribute during creating object
I know that I could just define init method with passing "car_id" but for some reasons I would love to avoid it if possible. If not, I would propably just go for it.
to set class attribute, and then call it from CarElement class within classmethod e.g.:
#classmethod
def get_id(cls):
return Car.id
But issue with this solution is that, I can have many child-classes for Car class (MiniVan, Truck, etc.) and I want have it still working.
Trying to use descriptor
def __get__(self, instance, owner):
return instance.id
But I could understand it wrong, and actually getter (as far as I understand clean code) should return instance of a class and not any attribute.
Additional Info
I will ALWAYS use CarElement (or child classes) instances as attributes of instance of Car (or child classes) instances - different usage will be treated as use-error
There can be a lot of different child classes of Car class, but always within inheritance way ( Car <- RacingCar(Car) <- FormulaOneCar(RacingCar) ) but no composition
In order for your code to work, you would have to initialize all CarElement-s with car_id. Currently, the error you are getting comes from lack of such a variable in the scope of the method. My idea of a change is this:
class CarElement:
def __init__(self, name, car_id):
self.name = name
self.car_id = car_id
def get_car_id(self):
# Body which will access value of attribute id
return self.car_id
I can't see any other magic way.

Initialising nested classes in Python

Let's say I want to create a class 'House' that has some attributes of its own, but also has a (nested?) 'Resident' class which has some attributes and has a mandatory attribute 'surname'. A house instance may exist though without any residents. How can create this so that I can eventually do the following?
myhouse = House()
residentX = myhouse.resident('Smith')
Currently I set this up as a nested class but run into trouble when I try and initialise myhouse given that it is requiring a surname at this point for the nested Resident class (which I don't necessarily have at this point)
class House:
def __init__(self):
self.someattribute = <someattribute>
self.resident = self.Resident()
class Resident:
def __init__(self, surname):
self.surname = surname
I know I can restructure the code to not use nested classes and then explicitly tie any resident to a house in my code. However, I would like to use the dot notation here (myhouse.resident) to automatically tie a resident to a house.
Also, I understand that nested classes in python are somewhat frowned upon - I'm open to suggestions on how to do the above in a more pythonic manner.
I would break out the Resident class and use a property/setter for .resident
Like this:
class House:
def __init__(self):
self.someattribute = <someattribute>
self._resident = None
#property
def resident(self):
return self._resident
#resident.setter
def resident(self, surname):
r = Resident(surname)
self._resident = r
class Resident:
def __init__(self, surname):
self.surname = surname
However, if you want .resident to be callable but also want to track the house's residents, you can still break out the Resident class, and use:
class House:
def __init__(self):
self.someattribute = <someattribute>
self.residents = []
def resident(self, surname):
'''
Add a resident to the house
'''
r = Resident(surname)
self.residents.append(r)
return r
class Resident:
def __init__(self, surname):
self.surname = surname

Python: showing attributes assigned to a class object in the class code

One of my classes does a lot of aggregate calculating on a collection of objects, then assigns an attribute and value appropriate to the specific object: I.e.
class Team(object):
def __init__(self, name): # updated for typo in code, added self
self.name = name
class LeagueDetails(object):
def __init__(self): # added for clarity, corrected another typo
self.team_list = [Team('name'), ...]
self.calculate_league_standings() # added for clarity
def calculate_league_standings(self):
# calculate standings as a team_place_dict
for team in self.team_list:
team.place = team_place_dict[team.name] # a new team attribute
I know, as long as the calculate_league_standings has been run, every team has team.place. What I would like to be able to do is to scan the code for class Team(object) and read all the attributes, both created by class methods and also created by external methods which operate on class objects. I am getting a little sick of typing for p in dir(team): print p just to see what the attribute names are. I could define a bunch of blank attributes in the Team __init__. E.g.
class Team(object):
def __init__(self, name): # updated for typo in code, added self
self.name = name
self.place = None # dummy attribute, but recognizable when the code is scanned
It seems redundant to have calculate_league_standings return team._place and then add
#property
def place(self): return self._place
I know I could comment a list of attributes at the top class Team, which is the obvious solution, but I feel like there has to be a best practice here, something pythonic and elegant here.
If I half understand your question, you want to keep track of which attributes of an instance have been added after initialization. If this is the case, you could use something like this:
#! /usr/bin/python3.2
def trackable (cls):
cls._tracked = {}
oSetter = cls.__setattr__
def setter (self, k, v):
try: self.initialized
except: return oSetter (self, k, v)
try: self.k
except:
if not self in self.__class__._tracked:
self.__class__._tracked [self] = []
self.__class__._tracked [self].append (k)
return oSetter (self, k, v)
cls.__setattr__ = setter
oInit = cls.__init__
def init (self, *args, **kwargs):
o = oInit (self, *args, **kwargs)
self.initialized = 42
return o
cls.__init__ = init
oGetter = cls.__getattribute__
def getter (self, k):
if k == 'tracked': return self.__class__._tracked [self]
return oGetter (self, k)
cls.__getattribute__ = getter
return cls
#trackable
class Team:
def __init__ (self, name, region):
self.name = name
self.region = region
#set name and region during initialization
t = Team ('A', 'EU')
#set rank and ELO outside (hence trackable)
#in your "aggregate" functions
t.rank = 4 # a new team attribute
t.ELO = 14 # a new team attribute
#see witch attributes have been created after initialization
print (t.tracked)
If I did not understand the question, please do specify which part I got wrong.
Due to Python's dynamic nature, I don't believe there is a general answer to your question. An attribute of an instance can be set in many ways, including pure assignment, setattr(), and writes to __dict__ . Writing a tool to statically analyze Python code and correctly determine all possible attributes of an class by analyzing all these methods would be very difficult.
In your specific case, as the programmer you know that class Team will have a place attribute in many instances, so you can decide to be explicit and write its constructor like so:
class Team(object):
def __init__(name ,place=None):
self.name = name
self.place = place
I would say there is no need to define a property of a simple attribute, unless you wanted side effects or derivations to happen at read or write time.

Can I iterate over a class in Python?

I have a class that keeps track of its instances in a class variable, something like this:
class Foo:
by_id = {}
def __init__(self, id):
self.id = id
self.by_id[id] = self
What I'd like to be able to do is iterate over the existing instances of the class. I can do this with:
for foo in Foo.by_id.values():
foo.do_something()
but it would look neater like this:
for foo in Foo:
foo.do_something()
is this possible? I tried defining a classmethod __iter__, but that didn't work.
If you want to iterate over the class, you have to define a metaclass which supports iteration.
x.py:
class it(type):
def __iter__(self):
# Wanna iterate over a class? Then ask that class for iterator.
return self.classiter()
class Foo:
__metaclass__ = it # We need that meta class...
by_id = {} # Store the stuff here...
def __init__(self, id): # new isntance of class
self.id = id # do we need that?
self.by_id[id] = self # register istance
#classmethod
def classiter(cls): # iterate over class by giving all instances which have been instantiated
return iter(cls.by_id.values())
if __name__ == '__main__':
a = Foo(123)
print list(Foo)
del a
print list(Foo)
As you can see in the end, deleting an instance will not have any effect on the object itself, because it stays in the by_id dict. You can cope with that using weakrefs when you
import weakref
and then do
by_id = weakref.WeakValueDictionary()
. This way the values will only kept as long as there is a "strong" reference keeping it, such as a in this case. After del a, there are only weak references pointing to the object, so they can be gc'ed.
Due to the warning concerning WeakValueDictionary()s, I suggest to use the following:
[...]
self.by_id[id] = weakref.ref(self)
[...]
#classmethod
def classiter(cls):
# return all class instances which are still alive according to their weakref pointing to them
return (i for i in (i() for i in cls.by_id.values()) if i is not None)
Looks a bit complicated, but makes sure that you get the objects and not a weakref object.
Magic methods are always looked up on the class, so adding __iter__ to the class won't make it iterable. However the class is an instance of its metaclass, so the metaclass is the correct place to define the __iter__ method.
class FooMeta(type):
def __iter__(self):
return self.by_id.iteritems()
class Foo:
__metaclass__ = FooMeta
...
Try this:
You can create a list with a global scope, define a list in the main module as follows:
fooList = []
Then add:
class Foo:
def __init__(self):
fooList.append(self)
to the init of the foo class
Then everytime you create an instance of the Foo class it will be added to the fooList list.
Now all you have to do is iterate through the array of objects like this
for f in fooList:
f.doSomething()
You can create a comprehension list and then call member methods as follows:
class PeopleManager:
def __init__(self):
self.People = []
def Add(self, person):
self.People.append(person)
class Person:
def __init__(self,name,age):
self.Name = name
self.Age = age
m = PeopleManager()
[[t.Name,t.Age] for t in m.People]
call to fill the object list:
m = PeopleManager()
m.Add( Person("Andy",38))
m.Add( Person("Brian",76))
You can create a class list and then call append in the init method as follows:
class Planet:
planets_list = []
def __init__(self, name):
self.name = name
self.planets_list.append(self)
Usage:
p1 = Planet("earth")
p2 = Planet("uranus")
for i in Planet.planets_list:
print(i.name)

Categories

Resources