ホームページ  >  記事  >  バックエンド開発  >  拡張算術代入「-=」演算の詳細説明

拡張算術代入「-=」演算の詳細説明

coldplay.xixi
coldplay.xixi転載
2020-09-11 17:11:102678ブラウズ

拡張算術代入「-=」演算の詳細説明

関連する学習の推奨事項: python チュートリアル

序文

この記事は一連の記事ですPython 構文シュガー 1 について。最新のソース コードは desugar プロジェクト (github.com/brettcannon…

Introduction

Python には 拡張算術代入 (拡張算術代入) と呼ばれるものがあります。この名前に聞き覚えがないかもしれません。実際には、算術演算を行う際の代入を意味します。たとえば、a -= b は、減算の拡張された算術代入です。

拡張された代入は、Python 2.0 バージョンで追加されました。(注釈) : PEP-203 で導入)

分析-=

Python では、特別/魔法のメソッドを持つ他の操作と比較して、代入の上書きが許可されていないため、

まず第一に、a -= b は意味的に a = a-b と同じであることを知っておく必要があります。ただし、オブジェクトを変数名に割り当てたいことが事前にわかっている場合は、a - b ブラインド操作よりも効率的である可能性があることにも注意してください。

たとえば、少なくとも次のようになります。利点は、新しいオブジェクトの作成を回避できることです: オブジェクトをその場で変更できる場合は、新しいオブジェクトを再構築するよりも self を返す方が効率的です。

したがって、Python は __isub__() メソッドを提供します。代入の左側で定義されている場合 (通常は左辺値と呼ばれます)、右側の値 (通常は右辺値と呼ばれます) が呼び出されます。 a. __isub__(b). 呼び出しの結果が NotImplemented であるか、結果がまったくない場合、Python は通常の二項算術演算 (

a - b

) に戻ります。二項演算に関する記事、翻訳はこちら)最終的には、どのメソッドを使用しても戻り値は a に代入されます。

以下は簡単な疑似コードです。

a - = b

は次のように分解されます。 <pre class="brush:php;toolbar:false;"># 实现 a -= b 的伪代码if hasattr(a, &quot;__isub__&quot;): _value = a.__isub__(b) if _value is not NotImplemented: a = _value else: a = a - b del _value else: a = a - b复制代码</pre>これらのメソッドを一般化する

すでにバイナリ算術を実装しているため、拡張された算術を一般化することはそれほど複雑ではありません。

バイナリ算術演算関数を渡し、いくつかのイントロスペクション (および発生する可能性のある TypeError の処理) を行うことで、次のようにきれいに要約できます。

def _create_binary_inplace_op(binary_op: _BinaryOp) -> Callable[[Any, Any], Any]:

    binary_operation_name = binary_op.__name__[2:-2]
    method_name = f"__i{binary_operation_name}__"
    operator = f"{binary_op._operator}="

    def binary_inplace_op(lvalue: Any, rvalue: Any, /) -> Any:
        lvalue_type = type(lvalue)        try:
            method = debuiltins._mro_getattr(lvalue_type, method_name)        except AttributeError:            pass
        else:
            value = method(lvalue, rvalue)            if value is not NotImplemented:                return value        try:            return binary_op(lvalue, rvalue)        except TypeError as exc:            # If the TypeError is due to the binary arithmetic operator, suppress
            # it so we can raise the appropriate one for the agumented assignment.
            if exc._binary_op != binary_op._operator:                raise
        raise TypeError(            f"unsupported operand type(s) for {operator}: {lvalue_type!r} and {type(rvalue)!r}"
        )

    binary_inplace_op.__name__ = binary_inplace_op.__qualname__ = method_name
    binary_inplace_op.__doc__ = (        f"""Implement the augmented arithmetic assignment `a {operator} b`."""
    )    return binary_inplace_op复制代码

これにより、 -= support _create_binary_inplace_op(__ sub__) の定義が作成されます。関数名、どの __i*__ 関数が呼び出されるか、バイナリ算術演算が失敗したときにどの呼び出し可能オブジェクトを呼び出すかなど、他のことも推測できます。

これを使用している人はほとんどいないことがわかりました

* *=

この記事のコードを書いているときに、**= という奇妙なテスト エラーが発生しました。 __pow__ が適切に呼び出されることを確認するすべてのテストのうち、Python 標準ライブラリの operator

モジュールで失敗するテストがあります。

私のコードは通常は問題なく、そのコードと CPython のコードに違いがある場合は、通常、何か間違ったことをしていることを意味します。

しかし、コードのトラブルシューティングをどれだけ慎重に行っても、テストが成功し、標準ライブラリが失敗する理由を正確に特定することはできません。

CPython の内部で何が起こっているのかを詳しく調べてみることにしました。逆アセンブルされたバイトコードから始めます:

>>> def test(): a **= b... >>> import dis>>> dis.dis(test)  1           0 LOAD_FAST                0 (a)              2 LOAD_GLOBAL              0 (b)              4 INPLACE_POWER              6 STORE_FAST               0 (a)              8 LOAD_CONST               0 (None)             10 RETURN_VALUE复制代码

それを通して、eval ループで

INPLACE_POWER

を見つけました:

        case TARGET(INPLACE_POWER): {
            PyObject *exp = POP();
            PyObject *base = TOP();
            PyObject *res = PyNumber_InPlacePower(base, exp, Py_None);
            Py_DECREF(base);
            Py_DECREF(exp);
            SET_TOP(res);            if (res == NULL)                goto error;
            DISPATCH();
        }复制代码
出典: github.com/python/cpyt …

次に、

PyNumber_InPlacePower()

を見つけます:

PyObject *PyNumber_InPlacePower(PyObject *v, PyObject *w, PyObject *z){    if (v->ob_type->tp_as_number &&
        v->ob_type->tp_as_number->nb_inplace_power != NULL) {        return ternary_op(v, w, z, NB_SLOT(nb_inplace_power), "**=");
    }    else {        return ternary_op(v, w, z, NB_SLOT(nb_power), "**=");
    }
}复制代码
出典: github.com/python/cpyt…

安堵のため息をつきました~コードは定義されているかどうかを示します__ipow__ が存在すると呼び出されますが、__pow__ は __ipow__ が存在しない場合にのみ呼び出されます。

ただし、正しいアプローチは次のとおりです。

__ipow__ の呼び出し時に問題があり、NotImplemented が返されるか、まったく返されない場合は、__pow__ および __rpow__ を呼び出す必要があります。

つまり、__ipow__ が存在する場合、上記のコードは a**b のフォールバック セマンティクスを予期せずスキップします。

実は、この問題は部分的に発見され、約 11 か月前にバグが提出されました。この問題を修正し、python-dev に文書化しました。

現時点では、これは Python 3.10 で修正されるようです。また、3.8 および 3.9 のドキュメントに **= にバグがあることに関する通知を追加する必要があります (問題は初期のものである可能性がありますが、古いものである可能性があります)。 Python バージョンはすでにセキュリティのみのメンテナンス モードになっているため、ドキュメントは変更されません)。

修正コードはセマンティクスの変更であり、誰かが誤って問題のあるセマンティクスに依存したかどうかを判断するのが難しいため、移植されない可能性が高くなります。しかし、この問題に気づくまでに非常に時間がかかりました。これは、**= が広く使用されていないことを示唆しています。そうでなければ、問題はずっと前に発見されていたでしょう。

プログラミング学習について詳しく知りたい方は、

php training
のコラムに注目してください!

以上が拡張算術代入「-=」演算の詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.imで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。