2012-03-17 28 views
2

python 2と3の間の変更の1つは、後者が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)3ではなく2を返します)round(x[, n])()は、常にfloatを返す代わりにx.__round__([n])に委譲します。通常、引数が1つの と呼び出された場合は整数を返し、2つの引数を持つ と呼び出された場合は、同じタイプの値をxと返します。

+0

それは最初__future__で利用可能だった場合、私にも見えました!あなたの再定義の問題は、私がライブラリを構築しているということです。だから私はどのくらいのラウンドが使用されるのかを制御しません。私はスタックをイントロスペクトして、それがラウンド()によって呼び出されたかどうかを調べることが可能であることを知りました。 – Hernan

+2

ああ、そうしないでください。それはひどく壊れやすく、バギーになるだろう。 'round'で実装したい異なる動作は何ですか?つまり、 'round(x)'の値をどのようにしたいと思いますか? – katrielalex

+0

Python 2.xでは 'round'は常に浮動小数点数を返しますので、あなたのクラスがその契約を途切れさせたくないことを覚えておいてください! – katrielalex

0

にはどうすればそのラウンドは、(X)のpython 2における呼適切な再ルーティングと行動のようなのpython 3を得るために呼ばれていた(と(x)が浮いていない)を知ることができます。

あなたはそうする必要はありません。 round(x)__float__メソッドを呼び出す場合、floatの通常のロジックを使用して、返されたfloatを丸めます。あなたは__float__実装でそれを考慮する必要はありません。呼び出しコンテキストに関係なく同じものを返す必要があります。他のすべてが呼び出しコンテキストの責任です。

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

カスタム 'ラウンド'を持つ理由の1つは、 'float'を返さない場合です。 (例えば、私は常に '山車のround'はint型であるべきだと思います。) – katrielalex

+0

私は(私のpython 3で行うように)私自身の__round__を実装したいが、私はラウンド(HAXを())を行うときには、__float__を呼び出します。 Katrielalexは良い例を与えました。 – Hernan

+1

確かに、それは別の質問です。 '__float__'が' round'または 'float'によって呼び出されたかどうかは分かりません。試してはいけません。これが私の主張でした。 –

1

Python 2では、round()が無効にすることはできません。それは__float__にないデリゲートを行います。それは最初(__float__にターン代表者に)、その後、丸めを行うfloat()を呼び出します。それはあなたのために丸めを行いますよう__float__は、round()かから呼び出されたかどうかを知るにはポイントがゆえありません。それを委任することはできません。

Python 2で独自のカスタム丸めを実装する場合は、round()の代わりにカスタム丸めを行うcustom_round()メソッドを実装する必要があります。

関連する問題