Una pregunta un poco larga para explicar suficientemente el trasfondo...
Suponiendo que hay una class A incorporada:
class A: def __init__(self, a=None): self.a = a def __eq__(self, other): return self.a == other.aSe espera comparar de esta manera:
a1, a2 = A(1), A(2) a1 == a2 # FalsePor alguna razón, el equipo introdujo un envoltorio encima ( el código de ejemplo en realidad no envuelve A para simplificar la complejidad del código).
class WrapperA: def __init__(self, a=None): self.pa = a def __eq__(self, other): return self.pa == other.paNuevamente, se espera comparar de esta manera:
wa1, wa2 = WrapperA(1), WrapperA(2) wa1 == wa2 # False Aunque se espera que use A o WrapperA , el problema es que algunas bases de código contienen ambos usos, por lo que la siguiente comparación falló:
a, wa = A(), WrapperA() wa == a # AttributeError a == wa # AttributeError Una solución conocida es modificar __eq__ :
Para 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 Para 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.aSe espera modificar WrapperA. Para A, dado que es una cosa incorporada, dos soluciones son:
setattr(A, '__eq__', eq_that_supports_WrapperA)wa == a (y luego no se preocupe por a == wa ). La primera opción es obviamente fea con la implementación duplicada, y la segunda le da al desarrollador una "sorpresa" innecesaria. Así que mi pregunta es, ¿hay alguna forma elegante de reemplazar cualquier uso de a == wa to wa == a por la implementación de Python internamente?
Realmente no me gusta todo esto, ya que creo que envolver una función integrada y usar diferentes nombres de atributos conducirá a cosas inesperadas, pero de todos modos, esto funcionará para ti.
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=}')Producción:
True True False False wrap_a.pa=7Citando el comentario de MisterMiyagi bajo la pregunta:
Tenga en cuenta que generalmente se espera que == funcione en todos los tipos.
A.__eq__requerir que otro sea un A es en realidad un error que debe corregirse. Como mínimo, debería devolver NotImplemented cuando no puede tomar una decisión.
Esto es importante, no solo una cuestión de estilo. De hecho, segúnla documentación :
Cuando un método binario (o in situ) devuelve
NotImplementedel intérprete intentará la operación reflejada en el otro tipo.
Por lo tanto, si solo aplica el comentario de MisterMiyagi y corrige la lógica de __eq__ , verá que su código ya funciona bien:
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=}')Rendimientos:
True True False False wrap_a.pa=7 Bajo el capó, a == wrap_a llama a A.__eq__ primero, que devuelve NotImplemented . Python luego prueba automáticamente WrapperA.__eq__ en su lugar.
Similar a la respuesta de Ron Serruya:
Esto usa __getattr__ en lugar de __getattribute__ , donde el primero solo se llama si el segundo genera un AttributeError o lo llama explícitamente (ref) . Esto significa que si el envoltorio no implementa __eq__ y la igualdad solo debe realizarse en la estructura de datos subyacente (almacenada en objetos de clase A ), un ejemplo de trabajo es el siguiente:
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