On Sat, Nov 28, 2020 at 12:57:12PM +1100, Cameron Simpson wrote: > Got a nice example of somewhere where shadowing would be useful and hard > to do some task otherwise?
Mocking. For example: https://docs.python.org/3/library/unittest.mock.html#patch-builtins Monkey-patching. One nice trick I've used is if I have a function that is excessively verbose, printing oodles of unwanted output, I can monkey-patch the owner's module, without affecting other calls to print: import library library.print = lambda *args, **kw: None result = library.noisy_function() There are other ways to get the same result, of course (change sys.stdout, etc) but I find this works well. Another technique I've used occassionally: I need diagnostics to debug a function, but I don't want to insert debugging code into it. (Because I invariably forget to take it out again.) But I can sometimes monkey-patch a built-in to collect the diagnostics. I don't have to remove my debugging code from the source because it isn't in the source. I've even shadowed entire modules: - copy module A to a new directory; - modify the copy; - cd into the directory; - run your code as normal, with the sole difference that "import A" will give you the patched (experimental) A rather than the regular A. But most important of all: we must be able to shadow module level globals with locals with the same name, otherwise the addition of a global "x" will suddenly and without warning break any function that happens to also use "x". > >The problem is, if your inner block scope must not reuse variable names > >in the outer function scope, well, what's the advantage to making them > >seperate scopes? > > Lifetime, but in terms of referencing resources and also accidental > (mis)use of something beyond where it was supposed to be used. I used to > do short {...} scopes in C all the time for this purpose. I keep hearing about "accidental misuse" of variables. Are people in the habit of coding by closing their eyes and mashing the keyboard until the code compiles? *wink* Seriously, how big and complex are your functions that you can't tell whether you are re-using a variable within a single function and are at risk of doing so *accidentally*? Martin Fowler suggests that (Ruby) functions with more than, say, half a dozen lines are a code smell: https://www.martinfowler.com/bliki/FunctionLength.html 75% of his functions are four lines or less. Even if you think this is a tad excessive, surely we're not in the habit of writing functions with hundreds of lines and dozens of variables, and consequently keep accidentally re-using variables? This seems especially ... odd ... in a language with declarations like C, where you ought to easily be able to tell what variables are in use by looking at the declarations. Unless of course you are in the habit of scattering your declarations all through the function, an anti-pattern that block-scopes encourages. "Only block-scopes can save us from the incomprehensible mess of code that uses block-scopes!" *wink* > >Analogy: I think most of us would consider it *really weird* if this > >code was prohibited: > > > > a = None > > > > def func(): > > a = 1 # Local with the same name as the global prohibited. > > Yes, but a function is a clean new scope to my mind. (Yes, closures.) As > opposed to this: > > f = None > > # define a lifespan for "f" using the fictitious "as new" syntax. > with open("foo") as new f: > process file f > > # f is None again > > I'd be all for forbidding that, and instead requiring a different name > for the with-statement "f". This implies that in your mind, with statements are *not* "clean new scopes" like functions are. If they were, you would be quite comfortable with the with statement introducing a separate variable that happened to be called f but was distinct from the local variable "f" or global variable "f" outside of the block. I think that's telling. Functions make naturally distinct scopes, which is why we're comfortable with locals and globals with the same name. Likewise comprehensions: they look and feel self-contained, which is why people were surprised that they leaked. Nobody said that we ought to prohibit the comprehension from using the same names as the surrounding function. But blocks don't make natural scopes. We can force them to be separate scopes, but even (some) people who want this feature don't actually think of them as naturally distinct scopes like functions with independent sets of names. If a function and a block use the same name, they see that as a clash, rather than two distinct, independent, non-clashing scopes like functions or comprehensions. > >One possible advantage, I guess, is that if your language only runs the > >garbage collector when leaving a scope, adding extra scopes helps to > >encourage the timely collection of garbage. I don't think that's a big > >advantage to CPython with it's reference counting gc. > > I'm not concerned directly with garbage collection so much as preventing > use of a name beyond its intended range. Which is the next bit: [...] > >Why do you care about the *name* "i" rather than whatever value is bound > > Because of intent. Why do we encapsulate things in functions? It isn't > just releasing resources and having convenient code reuse, but also to > say "all these names inside the function are only relevant to it, and > are _not_ of use after the function completes". That cannot possibly be true, because we use the same *names* after the function completes all the time. That's why functions introduce a new scope, so that distinct scopes can use the same names without stomping on each other! If we cared about the *names*, as you insist, we would insist that names in functions have to be globally unique. But we don't. Functions are their own distinct scopes so that we can use names inside a function without caring whether those names are used elsewhere. But I can't do the same for block scopes if names are forced to be unique. Instead of being independent, the scopes are strongly coupled: every time I use a name inside a block, I have to care whether it is already used outside of that block. So blocks are not actually independent scopes under the Java semantics. (They are independent in C.) [...] > >But I don't get why I might care about the lifetime of a *name*. > > Maybe not. But I very much do. I think you actually care about the value bound to the name, but mistake it for the name itself. Consider: with open(...) as new f: # new scope, the *name* f is only allowed in this block process(f) g = f # smuggle the *value* of f outside the block do_something_with(g) If you care about the *name* f, then you will be fine with that. Nobody is reusing f, so it's all good! -- Steve _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/DHU3YT5XE44G7HFETBVZT7BMGB62EGEO/ Code of Conduct: http://python.org/psf/codeofconduct/