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

时间:2022-09-28 14:15:48

I have a custom class,

我有一个自定义类,

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

The class is not iterable or indexable or anything like that. If at all possible, I would like to keep it that way. Is it possible to have something like the following work?

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

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

What got me thinking about this is that min and max are listed as "Common Sequence Operations" in the docs. Since range is considered to be a sequence type by the very same docs, I was thinking that there must be some sort of optimization that is possible for range, and that perhaps I could take advantage of it.

让我想到这一点的是,min和max被列为文档中的“Common Sequence Operations”。由于范围被同一个文档视为序列类型,我认为必须有一些范围可能的优化,也许我可以利用它。

Perhaps there is a magic method that I am not aware of that would enable this?

也许有一种我不知道的神奇方法可以实现这个目的吗?

3 个解决方案

#1


20  

Yes. When min takes one arguments it assumes it to be an iterable, iterates over it and takes the minimum value. So,

是。当min接受一个参数时,它假定它是一个可迭代的,迭代它并取最小值。所以,

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

Should work.

应该管用。

Additional Note: If you don't want to use __iter__, I don't know of way to do that. You probably want to create your own min function, that calls some __min__ method if there is one in the argument it is passed to and calls the old min else.

附加说明:如果您不想使用__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


6  

There are no __min__ and __max__ special methods*. This is kind of a shame since range has seen some pretty nice optimizations in Python 3. You can do this:

没有__min__和__max__特殊方法*。这是一种耻辱,因为范围已经在Python 3中看到了一些非常好的优化。您可以这样做:

>>> 1000000000000 in range(1000000000000)
False

But don't try this unless you want to wait a long time:

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

>>> max(range(1000000000000))

However creating your own min/max functions is a pretty good idea, as suggested by Lærne.

然而,正如Lærne所建议的,创建自己的最小/最大函数是个不错的主意。

Here is how I would do it. UPDATE: removed the dunder name __min__ in favor of _min, as recommended by PEP 8:

我就是这样做的。更新:根据PEP 8的建议,删除了dunder名称__min__,转而使用_min:

Never invent such names; only use them as documented

不要发明这样的名字;仅按记录使用它们

Code:

码:

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.')

I think this way is maybe a little bit better because it handles some corner cases the other answer hasn't considered.

我认为这种方式可能会更好一点,因为它处理了一些其他答案没有考虑的极端情况。

Note that an iterable object with a _min method will still be consumed by oldmin as per usual, but the return value is overridden by the special method.

请注意,具有_min方法的可迭代对象仍将按照惯例使用oldmin,但返回值将被特殊方法覆盖。

HOWEVER, if the _min method requires the iterator to still be available for consumption, this will need to be tweaked because the iterator is getting consumed by oldmin first.

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

Note also that if the __min method is simply implemented by calling oldmin, things will still work fine (even though the iterator was consumed; this is because oldmin raises a ValueError in this case).

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

* Such methods are often called "magic", but this is not the preferred terminology.

*这种方法通常被称为“魔术”,但这不是首选的术语。

#3


6  

Since range is considered to be a sequence type by the very same docs, I was thinking that there must be some sort of optimization that is possible for range, and that perhaps I could take advantage of it.

由于范围被同一个文档视为序列类型,我认为必须有一些范围可能的优化,也许我可以利用它。

There's no optimization going on for ranges and there are no specialized magic methods for min/max.

范围没有优化,最小/最大没有专门的魔术方法。

If you peek at the implementation for min/max you'll see that after some argument parsing is done, a call to iter(obj) (i.e obj.__iter__()) is made to grab an iterator:

如果您查看min / max的实现,您将看到在完成一些参数解析之后,调用iter(obj)(即obj .__ iter __())来获取迭代器:

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

then calls to next(it) (i.e it.__next__) are performed in a loop to grab values for comparisons:

然后调用next(it)(即它.__ next__)在循环中执行以获取比较值:

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

Is it possible to have something like the following work?

是否有可能像以下工作一样?

No, if you want to use the built-in min* the only option you have is implementing the iterator protocol.

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


*By patching min, you can of-course, make it do anything you want. Obviously at the cost of operating in Pythonland. If, though, you think you can utilize some optimizations, I'd suggest you create a min method rather than re-defining the built-in min.

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

