Есть ли способ вернуть пользовательское значение для min и max в Python?

у меня есть пользовательский класс

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

класс не является итерационным или индексируемым или чем-либо подобным. Если это вообще возможно, я бы хотел, чтобы так и оставалось. Можно ли как нибудь после работы?

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

что заставило меня задуматься об этом, так это то min и max перечислены как "операции общей последовательности" в docs. С range считается последовательность введите те же документы, я думал, что должна быть какая-то оптимизация, которая возможна для range, и, возможно, я мог бы воспользоваться этим.

возможно, есть магический метод, о котором я не знаю, который позволил бы это?

3 ответов


да. Когда 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 )

нет __min__ и __max__ специальные методы*. Это своего рода позор, так range несколько довольно хорошая оптимизация в Python 3. Вы можете сделать это:

>>> 1000000000000 in range(1000000000000)
False

но не пытайтесь это, если вы не хотите ждать долгое время:

>>> max(range(1000000000000))

однако создание собственного min/max функции-довольно хорошая идея, как предложено Lærne.

вот как я бы это сделал. Обновление: удален dunder имя __min__ в пользу _min, как было рекомендовано PEP 8:

никогда не изобретайте такие имена; используйте их только как документированные

код:

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 в данном случае).

* такие методы часто называют "магическими", но это не самая предпочтительная терминология.


С range считается типом последовательности теми же документами, я думал, что должна быть какая-то оптимизация, которая возможна для range, и, возможно, я мог бы воспользоваться этим.

нет никакой оптимизации для диапазонов, и нет специализированных магических методов для min/max.

если вы посмотрите на реализация min/max вы увидите это через некоторое время разбор аргументов выполнен,вызов iter(obj) (я.е obj.__iter__()) сделано, чтобы захватить итератор:

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

затем звонки next(it) (я.е it.__next__) выполняются в цикле для захвата значений для сравнения:

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

можно ли как нибудь после работы?

нет, если вы хотите использовать встроенный min* единственный вариант, вы уже реализует итератор протокол.


*на ямочный min, вы можете-конечно, сделать все, что вы хотите. Очевидно, за счет работы в Pythonland. Если, однако, вы думаете, что можете использовать некоторые оптимизации, я бы предложил вам создать min метод, а не повторное определение встроенного min.

кроме того, если у вас есть только ints в качестве переменных экземпляра, и вы не возражаете против другого вызова, вы всегда можете использовать vars чтобы захватить instance.__dict__ и после этого поставьте его .values() to min:

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