Python, represent non-string object as string? - python

I'd like to represent an object as a string so that it can be accessed both as a dictionary key and as an object in itself. i.e.
class test(object):
def __init__(self, name, number_array):
self.name = name
self.number_array = number_array
self.graph= barChart(number_array)
sample_obj = test('test_object', [(x1,y1), (x2,y2)etc.])
but so that {sample_obj: another_object} would look like {'test_object': another_object}
while still making something like this possible:
for key, val in sample_dict.items(): print(key.name, key.graph)
as well as:
>>> sample_dict['test_object']
another_object

You must define eq that returns positive when comparing with the string i.e.:
def __eq__(self, other):
if self.name == other:
return True
... continue with comparison ...
You must also define hash that returns the same hash as the compared string:
def __hash__(self):
return hash(self.name)
UPDATE: The following code does exactly what the author wanted:
class test(object):
def __init__(self, name, number_array):
self.name = name
self.number_array = number_array
#self.graph= barChart(number_array)
def __eq__(self, other):
try:
return (self.name == other.name) and (self.number_array == other.number_array)
except AttributeError:
return self.name == other
def __hash__(self):
return hash(self.name)
sample_obj = test('test_object', [(0, 1), (2, 3)])
dict1 = {sample_obj: "Hurray"}
print("dict1[sample_obj]", dict1[sample_obj])
print("dict1['test_object']", dict1['test_object'])
dict2 = {'test_object': "Yippie"}
print("dict2[sample_obj]", dict2[sample_obj])
print("dict2['test_object']", dict2['test_object'])

To use a class as a dictionary key, implement __hash__ and __eq__. To change how it appears when you print the dictionary, implement __repr__:
class Test(object):
def __init__(self, name, number_array):
self.name = name
self.number_array = number_array
self.graph = barChart(number_array)
def __eq__(self, other):
return self.name == other.name and self.number_array == other.number_array
def __hash__(self):
return hash(self.name) ^ hash(self.number_array)
def __repr__(self):
return "test_object"
In use:
>>> t = Test("foo", (1, 2, 3))
>>> d = {t: [1, 2, 3]}
>>> t
test_object
>>> d
{test_object: [1, 2, 3]}
>>> d[t]
[1, 2, 3]
Note that this means that both the name and number_array attributes must be hashable - I have used a string and a tuple to ensure this. Also, it is better if __repr__ represents the actual object more closely, e.g.
def __repr__(self):
return "Test({0.name!r}, {0.number_array!r})".format(self)

Related

Is there a way to delete an object in list by value?

the 'list.remove' function does not compare objects by value
suppose the code is:
class item:
def __init__(self, a, b):
self.feild1 = a
self.field2 = b
a = item(1,4)
b = item(1,4)
l = [a]
l.remove(b) # doesn't remove l[0]
Because you don't provide a __eq__ implementation, your class inherits the method from object. object.__eq__ doesn't compare the values of attribute, it just checks to see that id(a) == id(b). You need to write your own __eq__:
class item:
def __init__(self, a, b):
self.field1 = a
self.field2 = b
def __eq__(self, other):
if not isinstance(other, item):
return NotImplemented
return self.field1 == other.field1 and self.field2 == other.field2
a = item(1,4)
b = item(1,4)
l = [a]
l.remove(b)
print(l)
# []

Generalized __eq__() method in Python

I'd like to create a generalized __eq__() method for the following Class. Basically I'd like to be able to add another property (nick) without having to change __eq__()
I imagine I can do this somehow by iterating over dir() but I wonder if there is a way to create a comprehension that just delivers the properties.
class Person:
def __init__(self, first, last):
self.first=first
self.last=last
#property
def first(self):
assert(self._first != None)
return self._first
#first.setter
def first(self,fn):
assert(isinstance(fn,str))
self._first=fn
#property
def last(self):
assert(self._last != None)
return self._last
#last.setter
def last(self,ln):
assert(isinstance(ln,str))
self._last=ln
#property
def full(self):
return f'{self.first} {self.last}'
def __eq__(self, other):
return self.first==other.first and self.last==other.last
p = Person('Raymond', 'Salemi')
p2= Person('Ray', 'Salemi')
You could use __dict__ to check if everything is the same, which scales for all attributes:
If the objects are not matching types, I simply return False.
class Person:
def __init__(self, first, last, nick):
self.first = first
self.last = last
self.nick = nick
def __eq__(self, other):
return self.__dict__ == other.__dict__ if type(self) == type(other) else False
>>> p = Person('Ray', 'Salemi', 'Ray')
>>> p2= Person('Ray', 'Salemi', 'Ray')
>>> p3 = Person('Jared', 'Salemi', 'Jarbear')
>>> p == p2
True
>>> p3 == p2
False
>>> p == 1
False
You can get all the properties of a Class with a construct like this:
from itertools import chain
#classmethod
def _properties(cls):
type_dict = dict(chain.from_iterable(typ.__dict__.items() for typ in reversed(cls.mro())))
return {k for k, v in type_dict.items() if 'property' in str(v)}
The __eq__ would become something like this:
def __eq__(self, other):
properties = self._properties() & other._properties()
if other._properties() > properties and self._properties() > properties:
# types are not comparable
return False
try:
return all(getattr(self, prop) == getattr(other, prop) for prop in properties)
except AttributeError:
return False
The reason to work with the reversed(cls.mro()) is so something like this also works:
class Worker(Person):
#property
def wage(self):
return 0
p4 = Worker('Raymond', 'Salemi')
print(p4 == p3)
True
you can try to do this, it will also work if you want eq inside dict and set
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(self, other.__class__):
return self.__hash__() == other.__hash__()
return NotImplemented
def __hash__(self):
"""Overrides the default implementation,
and set which fieds to use for hash generation
"""
__make_hash = [
self.first
]
return hash(tuple(sorted(list(filter(None, __make_hash)))))

