2012-03-17 87 views
2

python 2和3之間的一個變化是後者代表x.__round__([n])操作round(x, n)。在python 2中,對於我的類實現__round____float__,當我調用round(x)時,調用了x.__float____float__和__round__在python 2和3中

我怎麼知道round(x)(而不是float(x))被調用來重新路由在Python 2適當的調用,並獲得類似python 3的行爲。

感謝

更新:我想出了一個醜陋的黑客。我確信:

  • 它可以改進。
  • 它不會總是工作。
  • ndigits參數不在python 2中處理。
  • 它不應該用於生產。

但無論如何建立它很有趣。感謝所有的澄清。

import dis 
import sys 
import inspect 
import functools 

#'CALL_FUNCTION', 'CALL_FUNCTION_VAR', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR_KW' 
HUNGRY = (131, 140, 141, 142) 

if sys.version < '3': 
    def is_round(frame): 
     """Disassemble a code object.""" 
     co = frame.f_code 
     lasti = frame.f_lasti 
     code = co.co_code 
     i, n = 0, len(code) 
     extended_arg = 0 
     free = None 
     codes = list() 
     while i < n: 
      c = code[i] 
      op = ord(c) 
      tmp = [op, ] 
      i += 1 
      if op >= dis.HAVE_ARGUMENT: 
       oparg = ord(code[i]) + ord(code[i + 1]) * 256 + extended_arg 
       extended_arg = 0 
       i += 2 
       if op == dis.EXTENDED_ARG: 
        extended_arg = oparg * long(65536) 
       tmp.append(oparg) 
       if op in dis.hasconst: 
        tmp.append(repr(co.co_consts[oparg])) 
       elif op in dis.hasname: 
        tmp.append(co.co_names[oparg]) 
       elif op in dis.hasjrel: 
        tmp.append(repr(i + oparg)), 
       elif op in dis.haslocal: 
        tmp.append(co.co_varnames[oparg]) 
       elif op in dis.hascompare: 
        tmp.append(dis.cmp_op[oparg]) 
       elif op in dis.hasfree: 
        if free is None: 
         free = co.co_cellvars + co.co_freevars 
        tmp.append(free[oparg]) 
       else: 
        tmp.append(None) 
      else: 
       tmp.append(None) 
       tmp.append(None) 

      codes.append(tmp) 
      if i > lasti: 
       break 

     pending = 1 
     for (opcode, arguments, param) in reversed(codes): 
      pending -= 1 
      if opcode in HUNGRY: 
       pending += arguments + 1 
      if not pending: 
       seen = dict(frame.f_builtins) 
       seen.update(frame.f_globals) 
       seen.update(frame.f_locals) 
       while param in seen: 
        param = seen[param] 
       return param == round 

    def round_check(func): 
     @functools.wraps(func) 
     def wrapped(self): 
      if is_round(inspect.currentframe().f_back): 
       return self.__round__() 
      return func(self) 
     return wrapped 

else: 

    def round_check(func): 
     return func 

class X(): 

    @round_check 
    def __float__(self): 
     return 1.0 

    def __round__(self, ndigits=0): 
     return 2.0 

x = X() 

r = round 
f = float 

assert round(x) == 2.0 
assert float(x) == 1.0 

assert r(x) == 2.0 
assert f(x) == 1.0 

assert round(float(x)) == 1.0 
assert float(round(x)) == 2.0 

回答

3

你總是可以重新定義round嘗試__round__第一。不幸的是,這不是一個__future__導入,所以我不認爲你可以做什麼。

>>> class X(object): 
...  def __round__(self, n=0): return 1. 
...  def __float__(self): return 2. 
... 
>>> x = X() 
>>> round(x) 
2.0 
>>> float(x) 
2.0 
>>> old_round = round 
>>> def round(x, n=0): 
...  try: 
...    return x.__round__(n) 
...  except AttributeError: 
...    return old_round(x) 
... 
>>> 
>>> round(x) 
1.0 
>>> float(x) 
2.0 
>>> 

注意,這至少是一個documented change

round()功能四捨五入戰略和返回類型發生了變化。 準確的中途病例現在四捨五入到最接近的結果,而不是從零開始的 。 (例如,round(2.5)現在返回2而不是 3。)round(x[, n])()現在代表x.__round__([n])而不是總是返回一個浮點數。當調用 帶有兩個參數時,它通常會返回一個整數,其形式爲 ,帶有一個參數和一個與x相同類型的值。

+0

我也看看它是否可用__future__第一!重新定義問題的關鍵是我正在建立一個圖書館。所以我不能控制如何使用圓。我在跳躍,有可能反思堆棧,並找出它是否被round() – Hernan 2012-03-17 20:21:05

+2

調用哦,不要那樣做。這將是可怕的脆弱和越野車。你想在'round'中執行不同的行爲是什麼?也就是說,你認爲'round(x)'的價值是什麼? – katrielalex 2012-03-17 20:28:37

+0

請記住,在Python 2.x中'round'總是返回一個浮點數 - 所以你不希望你的類違反合同! – katrielalex 2012-03-17 20:29:08

0

我怎麼能知道這一輪(x)的(而不是浮動(X​​))被稱爲重新路由調用適當的蟒蛇2並獲得一個Python 3樣的行爲。

你不需要。如果round(x)調用您的__float__方法,它將使用float s的正常邏輯對返回的float進行舍入。您不需要在__float__實施中考慮它;無論調用上下文如何,您都應該返回相同的內容。其他一切都是調用環境的責任。

>>> class hax(object): 
... def __float__(self): return 2.6 
... 
>>> round(hax()) 
3.0 
+0

定製'round'的一個原因是如果你不想返回一個'float'。 (例如,我總是認爲花車的圓形應該是一個整數。) – katrielalex 2012-03-17 19:40:36

+0

我想實現我自己的__round__(就像我在python 3中做的那樣),但是當我做圓(hax())時,它會調用__float__。 Katrielalex舉了一個很好的例子。 – Hernan 2012-03-17 20:17:58

+1

當然,但這是一個單獨的問題。你不能分辨'__float__'是由'round'還是'float'調用的,不應該嘗試,這是我的觀點。 – 2012-03-17 20:22:52

1

在Python 2中,您無法覆蓋round()的功能。它不是代表__float__;它首先調用float()(然後代表__float__),然後進行四捨五入。因此沒有必要知道round()是否被調用__float__,因爲它會爲你做四捨五入。你不能委託它。

如果您想在Python 2中實現自己的自定義四捨五入,則應該實現執行自定義四捨五入的custom_round()方法,並使用該方法代替round()