<!-- language: lang-py -->
class File:
### Сlass initialization
def __init__(self, path, name, size, date):
self.path = path
self.name = name
self.size = size
self.date = date
def __eq__(self, other):
# if self.name == other.name and self.size == other.size and self.date == other.date:
if self.name == other.name and self.size == other.size:
# if self.size == other.size and self.date == other.date:
return True**
How change (eq) of class during script execution?
def __eq__(self, other):
# if self.name == other.name and self.size == other.size and self.date == other.date:
if self.name == other.name and self.size == other.size:
# if self.size == other.size and self.date == other.date:
return True
Different variants must be triggered when certain conditions occur
Well, this is certainly possible:
class Foo(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
return other.x == self.x
foo1 = Foo(1)
foo2 = Foo(2)
print (foo1 == foo2)
def new_eq(self, other):
return other.x - 1 == self.x
Foo.__eq__ = new_eq
print (foo1 == foo2)
Explanation:
__eq__ is an attribute of the class Foo, and it's a function bound to the class (a class method). You can set the __eq__ attribute to a new function to replace it. Note that because this is modifying the class, all instances see a change, including foo1 and foo2 that are already instantiated.
All that said, this is a pretty sketchy practice, especially for something like __eq__, so I want to say that this is probably not a good solution to your problem, but not knowing what that problem is, I'll just say that if I were to see this sort of thing in code, it would make me rather nervous.
Instead of swapping __eq__ out on the fly, why not use the conditions to determine which case to use when __eq__ is called?
class Foo:
def __eq__(self, other):
if (self._condition_1):
return self._eq_condition_1(other)
elif (self._condition_2):
return self._eq_condition_2(other)
else:
return self._eq_condition_default(other)
def _eq_condition_1(self, other):
return True
def _eq_condition_2(self, other):
return False
def _eq_condition_default(self, other):
return True
Related
A slightly long question to sufficiently explain the background...
Assuming there's a builtin class A:
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
return self.a == other.a
It's expected to compare in this way:
a1, a2 = A(1), A(2)
a1 == a2 # False
For some reason, the team introduced a wrapper on top of it (The code example doesn't actually wrap A to simplify the code complexity.)
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
return self.pa == other.pa
Again, it's expected to compare in this way:
wa1, wa2 = WrapperA(1), WrapperA(2)
wa1 == wa2 # False
Although it's expected to use either A or WrapperA, the problem is some code bases contain both usages, thus following comparison failed:
a, wa = A(), WrapperA()
wa == a # AttributeError
a == wa # AttributeError
A known solution is to modify __eq__:
For wa == a:
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
if isinstance(other, A):
return self.pa == other.a
return self.pa == other.pa
For a == wa:
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
if isinstance(other, WrapperA):
return self.a == other.pa
return self.a == other.a
Modifying WrapperA is expected. For A, since it is a builtin thing, two solutions are:
Use setattr to extend A to support WrapperA.
setattr(A, '__eq__', eq_that_supports_WrapperA)
Enforce developer to only compare wa == a (And then don't care about a == wa).
1st option is obviously ugly with duplicated implementation, and 2nd gives developer unnecessary "surprise". So my question is, is there an elegant way to replace any usage of a == wa to wa == a by the Python implementation internally?
Quoting the comment from MisterMiyagi under the question:
Note that == is generally expected to work across all types. A.__eq__ requiring other to be an A is actually a bug that should be fixed. It should at the very least return NotImplemented when it cannot make a decision
This is important, not just a question of style. In fact, according to the documentation:
When a binary (or in-place) method returns NotImplemented the interpreter will try the reflected operation on the other type.
Thus if you just apply MisterMiyagi's comment and fix the logic of __eq__, you'll see your code works fine already:
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
if isinstance(other, A):
return self.a == other.a
return NotImplemented
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
if isinstance(other, A):
return self.pa == other.a
elif isinstance(other, WrapperA):
return self.pa == other.pa
return NotImplemented
# Trying it
a = A(5)
wrap_a = WrapperA(5)
print(a == wrap_a)
print(wrap_a == a)
wrap_a.pa = 7
print(a == wrap_a)
print(wrap_a == a)
print(f'{wrap_a.pa=}')
Yields:
True
True
False
False
wrap_a.pa=7
Under the hood, a == wrap_a calls A.__eq__ first, which returns NotImplemented. Python then automatically tries WrapperA.__eq__ instead.
I dont really like this whole thing, since I think that wrapping a builtin and using different attribute names will lead to unexpected stuff, but anyway, this will work for you
import inspect
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
return self.a == other.a
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
if isinstance(other, A):
return self.pa == other.a
return self.pa == other.pa
def __getattribute__(self, item):
# Figure out who tried to get the attribute
# If the item requested was 'a', check if A's __eq__ method called us,
# in that case return pa instead
caller = inspect.stack()[1]
if item == 'a' and getattr(caller, 'function') == '__eq__' and isinstance(caller.frame.f_locals.get('self'), A):
return super(WrapperA, self).__getattribute__('pa')
return super(WrapperA, self).__getattribute__(item)
a = A(5)
wrap_a = WrapperA(5)
print(a == wrap_a)
print(wrap_a == a)
wrap_a.pa = 7
print(a == wrap_a)
print(wrap_a == a)
print(f'{wrap_a.pa=}')
Output:
True
True
False
False
wrap_a.pa=7
Similar to Ron Serruyas answer:
This uses __getattr__ instead of __getattribute__, where the first one is only called if the second one raises an AttributeError or explicitly calls it (ref). This means if the wrapper does not implement __eq__ and the equality should only be performed on the underlying data structure (stored in objects of class A), a working example is given by:
class A(object):
def __init__(self, internal_data=None):
self._internal_data = internal_data
def __eq__(self, other):
return self._internal_data == other._internal_data
class WrapperA(object):
def __init__(self, a_object: A):
self._a = a_object
def __getattr__(self, attribute):
if attribute != '_a': # This is neccessary to prevent recursive calls
return getattr(self._a, attribute)
a1 = A(internal_data=1)
a2 = A(internal_data=2)
wa1 = WrapperA(a1)
wa2 = WrapperA(a2)
print(
a1 == a1,
a1 == a2,
wa1 == wa1,
a1 == wa1,
a2 == wa2,
wa1 == a1)
>>> True False True True True True
I need transaction and other_transaction to be equal. Can anyone see what I'm missing?
def test_eq_LedgerTransaction():
> assert LedgerTransaction.__eq__(transaction, equal_transaction) is True
E assert False is True
E + where False = <function LedgerTransaction.__eq__ at 0x7f49c3183550>(transaction, equal_transaction)
E + where <function LedgerTransaction.__eq__ at 0x7f49c3183550> = LedgerTransaction.__eq__
Code
class LedgerTransaction():
def __init__(self, date: str, payee: str, amount: float):
self.date = date
self.payee = payee
self.amount = float(amount)
def __eq__(self, other):
return self == other
def __repr__(self):
return f'Transaction:{self.date, self.payee, self.amount}'
def __hash__(self):
return hash((self.date, self.payee, self.amount))
# Tests
import pytest
#pytest.fixture
def transaction():
return LedgerTransaction("2021/12/31", "transaction", 5.0)
#pytest.fixture
def equal_transaction():
return LedgerTransaction("2021/12/31", "transaction", 5.0)
#pytest.fixture
def other_transaction():
return LedgerTransaction("2021/12/31", "peach", 5.0)
def test_eq_LedgerTransaction():
assert LedgerTransaction.__eq__(transaction, equal_transaction) is True
assert LedgerTransaction.__eq__(transaction, other_transaction) is False
You need to compare each field, so like the equal should be something like:
def __eq__(self, other):
return self.date == other.date and self.payee == other.payee and self.amount == other.amount
Right now you're just comparing the references to the objects, so like their addresses in memory rather than the actual contents of the objects.
Annotate LedgerTransaction with #dataclass and let the interpreter generate the methods for you.
I am unsure how to determine that classes that inherit from other classes are not equal.
I have tried using isinstance to do this but I am not very well versed in this method.
class FarmAnimal:
def __init__(self, age):
self.age = age
def __str__(self):
return "{} ({})".format(self, self.age)
from src.farm_animal import FarmAnimal
class WingedAnimal(FarmAnimal):
def __init__(self, age):
FarmAnimal.__init__(self, age)
def make_sound(self):
return "flap, flap"
from src.winged_animal import WingedAnimal
class Chicken(WingedAnimal):
def __init__(self, age):
WingedAnimal.__init__(self, age)
def __eq__(self, other):
if self.age == other.age:
return True
else:
return False
def make_sound(self):
return WingedAnimal.make_sound(self) + " - cluck, cluck"
from src.chicken import Chicken
from src.winged_animal import WingedAnimal
class Duck(WingedAnimal):
def __init__(self, age):
WingedAnimal.__init__(self, age)
def make_sound(self):
return WingedAnimal.make_sound(self) + " - quack, quack"
def __eq__(self, other):
if not isinstance(other, Duck):
return NotImplemented
return self.age == other.age
if __name__ == "__main__":
print(Chicken(2.1) == Duck(2.1))
So in the main method it says to print(Chicken(2.1) == Duck(2.1)) and it prints True, but because they are different classes, I want it to return False. Any help would be greatly appreciated.
You can define your __eq__ method in FarmAnimal, checking if the class of self is the same as the class of other as well:
def __eq__(self, other):
if self.age == other.age and self.__class__ == other.__class__
return True
else:
return False
and you don't have to write specific __eq__ methods in your subclasses.
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)))))
I'd like to implement an object, that bounds values within a given range after arithmetic operations have been applied to it. The code below works fine, but I'm pointlessly rewriting the methods. Surely there's a more elegant way of doing this. Is a metaclass the way to go?
def check_range(_operator):
def decorator1(instance,_val):
value = _operator(instance,_val)
if value > instance._upperbound:
value = instance._upperbound
if value < instance._lowerbound:
value = instance._lowerbound
instance.value = value
return Range(value, instance._lowerbound, instance._upperbound)
return decorator1
class Range(object):
'''
however you add, multiply or divide, it will always stay within boundaries
'''
def __init__(self, value, lowerbound, upperbound):
'''
#param lowerbound:
#param upperbound:
'''
self._lowerbound = lowerbound
self._upperbound = upperbound
self.value = value
def init(self):
'''
set a random value within bounds
'''
self.value = random.uniform(self._lowerbound, self._upperbound)
def __str__(self):
return self.__repr__()
def __repr__(self):
return "<Range: %s>" % (self.value)
#check_range
def __mul__(self, other):
return self.value * other
#check_range
def __div__(self, other):
return self.value / float(other)
def __truediv__(self, other):
return self.div(other)
#check_range
def __add__(self, other):
return self.value + other
#check_range
def __sub__(self, other):
return self.value - other
It is possible to use a metaclass to apply a decorator to a set of function names, but I don't think that this is the way to go in your case. Applying the decorator in the class body on a function-by-function basis as you've done, with the #decorator syntax, I think is a very good option. (I think you've got a bug in your decorator, BTW: you probably do not want to set instance.value to anything; arithmetic operators usually don't mutate their operands).
Another approach I might use in your situation, kind of avoiding decorators all together, is to do something like this:
import operator
class Range(object):
def __init__(self, value, lowerbound, upperbound):
self._lowerbound = lowerbound
self._upperbound = upperbound
self.value = value
def __repr__(self):
return "<Range: %s>" % (self.value)
def _from_value(self, val):
val = max(min(val, self._upperbound), self._lowerbound)
# NOTE: it's nice to use type(self) instead of writing the class
# name explicitly; it then continues to work if you change the
# class name, or use a subclass
return type(self)(val, rng._lowerbound, rng._upperbound)
def _make_binary_method(fn):
# this is NOT a method, just a helper function that is used
# while the class body is being evaluated
def bin_op(self, other):
return self._from_value(fn(self.value, other))
return bin_op
__mul__ = _make_binary_method(operator.mul)
__div__ = _make_binary_method(operator.truediv)
__truediv__ = __div__
__add__ = _make_binary_method(operator.add)
__sub__ = _make_binary_method(operator.sub)
rng = Range(7, 0, 10)
print rng + 5
print rng * 50
print rng - 10
print rng / 100
printing
<Range: 10>
<Range: 10>
<Range: 0>
<Range: 0.07>
I suggest that you do NOT use a metaclass in this circumstance, but here is one way you could. Metaclasses are a useful tool, and if you're interested, it's nice to understand how to use them for when you really need them.
def check_range(fn):
def wrapper(self, other):
value = fn(self, other)
value = max(min(value, self._upperbound), self._lowerbound)
return type(self)(value, self._lowerbound, self._upperbound)
return wrapper
class ApplyDecoratorsType(type):
def __init__(cls, name, bases, attrs):
for decorator, names in attrs.get('_auto_decorate', ()):
for name in names:
fn = attrs.get(name, None)
if fn is not None:
setattr(cls, name, decorator(fn))
class Range(object):
__metaclass__ = ApplyDecoratorsType
_auto_decorate = (
(check_range,
'__mul__ __div__ __truediv__ __add__ __sub__'.split()),
)
def __init__(self, value, lowerbound, upperbound):
self._lowerbound = lowerbound
self._upperbound = upperbound
self.value = value
def __repr__(self):
return "<Range: %s>" % (self.value)
def __mul__(self, other):
return self.value * other
def __div__(self, other):
return self.value / float(other)
def __truediv__(self, other):
return self / other
def __add__(self, other):
return self.value + other
def __sub__(self, other):
return self.value - other
As it is wisely said about metaclasses: if you wonder wether you need them, then you don't.
I don't fully understand your problem, but I would create a BoundedValue class, and us only instances of said class into the class you are proposing.
class BoundedValue(object):
default_lower = 0
default_upper = 1
def __init__(self, upper=None, lower=None):
self.upper = upper or BoundedValue.default_upper
self.lower = lower or BoundedValue.default_lower
#property
def val(self):
return self._val
#val.setter
def val(self, value):
assert self.lower <= value <= self.upper
self._val = value
v = BoundedValue()
v.val = 0.5 # Correctly assigns the value 0.5
print v.val # prints 0.5
v.val = 10 # Throws assertion error
Of course you could (and should) change the assertion for the actual behavior you are looking for; also you can change the constructor to include the initialization value. I chose to make it an assignment post-construction via the property val.
Once you have this object, you can create your classes and use BoundedValue instances, instead of floats or ints.