【问题标题】:Usage of python function comparison dunderspython函数比较dunders的使用
【发布时间】:2020-09-09 18:13:03
【问题描述】:

问题

Python 函数有比较漏洞(见下面的打印输出)。但他们是NotImplemented。很公平。但是它们的预期用途是什么,如何使用它们?当我将一个可调用对象分配给func.__gt__ 时,我没有看到它在我执行func < other_func 时被调用。

示例代码

我可以看到使用 (foo > bar) 是一个等效于 lambda x: foo(x) > bar(x) 的函数,但同样(并且可以说更有用),它可以用于构造管道。

例如,我们可以有

def composeable(func):
    func.__gt__ = lambda g: lambda x: g(f(x))
    func.__lt__ = lambda g: lambda x: f(g(x))
    return func

可以用作

>>> def composeable(func):
...     func.__gt__ = lambda g: lambda x: g(f(x))
...     func.__lt__ = lambda g: lambda x: f(g(x))
...     return func
...
>>> @composeable
... def f(x):
...     return x + 2
...
>>> def g(x):
...     return x * 10
...
>>> h = f.__gt__(g)
>>> assert h(3) == 50  # (3 + 2) * 10
>>> h = f.__lt__(g)
>>> assert h(3) == 32  # (3 * 10) + 2

然而,越来越好奇,这行不通:

>>> h = f > g
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'function' and 'function'

注意:可调用的 NotImplemented 函数 dunders。

__eq__: NotImplemented
__ge__: NotImplemented
__gt__: NotImplemented
__le__: NotImplemented
__lt__: NotImplemented
__ne__: NotImplemented

生成上述打印输出的代码:

from inspect import signature

def f(x): ...

for aname in dir(f):
    attr = getattr(f, aname)
    if callable(attr):
        try:
            x = attr(*len(signature(attr).parameters) * [''])
            if x is NotImplemented:
                print(f"{aname}: NotImplemented")
        except Exception as e:
            pass

【问题讨论】:

  • 在类上查找 Dunder 方法,而不是在类的实例上。
  • 是的。 Sill,如何使用它们,以及如何应该使用它们。 (我会有点犹豫是否通过 f.__class__.__lt__ = ... 更改 FunctionType(如果允许的话!))。
  • 很抱歉,我没有得到您想要获得的内容。 > 或 另一个函数是什么意思?
  • @Anwarvic 他试图使用f &lt; gg &gt; f 来表示函数组合:(f &lt; g)(x) == f(g(x))
  • 另外,请注意f &gt; g &gt; h 不会按照您想要的方式工作,因为比较运算符在涉及底层 dunder 方法之前被专门解析。

标签: python python-3.x python-datamodel


【解决方案1】:

您可以做的最简单的事情是定义一个支持组合的包装器。由于比较运算符的处理,我建议使用&gt;&gt;&lt;&lt; 进行组合。

# Assumption: the wrapped callable take a single argument
class ComposableCallable:
    def __init__(self, func):
        self.func = func

    def __lshift__(self, other):
        @ComposableCallable
        def _(x):
            return self.func(other.func(x))

        return _

    def __rshift__(self, other):
        @ComposableCallable
        def _(x):
            return other.func(self.func(x))

        return _

    def __call__(self, x):
        return self.func(x)


@ComposableCallable
def f(x):
    return x + 1

@ComposableCallable
def g(x):
    return 2 * x


assert (f << g)(3) == 7  # 2*3 + 1 == 7
assert (f >> g)(3) == 8  # (3 + 1) * 2 == 8

【讨论】:

  • 我 +1 代码,因为它是使操作员实现组合的优雅解决方案。但作文只是一个例子,而不是问题(事实上,让我想到这个的实际问题是不同的,但作文更容易解释)。问题是(1)它们的预期用途是什么(如果有的话),以及(2)人们如何使用它们(给定(简单地说)dunders 与类一起工作,你不能子类化函数)。如果一段时间后我的实际问题没有得到任何答案,我会接受你的答案,以奖励不只是丢弃问题的努力。
  • 我不确定我是否理解您的问题。 __gt____lt__ 以便您可以自定义新类的实例如何相互比较。这里的问题只是你不能修改现有的内置类。
  • 不知道怎么说。也许“如果可能的话,如何/应该如何使用函数的比较函数”。在这一点上,答案很可能是你不能使用它们,它们只是object的被禁用的残留特性。显然,我们可以委托一个函数来使用这些 dunder,但是当基类已经包含这些时,必须这样做似乎很奇怪。
【解决方案2】:

然而,越来越好奇,这行不通:

这与 dunder 方法的实现无关,与您尝试修改内置函数类型的现有行为有关。您将无法修改基础类,并且忽略为实例分配这些属性。

如何使用它们,以及应该如何使用它们。

您将它们用于您自己的类,在这些类中实例之间存在自然比较。例如,标准库fractions.Fraction 类支持这些比较;并且在Python中实现,需要使用这些方法。

它可以用来构建管道。

啊啊啊啊。

这非常违背 Python 的禅宗。它还没有那样工作是有原因的。恕我直言,Numpy 对运营商所做的事情已经在推动它,并且由于它们非常有用,因此大多被容忍。 (另外,正如 cmets 中所指出的,Python 对链式关系运算符的特殊处理将使其无法按照您希望的方式处理非平凡的情况。)

【讨论】:

  • 管道没有什么特别的问题。函数组合将是一个很棒的特性,但主要的反对意见是 Python 函数过于笼统,无法支持单一的组合概念。
  • 我反对的主要是这样拼写组合操作。
  • 再一次,我的问题的重点是不是关于管道的。这只是一个例子。但我绝对对 Aaaaaaaugh 的反应以及许多 pythonistas 将其视为法律而不是作为反思的美丽邀请的倾向感到好笑。我的反思使我得出的结论是,对于 我的 上下文,这些注意事项和注意事项更多的是让代码镜像思维尽可能简单和优雅。
猜你喜欢
  • 2010-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多