【问题标题】:Check if two objects are comparable to each other, without relying on raised errors检查两个对象是否可以相互比较,而不依赖于引发的错误
【发布时间】:2019-08-29 20:05:28
【问题描述】:

“可比较”的意思是“能够相互执行比较操作><>=<===!= 而不会引发!=”。该属性确实适用于许多不同的类:

1 < 2.5  # int and float
2 < decimal.Decimal(4)  # int and Decimal
"alice" < "bob"  # str and str
(1, 2) < (3, 4)  # tuple and tuple

但它没有:

1 < "2"  # int and str
1.5 < "2.5"  # float and str

即使看起来确实应该这样做:

datetime.date(2018, 9, 25) < datetime.datetime(2019, 1, 31)  # date and datetime 
[1, 2] < (3, 4)  # list and tuple

As demonstrated in this similar question,您显然可以通过使用“请求宽恕,而不是许可”的传统 python 方法并使用 try/except 块来检查两个未知类型的对象 ab

try: 
    a < b
    # do something
except TypeError:
    # do something else

但是catching exceptions is expensive,我希望第二个分支足够频繁地被采用,所以我想在if/else 语句中抓住这一点。我该怎么做?

【问题讨论】:

  • 简短的回答是这是不可能的,因为解释器永远不会知道比较方法是否会抛出NotImplemented 异常,直到该方法实际执行。底层机制的详细信息,请参考链接问题的答案。
  • 如您的链接所示,异常处理非常便宜。它实际上不是免费的,但它很便宜,而且比您在此处避免异常处理所产生的所有开销要便宜得多。

标签: python comparison-operators


【解决方案1】:

由于在您实际执行此类操作之前,无法事先知道是否可以对两种特定类型的操作数执行比较操作,因此您可以做的最接近的事情是实现避免捕获@987654321 的期望行为@是缓存已知的运算符组合以及之前已经引起TypeError的左右操作数的类型。您可以通过创建一个具有此类缓存和包装方法的类来执行此操作,这些方法在继续比较之前进行此类验证:

from operator import gt, lt, ge, le

def validate_operation(op):
    def wrapper(cls, a, b):
        # the signature can also be just (type(a), type(b)) if you don't care about op
        signature = op, type(a), type(b)
        if signature not in cls.incomparables:
            try:
                return op(a, b)
            except TypeError:
                cls.incomparables.add(signature)
        else:
            print('Exception avoided for {}'.format(signature)) # for debug only
    return wrapper

class compare:
    incomparables = set()

for op in gt, lt, ge, le:
    setattr(compare, op.__name__, classmethod(validate_operation(op)))

这样:

import datetime
print(compare.gt(1, 2.0))
print(compare.gt(1, "a"))
print(compare.gt(2, 'b'))
print(compare.lt(datetime.date(2018, 9, 25), datetime.datetime(2019, 1, 31)))
print(compare.lt(datetime.date(2019, 9, 25), datetime.datetime(2020, 1, 31)))

会输出:

False
None
Exception avoided for (<built-in function gt>, <class 'int'>, <class 'str'>)
None
None
Exception avoided for (<built-in function lt>, <class 'datetime.date'>, <class 'datetime.datetime'>)
None

这样您就可以使用if 语句而不是异常处理程序来验证比较:

result = compare.gt(obj1, obj2)
if result is None:
    # handle the fact that we cannot perform the > operation on obj1 and obj2
elsif result:
    # obj1 is greater than obj2
else:
    # obj1 is not greater than obj2

这里有一些时间统计:

from timeit import timeit
print(timeit('''try:
    1 > 1
except TypeError:
    pass''', globals=globals()))
print(timeit('''try:
    1 > "a"
except TypeError:
    pass''', globals=globals()))
print(timeit('compare.gt(1, "a")', globals=globals()))

这个输出,在我的机器上:

0.047088712933431365
0.7171912713398885
0.46406612257995117

如您所见,当比较引发异常时,缓存的比较验证确实为您节省了大约 1/3 的时间,但在不引发异常时会慢大约 10 倍,因此这种缓存机制只有在您预期时才有意义你的绝大多数比较都会抛出异常。

【讨论】:

    【解决方案2】:

    您可以做的是在比较之前使用isinstance,并自己处理异常。

    if(isinstance(date_1,datetime) != isinstance(date_2,datetime)):
    #deal with the exception
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-27
      • 1970-01-01
      • 2021-09-19
      • 2016-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-22
      相关资源
      最近更新 更多