In-place custom object unpacking different behavior with __getitem__ python 3.5 vs python 3.6

2024/10/5 9:20:50

a follow-up question on this question: i ran the code below on python 3.5 and python 3.6 - with very different results:

class Container:KEYS = ('a', 'b', 'c')def __init__(self, a=None, b=None, c=None):self.a = aself.b = bself.c = cdef keys(self):return Container.KEYSdef __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)

as stated in the previous question this raises

TypeError: type object got multiple values for keyword argument 'b'

on python 3.6. but on python 3.5 i get the exception:

KeyError: 0

moreover if i do not raise KeyError but just print out the key and return in __getitem__:

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

this will print out the int sequence 0, 1, 2, 3, 4, .... (python 3.5)

so my questions are:

  • what has changed between the releases that makes this behave so differently?

  • where are these integers coming from?


UPDATE : as mentioned in the comment by λuser: implementing __iter__ will change the behavior on python 3.5 to match what python 3.6 does:

def __iter__(self):return iter(Container.KEYS)
Answer

This is actually a complicated conflict between multiple internal operations during unpacking a custom mapping object and creating the caller's arguments. Therefore, if you wan to understand the underlying reasons thoroughly I'd suggest you to look into the source code. However, here are some hints and starting points that you can look into for greater details.

Internally, when you unpack at a caller level, the byte code BUILD_MAP_UNPACK_WITH_CALL(count) pops count mappings from the stack, merges them into a single dictionary and pushes the result. In other hand, the stack effect of this opcode with argument oparg is defined as following:

case BUILD_MAP_UNPACK_WITH_CALL:return 1 - oparg;

With that being said lets look at the byte codes of an example (in Python-3.5) to see this in action:

>>> 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                115 BUILD_MAP_UNPACK_WITH_CALL   25818 CALL_FUNCTION_KW         0 (0 positional, 0 keyword pair)21 POP_TOP22 LOAD_CONST               0 (None)25 RETURN_VALUE
>>> 

As you can see, at offset 15 we have BUILD_MAP_UNPACK_WITH_CALL byte code which is responsible for the unpacking.

Now what happens that it returns 0 as the key argument to the __getitem__ method?

Whenever the interpreter encounters an exception during unpacking, which in this case is a KeyError, It stops continuing the push/pop flow and instead of returning the real value of your variable it returns the stack effect which is why the key is 0 at first and if you don't handle the exception each time you get an incremented result (due to the stack size).

Now if you do the same disassembly in Python-3.6 you'll get the following result:

>>> dis.dis(bar)1           0 LOAD_GLOBAL              0 (foo)2 BUILD_TUPLE              04 LOAD_FAST                0 (data0)6 LOAD_CONST               1 ('b')8 LOAD_CONST               2 (4)10 BUILD_MAP                112 BUILD_MAP_UNPACK_WITH_CALL     214 CALL_FUNCTION_EX         116 POP_TOP18 LOAD_CONST               0 (None)20 RETURN_VALUE

Before creating the local variables (LOAD_FAST) and after LOAD_GLOBAL there is a BUILD_TUPLE which is responsible for creating a tuple and consuming count items from the stack.

BUILD_TUPLE(count)

Creates a tuple consuming count items from the stack, and pushes the >resulting tuple onto the stack.

And this is, IMO, why you don't get a key error and instead you get TypeError. Because during the creation of a tuple of arguments it encounters a duplicate name and therefore, properly, returns the TypeError.

https://en.xdnf.cn/q/70505.html

Related Q&A

Different ways of using __init__ for PyQt4

So... Im working on trying to move from basic Python to some GUI programming, using PyQt4. Im looking at a couple different books and tutorials, and they each seem to have a slightly different way of k…

Numpy: Reshape array along a specified axis

I have the following array:x = np.arange(24).reshape((2,3,2,2)) array([[[[ 0, 1],[ 2, 3]],[[ 4, 5],[ 6, 7]],[[ 8, 9],[10, 11]]],[[[12, 13],[14, 15]],[[16, 17],[18, 19]],[[20, 21],[22, 23]]]])I wou…

python suds wrong namespace prefix in SOAP request

I use python/suds to implement a client and I get wrong namespace prefixes in the sent SOAP header for a spefic type of parameters defined by element ref= in the wsdl. The .wsdl is referencing a data …

Allow help() to work on partial function object

Im trying to make sure running help() at the Python 2.7 REPL displays the __doc__ for a function that was wrapped with functools.partial. Currently running help() on a functools.partial function displ…

How To Fix Miscased Procfile in Heroku

Heroku will not reload my corrected ProcfileI have ran git status which shows me the Procfile and I realized that I spelled Procfile with a lower case p. I saw the error and updated the file name in my…

Using Pythons xml.etree to find element start and end character offsets

I have XML data that looks like:<xml> The captial of <place pid="1">South Africa</place> is <place>Pretoria</place>. </xml>I would like to be able to extra…

How to get public key using PyOpenSSL?

Im tring to create python script, that would take PKCS#12 package and print some information contained in x509 certificate and using for this purpouses PyOpenSSL module. So far i want to fetch from cer…

what is the best way to extract data from pdf

I have thousands of pdf file that I need to extract data from.This is an example pdf. I want to extract this information from the example pdf.I am open to nodejs, python or any other effective method. …

Get random key:value pairs from dictionary in python

Im trying to pull out a random set of key-value pairs from a dictionary I made from a csv file. The dictionary contains information for genes, with the gene name being the dictionary key, and a list of…

UnicodeDecodeError: ascii codec cant decode byte 0xc5

UnicodeDecodeError: ascii codec cant decode byte 0xc5 in position 537: ordinal not in range(128), referer: ...I always get this error when I try to output my whole website with characters "č"…