python 2和3中的__float__和__round__

时间:2012-03-17 16:03:50

标签: python python-3.x porting

python 2和3之间的一个变化是后者委托x.__round__([n])操作round(x, n)。在python 2中,对于我的实现__round____float__的类,当我调用round(x)时,会调用x.__float__

我怎么知道round(x)(而不是float(x))被调用来重新路由python 2中适当的调用并获得类似python 3的行为。

感谢

更新:我想出了一个丑陋的黑客。我确信:

  • 可以改进。
  • 它不会一直有效。
  • 在python 2中不处理ndigits参数。
  • 不应该用于生产。

但无论如何构建它都很有趣。感谢所有的澄清。

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 个答案:

答案 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而不是总是返回浮动。它通常在调用时返回一个整数   调用时使用单个参数和与{{1}}相同类型的值   有两个论点。

答案 1 :(得分:1)

在Python 2中,您无法覆盖round()所做的事情。它没有委托__float__;它首先调用float()(然后委托给__float__),然后进行舍入。因此,知道__float__是否从round()调用是没有意义的,因为它将为您进行舍入。你不能委托它。

如果要在Python 2中实现自己的自定义舍入,则应实现执行自定义舍入的custom_round()方法,并使用该方法代替round()

答案 2 :(得分:0)

  

我怎么知道round(x)(而不是float(x))被调用来重新路由python 2中适当的调用并获得类似python 3的行为。

你不需要。如果round(x)调用了您的__float__方法,则会使用float的常规逻辑对返回的float进行舍入。您无需在__float__实施中考虑它;无论调用上下文如何,都应该返回相同的内容。其他一切都是呼唤环境的责任。

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