On Wed, May 5, 2021, 02:11 Steven D'Aprano <[email protected]> wrote:
> 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. > I agree with this. Great proposal though. > > > - 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 -- [email protected] > To unsubscribe send an email to [email protected] > https://mail.python.org/mailman3/lists/python-ideas.python.org/ > Message archived at > https://mail.python.org/archives/list/[email protected]/message/JF6OWQJ7JRH4CGUWU3APCDMSAB37MR67/ > Code of Conduct: http://python.org/psf/codeofconduct/ >
_______________________________________________ Python-ideas mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/[email protected]/message/FUKJQCPMSBWFQ6YHZCKLOXJOD2BV2YKA/ Code of Conduct: http://python.org/psf/codeofconduct/
