Есть ли способ вернуть пользовательское значение 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
и, возможно, я мог бы воспользоваться этим.
Возможно, есть волшебный метод, о котором я не знаю, что бы это сделать?
Ответы
Ответ 1
Да. Когда 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)
Ответ 2
Так как range
считается типом последовательности теми же документами, я думал, что для range
должна быть какая-то оптимизация, и, возможно, я смогу воспользоваться ею.
Там нет оптимизации для диапазонов и нет специальных методов магии для min
/max
.
Если вы заглянете в реализацию для min
/max
, вы увидите, что после того, как будет проведен синтаксический анализ аргументов, вызывается вызов iter(obj)
(т.е. obj.__iter__()
) для захвата итератора:
it = PyObject_GetIter(v);
if (it == NULL) {
return NULL;
}
тогда вызовы next(it)
(i.e 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
Ответ 3
Нет __min__
и __max__
специальных методов *. Это позор, так как range
видел некоторые довольно приятные оптимизации в Python 3. Вы можете сделать это:
>>> 1000000000000 in range(1000000000000)
False
Но не пробуйте это, если вы не хотите долго ждать:
>>> max(range(1000000000000))
Однако создание ваших собственных функций min
/max
- довольно хорошая идея, как предложено Lærne.
Вот как я это сделаю. UPDATE: удалено имя 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
).
* Такие методы часто называют "волшебными", но это не предпочтительная терминология.