Python Object as Dict Key using __hash__ for access - python

Take this super simple class:
class Foo():
def __init__(self, iden):
self.iden = iden
def __hash__(self):
return hash(self.iden)
def __repr__(self):
return str(self.iden)
The goal is to create instances of the class to use as dict keys. If __repr__ is omitted, the keys are the standard object address. With __repr__ a printable representation might be:
f = Foo(1)
g = Foo(2)
d = {f:'a', g:'b'}
print(d)
>>> {1:'a', 2:'b'}
When attempting to access the dict by key though, it does not appear to be immediately obvious how to utilize the __repr__ (or __str__ for that matter) representation as the key.
print(d[1])
>>> KeyError

First thing's first: __repr__() is a red herring. It only affects how the object is displayed. It has nothing to do with what you're trying to do.
If you want to have two separate objects refer to the same slot in a dict, you need two things (reference):
The objects must have the same hash (hash(obj1) == hash(obj2)).
The objects must compare equal (obj1 == obj2).
Your above implementation does the former, but not the latter. You need to add an __eq__() method (which is actually required by the documentation when you define __hash__(), anyway).
class Foo():
def __init__(self, iden):
self.iden = iden
def __hash__(self):
return hash(self.iden)
def __eq__(self, other):
return self.iden == other
>>> d = {Foo(1) : 'a'}
>>> d[1]
'a'

Related

Why is Python printing memory location instead of value when using a for loop to insert key / value into hashtable I built [duplicate]

In Java, if I call List.toString(), it will automatically call the toString() method on each object inside the List. For example, if my list contains objects o1, o2, and o3, list.toString() would look something like this:
"[" + o1.toString() + ", " + o2.toString() + ", " + o3.toString() + "]"
Is there a way to get similar behavior in Python? I implemented a __str__() method in my class, but when I print out a list of objects, using:
print 'my list is %s'%(list)
it looks something like this:
[<__main__.cell instance at 0x2a955e95f0>, <__main__.cell instance at 0x2a955e9638>, <__main__.cell instance at 0x2a955e9680>]
how can I get python to call my __str__() automatically for each element inside the list (or dict for that matter)?
Calling string on a python list calls the __repr__ method on each element inside. For some items, __str__ and __repr__ are the same. If you want that behavior, do:
def __str__(self):
...
def __repr__(self):
return self.__str__()
You can use a list comprehension to generate a new list with each item str()'d automatically:
print([str(item) for item in mylist])
Two easy things you can do, use the map function or use a comprehension.
But that gets you a list of strings, not a string. So you also have to join the strings together.
s= ",".join( map( str, myList ) )
or
s= ",".join( [ str(element) for element in myList ] )
Then you can print this composite string object.
print 'my list is %s'%( s )
Depending on what you want to use that output for, perhaps __repr__ might be more appropriate:
import unittest
class A(object):
def __init__(self, val):
self.val = val
def __repr__(self):
return repr(self.val)
class Test(unittest.TestCase):
def testMain(self):
l = [A('a'), A('b')]
self.assertEqual(repr(l), "['a', 'b']")
if __name__ == '__main__':
unittest.main()
I agree with the previous answer about using list comprehensions to do this, but you could certainly hide that behind a function, if that's what floats your boat.
def is_list(value):
if type(value) in (list, tuple): return True
return False
def list_str(value):
if not is_list(value): return str(value)
return [list_str(v) for v in value]
Just for fun, I made list_str() recursively str() everything contained in the list.
Something like this?
a = [1, 2 ,3]
[str(x) for x in a]
# ['1', '2', '3']
This should suffice.
When printing lists as well as other container classes, the contained elements will be printed using __repr__, because __repr__ is meant to be used for internal object representation.
If we call: help(object.__repr__) it will tell us:
Help on wrapper_descriptor:
__repr__(self, /)
Return repr(self).
And if we call help(repr) it will output:
Help on built-in function repr in module builtins:
repr(obj, /)
Return the canonical string representation of the object.
For many object types, including most builtins, eval(repr(obj)) == obj.
If __str__ is implemented for an object and __repr__ is not repr(obj) will output the default output, just like print(obj) when non of these are implemented.
So the only way is to implement __repr__ for your class. One possible way to do that is this:
class C:
def __str__(self):
return str(f"{self.__class__.__name__} class str ")
C.__repr__=C.__str__
ci = C()
print(ci) #C class str
print(str(ci)) #C class str
print(repr(ci)) #C class str
The output you're getting is just the object's module name, class name, and then the memory address in hexadecimal as the the __repr__ function is not overridden.
__str__ is used for the string representation of an object when using print. But since you are printing a list of objects, and not iterating over the list to call the str method for each item it prints out the objects representation.
To have the __str__ function invoked you'd need to do something like this:
'my list is %s' % [str(x) for x in myList]
If you override the __repr__ function you can use the print method like you were before:
class cell:
def __init__(self, id):
self.id = id
def __str__(self):
return str(self.id) # Or whatever
def __repr__(self):
return str(self) # function invoked when you try and print the whole list.
myList = [cell(1), cell(2), cell(3)]
'my list is %s' % myList
Then you'll get "my list is [1, 2, 3]" as your output.

