My comments follow, interleaved with Matt's.
On Mon, May 03, 2021 at 11:30:51PM +0100, Matt del Valle wrote: > But you've pretty much perfectly identified the benefits here, I'll just > elaborate on them a bit. > > - the indentation visually separates blocks of conceptually-grouped > attributes/methods in the actual code (a gain in clarity when code is read) Indeed, that is something I often miss: a way to conceptually group named functions, classes and variables which is lighter weight than separating them into a new file. But you don't need a new keyword for that. A new keyword would be nice, but grouping alone may not be sufficient to justify a keyword. > - the dot notation you use to invoke such methods improves the experience > for library consumers by giving a small amount conceptually-linked > autocompletions at each namespace step within a class with a large API, > rather getting a huge flat list. We don't need a new keyword for people to separate names with dots. Although I agree with your position regarding nested APIs, *to a point*, I should mention that, for what it is worth, it goes against the Zen: Flat is better than nested. [...] > - you can put functions inside a namespace block, which would become > methods if you had put them in a class block This is a feature which I have *really* missed. > - you don't have the same (in some cases extremely unintuitive) > scoping/variable binding rules that you do within a class block (see the > link in my doc). It's all just module scope. On the other hand, I don't think I like this. What I would expect is that namespaces ought to be a separate scope. To give an example: def spam(): return "spam spam spam!" def eggs(): return spam() namespace Shop: def spam(): return "There's not much call for spam here." def eggs(): return spam() print(eggs()) # should print "spam spam spam!" print(Shop.eggs()) # should print "There's not much call for spam here." If we have a namespace concept, it should actually be a namespace, not an weird compiler directive to bind names in the surrounding global scope. > - it mode clearly indicates intent (you don't want a whole new class, just > a new namespace) Indeed. > When using the namespaces within a method (using self): > > - It allows you to namespace out your instance attributes without needing > to create intermediate objects (an improvement to the memory footprint, and > less do-nothing classes to clutter up your codebase) > > - While the above point of space complexity will not alway be relevant I > think the more salient point is that creating intermediate objects for > namespacing is often cognitively more effort than it's worth. And humans > are lazy creatures by nature. So I feel like having an easy and intuitive > way of doing it would have a positive effect on people's usage patterns. > It's one of those things where you likely wouldn't appreciate the benefits > until you'd actually gotten to play around with it a bit in the wild. I'm not entirely sure what this means. > For example, you could rewrite this: > > class Legs: > def __init__(self, left, right): > self.left, self.right = left, right > > > class Biped: > def __init__(self): > self.legs = Legs(left=LeftLeg(), right=RightLeg()) > > > As this: > > class Biped: > def __init__(self): > namespace self.legs: > left, right = LeftLeg(), RightLeg() Oh, I hope that's not what you consider a good use-case! For starters, the "before" with two classes seems to be a total misuse of classes. `Legs` is a do-nothing class, and `self.legs` seems to be adding an unnecessary level of indirection that has no functional or conceptual benefit. I hope that the purpose of "namespace" is not to encourage people to write bad code like the above more easily. > And sure, the benefit for a single instance of this is small. But across a > large codebase it adds up. It completely takes away the tradeoff between > having neatly namespaced code where it makes sense to do so and writing a > lot of needless intermediate classes. > > SimpleNamespace does not help you here as much as you would think because > it cannot be understood by static code analysis tools when invoked like > this: > > class Biped: > def __init__(self): > self.legs = SimpleNamespace(left=LeftLeg(), right=RightLeg()) Surely that's just a limitation of the *specific* tools. There is no reason why they couldn't be upgraded to understand SimpleNamespace. [...] > > 2. If __dict__ contains "B.C" and "B", then presumably the interpreter > > would need to try combinations against the outer __dict__ as well as B. Is > > the namespace proxy you've mentioned intended to prevent further lookup in > > the "B" attribute? > > > > The namespace proxy must know its fully-qualified name all the way up to > its parent scope (this is the bit that would require some magic in the > python implementation), so it only needs to forward on a single attribute > lookup to its parent scope. It does not need to perform several > intermediate lookups on all of its parent namespaces. > > So in the case of: > > namespace A: > namespace B: > C = True > > > >>>A.B > <namespace object <A.B> of <module '__main__' (built-in)>> > > > Note that namespace B 'knows' that its name is 'A.B', not just 'B' If I have understood you, that means that things will break when you do: Z = A del A Z.B.C # NameError name 'A.B' is not defined Objects should not rely on their parents keeping the name they were originally defined under. [...] > Traversing all the way through A.B.C does involve 2 intermediate lookups > (looking up 'A.B' on the parent scope from namespace A, then looking up > 'A.B.C' on the parent scope from namespace A.B). But once you have a > reference to a deeply nested namespace, looking up any value on it is only > a single lookup step. That's no different from the situation today: obj = spam.eggs.cheese.aardvark.hovercraft obj.eels # only one lookup needed > > 3. Can namespaces be nested? If so, will their attributed they always > > resolve to flat set of attributes in the encapsulating class? > > > > Yes, namespaces can be nested arbitrarily, and they will always set their > attributes in the nearest real scope (module/class/locals). There's an > example of this early on in the doc: > > namespace constants: > NAMESPACED_CONSTANT = True > > namespace inner: > ANOTHER_CONSTANT = "hi" > > Which is like: > > vars(sys.modules[__name__])["constants.NAMESPACED_CONSTANT"] = > Truevars(sys.modules[__name__])["constants.inner.ANOTHER_CONSTANT"] = > "hi" Can I just say that referencing `vars(sys.modules[__name__])` *really* works against the clarity of your examples? Are there situations where that couldn't be written as globals()["constants.NAMESPACED_CONSTANT"] instead? And remind me, what's `Truevars`? -- 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/JF6OWQJ7JRH4CGUWU3APCDMSAB37MR67/ Code of Conduct: http://python.org/psf/codeofconduct/