typecast classes in python: how? - python

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.

Related

What exactly does "in" check in Python sets? [duplicate]

This question already has answers here:
What happens when objects in a Set are altered to match each other?
(4 answers)
Closed 11 months ago.
I have a simple custom object that represent custom tag, that user can attach to another object.
I want to store tags in a set, because I want to avoid duplicates and because order doesn't matter.
Each tag contain values "name" and "description". Later on, I might add another variables, but the key identifier for tag is "name".
I want to check whether tag is equal to other either by tag.name == other.name or against string tag == 'whatever'.
I want users to be able to edit tags including renaming them.
I have defined the object like this and everything worked as expected:
class Tag:
def __init__(self, name, description=""):
self.name = name
self.description = description
def __str__(self):
return self.name
def __repr__(self):
return self.name
def __eq__(self, other):
if isinstance(other, Tag):
return self.name == other.name
else:
return self.name == other
def __hash__(self):
return hash(self.name)
The problem appeared, when I tried to change the tag name:
blue_tag = Tag("blue")
tags = {blue_tag}
blue_tag in tags # returns True as expected
"blue" in tags # returns True as expected
blue_tag.name = "navy"
"navy" in tags # returns False. Why?
I don't understand why. The tag is correctly renamed, when I do print(tags). The id of bluetag object is also the same, hash of the name is also the same.
Everywhere, including Python official documentation, I found just basic info that in checks whether item is present in container and to define custom container, I need to define custom methods like __contains__ But I don't want to create custom set method.
The closest thing I found I found was a question here on SO:
Custom class object and "in" set operator
But it still didn't solve the problem.
The problem is that in changing a tag name attribute, you change its hash in the class above: and the hash of an object must not change after it is added to a set or as dictionary as a key.
The thing is that if two objects are "equal" they must have the same hash value - since you want your tags to be comparable by name, this implies that they can't have their name changed at all: if an object compares equal to another, their hash values must also be the equal: i.e. you can't simply add another immutable attribute to your class and base your hash value on that instead of the name.
The workaround I see in this case is to have a special "add_to_set" method on your Tag class; it would then track the sets it belongs to, and turn name into a property instance, so that whenever name is changed, it removes and re-adds the Tag itself from all sets it belongs to. The newly re-inserted tag would behave accordingly.
Making this work properly in parallel code would take somewhatmore work: as one could make use of the sets in another thread during the renaming - but if that is not a problem, then what is needed is:
class Tag:
def __init__(self, name, description=""):
self.sets = []
self.name = name
self.description = description
... # other methods as in your code
def __hash__(self):
return hash(self.name)
def add_to_set(self, set_):
self.sets.append(set_)
set_.add(self)
def remove_from_set(self, set_):
self.sets.remove(set_)
set_.remove(self)
#property
def name(self):
return self._name
#name.setter
def name(self, value):
# WARNING: this is as thread unsafe as it gets! Do not use this class
# in multi-threaded code. (async is ok)
try:
for set_ in self.sets:
set_.remove(self)
self._name = value
finally:
for set_ in self.sets:
set_.add(self)
And now:
In [17]: a = Tag("blue")
In [18]: b = set()
In [19]: a.add_to_set(b)
In [20]: a in b
Out[20]: True
In [21]: b
Out[21]: {blue}
In [22]: a.name = "mauve"
In [23]: b
Out[23]: {mauve}
In [24]: a in b
Out[24]: True
It is possible to specialize a set class that would automatically call the add_to_set and remove_from_set methods for you as well, but this is likely enough.

Why isn't the hash function deterministic?

I'm developing a program using Python 3.6
I have a problem: if I use the deterministic hash function (from standard library of the language) on the same object, the string that results in output (after a run), is different for some runs!
For example:
class Generic:
def __init__(self, id, name, property):
self.id = id
self.name = name
self.property = property
def main():
my_object = Generic(3,'ddkdjsdk','casualstring')
print(hash(my_object))
I would like the output to always be the same (deterministic), but unfortunately different strings appear on the console:
8765256330262, -9223363264515786864, -9223363262437648366 and others...
Why this happens? I would like to guarantee the determinism with this function throughout my application! How do I solve the problem?
In this case it's probably easiest to define your own __eq__ function and __hash__ function. This will return the same hash every time for you:
class Generic:
def __init__(self, id, name, property):
self.id=id
self.name = name
self.property = property
def __eq__(self, other):
assert self.__class__ == other.__class__, "Types do not match"
return self.id == other.id and self.name == other.name and self.property == other.property
def __hash__(self):
return hash ( (self.id, self.name, self.property) )
This will also make hashes of equivalent objects equal, as well:
>>>obj = Generic(1, 'blah', 'blah')
>>>obj2 = Generic(1, 'blah', 'blah')
>>>obj == obj2
True
>>>hash(obj) == hash(obj2)
True
hope that helps!
For those looking to get hashes of built-in types, Python's built in hashlib might be easier than subclassing to redefine __hash__. Here's an example with for string.
from hashlib import md5
def string_hash(string):
return md5(string.encode()).hexdigest()
This will return the same hash for different string objects so long as the content is the same. Not all objects will work, but it could you save you time depending on your use case.

Generate classes based on a list of names in Python

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,), {})())

Alphabetizing a list through class methods