Enabling list(instance) or tuple(instance) on instance of user-defined class

Is there a way to enable using the standard type constructors such as int, set, dict, list, tuple, etc. to coerce an instance of a user-defined class to one of those types in a user-defined way? For example
class Example:
def __init__(self):
self.a=1
self.b=2
and then having
>>> ex = Example()
>>> dict(ex)
{"a":1, "b":2}
I don't know if that's possible, and if it is, what I would need to add in the class definition. Right now I need this and I implement a "as_dict" method which I call on the object, but it doesn't look as natural.
Make your type iterable by adding an __iter__() method. Trivially:
class Example:
def __init__(self):
self.a = 1
self.b = 2
def __iter__(self):
yield "a", self.a
yield "b", self.b
This yields a sequence of tuples containing name/value pairs, which dict() is happy to consume.
dict(Example()) # {'a': 1, 'b': 2}
Of course, there's a lot of repeating yourself in there. So you could instead write __iter__() to work with a predefined list of attributes:
def __iter__(self):
names = "a", "b"
for name in names:
yield name, getattr(self, name)
You could also have it introspect all the attributes from the instance, omitting attributes whose values are callable:
def __iter__(self):
names = dir(self)
for name in names:
value = getattr(self, name)
if not callable(value):
yield name, value
Or have it yield from the instance's __dict__ attribute, which contains only the attributes stored directly on the instance (the dir() method above also finds inherited attributes):
def __iter__(self):
yield from self.__dict__.items()
You need to make your object iterable.
Both list and tuple accept an iterable as their argument, which they will repeatedly consume until to construct the new collection. To enable your class to work with this mechanism, you will need to define at least the __iter__ method, and possibly also the __next__ method, depending on the specific semantics of your class.
Your implementation of __iter__ will need to return an object that implentents the iterator protocol. If you internally use an iterable collection, this can be as simple as returning that collection.
In your case, it seems you want your object to behavior like an iterable collection of tuples. A possible implementation for the behavior you're looking for would be
class Example:
def __init__(self):
self.a=1
self.b=2
def __iter__(self):
for elem in ('a', 'b'):
yield (elem, getattr(self, elem))
>>> dict(Example())
{'a': 1, 'b': 2}
Here, we make use of a generator to produce an iterable that will yield the tuples ('a', self.a) and ('b', self.b) in turn.

Dictionary function on an instance of a class [duplicate]

Is there a way to enable using the standard type constructors such as int, set, dict, list, tuple, etc. to coerce an instance of a user-defined class to one of those types in a user-defined way? For example
class Example:
def __init__(self):
self.a=1
self.b=2
and then having
>>> ex = Example()
>>> dict(ex)
{"a":1, "b":2}
I don't know if that's possible, and if it is, what I would need to add in the class definition. Right now I need this and I implement a "as_dict" method which I call on the object, but it doesn't look as natural.
Make your type iterable by adding an __iter__() method. Trivially:
class Example:
def __init__(self):
self.a = 1
self.b = 2
def __iter__(self):
yield "a", self.a
yield "b", self.b
This yields a sequence of tuples containing name/value pairs, which dict() is happy to consume.
dict(Example()) # {'a': 1, 'b': 2}
Of course, there's a lot of repeating yourself in there. So you could instead write __iter__() to work with a predefined list of attributes:
def __iter__(self):
names = "a", "b"
for name in names:
yield name, getattr(self, name)
You could also have it introspect all the attributes from the instance, omitting attributes whose values are callable:
def __iter__(self):
names = dir(self)
for name in names:
value = getattr(self, name)
if not callable(value):
yield name, value
Or have it yield from the instance's __dict__ attribute, which contains only the attributes stored directly on the instance (the dir() method above also finds inherited attributes):
def __iter__(self):
yield from self.__dict__.items()
You need to make your object iterable.
Both list and tuple accept an iterable as their argument, which they will repeatedly consume until to construct the new collection. To enable your class to work with this mechanism, you will need to define at least the __iter__ method, and possibly also the __next__ method, depending on the specific semantics of your class.
Your implementation of __iter__ will need to return an object that implentents the iterator protocol. If you internally use an iterable collection, this can be as simple as returning that collection.
In your case, it seems you want your object to behavior like an iterable collection of tuples. A possible implementation for the behavior you're looking for would be
class Example:
def __init__(self):
self.a=1
self.b=2
def __iter__(self):
for elem in ('a', 'b'):
yield (elem, getattr(self, elem))
>>> dict(Example())
{'a': 1, 'b': 2}
Here, we make use of a generator to produce an iterable that will yield the tuples ('a', self.a) and ('b', self.b) in turn.

is there a magic method for sorted() in Python?

