Python Child Class with multiple init methods? - python

I have an application I'm building in Python2.7 that works as-is, but doesn't feel clean, nor is it very explicit in what's happening, so if I walk away from the code for a while I have a hard time remembering how it's actually working under the hood, which is obviously not good. I've refactored the code and it seems more explicit, but not really any cleaner.
I'm trying to figure out the cleanest way to initialize these classes in two different ways - 1) from a user-generated instantiation (in the event of adding a new object from scratch during program execution), or 2) from importing the history of an object (from a previous program execution) from JSON. Here's my latest way of going about this:
class Device(object):
def __init__(self, dev_type, preset_prefix, default_preset,
from_json=False, json_path=None, **device_attrs):
if not from_json: # otherwise set in child class __init__
self.name = device_attrs['name']
self.sn = device_attrs['sn']
self.mfg = device_attrs['mfg']
self.tech = device_attrs['tech']
self.model = device_attrs['model']
self.sw_ver = device_attrs['sw_ver']
self.hours = 0
else:
self.hours = device_attrs['hours']
self.type = dev_type
self.json = json_path
self.preset_prefix = preset_prefix
self.preset = default_preset
class Monitor(Device):
def __init__(self, name, sn, mfg, tech, model, sw_ver, from_json=False,
json_path=None, **monitor_dict):
if from_json:
self.__dict__ = monitor_dict
device_properties = {'name': name, 'sn': sn, 'mfg': mfg, 'tech': tech,
'model': model, 'sw_ver': sw_ver}
monitor_dict.update(device_properties)
super(Monitor, self).__init__('monitor', 'user', 1, from_json,
json_path, **monitor_dict)
if cals:
self._init_cal_from_json(monitor_dict['cals'])
Now I can initialize from a previously saved JSON (generated from this object so I can be sure the key/value pairs are correct):
my_monitor = Monitor(from_json=True, json_path=device_json_file, **device_json_dict))
Or as a new object from scratch:
my_monitor = Monitor('monitor01', '12345', 'HP', 'LCD',
'HP-27', 'v1.0')
This seems a little bit messy, but still better than my original version which didn't have any positional arguments for the child init (making it hard to know what data MUST be passed in), it just took **monitor_dict hoping it contained the right key/value pairs. However this method of taking those arguments and merging them into a dict seems strange, but I've refactored this multiple times and this seems to be the cleanest way of going about it.
Is this the best way to handle initializing an object in multiple ways or can I somehow create two separate init functions, one for loading from JSON, and one for new creation of brand new objects?

I prefer to create new constructors as class methods, something like this, you could create more if you need, or adjust it if necesary:
class Device(object):
def __init__(self, dev_type, preset_prefix, default_preset, json_path=None, **device_attrs):
self.name = device_attrs['name']
self.sn = device_attrs['sn']
self.mfg = device_attrs['mfg']
self.tech = device_attrs['tech']
self.model = device_attrs['model']
self.sw_ver = device_attrs['sw_ver']
self.hours = 0
self.type = dev_type
self.json = json_path
self.preset_prefix = preset_prefix
self.preset = default_preset
class Monitor(Device):
#classmethod
def new_from_json(self, name, sn, mfg, tech, model, sw_ver, json_path=None, **monitor_dict):
self.__dict__ = monitor_dict
device_properties = {'name': name, 'sn': sn, 'mfg': mfg, 'tech': tech,
'model': model, 'sw_ver': sw_ver}
monitor_dict.update(device_properties)
super(Monitor, self).__init__('monitor', 'user', 1,
json_path, **monitor_dict)
As an example:
class Parent():
def __init__(self,some):
self.some = some
class Object(Parent):
#classmethod
def new_from_dict(self,some):
Parent.__init__(self,some)
self.adress = {"Me": 123}
return self
then:
obj = Object.new_from_dict("ME")
obj.adress
{"Me": 123}
obj.some
"ME"

Related

divide classes into sub functions/classes python