In addition, if you only have ints as instance variables and you don't mind a different call, you can always use vars to grab the instance.__dict__ and then supply it's .values() to min:

另外,如果你只有int作为实例变量并且你不介意不同的调用,你总是可以使用vars来获取实例.__ dict__然后将它的.values()提供给min:

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

#1


20  

Yes. When min takes one arguments it assumes it to be an iterable, iterates over it and takes the minimum value. So,

是。当min接受一个参数时,它假定它是一个可迭代的,迭代它并取最小值。所以,

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

Should work.

应该管用。

Additional Note: If you don't want to use __iter__, I don't know of way to do that. You probably want to create your own min function, that calls some __min__ method if there is one in the argument it is passed to and calls the old min else.

附加说明:如果您不想使用__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


6  

There are no __min__ and __max__ special methods*. This is kind of a shame since range has seen some pretty nice optimizations in Python 3. You can do this:

没有__min__和__max__特殊方法*。这是一种耻辱,因为范围已经在Python 3中看到了一些非常好的优化。您可以这样做:

>>> 1000000000000 in range(1000000000000)
False

But don't try this unless you want to wait a long time:

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

>>> max(range(1000000000000))

However creating your own min/max functions is a pretty good idea, as suggested by Lærne.

然而,正如Lærne所建议的,创建自己的最小/最大函数是个不错的主意。

Here is how I would do it. UPDATE: removed the dunder name __min__ in favor of _min, as recommended by PEP 8:

我就是这样做的。更新:根据PEP 8的建议,删除了dunder名称__min__,转而使用_min:

Never invent such names; only use them as documented

不要发明这样的名字;仅按记录使用它们

Code:

码:

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.')

I think this way is maybe a little bit better because it handles some corner cases the other answer hasn't considered.

我认为这种方式可能会更好一点,因为它处理了一些其他答案没有考虑的极端情况。

Note that an iterable object with a _min method will still be consumed by oldmin as per usual, but the return value is overridden by the special method.

请注意,具有_min方法的可迭代对象仍将按照惯例使用oldmin,但返回值将被特殊方法覆盖。

HOWEVER, if the _min method requires the iterator to still be available for consumption, this will need to be tweaked because the iterator is getting consumed by oldmin first.

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

Note also that if the __min method is simply implemented by calling oldmin, things will still work fine (even though the iterator was consumed; this is because oldmin raises a ValueError in this case).

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

* Such methods are often called "magic", but this is not the preferred terminology.

*这种方法通常被称为“魔术”,但这不是首选的术语。

#3


6  

Since range is considered to be a sequence type by the very same docs, I was thinking that there must be some sort of optimization that is possible for range, and that perhaps I could take advantage of it.

由于范围被同一个文档视为序列类型,我认为必须有一些范围可能的优化,也许我可以利用它。

There's no optimization going on for ranges and there are no specialized magic methods for min/max.

范围没有优化,最小/最大没有专门的魔术方法。

If you peek at the implementation for min/max you'll see that after some argument parsing is done, a call to iter(obj) (i.e obj.__iter__()) is made to grab an iterator:

如果您查看min / max的实现,您将看到在完成一些参数解析之后,调用iter(obj)(即obj .__ iter __())来获取迭代器:

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

then calls to next(it) (i.e it.__next__) are performed in a loop to grab values for comparisons:

然后调用next(it)(即它.__ next__)在循环中执行以获取比较值:

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

Is it possible to have something like the following work?

是否有可能像以下工作一样?

No, if you want to use the built-in min* the only option you have is implementing the iterator protocol.

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


*By patching min, you can of-course, make it do anything you want. Obviously at the cost of operating in Pythonland. If, though, you think you can utilize some optimizations, I'd suggest you create a min method rather than re-defining the built-in min.

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

In addition, if you only have ints as instance variables and you don't mind a different call, you can always use vars to grab the instance.__dict__ and then supply it's .values() to min:

另外,如果你只有int作为实例变量并且你不介意不同的调用,你总是可以使用vars来获取实例.__ dict__然后将它的.values()提供给min:

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