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