Python is adding values to multiple instances

I have created a descriptor for lists.
After testing it seems that every time I append a value to a list of one instance, it is being added to another instance as well.
Even weirder, in the unittests it keeps appending to the list, and not resetting on every test.
My descriptor main class:
class Field(object):
def __init__(self, type_, name, value=None, required=False):
self.type = type_
self.name = "_" + name
self.required = required
self._value = value
def __get__(self, instance, owner):
return getattr(instance, self.name, self.value)
def __set__(self, instance, value):
raise NotImplementedError
def __delete__(self, instance):
raise AttributeError("Can't delete attribute")
#property
def value(self):
return self._value
#value.setter
def value(self, value):
self._value = value if value else self.type()
Descriptor list class:
class ListField(Field):
def __init__(self, name, value_type):
super(ListField, self).__init__(list, name, value=[])
self.value_type = value_type
def __set__(self, instance, value):
if not isinstance(value, list):
raise TypeError("{} must be a list".format(self.name))
setattr(instance, self.name, value)
def __iter__(self):
for item in self.value:
yield item
def __len__(self):
return len(self.value)
def __getitem__(self, item):
return self.value[item]
def append(self, value):
if not isinstance(value, self.value_type):
raise TypeError("Value is list {} must be of type {}".format(self.name, self.value_type))
self.value.append(value)
Unittests:
# Class I created solely for testing purposes
class ListTestClass(object):
l = ListField("l", int)
class TestListFieldClass(unittest.TestCase):
def setUp(self):
self.listobject = ListTestClass()
def test_add(self):
# The first number is added to the list
self.listobject.l.append(2)
def test_multiple_instances(self):
# This test works just fine
l1 = ListField("l1", int)
l2 = ListField("l2", int)
l1.append(1)
l2.append(2)
self.assertEqual(l1[0], 1)
self.assertEqual(l2[0], 2)
def test_add_multiple(self):
# This test works just fine
l1 = ListField("l1", int)
l1.append(1)
l1.append(2)
self.assertEqual(l1[0], 1)
self.assertEqual(l1[1], 2)
def test_add_error(self):
# This test works just fine
with self.assertRaises(TypeError):
l1 = ListField("l1", int)
l1.append("1")
def test_overwrite_list(self):
# This test works just fine
l1 = ListField("l1", int)
l1 = []
l1.append(1)
def test_overwrite_error(self):
# This test works just fine
l1 = ListTestClass()
l1.l.append(1)
with self.assertRaises(TypeError):
l1.l = "foo"
def test_multiple_model_instances(self):
# I create 2 more instances of ListTestClass
l1 = ListTestClass()
l2 = ListTestClass()
l1.l.append(1)
l2.l.append(2)
self.assertEqual(l1.l[0], 1)
self.assertEqual(l2.l[0], 2)
The last test fails
Failure
Traceback (most recent call last):
File "/home/user/project/tests/test_fields.py", line 211, in test_multiple_model_instances
self.assertEqual(l1.l[0], 1)
AssertionError: 2 != 1
When I look at the values for l1.1 and l2.l, they both have a list containing [2, 1, 2]
What am I missing here?
I looked to the memory addresses and it seems that the lists all point to the same object.
class ListFieldTest(object):
lf1 = ListField("lf1", int)
class TestClass(object):
def __init__(self):
l1 = ListFieldTest()
l2 = ListFieldTest()
l1.lf1.append(1)
l2.lf1.append(2)
print(l1.lf1)
print(l2.lf1)
print(hex(id(l1)))
print(hex(id(l2)))
print(hex(id(l1.lf1)))
print(hex(id(l2.lf1)))
This prints
[1, 2]
[1, 2]
0x7f987da018d0 --> Address for l1
0x7f987da01910 --> Address for l2
0x7f987d9c4bd8 --> Address for l1.lf1
0x7f987d9c4bd8 --> Address for l2.lf1
ListTestClass.l is a class attribute, so it is shared by all instances of the class. Instead, you should create an instance attribute, eg in the __init__ method:
class ListTestClass(object):
def __init__(self):
self.l = ListField("l", int)
Similar remarks apply to ListFieldTest. There may be other similar problems elsewhere in your code, I haven't examined it closely.
According to this source, the proper form is
class ListTestClass(object):
l_attrib = ListField("l", int)
def __init__(self)
self.l = l_attrib
Thanks to both #PM 2Ring and volcano I found the answer.
In the end this works great for value types:
class IntTestClass(object):
i = IntegerField("i")
However for a reference type (like a list) that won't work and you have to add a new list
class ListTestClass(object):
l = ListField("l", int)
def __init__(self):
self.l = []

