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
それは最初__future__で利用可能だった場合、私にも見えました!あなたの再定義の問題は、私がライブラリを構築しているということです。だから私はどのくらいのラウンドが使用されるのかを制御しません。私はスタックをイントロスペクトして、それがラウンド()によって呼び出されたかどうかを調べることが可能であることを知りました。 – Hernan
ああ、そうしないでください。それはひどく壊れやすく、バギーになるだろう。 'round'で実装したい異なる動作は何ですか?つまり、 'round(x)'の値をどのようにしたいと思いますか? – katrielalex
Python 2.xでは 'round'は常に浮動小数点数を返しますので、あなたのクラスがその契約を途切れさせたくないことを覚えておいてください! – katrielalex