Monday, August 07, 2017

Python 2.7 scoping bug

Here is a piece of code that does not work on Python 2.7:

 #!/usr/bin/python  
 def img_type(s):  
   return str(s)  
 print (img_type(50))  
 a = [img_dir for (img_dir, img_type) in [("a",1),("b",2)]]  
 print (img_type(20))  

On the second print, it raises a TypeError:

TypeError: 'int' object is not callable

The interpreter is incorrectly identifying the scoped variables img_dir, img_type to be in global scope. Since the function is of the same name, the variable takes precedence. Actually it overwrites the function!

We can see what is happening by looking at the globals().items() and locals().items(). Each is a list of tuples where each tuple contains the variable name and its currently assigned value. Here is a modified program that lists the variables, before and after we define the list comprehension:

#!/usr/bin/python

def img_type(s):
    return str(s)

print (img_type(50))

print ("BEFORE")
print (globals().items())
print (locals().items())


a = [img_dir for (img_dir, img_type) in [("a",1),("b",2)]]

print ("AFTER")
print (globals().items())
print (locals().items())

print (img_type(20))

This outputs:

50
BEFORE
[('img_type', <function img_type at 0x7f740ad5eb18>), ('__builtins__', <module '__builtin__' (built-in)>), ('__file__', './proof1.py'), ('__package__', None), ('__name__', '__main__'), ('__doc__', None)]
[('img_type', <function img_type at 0x7f740ad5eb18>), ('__builtins__', <module '__builtin__' (built-in)>), ('__file__', './proof1.py'), ('__package__', None), ('__name__', '__main__'), ('__doc__', None)]
AFTER
[('img_type', 2), ('a', ['a', 'b']), ('__builtins__', <module '__builtin__' (built-in)>), ('img_dir', 'b'), ('__file__', './proof1.py'), ('__package__', None), ('__name__', '__main__'), ('__doc__', None)]
[('img_type', 2), ('a', ['a', 'b']), ('__builtins__', <module '__builtin__' (built-in)>), ('img_dir', 'b'), ('__file__', './proof1.py'), ('__package__', None), ('__name__', '__main__'), ('__doc__', None)]
Traceback (most recent call last):
  File "./proof1.py", line 19, in <module>
    print (img_type(20))
TypeError: 'int' object is not callable

Notice how after the list comprehension the img_type() function got clobbered by the locally scoped variable by the same name.

This is fixed as of Python 3.2. Here is the output running the second version of the program:

50
BEFORE
dict_items([('__name__', '__main__'), ('__doc__', None), ('__loader__', <_frozen_importlib_external.SourceFileLoader object at 0x7ff93f4b7780>), ('__file__', './proof1.py'), ('__builtins__', <module 'builtins' (built-in)>), ('__spec__', None), ('img_type', <function img_type at 0x7ff93f41c2f0>), ('__package__', None), ('__cached__', None)])
dict_items([('__name__', '__main__'), ('__doc__', None), ('__loader__', <_frozen_importlib_external.SourceFileLoader object at 0x7ff93f4b7780>), ('__file__', './proof1.py'), ('__builtins__', <module 'builtins' (built-in)>), ('__spec__', None), ('img_type', <function img_type at 0x7ff93f41c2f0>), ('__package__', None), ('__cached__', None)])
AFTER
dict_items([('__name__', '__main__'), ('__doc__', None), ('a', ['a', 'b']), ('__loader__', <_frozen_importlib_external.SourceFileLoader object at 0x7ff93f4b7780>), ('__file__', './proof1.py'), ('__builtins__', <module 'builtins' (built-in)>), ('__spec__', None), ('img_type', <function img_type at 0x7ff93f41c2f0>), ('__package__', None), ('__cached__', None)])
dict_items([('__name__', '__main__'), ('__doc__', None), ('a', ['a', 'b']), ('__loader__', <_frozen_importlib_external.SourceFileLoader object at 0x7ff93f4b7780>), ('__file__', './proof1.py'), ('__builtins__', <module 'builtins' (built-in)>), ('__spec__', None), ('img_type', <function img_type at 0x7ff93f41c2f0>), ('__package__', None), ('__cached__', None)])
20

No comments: