python 2和3之间的一个变化是后者委托给x.__round__([n])操作round(x, n)。在python 2中,对于实现__round____float__的类,当我调用round(x)时,x.__float__被调用。
我怎么知道调用round(x)(而不是float(x))是为了重新路由Python2中适当的调用并获得类似Python3的行为。
谢谢
更新:我想出了一个丑陋的黑客。我确信:
这是可以改进的。
它不会总是起作用的。
python 2中不处理ndigts参数。
不应在生产中使用。
但不管怎样建造它还是很有趣的。谢谢你的澄清。

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

最佳答案

您可以重新定义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而不是
3round(x[, n])()现在委托给x.__round__([n]),而不是总是返回浮点值。它通常在调用时返回一个整数
调用时使用单个参数和与x类型相同的值
有两个论点。

10-04 10:22