Obtain difference between two lists of objects in Python using hash

My objective is to get the difference between two lists containing objects.
I have implemented a class named Branch and overwritten its __eq__ and __ne__ methods as follows:
class Branch(object):
def __str__(self):
return self.name
def __eq__(self, other):
if isinstance(other, Branch):
return (self.valueFrom == other.valueFrom) \
and (self.valueTo == other.valueTo) \
and (self.inService == other.inService)
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return result
return not result
def __init__(self, name, valueFrom, valueTo, inService=True):
self.name = name
self.valueFrom = valueFrom
self.valueTo = valueTo
self.inService = inService
My first attempt was to use the method difference from the set type. However it appears this is not possible as it uses the hash of the object and not the __eq__method as I would like to.
Following code shows the problem:
b1 = Branch("branch1", 1, 2)
b1b = Branch("equal to branch1", 1, 2)
b2 = Branch("branch2", 2, 3)
b3 = Branch("branch3", 3, 1)
b3_off = Branch("branch3 not in service", 3, 1, False)
l1 =[b1,b2,b3]
l2 =[b1b,b2,b3_off]
difference = set(l1).difference(l2)
for branch in difference:
print branch
Output is:
>>>
branch1
branch3
However I wish to get as output only branch3 as b1 and b1b should be treated as equal.
Is it possible to use sets to resolve this? Or should I approach the problem from a different perspective?
You would need to implement hash, what you choose to is up to you but the following would work:
def __hash__(self):
return hash((self.valueFrom , self.valueTo , self.inService))
All you need to implement is hash and eq:
class Branch(object):
def __init__(self, name, valueFrom, valueTo, inService=True):
self.name = name
self.valueFrom = valueFrom
self.valueTo = valueTo
self.inService = inService
def __eq__(self, other):
if isinstance(other, Branch):
return (self.valueFrom,self.valueTo,self.inService )\
==(other.valueFrom, other.valueTo, other.inService)
return NotImplemented
def __str__(self):
return self.name
def __hash__(self):
return hash((self.valueFrom, self.valueTo,self.inService))

How to create a class instance without calling initializer?