First time poster and python newbie here, this question is probably asked before, but I am not able to find any answer.
I have a Class that reads robot status data, this works fine and I am able to dive my data into methods that are working fine when i call them. But I would like to divide my class even more, so the data is structured better. for example
I have some methods the reads and return target_data
def target_joint_positions(self):
t_j_p = self.read_data()[1:7]
return t_j_p
def target_joint_velocities(self):
t_j_v = self.read_data()[7:13]
return t_j_v
def target_joint_currents(self):
t_j_c = self.read_data()[19:25]
return t_j_c
And similar methods returning actual_data:
def actual_joint_positions(self):
a_j_p = self.read_data()[31:37]
return a_j_p
def actual_joint_velocities(self):
a_j_v = self.read_data()[37:43]
return a_j_v
def actual_joint_currents(self):
a_j_c = self.read_data()[43:49]
return a_j_c
So what I would like to accomplish is that when i make a instance of my class, instead of getting all the methods i would like something like this:
inst = Class_Name()
inst.target. (list of target methods)
inst.actual. (list of actual methods)
I have looked into nested classes and inheritance but I have not been successful in achieving my goal. Thanks for any pointers.
Welcome!
You can do the following:
class TargetMetrics:
def __init__(self, data):
self.data = data
def joint_positions(self):
return self.data[1:7]
...
class ActualMetrics:
def __init__(self, data):
self.data = data
def joint_positions(self):
return self.data[31:37]
...
class RobotMetrics:
def __init__(self):
data = read_data()
self.actual = ActualMetrics(data)
self.target = TargetMetrics(data)
...

Python project help (classes/expected type)