I understand that there are magic methods in python that can be overwritten by classes to control the way certain built in functions treat the members of these classes. For example, the behavior of len() and str() can be overwritten via magic methods __len__() and __str__():
class EmptySet(object):
def __len__(self):
return 0
def __str__(self):
return '[]'
>>> e = EmptySet()
>>> str(e)
[]
>>> len(e)
0
There are also __cmp__() and __ge__(), __le__() etc methods to control how these objects can be compared and how a list of them should be ordered by list.sort(). My question is not about customizing the ordering of objects in a list but about sorting the object itself. Suppose the set weren't empty and I want to use sorted() to sort it:
class SetOfTwo(object):
def __init__(self, a , b):
el_0 = a
el_1 = b
def __len__(self):
return 2
def __str__(self):
return '[{}, {}]'.format(el_0, el_1)
Is there a magic method I can implement to have sorted() flip the elements if they aren't in order? I'm picturing the following behavior:
>>> s = SetOfTwo(2, 1)
>>> str(s)
[2, 1]
>>> t = sorted(s)
>>> str(t)
[1, 2]
>>> type(t)
>>> SetOfTwo
You should definitely read the official documentation of how to emulate container types. Basically a class supposed to work as a container (list, dict etc.) needs to implement methods to set or get members __getitem__(), __setitem__() and iterate over items __iter__() and to get the number of items - method __len__(). This is the minimum. But you can also add the ability to delete items and other operations.
The behaviour of sorted() built-in function is to iterate over elements of your container and compare them using methods you mentioned __cmp__(), __ge__(), __le__() which should be defined for items and not the container as you know already. Then a new list instance is created with items sorted and this new instance is returned. You can pass it to the constructor of your custom container then or you can wriap sorted() with a custom function which will return the desired class instance.
As some have said in the comments, sets are unordered but I don't think your question is really about sets.
Python uses the data model methods you mentioned, ge, le, and cmp to determine how a class behaves when sorted() is called on it. You can see how I try to call it here, but Python objects and asks me to implement <.
>>> class a(object):
... pass
...
>>> b = a()
>>> c = a()
>>> d = [b, c]
>>> sorted(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'a' and 'a'
Hope this helps. Aslo, as other people said, it's a good idea to subclass something in collections.abc. I'd read Item 28 in effective python that talks about this to get a good idea.
len() and str() are functions who take an object as parameter and return an integer (resp. string). The object can personalize the way the len is calculated, or the string is generated, via the __len__() and __str__() magic methods.
Similarly, sorted() is a function that takes a list (or any iterable) of objects and returns a list of the sorted objects. The objects can personalize the way they get compared through the __lt__() magic method.
Some confusion arises when we think of `sorted(my_list) as a function that "sorts the list", rather than "sorts the elements of the list".
You don't want to sort your objects (i.e. make an ordered list of objects), but only sort some data in their internal representation. So you need an instance method on your object that will update that internal representation. You can name it as you wish, .sort() if you'd like, but you will have to call it on your one object, and it will not be involved in comparing objects.
You have to implement comparison operator magic methods. Python will automatically take care of the sort and sorted.
class Edge:
def __init__(self,source, dest, weight=float('inf')):
self.source = source
self.dest = dest
self.weight = weight
def __repr__(self):
return f"Edge: ({self.source}, {self.dest}, {self.weight})"
def __lt__(self, other):
return self.weight < other.weight
e1 = Edge(0, 1, 2)
e2 = Edge(1, 2, 3)
e3 = Edge(2, 3, 10)
e4 = Edge(2, 4, 10)
e5 = Edge(2, 4, 0)
l = [e1, e3, e4, e2, e5]
print(l)
print(e3 > e2)
print(e3 == e4)
print(sorted(l))

Python: Subclassing frozenset not iterable?

Subclassing frozenset and set doesn't seem to work the same when it comes to iterables. Try to run the following MWE:
class MonFrozenSet(frozenset):
def __new__(self, data):
super(MonFrozenSet,self).__init__(data)
return self
class MonSet(set):
def __init__(self, data):
super(MonSet,self).__init__(data)
x=(1,2,3,4)
A=MonSet(x)
B=MonFrozenSet(x)
for y in A: #Works
print y
for y in B: #Doesn't work
print y
The second for returns:
for y in B:
TypeError: 'type' object is not iterable
Any idea on how I can solve this?
If you are asking yourselves why I would like to use frozenset, the anwer is that I am trying to create a set of sets of tuples. The sets of tuples will be frozenset and the set of sets of tuples will be a set.
I use Python-2.7
When overriding __new__ you need to call the superclass's __new__, not its __init__. Also, you need to pass self (better named cls), since __new__ is a classmethod. Also, you need to return the result, since __new__ actually creates an object, it doesn't modify self. So:
class MonFrozenSet(frozenset):
def __new__(cls, data):
return super(MonFrozenSet,cls).__new__(cls, data)
Then:
>>> a = MonFrozenSet([1, 2, 3])
>>> for item in a:
... print item
1
2
3

Categories