How do they differ from the modules themselves?
@BenjyWiener talked about that question a few comments up
Where? The only thing I can see is the post where you talked about namespaces with parameters, but that’s not the original proposal here.
As far as I can see, a module is pretty much equivalent to the namespace idea proposed originally, and the only objection given to using a module was “creating a submodule isn’t always desirable”. Which may or may not be true, it’s hard to tell without evidence - I personally think the inconvenience of creating a module is far too small to justify a whole new language construct.
If you want to propose something more complicated than the original proposal, such as parametrised namespaces, you’ll need to offer a complete proposal, including evidence that there’s enough real-world use cases (not just theoretical ones like your math_funcs example) to justify the feature.
Going back to the original proposal:
What’s wrong with the following?
def get_xy():
big_object1 = json.loads(big_json.read_string())
big_object2 = json.loads(big_json.read_string())
return big_object1['x'], big_object2['y']
x, y = get_xy()
I said that benjywiener talked about it a few comments up not myself. maybe other people can’t see it if i tag someone? Still figuring out how this forum works.
Yeah I didn’t mean to distract from the original proposal sorry about that. I just got really excited because I’ve been exploring this same domain pretty much!
They would have direct access their outer scope.
I mentioned that I started exploring the idea in the context of the public/private discussions (see the example in my original post).
It’s not clear that the point of the function is (to me it looks like the result of an incomplete refactor, and without an explanatory comment I could easily see someone coming along later and “simplifying” it away.
You also polute the module with an extra name (and now-useless function).
And what if there are more than two names?
Do you have a non-trivial example to discuss? I’ve often found that, if it’s worth hiding names away, there’s some value in the task being done, making the function non-useless (eg “load the configuration from this directory”).
No, I just misread what you wrote, sorry.
OK, I guess that’s subjective. Also, I didn’t try very hard to make it maintainable, as it was just a one-off example.
True. You could call it _ if it mattered that much to you.
Return more than two values? Or you could use global like your namespace version did, if you prefer that.
Ultimately, it’s a matter of preferred style, I suspect. I tend to prefer something that works right now, and is pretty readable (to me). If your preference is different, that’s fine - although you’ll need more than just preference to justify a language change, so I suggest you look for some more compelling use cases (preferably real-world code that would benefit from the feature).
There was a proposal many years ago (not PEP 834, the one I’m thinking of pre-dated that) for something that sort of combined PEP 834 and this proposal. Unfortunately, I can’t find a reference to it. It ultimately got rejected, from what I recall because it was mostly of theoretical use, but didn’t have enough practical use cases. So there’s definitely precedent for this type of idea, but they doo need good, practical use cases.
Will the usual dunders work? For example:
namespace MISSING:
def __bool__():
return False
assert not MISSING
I’m not against some kind of real scoping mechanism other than classes, submodules, or a function that only exists to create a closure, but I don’t see this quite being it.
Personally, I’d be much more interested in arbitrary scope blocks that can optionally return an object to be assigned to a name, and that names not returned from the scope don’t escape the scope.
The stdlib contains many del statements in an attempt to keep modules “clean”[1]. Whether simple intermediate values, helper functions, or loop variables, many of these could be re-written more cleanly and DRY with an anonymous namespace. What I was more interested in, though, was missing dels. For that I had Claude help me scan the stblib, and I’ve included a couple of examples that I’ve manually verified (the prose is all mine):
opname = ['<%r>' % (op,) for op in range(max(opmap.values()) + 1)]
for m in (opmap, _specialized_opmap):
for op, i in m.items():
opname[i] = op
m, op, and i are all leaked.
for name, op in _specialized_opmap.items():
# fill opname and opmap
assert op < len(_all_opname)
_all_opname[op] = name
_all_opmap[name] = op
name and op survive, despite other dels in the same file.
The above examples can be “fixed” with a one-off function that’s then deleted. But that still requires repeating a name, and the resulting code is[2] pretty ugly. I see def/call/del → namespace as try/finally → with; namespace is essentially context manager for name bindings. Using del like this feels to me like C-style manual memory management, reminiscent of free.
Then there’s the closure use case. os.py contains the following code:
def _fscodec():
encoding = sys.getfilesystemencoding()
errors = sys.getfilesystemencodeerrors()
def fsencode(filename):
"""Encode filename (an os.PathLike, bytes, or str) to the filesystem
encoding with 'surrogateescape' error handler, return bytes unchanged.
On Windows, use 'strict' error handler if the file system encoding is
'mbcs' (which is the default encoding).
"""
filename = fspath(filename) # Does type-checking of `filename`.
if isinstance(filename, str):
return filename.encode(encoding, errors)
else:
return filename
def fsdecode(filename):
"""Decode filename (an os.PathLike, bytes, or str) from the filesystem
encoding with 'surrogateescape' error handler, return str unchanged. On
Windows, use 'strict' error handler if the file system encoding is
'mbcs' (which is the default encoding).
"""
filename = fspath(filename) # Does type-checking of `filename`.
if isinstance(filename, bytes):
return filename.decode(encoding, errors)
else:
return filename
return fsencode, fsdecode
fsencode, fsdecode = _fscodec()
del _fscodec
This works, but what about the __qualname__s of fsencode and fsdecode?
>>> import os
>>> os.fsencode
<function _fscodec.<locals>.fsencode at 0x1030f8eb0>
>>> os.fsdecode
<function _fscodec.<locals>.fsdecode at 0x1030f8f60>
>>>
Finally, ipaddress.py has the _IPv4Constants and _IPv6Constants namespace classes.
None of these things requires namespace, but I think all would benefit in terms of clarity and maintainability.
No, they would essentially be objects with instance attributes. Maybe __getattr__ (like modules), but I think that’s already bike-shedding territory.
I am not sure whether that is a namespace definition or whether it runs right away. What does the comment “y and z are undefined here” mean, and why? Usually, they shouldn’t be. Why is the comment there?
There’s no such thing as a “namespace definition”—the body of a namespace always runs immediately, albeit with its own locals dict/scope. y and z belong to that scope. It would be roughly equivalent to the following:
def _f():
global x
y = ...
z = ...
x = y + z
_f()
del _f
When a name is provided (namespace some_name: ...), it’s roughly equivalent to this code:
@(lambda f: types.SimpleNamespace(f()))
def some_name():
...
return locals()
A big advantage that classes have over a would-be primitive namespace is metaclasses.
Eg: I have this code with which one can create color palettes by specifying simple "#hexhex" color strs, but the metaclass ColorPaletteMeta transforms them into high-level Color instances automatically for us ![]()
code
class Color:
def __init__(self, name: str, color: tuple[int, int, int] | str):
self.name = name
if isinstance(color, tuple):
self.rgb = color
else:
self.rgb = self._hex_to_rgb(color)
def _hex_to_rgb(self, color_hex: str) -> tuple[int, int, int]:
color_hex = color_hex.lstrip("#")
return tuple(int(color_hex[i: i + 2], 16) for i in [0, 2, 4])
@property
def r(self) -> int:
return self.rgb[0]
@property
def g(self) -> int:
return self.rgb[1]
@property
def b(self) -> int:
return self.rgb[2]
@property
def hex(self) -> str:
return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
@property
def HEX(self) -> str:
return self.hex.upper()
def csi_background(self) -> str:
return f"\x1b[48;2;{self.r};{self.g};{self.b}m"
@staticmethod
def csi_reset() -> str:
return f"\x1b[0m"
def print_sample(self, width: int = 40) -> None:
bg = self.csi_background()
rst = self.csi_reset()
print(bg + f"{'': ^{width}}" + rst)
print(bg + f"{self.name: ^{width}}" + rst)
print(bg + f"{self.hex: ^{width}}" + rst)
print(bg + f"{'': ^{width}}" + rst)
class ColorPaletteMeta(type):
def __new__(cls, name, bases, classdict):
for k, v in classdict.items():
if k.startswith("__"):
continue
classdict[k] = Color(k, v)
return super().__new__(cls, name, bases, classdict)
class ColorPaletteBase:
@classmethod
def colors(cls) -> list[Color]:
return [v for (k, v) in cls.__dict__.items() if isinstance(v, Color)]
class ColorPalette(ColorPaletteBase, metaclass = ColorPaletteMeta):
pass
class ExamplePalette(ColorPalette):
vs_purple = "#865FC5"
moon_cheese = "#FFEEAD"
rebane_green = "#7BAE7F"
favorite_teal = "#4D8A8B"
for color in ExamplePalette.colors():
color.print_sample()
The reason for the separate ColorPaletteBase and ColorPalette is so that the ColorPaletteBase.colors is untouched by the ColorPaletteMeta.__new__.
I remember writing this code because I was trying to understand how Flask WTForms’s form metaclasses work; it was my first direct interaction with metaclasses ![]()
The proposal was never to replace classes. Your example isn’t much different that every other normal usage of class—you’re using a class because you want a Python class with the features they come with (in this case metaclasses and not instances).
In the examples I gave above, class’s semantics would be unnecessary at best, and once you start dealing with functions or nested namespace classes, actively detrimental.
I think the second part applies even more to namespaces, and the general ideas of public and private that are showing renewed interest at present.
I have multiple thoughts in some order:
Why introduce a lobotomized class? Every time I learn something new about how classes work internally I’m impressed at the forethought that has been put into them over the years. eg descriptors, metaclasses, __getattribute__ and __getattr__, etc. It seems a lot easier for one to just use a class and ignore the features that one doesn’t currently need (which one probably is using behind the scenes, just with sane defaults) than to add a primitive ‘namespace’ which would be a step backwards.
I agree with
in that classes can be used as ‘namespace’ containers for constants and static methods, if not a module. Both are “just regular programming” not an anti-pattern; it doesn’t have to explicitly say ‘namespace’ on the tin to behave as a namespace.
When thinking about namespaces being imported across multiple files, it seems like an unnecessary extra layer of indirection on top of importing the files / modules themselves. It’s reinventing the wheel of defining a src/MyModule/constants.py, replacing it with namespace constants: inside eg src/MyModule/__init__.py.
It seems easy to rename a constants.py → constants_renamed.py and use import constants_renamed as constants, or rename a class Constants → class ConstantsRenamed and use from MyModule import ConstantsRenamed as Constants to keep git diffs small. How would one rename a namespace and keep lazy compatibility?
Would namespaces be reusable / extendable?
namespace consts:
x = 1
namespace consts:
y = 2
C++ allows reusing namespaces (not that I wish to encourage any C++ nonsense) but isn’t that ultimately just for name mangling?
Related to the public and internal scoping thread, I’m of the (naive?) opinion that public, private, protected, friend access modifiers smell like 90s (P)OOP: corporate gurus and fizzbuzz professors trying to evangelize and restrict people to their dogmatic code style. It just leads to fighting over ‘I wanted to use that variable but you made it inaccessible’ which is part of the “actively detrimental” we should avoid ![]()
I like OOP, but I don’t think that we should add access modifiers to Python just because other languages have it. I’m in favor of the view: “leave it at _my_variable with an underscore and ‘consenting adults’ can decide if they want to use it”. Namespaces feel like the same issue as you say. Next there’d be pushes for ‘protected namespace’-like behavior that only the “internal” modules can access, with shortcut functions and testing goodies locked behind an access modifier glass case.
The only issue I see addressed by namespaces is less @staticmethods and
which I +0 agree with. JavaScript allows this to behave as the class within a static method, but then again how JS handles this is historically infamous.
class Vec4 extends VecN {
constructor(x, y, z, w) {
super(x, y, z, w);
}
static from_vec3(v3, w = 1.0) {
return new this(v3.x, v3.y, v3.z, w); // 'this' is Vec4
}
...
}
It’s not a lobotomized class. I only mentioned class as something that can kind of fill the need for namespace but not quite. Trust me, I love classes and all of the shenanigans I can do with them
.
That sounds like a case where you really should use a submodule; not a usecase for namespace.
I though about it briefly, and I don’t think it would be the right direction. I don’t feel strongly about that though.
What about imported names? The current “standard” is that imports aren’t part of the public API unless explicitly re-exported (via __all__ or import A as A). But that doesn’t help with auto-complete clutter (whether in an IDE or REPL).
namespace makes it trivial to put everything that’s not public API behind a clearly private, well, namespace. It’s still very much accessible to “consenting adults”, it just helps ensure informed consent
.
… and anonymous namespaces/scopes, and nested namespaces.
That’s all without even getting into the topic of static analysis and generating documentation, which would also benefit from a dedicated namespace construct.
You mentioned JavaScript. I imagine namespace to be the Python version of a bare JavaScript block, with the option to assign the resulting locals to an object.