I am working on a project for school, simulating a payroll program, and I am getting an error. The error I am getting is
'Expected type 'Classification', got 'Employee' instead'. The relevant code is (I put *** around the code generating the error, it is the 5th function under the Employee Class).
class Employee:
def __init__(self, emp_id, first_name, last_name, address, city, state, zipcode, clas = None):
self.emp_id = emp_id
self.first_name = first_name
self.last_name = last_name
self.address = address
self.city = city
self.state = state
self.zipcode = zipcode
self.classification = clas
def make_hourly(self, hourly_rate):
self.clas = Hourly(hourly_rate)
self.classification = self.clas
def make_salaried(self, salary):
self.clas = Salaried(salary)
self.classification = self.clas
def make_commissioned(self, salary, rate):
self.clas = Commissioned(rate, salary)
self.classification = self.clas
def issue_payment(self):
***pay = Classification.compute_pay(self)***
print('Mailing', pay, 'to', self.first_name, self.last_name, 'at', self.address, self.city, self.state, self.zipcode)
class Classification(ABC):
''' Interface for employee classifications '''
#abstractmethod
def compute_pay(self):
pass
class Hourly(Classification):
''' Manages timecard info. Computes pay '''
def __init__(self, hourly_rate):
self.hourly_rate = hourly_rate
self.timecards = [] # A list of floats representing hours worked
def compute_pay(self):
for i in list_of_timecards:
if i[0] == self.emp_id:
self.timecards.extend(i[1:])
total = list(map(float, self.timecards))
total = sum(total)
self.timecards.clear()
return total * self.hourly_rate
def add_timecard(self, hours):
self.timecards.append(hours)
class Salaried(Classification):
def __init__(self, salary):
self.salary = salary
def compute_pay(self):
return self.salary / 24
class Commissioned(Salaried):
def __init__(self, salary, commission_rate):
self.commission_rate = commission_rate
self.salary = salary
self.receipts = []
def add_receipt(self, amount):
self.receipts.append(amount)
def compute_pay(self):
for i in list_of_receipts:
if i[0] == self.emp_id:
self.receipts.extend(i[1:])
total = list(map(float, self.receipts))
total = sum(total)
self.receipts.clear()
return (self.salary / 24) + ((self.commission_rate / 100) * total)
My understanding of the problem is that I need to pass my 'employee' object to the 'compute_pay' function, which then passes it to the relevant child class (hourly etc...) to run and return the result. I have tried changing
pay = Classification.compute_pay(self)
to
pay = Classification.compute_pay(self.clas)
however that returns error 'AttributeError: 'Employee' object has no attribute 'clas'
which makes no sense. Maybe it is that I am not assigning the employees to the class correctly?
The code for that is (it pulls from a CSV file, and it is pulling the data correctly and generating the class objects, I have checked)
def load_employees():
f = open("employees.csv")
f.readline() # skip header line
for line in f:
fields = line.strip().split(',')
emp = Employee(*fields[:7])
if fields[7] == '3':
clas = Hourly(fields[10]) # Need to define Hourly
emp.classification = clas
elif fields[7] == '2':
clas = Commissioned(fields[8], fields[9])
emp.classification = clas
elif fields[7] == '1':
clas = Salaried(fields[8])
emp.classification = clas
employees.append(emp)
I will figure out your line Classification.compute_pay(self):
Classification => the class Classification
compute_pay => class
method self => this = an Employee instance
pass means do nothing and is used to avoid unneccessary code.
Every class method has self as an argument to allow refering to this instance of the class.
To pass an argument (here your employee) use a parameter. Also implementing a method of the parent class overrides this method.
Every function compute_pay should have a second argument
def compute_pay(self, employee):
# do your stuff
And then you can use this line in issue_payment
pay = self.clas.compute_pay(self)
Two issues here,
Firstly, your Employee instance has two attributes: clas and classification. However, in your constructor, only classification is set.
def __init__(...
...
self.classification = clas
But self.clas is not set to anything. That's why you are getting that error 'Employee' object has no attribute 'clas'. It is only set when one of the make_hourly, make_salaried, or make_commissioned methods are invoked. So when you load the employees CSV, instead of manually creating the instance like you are doing here
clas = Hourly(fields[10])
you should be calling the method make_hourly on your emp instance, like so
emp.make_hourly(fields[10])
It's worth noting that fields[10] is terrible naming. Instead of unpacking all the fields at once, try to unpack them during the for loop:
for a, b, c, d in csv:
...
Secondly, this line of code is wrong in multiple ways
pay = Classification.compute_pay(self)
compute_pay is not a static function or a classmethod. So it shouldn't be called on the Classification class itself, but the Classification instance. This is what you stored in your self.clas attribute. So, compute_pay should be called on self.clas:
def issue_payment(self):
pay = self.clas.compute_pay()
...
In addition to that, when you call a method of a class from inside of another method in the same class, you don't ever need to pass the self argument. It is implied. So even if compute_pay was static or a class method, which it isn't, it would be called like so,
Classification.compute_pay()
Notice there is no self inside the parentheses. Similarly, when you call another method that is not static, self is never passed as an argument:
def my_method(self):
self.another_method()

Python - Assigning attributes to a class instance using a for loop and a list

Hi folks I am experimenting with Python (I found pygame.org and wanted to play around) and I am trying to read some settings from a configuration file. I want to be able to change stats on the fly. (So if I wanted to change how hard a fighter hits or how fast a wizard runs then I'd be able to do that.) I was hoping to be able to read from a list and create an attribute for each instance in the list basically this:
for stat in Character.stats:
self.stat = parser.get(self.char_class, stat)
What ends up happening is there is an object with an attribute names 'stat' that contains the last value assigned. What I would LIKE to happen is to have an attribute created for each item in the list, and then get assigned the related value from the config file.
here is more code for context:
class Character(object):
stats = ["level_mod",
"power",
"speed",
"hit",
"evade",
"magic",
"stamina",
"magic_defense",
"intelligence"]
def __init__(self, name, rpg_id):
self.name = name
self.rpg_id = rpg_id
self.__setStats()
def __setStats(self):
parser = SafeConfigParser()
parser.read('char_config.cfg')
for stat in Character.stats:
self.stat = parser.get(self.char_class, stat)
Thanks for your time!
You can use, setattr:
for stat in Character.stats:
setattr(self, stat, parser.get(self.char_class, stat))
Or manually access dict
for stat in Character.stats:
self.__dict__[stat] = parser.get(self.char_class, stat))
You want setattr(obj, attrname, value)
You better re-design that part of the game by adding a Stats class.
class Stats:
STATS = ["level_mod",
"power",
"speed",
"hit",
"evade",
"magic",
"stamina",
"magic_defense",
"intelligence"]
def __init__(self, conf_file=None):
self.__stats = {}
if conf_file is not None:
self.loads_stats_from_file(conf_file)
def load_stats_from_file(self, conf_file):
"""
Here add the pairs <stat_name>:<value>
to the self.__stats dict. For that just parse the config
file like before.
"""
pass
def get_stat(self, stat_name):
return self.__stats[stat_name]
def set_stat(self, stat_name, value):
self.__stats[stat_name] = value
Then you can add a Stats instance to your Character.
class Character(object):
def __init__(self, name, rpg_id):
self.stats = Stats("char_config.cfg")
self.name = name
self.rpg_id = rpg_id
This way you improve usability and decouple the Stats and Character logics. And besides, your problem is reduced from "Adding attributes to an object" to "Adding items to a dictionary".

python class adds instances of itself to a list

I have a class for rooms. I want that every time I create an object using that class the object would be added to a list of all rooms.
Rooms class:
class Rooms:
"""Room class, takes type,days, occupied or not and when it frees up"""
def __init__(self, room_type, days, occupied, when_free):
self.room_type = room_type
self.days = days
self.occupied = occupied
self.when_free = arrow.get(when_free,'YYYY-MM-DD')
Any other feedback is appreciated as well!
also not sure if I should create new topic on this but is it possible that when the object is created and True on occupied is passed to the object you wouldn't need to pass 4th variable and it would take it as the current date instead? in short if there is no 4th variable it passes arrow.get(str(arrow.utcnow()),'YYYY-MM-DD') instead
figured out my second issue. I did change the init to:
def __init__(self, room_type, days, occupied, when_free=str(arrow.get(str(arrow.utcnow()),'YYYY-MM-DD'))):
self.room_type = room_type
self.days = days
self.occupied = occupied
self.when_free = arrow.get(when_free,'YYYY-MM-DD')
I would suggest a slightly more elegant and logical way than the above:
class Building(object):
def __init__(self):
self.rooms = []
class Room(object):
def __init__(self, building=None)
if building:
building.rooms.append(self)
self.building = building
b = Building()
r = Room(b)
That way, you don't need every time call b.rooms.append and now it more agreese with OOP.
Ideally, you would want the scope of your room list to be where you plan to use it. Not as part of a room itself. So, if you have a building with rooms:
class Building():
def __init__(self):
self.rooms = []
b = Building()
b.rooms.append(Room(room_type, days, occupied, when_free))
The building is just for an example. The important part is rooms.append(). That should be declared and used wherever you actually need to use the list of rooms.
Might be better just to make the list a class variable:
class Room(object):
rooms = []
def __init__(self, room_type, days, occupied, when_free):
self.room_type = room_type
self.days = days
self.occupied = occupied
self.when_free = arrow.get(when_free,'YYYY-MM-DD')
Room.rooms.append(self)
r = Room('x', 1,2, True)
Room.rooms
[<Room object at 0x00000000C6325550>]
r.rooms
[<Room object at 0x00000000C6325550>]
Since it's a class variable, you can get to it through any class instance, or the class type itself.
edited to go through 'Room' instead of 'self', which is safer...
I was thinking you could decorate the __init__ method with a decorator that appends the instance to a list, to avoid cluttering the __init__ method with the instance registering. Now you only have to add one decorator to each class' init method if you want to keep track of the instances. Something like:
#!/usr/bin/env python3
import sys
class InstanceRegister:
def __call__(self, init):
def register(instance, *args, **kwargs):
init(instance, *args, **kwargs)
try :
instance.__class__.__instances__
except:
instance.__class__.__instances__ = []
instance.__class__.__instances__.append(instance)
return register
class Room:
"""Room class, takes type,days, occupied or not and when it frees up"""
#InstanceRegister()
def __init__(self, room_type, days, occupied, when_free):
self.room_type = room_type
self.days = days
self.occupied = occupied
self.when_free = arrow.get(when_free,'YYYY-MM-DD')
def __str__(self):
return "Room of type {:s}".format(self.room_type)
def main():
r1 = Room('type_a', 1, True, '1999-12-30')
r2 = Room('type_b', 2, True, '2000-12-30')
r3 = Room('type_c', 3, True, '2001-01-30')
for room in Room.__instances__:
print("{:s}".format(room))
return 0
if __name__ == '__main__':
sys.exit(main())
More on decorators at Understanding Python Decorators in 12 Easy Steps!

Python: Preventing duplication of data when using dictionaries and lists

Hello Stack Overflow!
I am executing a simple command in a program that compiles a report of all the books contained in a library. The library contains a list of shelves, each shelves contains a dictionary of books. However, despite my best efforts, I am always duplicating all my books and placing them on every shelf, instead of the shelf I've instructed the program to place the book on.
I expect I have missed out on some kind of fundamental rule with object creation and organization.
I believe the culprits are the enshelf and unshelf methods in the book class.
Thank you so much for your time,
Jake
Code below:
class book():
shelf_number = None
def __init__(self, title, author):
super(book, self).__init__()
self.title = title
self.author = author
def enshelf(self, shelf_number):
self.shelf_number = shelf_number
SPL.shelves[self.shelf_number].books[hash(self)] = self
def unshelf(self):
del SPL.shelves[self.shelf_number].books[hash(self)]
return self
def get_title(self):
return self.title
def get_author(self):
return self.author
class shelf():
books = {}
def __init__(self):
super(shelf, self).__init__()
def get_books(self):
temp_list = []
for k in self.books.keys():
temp_list.append(self.books[k].get_title())
return temp_list
class library():
shelves = []
def __init__(self, name):
super(library, self).__init__()
self.name = name
def make_shelf(self):
temp = shelf()
self.shelves.append(temp)
def remove_shelf(shelf_number):
del shelves[shelf_number]
def report_all_books(self):
temp_list = []
for x in range(0,len(self.shelves)):
temp_list.append(self.shelves[x].get_books())
print(temp_list)
#---------------------------------------------------------------------------------------
#----------------------SEATTLE PUBLIC LIBARARY -----------------------------------------
#---------------------------------------------------------------------------------------
SPL = library("Seattle Public Library")
for x in range(0,3):
SPL.make_shelf()
b1 = book("matterhorn","karl marlantes")
b2 = book("my life","bill clinton")
b3 = book("decision points","george bush")
b1.enshelf(0)
b2.enshelf(1)
b3.enshelf(2)
print(SPL.report_all_books())
b1.unshelf()
b2.unshelf()
b3.unshelf()
OUTPUT:
[['decision points', 'my life', 'matterhorn'], ['decision points', 'my life', 'matterhorn'], ['decision points', 'my life', 'matterhorn']]
None
[Finished in 0.1s]
..instead of [["decision points"],["my life"],["matterhorn"]]
Use dict.pop() instead of del.
Add self.books = {} to shelf's __init__. Don't declare books outside of the __init__, because if you do so, all of the instances of that class are going to refer to the same thing. Instead, this makes each instance have its own dictionary, which is of course what you want since a book can't be in two shelves at once.
Do the same for library and its shelves and book and its shelf_number.
Pass a library instance as an argument to enshelf and unshelf. When you refer to SPL from within your objects' methods, Python finds that there is no local SPL defined, so it searches for one outside of the local scope; but if you were to try to assign something to SPL or do some other sort of mutative business, you would get an UnboundLocalError.
Bonuses:
class book(object), class shelf(object), and class library(object). (Won't fix your problem, but you should do that anyway.)
You don't need to hash the keys before using them, they will be hashed (if they are hashable, but if you're hashing them, then they are).
There is no need to call super() unless you are inheriting from something, in which case you can delegate a method call to a parent or sibling using it - but you aren't doing that.
get_books() can be implemented as nothing more than return [self.books[k].get_title() for k in self.books.iterkeys()]
Likewise for report_all_books(): return [shlf.get_books() for shlf in self.shelves]. Note that I am not iterating over the indices, but rather over the elements themselves. Try for c in "foobar": print(c) in the interactive shell if you want to see for yourself.

Categories

Resources