有没有办法 return 在 Python 中自定义最小值和最大值?

Is there a way to return a custom value for min and max in Python?

我有一个自定义 class,

class A:
    def __init__(self, a, b):
        self.a = a
        self.b = b

class 不可迭代或可索引或类似的东西。如果可能的话,我想保持这种状态。有没有可能有类似下面的作品?

>>> x = A(1, 2)
>>> min(x)
1
>>> max(x)
2

让我想到这一点的是 min and max are listed as "Common Sequence Operations" in the docs. Since range 被相同的文档认为是序列类型,我在想一定有某种可能的优化 range,也许我可以利用它。

也许有一种我不知道的神奇方法可以实现这一点?

是的。当 min 接受一个参数时,它假定它是一个可迭代对象,对其进行迭代并取最小值。所以,

class A:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __iter__(self):
        yield self.a
        yield self.b

应该可以。

附加说明:如果您不想使用 __iter__,我不知道该怎么做。您可能想创建自己的 min 函数,如果传递给它的参数中有一个 _min_ 方法,则调用它并调用旧的 min else.

oldmin = min
def min(*args):
    if len(args) == 1 and hasattr(args[0], '_min_'):
        return args[0]._min_()
    else:
        return oldmin(*args)

Since range is considered to be a sequence type by the very same docs, I was thinking that there must be some sort of optimization that is possible for range, and that perhaps I could take advantage of it.

范围没有进行优化,min/max 也没有专门的魔术方法。

如果你偷看the implementation for min/max you'll see that after some argument parsing is done, a call to iter(obj)(即obj.__iter__())是为了抓取一个迭代器:

it = PyObject_GetIter(v);
if (it == NULL) {
    return NULL;
}

然后calls to next(it)(即it.__next__)在循环中执行以获取用于比较的值:

while (( item = PyIter_Next(it) )) {
    /* Find min/max  */

Is it possible to have something like the following work?

不,如果您想使用内置的 min*,您唯一的选择就是实现迭代器协议。


*通过修补 min,你当然可以让它做任何你想做的事。显然是以在 Pythonland 中运行为代价的。但是,如果您认为可以利用一些优化,我建议您创建一个 min 方法,而不是重新定义内置的 min.

此外,如果您只有整数作为实例变量并且您不介意不同的调用,您总是可以使用 vars 获取 instance.__dict__ 然后提供它的 .values()min:

>>> x = A(20, 4)
>>> min(vars(x).values())
4

没有 __min____max__ 特殊方法*。这有点遗憾,因为 range 看过一些 。你可以这样做:

>>> 1000000000000 in range(1000000000000)
False

但是除非你想等很长时间,否则不要尝试这个:

>>> max(range(1000000000000))

然而,正如 所建议的那样,创建您自己的 min/max 函数是一个不错的主意。

这是我的做法。更新:根据 PEP 8:

的建议,删除了名称 __min__ 以支持 _min

Never invent such names; only use them as documented

代码:

from functools import wraps

oldmin = min

@wraps(oldmin)
def min(*args, **kwargs)
    try:
        v = oldmin(*args, **kwargs)
    except Exception as err:
        err = err
    try:
        arg, = args
        v = arg._min()
    except (AttributeError, ValueError):
        raise err
    try:
        return v
    except NameError:
        raise ValueError('Something weird happened.')

我认为这种方式可能更好一些,因为它处理了其他答案未考虑的一些极端情况。

请注意,具有 _min 方法的可迭代对象仍将像往常一样被 oldmin 使用,但 return 值会被特殊方法覆盖。

但是,如果 _min 方法要求迭代器仍可用于使用,则需要对此进行调整,因为迭代器首先被 oldmin 使用。

还要注意,如果 __min 方法只是通过调用 oldmin 来实现,事情仍然会正常工作(即使迭代器被消耗;这是因为 oldmin 引发了一个ValueError 在这种情况下)。

* 此类方法通常称为 "magic",但这不是首选术语。