Is there any way to avoid calling __init__ on a class while initializing it, such as from a class method?
I am trying to create a case and punctuation insensitive string class in Python used for efficient comparison purposes but am having trouble creating a new instance without calling __init__.
>>> class String:
def __init__(self, string):
self.__string = tuple(string.split())
self.__simple = tuple(self.__simple())
def __simple(self):
letter = lambda s: ''.join(filter(lambda s: 'a' <= s <= 'z', s))
return filter(bool, map(letter, map(str.lower, self.__string)))
def __eq__(self, other):
assert isinstance(other, String)
return self.__simple == other.__simple
def __getitem__(self, key):
assert isinstance(key, slice)
string = String()
string.__string = self.__string[key]
string.__simple = self.__simple[key]
return string
def __iter__(self):
return iter(self.__string)
>>> String('Hello, world!')[1:]
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
String('Hello, world!')[1:]
File "<pyshell#1>", line 17, in __getitem__
string = String()
TypeError: __init__() takes exactly 2 positional arguments (1 given)
>>>
What should I replace string = String(); string.__string = self.__string[key]; string.__simple = self.__simple[key] with to initialize the new object with the slices?
EDIT:
As inspired by the answer written below, the initializer has been edited to quickly check for no arguments.
def __init__(self, string=None):
if string is None:
self.__string = self.__simple = ()
else:
self.__string = tuple(string.split())
self.__simple = tuple(self.__simple())
When feasible, letting __init__ get called (and make the call innocuous by suitable arguments) is preferable. However, should that require too much of a contortion, you do have an alternative, as long as you avoid the disastrous choice of using old-style classes (there is no good reason to use old-style classes in new code, and several good reasons not to)...:
class String(object):
...
bare_s = String.__new__(String)
This idiom is generally used in classmethods which are meant to work as "alternative constructors", so you'll usually see it used in ways such as...:
#classmethod
def makeit(cls):
self = cls.__new__(cls)
# etc etc, then
return self
(this way the classmethod will properly be inherited and generate subclass instances when called on a subclass rather than on the base class).
A trick the standard pickle and copy modules use is to create an empty class, instantiate the object using that, and then assign that instance's __class__ to the "real" class. e.g.
>>> class MyClass(object):
... init = False
... def __init__(self):
... print 'init called!'
... self.init = True
... def hello(self):
... print 'hello world!'
...
>>> class Empty(object):
... pass
...
>>> a = MyClass()
init called!
>>> a.hello()
hello world!
>>> print a.init
True
>>> b = Empty()
>>> b.__class__ = MyClass
>>> b.hello()
hello world!
>>> print b.init
False
But note, this approach is very rarely necessary. Bypassing the __init__ can have some unexpected side effects, especially if you're not familiar with the original class, so make sure you know what you're doing.
Using a metaclass provides a nice solution in this example. The metaclass has limited use but works fine.
>>> class MetaInit(type):
def __call__(cls, *args, **kwargs):
if args or kwargs:
return super().__call__(*args, **kwargs)
return cls.__new__(cls)
>>> class String(metaclass=MetaInit):
def __init__(self, string):
self.__string = tuple(string.split())
self.__simple = tuple(self.__simple())
def __simple(self):
letter = lambda s: ''.join(filter(lambda s: 'a' <= s <= 'z', s))
return filter(bool, map(letter, map(str.lower, self.__string)))
def __eq__(self, other):
assert isinstance(other, String)
return self.__simple == other.__simple
def __getitem__(self, key):
assert isinstance(key, slice)
string = String()
string.__string = self.__string[key]
string.__simple = self.__simple[key]
return string
def __iter__(self):
return iter(self.__string)
>>> String('Hello, world!')[1:]
<__main__.String object at 0x02E78830>
>>> _._String__string, _._String__simple
(('world!',), ('world',))
>>>
Addendum:
After six years, my opinion favors Alex Martelli's answer more than my own approach. With meta-classes still on the mind, the following answer shows how the problem can be solved both with and without them:
#! /usr/bin/env python3
METHOD = 'metaclass'
class NoInitMeta(type):
def new(cls):
return cls.__new__(cls)
class String(metaclass=NoInitMeta if METHOD == 'metaclass' else type):
def __init__(self, value):
self.__value = tuple(value.split())
self.__alpha = tuple(filter(None, (
''.join(c for c in word.casefold() if 'a' <= c <= 'z') for word in
self.__value)))
def __str__(self):
return ' '.join(self.__value)
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return self.__alpha == other.__alpha
if METHOD == 'metaclass':
def __getitem__(self, key):
if not isinstance(key, slice):
raise NotImplementedError
instance = type(self).new()
instance.__value = self.__value[key]
instance.__alpha = self.__alpha[key]
return instance
elif METHOD == 'classmethod':
def __getitem__(self, key):
if not isinstance(key, slice):
raise NotImplementedError
instance = self.new()
instance.__value = self.__value[key]
instance.__alpha = self.__alpha[key]
return instance
#classmethod
def new(cls):
return cls.__new__(cls)
elif METHOD == 'inline':
def __getitem__(self, key):
if not isinstance(key, slice):
raise NotImplementedError
cls = type(self)
instance = cls.__new__(cls)
instance.__value = self.__value[key]
instance.__alpha = self.__alpha[key]
return instance
else:
raise ValueError('METHOD did not have an appropriate value')
def __iter__(self):
return iter(self.__value)
def main():
x = String('Hello, world!')
y = x[1:]
print(y)
if __name__ == '__main__':
main()
Pass another argument to the constructor, like so:
def __init__(self, string, simple = None):
if simple is None:
self.__string = tuple(string.split())
self.__simple = tuple(self.__simple())
else:
self.__string = string
self.__simple = simple
You can then call it like this:
def __getitem__(self, key):
assert isinstance(key, slice)
return String(self.__string[key], self.__simple[key])
Also, I'm not sure it's allowed to name both the field and the method __simple. If only for readability, you should change that.

Categories