web-dev-qa-db-ja.com

Pythonで最小値と最大値のカスタム値を返す方法はありますか?

カスタムクラスがあります

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

クラスは、反復可能でもインデックス可能でもありません。できればそのままにしていきたいと思います。次のような作品はありますか?

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

minmaxdocsrange はまったく同じドキュメントでシーケンス型と見なされているため、rangeで可能な最適化の種類があるはずだと考えていました。たぶん私はそれを利用できるでしょう。

おそらく私が知らない魔法の方法がこれを可能にするでしょうか?

18
Mad Physicist

はい。 minが引数を1つ取るとき、それは反復可能であると想定し、それを反復処理して最小値を取ります。そう、

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)
22
Lærne

__min____max__の特別なメソッド*はありません。 rangeがいくつかの かなり良い最適化をPython で見たので、これはちょっと残念です。あなたはこれを行うことができます:

>>> 1000000000000 in range(1000000000000)
False

しかし、長い間待たない限り、これを試さないでください。

>>> max(range(1000000000000))

ただし、独自のmin/max関数を作成することは、 Lærne で提案されているように、かなり良いアイデアです。

ここに私がそれをする方法があります。更新: PEP 8 によって推奨されているように、__min__を優先して、dunder名_minを削除しました。

そのような名前を発明しないでください。文書化されているとおりにのみ使用する

コード:

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を呼び出すだけで実装されている場合も、イテレータが消費されても問題なく機能することに注意してください。これは、oldminValueErrorです)。

*そのような方法はしばしば「マジック」と呼ばれますが、これは好ましい用語ではありません。

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メソッドを作成することをお勧めします。

さらに、インスタンス変数としてintしかなく、別の呼び出しを気にしない場合は、常にvarsを使用して_instance.__dict___を取得し、それを.values()に指定できます。 minへ:

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