<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          詳解增強算術(shù)賦值:“-=”操作是怎么實現(xiàn)的?

          共 8684字,需瀏覽 18分鐘

           ·

          2021-06-12 20:33

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????


          原題 | Unravelling augmented arithmetic assignment
          作者 | Brett Cannon
          譯者 | 豌豆花下貓(“Python貓”公眾號)
          聲明 | 本翻譯是出于交流學(xué)習(xí)的目的,基于 CC BY-NC-SA 4.0 授權(quán)協(xié)議。為便于閱讀,內(nèi)容略有改動。

          序言

          本文是 Python語法糖 系列文章之一。最新的源代碼可以在 desugar 項目中找到(https://github.com/brettcannon/desugar)。

          介紹

          Python 有一種叫做增強算術(shù)賦值augmented arithmetic assignment)的東西??赡苣悴皇煜み@個叫法,其實就是在做數(shù)學(xué)運算的同時進(jìn)行賦值,例如 a -= b 就是減法的增強算術(shù)賦值。

          增強賦值是在 Python 2.0 版本中 加入進(jìn)來的。(譯注:在 PEP-203 中引入)

          剖析 -=

          因為 Python 不允許覆蓋式賦值,所以相比其它有特殊/魔術(shù)方法的操作,它實現(xiàn)增強賦值的方式可能跟你想象的不完全一樣。

          首先,要知道a -= b在語義上與 a = a-b 相同。但也要意識到,如果你預(yù)先知道要將一個對象賦給一個變量名,相比a - b 的盲操作,就可能會更高效。

          例如,最起碼的好處是可以避免創(chuàng)建一個新對象:如果可以就地修改一個對象,那么返回 self,就比重新構(gòu)造一個新對象要高效。

          因此,Python 提供了一個__isub__() 方法。如果它被定義在賦值操作的左側(cè)(通常稱為 lvalue),則會調(diào)用右側(cè)的值(通常稱為 rvalue )。所以對于a -= b ,就會嘗試去調(diào)用 a.__isub__(b)。

          如果調(diào)用的結(jié)果是 NotImplemented,或者根本不存在結(jié)果,那么 Python 會退回到常規(guī)的二元算術(shù)運算:a - b。

          最終無論用了哪種方法,返回值都會被賦值給 a。

          下面是簡單的偽代碼,a -= b 被分解成:

          # 實現(xiàn) a -= b 的偽代碼
          if hasattr(a, "__isub__"):
              _value = a.__isub__(b)
              if _value is not NotImplemented:
                  a = _value
              else:
                  a = a - b
              del _value
           else:
               a = a - b

          歸納這些方法

          由于我們已經(jīng)實現(xiàn)了二元算術(shù)運算,因此歸納增強算術(shù)運算并不太復(fù)雜。

          通過傳入二元算術(shù)運算函數(shù),并做一些自?。ㄒ约疤幚砜赡馨l(fā)生的 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

          這使得定義的 -= 支持 _create_binary_inplace_op(__ sub__),且可以推斷出其它內(nèi)容:函數(shù)名、調(diào)用什么 __i*__ 函數(shù),以及當(dāng)二元算術(shù)運算出問題時,該調(diào)用哪個可調(diào)用對象。

          我發(fā)現(xiàn)幾乎沒有人使用**=

          在寫本文的代碼時,我碰上了 **= 的一個奇怪的測試錯誤。在所有確保 __pow__ 會被適當(dāng)?shù)卣{(diào)用的測試中,有個測試用例對于 Python 標(biāo)準(zhǔn)庫中的operator 模塊卻是失敗。

          我的代碼通常沒問題,如果代碼與 CPython 的代碼之間存在差異,通常會意味著是我哪里出錯了。

          但是,無論我多么仔細(xì)地排查代碼,我都無法定位出為什么我的測試會通過,而標(biāo)準(zhǔn)庫則失敗。

          我決定深入地了解 CPython 內(nèi)部發(fā)生了什么。從反匯編字節(jié)碼開始:

          >>> 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 循環(huán)中的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();
                  }
          出處:https://github.com/python/cpython/blob/v3.8.3/Python/ceval.c#L1677

          然后找到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), "**=");
              }
          }
          出處:https://github.com/python/cpython/blob/v3.8.3/Objects/abstract.c#L1172

          松了口氣~代碼顯示如果定義了__ipow__,則會調(diào)用它,但是只在沒有__ipow__ 時,才會調(diào)用__pow__。

          然而,正確的做法應(yīng)該是:如果調(diào)用__ipow__ 時出問題,返回了 NotImplemented 或者根本不存在返回,那么就應(yīng)該調(diào)用 __pow__ 和__rpow__。

          換句話說,當(dāng)存在__ipow__時,以上代碼會意外地跳過 a**b 的后備語義!

          實際上,大約11個月前,這個問題被部分地發(fā)現(xiàn),并提交了 bug。我修復(fù)了該問題,并在 python-dev 上作了說明。

          截至目前,這似乎會在 Python 3.10 中修復(fù),我們還需要在 3.8 和 3.9 的文檔中添加關(guān)于 **= 有 bug 的通知(該問題可能很早就有了,但較舊的 Python 版本已處于僅安全維護(hù)模式,因此文檔不會變更)。

          修復(fù)的代碼很可能不會被移植,因為它是語義上的變化,并且很難判斷是否有人意外地依賴了有問題的語義。但是這個問題花了很長時間才被注意到,這就表明 **= 的使用并不廣泛,否則問題早就被發(fā)現(xiàn)了。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點這里好文分享給更多人↓↓

          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产免费又黄又爽 | 天天做天天爱天天高潮 | 欧美亚洲黄色电影免费收看 | 综合玖玖 | 国产无套精品久久久久久 |