In Python, how to know whether objects can be compared?

本秂侑毒 提交于 2019-12-14 01:21:38

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!