Many of my classes look like the following class to represent accounts
class Account(object):
def __init__(self, first, last, age, id, balance):
self.first = first
self.last = last
self.age = age
self.id = id
self.balance = balance
def _info(self):
return self.first, self.last, self.age, self.id, self.balance
def __eq__(self, other):
return self._info == other._info()
def __hash__(self):
return hash((type(self), self.info()))
def ... # other methods follow
But really the only relevant information is the list of attributes I care about first, last, age, id, balance. Is there a standard method to define Python classes that follow this structure?
At first glance I thought of namedtuple but I'm not sure that that allows me to add additional methods after the fact. Really, I want something like the following
class Account(object):
attributes = "first last age id balance"
def ... # other methods
What is the best way of obtaining this?
Not sure how idiomatic it is, but the following satisfies your requirements:
class Slottable:
def __init__(self, *args):
for slot, arg in zip(self.slots.split(' '), args):
setattr(self, slot, arg)
def _info(self):
return tuple(getattr(self, attr) for attr in self.slots.split())
def __eq__(self, other):
return self._info() == other._info()
def __hash__(self):
return hash((type(self), self._info()))
class Account(Slottable):
slots = "first last age id balance"
def fullname(self):
return self.first + " " + self.last
matt = Account("Matthew", "Smith", 28, 666, 1E6)
john = Account("John", "Jones", 46, 667, 1E7)
d = {matt: 5, john: 6} # Hashable
print matt.fullname()
#=> "Matthew Smith"
print john.fullname()
#=> "John Jones"
print matt == matt, matt == john
#=> True False
matt.age = 29 # Happy birthday!
print matt.age
#=> 29
Here are some recipes you can try: override __setattr__, __dict__, __slots__ and/or init. Let us know what works for you.
Many libraries out there exist to cover this need: attrs, dataclasses, pydantic, ... and my new addition to this landscape, pyfields.
Choice will mainly depend on the features you need or do not need. pyfields focuses on fields definition and optional validation and conversion, without any constraint on your class. Fields that can be native become as fast as python native attributes can be, while fields requiring callbacks (validators/converters) are implemented using descriptors.
You can blend your own constructor with the
from pyfields import field, init_fields
class Account(object):
first = field(doc="first name")
last = field(doc="last name")
age = field(doc="the age in years")
id = field(doc="an identifier")
balance = field(doc="current balance in euros")
#init_fields
def __init__(self, msg):
print(msg)
a = Account("hello, world!", first="s", last="marie", age=135, id=0, balance=-200000)
print(vars(a))
yields
hello, world!
{'balance': -200000, 'id': 0, 'age': 135, 'last': 'marie', 'first': 's'}
As opposed to other, more "all in one" libraries, pyfields concentrates on the fields and the constructor only, with a "minimum viable product" spirit. So if you would also like dict representation and conversion, hash, equality and comparison, you should add them on top of your class using another library. I am currently developing a mixture lib providing mix-in classes for this, with the same philosophy of a la carte features - that you will be able to use with or without pyfields.
See pyfields documentation for details. Do not hesitate to provide feedback !
Related
I'am using more class based programs, however in some cases it's not handy to provide all self.paramets into a class.
In those cases I want to use a regular input into a function in a class. I figured out a way to achieve both inputs, let me show this in following script:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def myfunc(a):
if (type(a) == str):
name = a
else:
name = a.name
print("Hello my name is " + name)
p1 = Person("John", 36)
p1.myfunc()
print("---------------------")
Person.myfunc("Harry")
Output:
Hello my name is John
---------------------
Hello my name is Harry
First, the name is initialized by the classes self.params.
Second, the name is provided in the method within the class as a string.
So a type check is necessary.
However I don't think this is a clean approach, because when I have >30 methods I need to implement these type checks again, including upcoming type-error results.
Does anyone know a better approach?
The simplest solution is to implement a __str__ method for your class. This method will be called whenever something tries to convert an instance of the class to a string.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name
p = Person('Jane', 25)
print('Hello', p)
'Hello Jane'
The approach might be just wrong to begin with, but I'm trying to do the following:
class Material:
pass
class Vacuum(Material):
def __str__(self):
return 'vacuum'
class Aluminum(Material):
def __str__(self):
return 'aluminum'
class Graphite(Material):
def __str__(self):
return 'graphite'
class Beryllium(Material):
def __str__(self):
return 'beryllium'
I have different pieces of code that deals with different materials. Instead of passing a string as argument to that other pieces I would prefer to give it objects. This allows to have tab-completion with ipython and it is also a way to enforce the type.
To avoid changing the already written pieces, those will just do str(argument): if it is a string it recovers the old behavior, if it is one of the objects it will work.
The question is now: I want to support a given list of materials:
allowed_materials = ['vacuum', 'aluminum', 'graphite',]
and that list might be growing. Instead of manually writing the classes, how could I generate them based on the list?
You can define a metaclass that can generate your classes for you.
class mattype(type):
def __new__(mcls, name, bases=(), d=None):
def __str__(self):
return name.lower()
if not d:
d = {}
d['__str__'] = __str__
bases = (*bases, Material)
return super().__new__(mcls, name.title(), bases, d)
allowed_materials = ['vacuum', 'aluminum', 'graphite',]
classes = {name: mattype(name) for name in allowed_materials}
str(classes['vacuum']())
# 'vacuum'
If you do not need different class name for different material you can simply initialise it inside the material class. If not I will delete my answer.
class Material:
def __init__(self,name):
self.name=name
def __str__(self):
return self.name
allowed_materials = ['vacuum', 'aluminum', 'graphite',]
obj_lst=[Material(material) for material in allowed_materials]
for obj in obj_lst:
print(str(obj))
output:
vacuum
aluminum
graphite
I ended up doing the following, also adding objects to the module.
import sys
class Material:
def __str__(self):
return self.__class__.__name__
pass
print(sys.modules[__name__])
_materials = ['Copper', 'Vacuum']
for m in _materials:
setattr(sys.modules[__name__], m, type(m, (Material,), {})())
Here, I am attempting to mock up a social media profile as a class "Profile", in which you have name, a group of friends, and the ability to add and remove friends. There is a method that I would like to make, that when invoked, will print the list of friends in alphabetical order.
The issue: I get a warning that I cannot sort an unsortable type. Python is seeing my instance variable as a "Profile object", rather than a list that I can sort and print.
Here is my code:
class Profile(object):
"""
Represent a person's social profile
Argument:
name (string): a person's name - assumed to uniquely identify a person
Attributes:
name (string): a person's name - assumed to uniquely identify a person
statuses (list): a list containing a person's statuses - initialized to []
friends (set): set of friends for the given person.
it is the set of profile objects representing these friends.
"""
def __init__(self, name):
self.name = name
self.friends = set()
self.statuses = []
def __str__(self):
return self.name + " is " + self.get_last_status()
def update_status(self, status):
self.statuses.append(status)
return self
def get_last_status(self):
if len(self.statuses) == 0:
return "None"
else:
return self.statuses[-1]
def add_friend(self, friend_profile):
self.friends.add(friend_profile)
friend_profile.friends.add(self)
return self
def get_friends(self):
if len(self.friends) == 0:
return "None"
else:
friends_lst = list(self.friends)
return sorted(friends_lst)
After I fill out a list of friends (from a test module) and invoke the get_friends method, python tells me:
File "/home/tjm/Documents/CS021/social.py", line 84, in get_friends
return sorted(friends_lst)
TypeError: unorderable types: Profile() < Profile()
Why can't I simply typecast the object to get it in list form? What should I be doing instead so that get_friends will return an alphabetically sorted list of friends?
Sorting algorithms look for the existence of __eq__, __ne__, __lt__, __le__, __gt__,__ge__ methods in the class definition to compare instances created from them. You need to override those methods in order to tweak their behaviors.
For performance reasons, I'd recommend you to define some integer property for your class like id and use it for comparing instead of name which has string comparison overhead.
class Profile(object):
def __eq__(self, profile):
return self.id == profile.id # I made it up the id property.
def __lt__(self, profile):
return self.id < profile.id
def __hash__(self):
return hash(self.id)
...
Alternatively, you can pass a key function to sort algorithm if you don't want to bother yourself overriding those methods:
>>> friend_list = [<Profile: id=120>, <Profile: id=121>, <Profile: id=115>]
>>> friend_list.sort(key=lambda p: p.id, reverse=True)
Using operator.attrgetter;
>>> import operator
>>> new_friend_list = sorted(friend_list, key=operator.attrgetter('id'))
I think i'll take a crack at this. first, here's teh codes:
from collections import namedtuple
class Profile(namedtuple("Profile", "name")):
def __init__(self, name):
# don't set self.name, it's already set!
self.friends = set({})
self.statuses = list([])
# ... and all the rest the same. Only the base class changes.
what we've done here is to create a class with the shape of a tuple. As such, it's orderable, hashable, and all of the things. You could even drop your __str__() method, namedtuple provides a nice one.
I am trying to use assert() to check the content of a simple class without the need to check each of the members of the class. Is something like the following possible?
def class class_data(object):
def __init__ (self, name = 'Richie', school = 'Jefferson High', age = 17):
self.name = name
self.school = school
self.age = age
myschool = class_data()
#check for correct data via assert
assert (myschool == class_data('Fonzie', 'Lincoln High', 17))
Please excuse me if my questions is nonsensical, and many thanks in advance.
You need to define the __eq__ method on your class.
When you do myschool == other, Python will call myschool.__eq__(other), which by default just whether checks both objects are the same (in your case, they aren't).
You can override __eq__ to achieve your purpose, here's an example:
def class class_data(object):
def __init__ (self, name = 'Richie', school = 'Jefferson High', age = 17):
self.name = name
self.school = school
self.age = age
def __eq__(self, other):
for attr in ("name", "school", "age"):
if getattr(self, attr) != getattr(other, attr):
return False
return True
Note: using getattr isn't necessarily the best thing to do here. The implementation you choose is up to you.
Reading a Book, i came across this code...
# module person.py
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay *(1 + percent))
def __str__(self):
return "[Person: %s, %s]" % (self.name,self.pay)
class Manager():
def __init__(self, name, pay):
self.person = Person(name, "mgr", pay)
def giveRaise(self, percent, bonus=.10):
self.person.giveRaise(percent + bonus)
def __getattr__(self, attr):
return getattr(self.person, attr)
def __str__(self):
return str(self.person)
It does what I want it to do, but i do not understand the __getattr__ function in the Manager class. I know that it Delegates all other attributes from Person class. but I do not understand the way it works. for example why from Person class? as I do not explicitly tell it to. person(module is different than Person(class)
Any help is highly appreciated :)
In your __init__ you instantiate a Person object which gets assigned to self.person.
You then override attribute lookups on the Manager instance (by implementing __getattr__ for this class) and redirect these attributes to be looked up on the self.person variable instead (which is the Person object from 1 in this particular case).
Like Felix Kling mentioned in the comments, it would make more sense to make Manager inherit from Person. In the current code above, it looks like the manager has a person while it's more logical to think that the manager is a person.
You could do something like this:
class Person(object):
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def give_raise(self, percent):
self.pay = int(self.pay *(1 + percent))
def __str__(self):
return "[Person: %s, %s]" % (self.name, self.pay)
class Manager(Person):
def __init__(self, name, pay):
super(Manager, self).__init__(name, "mgr", pay)
def give_raise(self, percent, bonus=.10):
self.pay = int(self.pay * (1 + (percent + bonus)))
# example usage
John = Person("John", "programmer", 1000)
Dave = Manager("Dave", 2000)
print John, Dave
John.give_raise(.20)
Dave.give_raise(.20)
print John, Dave
Actually, you do tell it explicitly - not by naming the class, but by providing an instance of that class.
In the init method, you bind self.person to an instance of Person. Now, every Manager instance will have this data member.
In __getattr__, you are delegating to the getattr builtin with self.person as the first argument. Regardless of the type of self.person, it will look for a member with the given name.
Beside reading a Book, you might want to consult The Book where you could have found a pretty clear explanation of how __getattr__() methods work.
In a nutshell, it gets called when there are no attributes of the specified name attached to the object it's being applied to, and also not to the object class or any of it's superclasses. In other words, it called when all else fails.
In the code in your example, the implementation of __getattr_() effectively redirects the search for the named attribute onto the self.person object, which is an instance of the Person class.
It also important to understand that __getattr_() is the first step in accessing both the data and the methods associated with any object.