On Tue, Apr 05, 2022 at 02:17:00PM +1000, Chris Angelico wrote:

> Do you ever have one module that's using statute miles and
> another that's using nautical miles, but *not both in the same
> module*? The only reason to have them namespaced to modules is to
> allow different modules to use them independently. If your application
> needs to use both statute and nautical miles in the same module (most
> likely the main module), then it's going to have an issue, and your
> proposal adds a ton of complexity (that's a real unit, by the way, I
> totally didn't make it up) for no benefit whatsoever.

That's not the real problem.

The real problem is that my program may:

* import ham, which registers mile as 1609.3412 m
* import spam, which registers mile as 1609.344 m
* import cheese, which registers mile as 1609.3472 m
* import aardvark, which registers mile as 1609.3426 m
* import hovercraft, which registers mile as 1853.181 m

and then do calculations in miles, before converting to metres, and the 
results I get will be subtly (or not so subtly) different depending on 
the order I import those modules.

(By the way, none of the above are nautical miles; of which there are at 
least three.)


> "If I import * from cheddar first, then camembert, then I have issues".

And that is why you shouldn't `import *`. This is an old, well-known 
issue with wildcard imports.


> What's the difference? You're looking at a fundamentally identical
> problem, and thinking that it's fundamentally solved by module-level
> separation? Show me some evidence.

You are correct that this is fundamentally identical to the problem that 
namespaces are designed to solve. This is why modern languages don't 
have one single system-wide namespace.

We have 30+ years of Python programming, and 40-odd years of programming 
prior to Python, showing that the solution to the name collusion problem 
is to have distinct namespaces rather than one single system-wide 
namespace that everything writes to. That's exactly my point.

Of course if I do this:

    from spam import mile
    from eggs import mile

then I have a namespace collision that results in last value winning. 
But that's kinda obvious doncha think? :-)

Importantly, just doing

    from spam import mile
    import eggs

will not collide, except under the very unusual case that eggs gets up 
to no good by writing to the importing module's namespace.

(Is that even possible? At import time, can eggs tell which module is 
importing it?)



> Have you ever mutated sys.modules?

Not directly, no, except by the approved method of calling `import`, 
which never over-writes an existing entry, only adds new entries.

Nor have I ever mutated the central registry of codecs to *replace* 
an existing encoder (like UTF-8) with my own. Likewise for error 
handlers.

There's only a relatively small number of each, and the two registries 
change so rarely that there is next to zero chance that I might 
accidently trample over an existing codecs or error handler with my own. 
And I do not expect that arbitrary imports will make changes to those 
registries.

Never have I worried that `import spam` might change the meaning of the 
'utf-8' codec, or replace some error handler with one with the same name 
but different behaviour.

But if there were thousands of codecs, and every second module I 
imported could potentially add or delete those codecs, then I would have 
to worry about these things. The system would be unworkable and we would 
have to find a better one.

With units, there are thousands of named units, with many name 
collisions. The system would be unworkable with only a single 
interpreter-wide registry.


> > This is exactly analogous to the situation Python would have if there
> > were no per-module globals, just the system-wide builtins, and every
> > library stored top-level variables and functions in that namespace.
> > *shudders*
> 
> Straw man. It's more like using decimal.getcontext() and making
> changes.

The situation is analogous, but not identical. The decimal context is 
not a interpreter-wide registry of long-lasting entities intended to be 
used by any and all modules. It is a per-thread part of the decimal API.

Its not even a very close analogy: aside from sharing the vague concept 
of "global state" with your units registry, there's nothing like 
registering a unit ("furlongs per fortnight") in the decimal context. 
There are only eight settings, and you cannot set arbitary attributes in 
decimal contexts.

The decimal context environment isn't even truly interpreter-wide. It is 
per thread storage, so every thread has its own independent environment.

Other modules (except possibly your application's main module) are not 
expected to modify the current context, although that's not enforced. 
(This is Python: you can shoot yourself in the foot if you really want 
to.) It would be considered *badly-behaved* for other modules or 
functions to directly modify the current decimal context instead of 
using localcontext() to temporarily change the current context.

P.S. localcontext makes a copy of the context. Just sayin'.


> That's global. Do we have per-module Decimal contexts?

If you want it, you can have it. See PEP 567.


> In fact, the only way to change context is globally - though you can 
> do so temporarily. That means you do not have any module-level 
> separation *at all*. I don't hear the Decimal folks screaming about 
> that.

They don't need to, because arbitrary modules don't make changes to the 
decimal current context. That would be harmful, so well-behaved 
code uses localcontext(), and badly-behaved code doesn't get used.

But with your central, interpreter-wide registry of units, modules which 
wish to use a named unit have no other choice than to register it with 
the central registry.

If my module aarvark.py wants to use a named unit, the pottle (a 
historical measurement equal to 1/2 a British gallon), what can I do?

I can check the registry to see if pottle is already defined. If it 
isn't, great, I can install it, and it will now be visible to every 
module, whether they need it or not.

But what if its already there, with a different value? Now I have only 
two equally bad choices:

1. overwrite the system-wide pottle entry, breaking other modules;

2. or do without.

Because the registry is system-wide, I cannot define my own pottle unit 
without nuking other module's pottle unit.



-- 
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/YO54JFNX576JT62TVSS4FZTQFTZIVURF/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to