I have been trying to print a list using a __str__ method of a class, but before the list can be printed it has to be alphabetized through other methods coming before the __str__ method in the class. The provided methods for this are:
def returnName(self):
'''return the name of the player first last'''
return(self.first + self.last)
def returnReverseName(self):
'''return the name of the player as last, first'''
reverseName = (str(self.last), str(self.first))
return(reverseName)
def __eq__ (self, other):
'''determine if this persons name is the same as the other personsname'''
if self.returnReverseName == other.returnReverseName:
return True
else:
return False
def __lt__(self,other):
'''determine if this persons name is less than the other persons name alphabetically'''
if str(self.returnReverseName) < str(other.returnReverseName):
return True
else:
return False
def __gt__ (self, other):
'''determine if this persons name is greater than the other persons name alphabetically'''
if self.returnReverseName > other.returnReverseName:
return True
else:
return False
def __str__(self):
'''return a string of the persons name and their rating in a nice format'''
Name1 = str(self.first) + ' ' + str(self.last)
return ('%-20s %10.2f' % (Name1, self.rating))
where the name needs to be sorted by last name, first name but printed in first name last name order. The methods __lt__ and __gt__ are what should be ordering them when printed. The code I have so far to compile the list is:
basicList.sort()
for line in playerDict:
print(playerDict[line])
but this doesn't utilize the list, and is only printing the dictionary line by line in an unordered manner. Although it does refer to the methods it is just unable to alphabetize the names. How do I properly use the methods to get the dictionary printed in the right order? If I need to use the list of player objects, how do I get it to print using the __str__ method that would be printed post-alphabetization?
This code has many mistakes and oddities, but the first thing it does that could actually cause wrong results is that __eq__ and __gt__ are comparing two functions rather than two names. __lt__, for some reason, compares the string representations of those two functions. I don't know why it has different behavior than the other functions, but it's still wrong (just in a different way).
And I'm not surprised that the sorted list isn't used - you never use it.
basicList.sort() # sort the list
for line in playerDict: # that's not basicList
print(playerDict[line]) # still no basicList
Your class methods, if repaired, might look like this:
def returnName(self):
'''return the name of the player first last'''
return(self.first + ' ' + self.last)
def returnReverseName(self):
'''return the name of the player as last, first'''
return self.last, self.first
def __eq__ (self, other):
'''determine if this persons name is the same as the other personsname'''
return self.returnReverseName() == other.returnReverseName()
def __lt__(self,other):
'''determine if this persons name is less than the other persons name alphabetically'''
return self.returnReverseName() < other.returnReverseName()
def __gt__ (self, other):
'''determine if this persons name is greater than the other persons name alphabetically'''
return self.returnReverseName() > other.returnReverseName()
def __str__(self):
'''return a string of the persons name and their rating in a nice format'''
return '%-20s %10.2f' % (self.returnName(), self.rating)
But you probably don't need any of that. It looks like playerDict is a dictionary that holds some kind of keys attached to values that are Player objects (I assume that's the class name). Just iterate over those values and use a given sort key:
for entry in sorted(playerDict.values(), key=lambda x: x.last, x.first):
print(entry)

How to define a type/class in Python dynamically?

In C, if I want to define a type from a name I could use the preprocessor. For example,
#define DEFINE_STRUCT(name) \
struct My##name##Struct \
{ \
int integerMember##name; \
double doubleMember##name; \
}
And then I could define a concrete struct like so
DEFINE_STRUCT(Useless);
and use the Useless struct like this
struct MyUseslessStruct instance;
So my question is
Is there a way to achieve this in Python?
I have the following class
class ClassName(SQLTable):
items = []
def __init__(self, value):
SQLTable.__init__(self)
# some common code
if value in self.items:
return
self.items.append(value)
For each ClassName the contents of items will be different, so I would like something like
def defineclass(ClassName):
class <Substitute ClassName Here>(SQLTable):
items = []
def __init__(self, value):
SQLTable.__init__(self)
# some common code
if value in self.items:
return
self.items.append(value)
I don't want to repeat the code over and over, I would like to generate it if possible.
You're very close:
def defineclass(ClassName):
class C(SQLTable):
items = []
def __init__(self, value):
SQLTable.__init__(self)
# some common code
if value in self.items:
return
self.items.append(value)
C.__name__ = ClassName
return C
As you can see, you define it using a placeholder name, then assign its __name__ attribute. After that, you return it so you can then use it as you desire in your client code. Remember, a Python class is an object just as much as any other, so you can return it, store it in a variable, put it into a dictionary, or whatever you like once you've defined it.
The __name__ attribute is a convenience, mainly so error messages make sense. You may not actually need to give each class a unique name.
An alternative for this particular use case might be to use subclassing:
class Base(SQLTable):
def __init__(self, value):
SQLTable.__init__(self)
# some common code
if value in self.items:
return
self.items.append(value)
class Thing1(Base): items = []
class Thing2(Base): items = []
By not defining items on the base class, you ensure that you must subclass it and define a per-class items to actually use the class.
kindall's answer is very clear and likely preferable, but there is a built-in function to generate classes: type. When called with one argument, it returns the type of an object. When called with three arguments it generates a new type/class. The arguments are class name, base classes, and the class dict.
def custom_init(self, value):
SqlTable.__init__(self)
if value in self.items:
return
self.items.append(value)
def defineclass(classname):
# __name__ __bases__ __dict__
return type(classname, (SQLTable,), { '__init__': custom_init,
'items': [] })

Categories

Resources