@Paul Bryan, I'll try to address your questions one-by-one

1. It seems that I could get close to what you're aiming for by just using
> underscores in names, and grouping them together in the class definition.
> Is there any advantage to declaring methods in a namespace beyond
> indentation and the dot notation?
>

What you're suggesting (using underscores for namespacing) is something
I've seen done many times (and done myself plenty of times) and it's
precisely one of the main things that I think could be better under this
proposal. Reusing a prefix like that across methods has always felt a bit
hacky to me and like it doesn't follow the DRY principle very well. If you
wanted to rename the prefix at a later point you would have to go through
every single method that uses it, instead of just doing a single
refactor/rename on the name at the top of the namespace block.

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)
- 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.

Furthermore, the proposed namespaces have other selling-points outside of
the use-case of grouping methods within a class.

While the benefit of namespaces within modules is more dubious because you
can use class blocks for namespacing (and people often do),  can see a few
ways that the namespace proposal is better:

- you can put functions inside a namespace block, which would become
methods if you had put them in a class block
- 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.
- it mode clearly indicates intent (you don't want a whole new class, just
a new namespace)

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. 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()


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())


So it is a terrible idea to use it in this way to write any kind of library
code. You could invoke it declaratively:

class Biped:
    def __init__(self):
        class Legs(SimpleNamespace):
            left, right = LeftLeg(), RightLeg()

        self.legs = Legs


Not only is this an unintuitive usage pattern and creates lots of
unnecessary classes (one for each new instance of Biped), but it
significantly reduces code clarity (imagine having lots of these inside a
single method). By contrast, a namespace block seems to me to increase code
clarity, though I will grant that this is subjective.


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'

The implementation of this namespace proxy object might look something like
this, only implemented in C to hopefully be more performant than this:

class NamespaceProxy:
    ...
    def __getattr__(self, name):
        return getattr(self.__parent_scope__,
f"{self.__namespace_name__}.{name}")

    def __setattr__(self, name, value):
        setattr(self.__parent_scope__,
f"{self.__namespace_name__}.{name}", value)


The names of the dunders are hypothetical and not something I'm concerned
about at this stage.

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.


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"

4. What would you expect getattr(A.B, "C") to yield?
>

It would be no different than:

>>>A.B.C


It happens in two steps:

1) A.B - this looks up vars(<module '__main__' (built-in)>)['A.B'], which
is a namespace object
2) getattr(<namespace object <A.B> of  <module '__main__' (built-in)>>,
"C") - this looks up "C" on namespace "A.B", which forwards this lookup on
to the module as: vars(<module '__main__' (built-in)>)['A.B.C']

The namespace proxies created in a namespace block do nothing more than
forward on any attribute access/attribute setting operations performed on
them to their parent scope, so you can do any attribute operations to them
that you could do to a 'real' intermediate object.


I hope I've answered everything with reasonable clarity.

I'm kind of exhausted from writing this up, but I'll finally just quickly
mention in response to David Mertz that your proposal to use a metaclass
for this wouldn't really serve to provide virtually any of the benefits I
listed out in my answer to Paul (part 1, in particular). it wouldn't work
for modules/locals, and it can't be used at the instance level with `self`,
only the class level. It would also restrict usage of other metaclasses.
What it would end up doing (saving class attributes of nested classes to
its own __dict__ with a fully-qualified name) is an implementation detail
of my proposal rather than one of the primary benefits.

>From where I'm standing it feels like you just took an immediate initial
dislike to the proposal and didn't even really give it a chance by actually
thinking through the pros/cons before giving your -1.

It's fine if you think it's not useful enough to be added. Maybe that's
true. But it would be more helpful to maybe ask questions like other people
are doing and be sure you understand the rationale/implications of the
proposal before making your mind up.

Three negative responses in you still didn't understand a really simple
implementation point that's written very early on in the toy example of the
doc, which differentiates it from types.SimpleNamespace. Then when Steve
pointed that out to you, you immediately dug your heels in again without
really thinking your 'it can be done with a metaclass' counterpoint all the
way through. At least give yourself the chance to change your mind! Even if
you end up settling on your initial intuition, at least that way you'll
know that you've explored all the options and done your best to land on the
most informed opinion you can.

Anyways, I'm sorry if I totally misrepresented the situation. It's hard
sometimes to tell over a medium like this what page someone else is on. I
just figured I'd tell you how it felt to me. I hope I wasn't totally
off-base.

On Mon, May 3, 2021 at 8:16 PM Paul Bryan <pbr...@anode.ca> wrote:

> Correction:
>
> 4. What would you expect getattr(A.B, "C") to yield?
>
> Paul
>
> On Mon, 2021-05-03 at 12:10 -0700, Paul Bryan wrote:
>
> I've read the proposal, and this thread.
>
> Questions:
>
> 1. It seems that I could get close to what you're aiming for by just using
> underscores in names, and grouping them together in the class definition.
> Is there any advantage to declaring methods in a namespace beyond
> indentation and the dot notation?
>
> 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?
>
> 3. Can namespaces be nested? If so, will their attributed they always
> resolve to flat set of attributes in the encapsulating class?
>
> 4. What would you expect getattr("A.B", "C") to yield?
>
> Paul
>
> On Mon, 2021-05-03 at 19:49 +0100, Stestagg wrote:
>
> The first example in the doc lays out the difference:
>
> Assignments within the namespace block have this special behaviour whereby
> the assigned-to name is changed to be:
> ‘<namespace name>.<assignment name>’
> And the assignment is made in the ‘parent scope’ of the namespace.
>
> I.e. (again, as described in the doc):
>
> class A:
>     Namespace B:
>         C = 1
>
> Results in:
>
> A.__dict__ == {‘B.C’: 1, ‘B’: <namespace proxy>}
>
> Note the ‘B.C’ entry
>
> Now for me, the only place where is starts to be interesting is with
> methods within namespaces, where the ‘self’ binding is made against to
> top-level class, and not against the namespace.  This does make for some
> nicer nested API definitions (a-la pandas DataFrame.str.xxx methods) where
> an intermediate name os used just to group and organise methods.
>
>
>
> On Mon, 3 May 2021 at 19:40, David Mertz <me...@gnosis.cx> wrote:
>
> On Mon, May 3, 2021 at 6:37 PM Stestagg <stest...@gmail.com> wrote:
>
> On Mon, 3 May 2021 at 19:24, David Mertz <me...@gnosis.cx> wrote:
>
> So yes... as I thought, SimpleNamespace does EVERYTHING described by the
> proposal, just without needing more keywords:
>
>
> Except that the code and description of the proposal explicitly outline
> behaviours that SimpleNamespace does not provide (and aren’t trivially
> possible to add)
>
>
> I've looked and been unable to find an example of that. Can you show one?
>
>
>
> _______________________________________________
> 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/YTARLBP3TIARJ4FUEPPDZAUIS33P2C3Q/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
>
> _______________________________________________
> 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/UAZBIPDC3DRSA5A2GST72KDO2Y2R6RBX/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
>
> _______________________________________________
> 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/Q2OTIBAPH7GUBAMKOY3DF3HZWQI4OZMO/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
_______________________________________________
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/DOHJKTKNDRDDEV6D76OKD7LVND77U76C/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to