No matter how I look at it I just cannot imagine how anybody ever accidentally uses a local binding where they intended to use a binding of larger scope, in any language. Well… okay, maybe in languages that have truly dynamic scope, by which I mean languages where if f
calls g
, then g
can see variables defined in f
. (these would be emacs 23, or Perl’s local
…) But lo and behold, those are also the cases of shadowing which are undecidable for a static lint! (I think)
I code in python all the time, for practically everything. Even there, I cannot see how anyone makes this mistake. Especially for the builtin functions; I think it takes a special sort of skill to accidentally shadow something like dir
or type
or next
or file
, and not get the world’s most obvious type error when you try to use the corresponding builtin function.
Meanwhile, what does happen to me is accidentally using something with a larger scope. I’ve wasted hours debugging pointless issues like this in jupyter notebook, where at some point I had a cell that looked like
for n in range(N):
print(f'Item {n}: ')
do = a(data[n])
bunch = of()
nontrivial(data[n])
stuff()
which I would at some point clean up and refactor because I needed it elsewhere:
def do_a_bunch_of_stuff(dataset):
do = a(dataset)
bunch = of()
nontrivial(data[n]) # <---- uh oh; this should be 'dataset', but there's no error because it
stuff() # "successfully" resolves to the global 'n' and 'data'
for (n, dataset) in enumerate(data):
print(f'Item {n}: ')
do_a_bunch_of_stuff(dataset) # worse yet, it still appears to work when I call it here!
# (NOTE: from this point onwards, 'n' is equal to 'len(data) - 1', causing
# the function to behave strangely)
It has gotten to the point where basically all global level code in my notebooks are now wrapped in dummy functions just to prevent the proliferation of globals.
@iife # defined in the first cell as: iife = lambda f: f()
def _():
for (n, dataset) in enumerate(data):
print(f'Item {n}: ')
do_a_bunch_of_stuff(dataset)
And now for something completely different
Little-known fact: Python supports some pretty aggressive forms of shadowing in list comprehensions.
def frobnicate(value):
print( # Equivalent to:
[ value for value in value # [ x for plane in value
if len(value) == 1 # if len(plane) == 1
for value in value # for row in plane
for value in value # for x in row
] # ]
) # [5, 6]
print(value) # [[[1, 2], [3, 4]], [[5, 6]]]
frobnicate([[[1, 2], [3, 4]], [[5, 6]]])
Even the authors of pylint don’t seem to be aware of this.
$ python3 -m pylint --disable=bad-whitespace,bad-continuation,missing-docstring travesty.py
No config file found, using default configuration
--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)