关于this question的后续问题:我在python 3.5和python 3.6上运行了以下代码-结果截然不同:

class Container:

    KEYS = ('a', 'b', 'c')

    def __init__(self, a=None, b=None, c=None):
        self.a = a
        self.b = b
        self.c = c

    def keys(self):
        return Container.KEYS

    def __getitem__(self, key):
        if key not in Container.KEYS:
            raise KeyError(key)
        return getattr(self, key)

    def __str__(self):
        # python 3.6
        # return f'{self.__class__.__name__}(a={self.a}, b={self.b}, c={self.c})'
        # python 3.5
        return ('{self.__class__.__name__}(a={self.a}, b={self.b}, '
                'c={self.c})').format(self=self)

data0 = Container(a=1, b=2, c=3)
print(data0)

data3 = Container(**data0, b=7)
print(data3)

如上一个问题所述,这引起了



在python 3.6上但是在python 3.5上我得到了异常(exception):



此外,如果我不提出KeyError,而只是在key中打印出return__getitem__:
def __getitem__(self, key):
    if key not in Container.KEYS:
        # raise KeyError(key)
        print(key)
        return
    return getattr(self, key)

这将打印出int序列0, 1, 2, 3, 4, ...。 (python 3.5)

所以我的问题是:
  • 在各个发行版之间进行了哪些更改,从而使它们的行为如此不同?
  • 这些整数从哪里来?


  • 更新:如λuser的注释中所述:实现__iter__将更改python 3.5上的行为以匹配python 3.6的行为:
    def __iter__(self):
        return iter(Container.KEYS)
    

    最佳答案

    实际上,这是在解压缩自定义映射对象和创建调用方参数期间的多个内部操作之间的复杂冲突。因此,如果您想彻底理解其根本原因,建议您研究一下源代码。但是,这里有一些提示和起点,您可以查看它们以获取更多详细信息。
    在内部,当您在调用者级别解压缩时,字节码 BUILD_MAP_UNPACK_WITH_CALL(count) 从堆栈中弹出计数映射,将它们合并为一个字典并推送结果。另一方面,带有参数oparg is defined as following的此操作码的堆栈效果:

    case BUILD_MAP_UNPACK_WITH_CALL:
        return 1 - oparg;
    
    话虽如此,让我们来看一个示例的字节码(在Python-3.5中),以查看实际效果:
    >>> def bar(data0):foo(**data0, b=4)
    ...
    >>>
    >>> dis.dis(bar)
      1           0 LOAD_GLOBAL              0 (foo)
                  3 LOAD_FAST                0 (data0)
                  6 LOAD_CONST               1 ('b')
                  9 LOAD_CONST               2 (4)
                 12 BUILD_MAP                1
                 15 BUILD_MAP_UNPACK_WITH_CALL   258
                 18 CALL_FUNCTION_KW         0 (0 positional, 0 keyword pair)
                 21 POP_TOP
                 22 LOAD_CONST               0 (None)
                 25 RETURN_VALUE
    >>>
    
    如您所见,在偏移量15处,我们有BUILD_MAP_UNPACK_WITH_CALL字节码,负责解压缩。
    现在发生什么了,它将返回0作为key方法的__getitem__参数?
    每当解释器在拆包过程中遇到异常时(在本例中为KeyError),它将停止继续进行push/pop流,而不是返回变量的实际值,而是返回堆栈效果,这就是为什么键首先为0且如果您每次都没有得到异常处理(由于堆栈大小),则不处理该异常。
    现在,如果您在Python-3.6中进行相同的反汇编,您将得到以下结果:
    >>> dis.dis(bar)
      1           0 LOAD_GLOBAL              0 (foo)
                  2 BUILD_TUPLE              0
                  4 LOAD_FAST                0 (data0)
                  6 LOAD_CONST               1 ('b')
                  8 LOAD_CONST               2 (4)
                 10 BUILD_MAP                1
                 12 BUILD_MAP_UNPACK_WITH_CALL     2
                 14 CALL_FUNCTION_EX         1
                 16 POP_TOP
                 18 LOAD_CONST               0 (None)
                 20 RETURN_VALUE
    
    在创建局部变量(LOAD_FAST)之前和LOAD_GLOBAL之后,有一个 BUILD_TUPLE ,它负责创建元组并从堆栈中使用计数项。

    IMO,这就是为什么您没有得到关键错误,而是得到TypeError的原因。因为在创建参数元组期间,它遇到一个重复的名称,因此正确地返回了TypeError

    关于python - 就地自定义对象使用__getitem__ python 3.5 vs python 3.6解包不同的行为,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50384549/

    10-11 20:06
    查看更多