【问题标题】:Overload Python 'in' to return non-bool重载 Python 'in' 以返回非布尔值
【发布时间】:2013-09-12 00:30:45
【问题描述】:

我正在尝试为类重载 in 运算符以返回非 bool 对象,但它似乎无论如何都会强制转换。这是我的用例:

class Dataset(object):
  def __init__(self):
    self._filters = []

  def filter(self, f):
    self._filters.append(f)
    return self

class EqualFilter(object):
  def __init__(self, field, val):
    ...

class SubsetFilter(object):
  def __init__(self, field, vals):
    ...

class FilterBuilder(object):
  def __init__(self, field):
    self._field = field

  def __eq__(self, val):
    return EqualFilter(self._field, val)

  def __contains__(self, vals):
    return SubsetFilter(self._field, vals)


veggie = FilterBuilder('veggie')
fruit = FilterBuilder('fruit')
ds = Dataset().filter(veggie == 'carrot').filter(fruit in ['apple', 'orange'])

在代码的末尾,ds 包含一个 EqualFilter 代表 veggie == 'carrot'True 代表 fruit in ['apple', 'orange']。有没有办法让 ds 以 SubsetFilter 结尾?

【问题讨论】:

    标签: python operator-overloading


    【解决方案1】:

    这里有两个问题。首先,in 总是将__contains__ 的结果转换为bool,所以你正在寻找的东西是不可能的。第二个问题是

    fruit in ['apple', 'orange']
    

    通话

    ['apple', 'orange'].__contains__(fruit)
    

    in 的左操作数无法覆盖运算符,因此这也会破坏您尝试做的事情。

    【讨论】:

    • 向 FilterBuilder.__contains__ 添加打印语句确实会导致打印,所以我相当肯定 FilterBuilder.__contains__ 被调用而不是 list.__contains__
    • @user108088:来自FilterBuilder.__eq__ 的某些代码路径可能正在调用FilterBuilder.__contains__。当我实现一个class Printer(object)__contains__ 方法中有一个print 时,4 in Printer() 打印,但Printer() in [1,2,3] 不打印。
    • @abarnert: 回复目标错误?
    • @user2357112:对不起,是的。
    • 顺便说一句,据我所知,不能保证in 会将结果转换为bool。所有相关文档都只是说,如果__contains__ 返回一个真值,它将是一个真值。它可以通过返回一个布尔值来做到这一点,但它也可以返回值本身。 CPython 返回一个布尔值,正如您在给定 PySequence_Contains 时所期望的那样,至少 Jython 和 PyPy 也是如此,但我认为它仍然依赖于实现。
    【解决方案2】:

    这里真正的问题是,正如the documentation 所说:

    对于定义__contains__() 方法的用户定义类,当且仅当y.__contains__(x) 为真时,x in y 为真。

    另见herehere

    user2357112 的回答比我能更好地解释这一点。你打电话给list.__contains__,而不是FilterBuilder.__contains__


    但为什么会这样呢?

    好吧,它还能如何工作?

    假设3 in [1, 2, 3] 调用int.__contains__3 in {1, 2, 3}3 in my_custom_sorted_bintree 也是如此。 int.__contains__(container) 怎么可能实现?当然不是通过迭代容器。这将意味着在集合和 bintrees 中查找内容的缓慢而详尽的搜索,这将破坏整个观点。我的 bintree 类甚至可能不是可迭代的,但可能仍然有成员的概念。

    但是如果他们调用list.__contains__set.__contains__CustomSortedBintree.__contains__ 怎么办?他们不需要了解 int、str 以及您可以提供给他们的所有其他可能的东西吗?一点都不。列表只需要知道如何比较 arg == elem 的每个元素。一个集合还需要知道如何调用hash(arg)。 bintree 还必须知道如何调用arg < elem。但是你不需要知道任何关于 arg 的类型的信息。


    您可能想知道如何处理这个问题。常见的解决方案有两种半。

    1:您可以轻松创建FilterList 类。然后,您只需编写:

    fruit in FilterList('apple', 'orange')
    

    1.5:或者,通过更多的工作,您可以构建一个更通用的“价值持有者”:

    fruit in const(['apple', 'orange'])
    

    2:或者,您可以编写FilterBuilder.in_ 方法。然后你写:

    fruit.in_(['apple', 'orange'])
    

    ……或者,如果您愿意:

    fruit.in_('apple', 'orange')
    

    我见过的大多数库要么提供第二个(sqlalchemy),要么同时提供,但在他们的教程中使用第二个(appscript),尽管“quick-lambda”库通常与第一个的通用版本一起使用。

    但您应该为自己的用例考虑权衡取舍。一般来说,第一个更容易实现,更明确,并且具有子过滤器/子查询可以返回充当FilterList的东西的优点;第二个不那么冗长,可以说更容易阅读。

    如果两者都不可接受,您可以考虑为类似 Python 的 DSL 编写解析器,而不是尝试通过表达式模板从实际 Python 代码构建 DSL。或者使用 MacroPy 之类的东西(我认为它甚至有一个类似于您正在寻找的示例,以及不需要“const”和朋友的快速 lambda 宏)。

    【讨论】:

    • @sharth:感谢您的修复。
    【解决方案3】:

    没有。 list.__contains__总是返回一个bool,C 类型不能被猴子补丁(你也不应该考虑这样做,因为你可能会破坏其他代码)。

    【讨论】:

      猜你喜欢
      • 2019-06-14
      • 2013-08-18
      • 2017-05-03
      • 2019-04-15
      • 2011-12-14
      • 2020-07-29
      • 2014-12-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多