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 forrange
, 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 forrange
, 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