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/

Reply via email to