问题
With abstract base classes, Python provides a way to know the behavior of objects without actually trying it out. In the standard library, we have some ABCs defined for containers in collections.abc. For example, one can test that an argument is iterable:
from collections.abc import Iterable
def function(argument):
if not isinstance(argument, Iterable):
raise TypeError('argument must be iterable.')
# do stuff with the argument
I was hoping that there would be one such ABC for deciding whether instances of a class can be compared but couldn't find one. Testing for the existence of __lt__
methods is not sufficient. For example, dictionaries cannot be compared but __lt__
is still defined (same with object
actually).
>>> d1, d2 = {}, {}
>>> d1 < d2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: dict() < dict()
>>> hasattr(d1, '__lt__')
True
So my question is: is there a simple way to do it without doing the comparisons themselves and catching the TypeError
?
My use case is similar to a sorted container: I'd like to raise an exception when I insert the first element and not to wait for a second element. I thought about comparing the element with itself, but is there a better approach:
def insert(self, element):
try:
element < element
except TypeError as e:
raise TypeError('element must be comparable.')
# do stuff
回答1:
No, there is no such ABC, because an ABC only dictates what attributes are there. ABCs cannot test for the nature of the implementation (or even if those attributes are actually methods).
The presence of comparison methods (__lt__
, __gt__
, __le__
, __ge__
and __eq__
) does not dictate that the class is going to be comparable with everything else. Usually you can only compare objects of the same type or class of types; numbers with numbers for example.
As such, most types* implement the comparison methods but return the NotImplemented
sentinel object when comparing with other incompatible types. Returning NotImplemented
signals to Python to give the right-hand value a say in the matter too. If a.__lt__(b)
returns NotImplemented
then b.__gt__(a)
is tested too.
The base object
provides default implementations for the methods, returning NotImplemented
:
>>> class Foo:
... pass
...
>>> Foo() < Foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Foo() < Foo()
>>> Foo().__lt__
<method-wrapper '__lt__' of Foo object at 0x10f1cf860>
>>> Foo().__lt__(Foo())
NotImplemented
which is exactly what dict.__lt__
does:
>>> {}.__lt__({})
NotImplemented
Numbers, however, only return NotImplemented
when the other type is not comparable:
>>> (1).__lt__(2)
True
>>> (1).__lt__('2')
NotImplemented
>>> 1 < 2
True
>>> 1 < '2'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()
As such, your best choice is to simply catch the TypeError
thrown when values are not comparable.
* I am not aware of any types in the Python 3 standard library that do not implement the comparison methods at this time.
来源:https://stackoverflow.com/questions/29457135/in-python-how-to-know-whether-objects-can-be-compared