有没有办法在Python中返回min和max的自定义值?

时间:2017-07-25 15:54:31

标签: python python-3.x magic-methods

我有一个自定义类,

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

该类不可迭代或可索引或类似的东西。如果可能的话,我想保持这种方式。是否可以进行以下工作?

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

让我思考的是minmax被列为" Common Sequence Operations"在docs。由于range被同一个文档视为序列类型,我认为range必须进行某种优化,也许我可以利用它。

也许有一种神奇的方法,我不知道会启用这个吗?

3 个答案:

答案 0 :(得分:21)

是。当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

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

答案 1 :(得分:6)

  

由于range被认为是同一个文档的序列类型,我认为必须对range进行某种优化,也许我可以利用它的。

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

如果您查看the implementation for min/max,您会看到在完成一些参数解析之后,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  */
  

是否可以进行以下工作?

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

*通过修补min,你当然可以做任何你想做的事情。显然以Pythonland的运营成本为代价。但是,如果您认为可以使用某些优化,我建议您创建一个min方法,而不是重新定义内置min

此外,如果您只将int作为实例变量并且不介意使用其他调用,则可以始终使用vars来抓取instance.__dict__,然后提供.values()min

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

答案 2 :(得分:6)

没有__min____max__特殊方法*。这是一种耻辱,因为range看到了一些pretty nice optimizations in Python 3。你可以这样做:

>>> 1000000000000 in range(1000000000000)
False

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

>>> max(range(1000000000000))

然而,根据Lærne的建议,创建自己的min / max函数是个不错的主意。

我将如何做到这一点。更新:根据PEP 8的建议删除了dunder名称__min__,转而使用_min

  

永远不要发明这样的名字;只使用它们记录

代码:

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方法的可迭代对象,但返回值会被特殊方法覆盖。

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

另请注意,如果__min方法只是通过调用oldmin来实现,那么事情仍然可以正常工作(即使迭代器已被使用;这是因为oldmin引发了ValueError 1}}在这种情况下)。

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