Есть ли способ вернуть пользовательское значение для 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