A two-namespace model for Python modules to distinguish public/internal

Certainly doable and worth considering. However, for a start, I’m inclined to keep the logic simple. Since you can import a whole module from . import foo, we’d need to somehow attach the info to foo that it’s ok to access the internal attributes. As far as I understand the mechanics there is no obvious place to put that information. Its solvable bot needs extra care. I’d therefore postpone this.

IMHO the key aspect of the proposal is the additional internal implementation namespace. How we manage package-internal accessibility is something that we can built on top.

Okay sure, makes sense to keep it lightweight, might make it easier to agree on. But for a proper proposal you need to commit to some sort of user interface for this, which from what I understand from your first post you also see as out of scope? If so, what is the proposal, exactly? A new internal namespace has no use if there’s no way to restrict what’s in the public namespace.

So if I’m following this correctly, the __internal__ namespace (or whatever it may be called) will exist within the module global namespace. That way default behavior is maintained and only specifically declared internals would be shifted into the __internal__ scope and be “hidden” from anyone accessing the module externally?

# mod.py

class A: ...

@internal
class B: ...

print(globals())

>>> import mod
{
    'A': <class A>,
    '__internal__': {
        'B': <class B>,
    }
}

And if a module is imported relatively (from within the library itself), the __internal__ namespace would be hoisted into the global namespace?

>>> from . import mod
{
    'A': <class A>,
    'B': <class B>,
}

The latter might be better solved by just allowing import to search __internal__ in relative mode, but that’s implementation details.

Please let me know if I’m way off, but that’s my interpretation of this idea while trying to maintain existing functionality (everything is in the global namespace by default).

In my first proposal I intentionally focused on the namespace as the underlying concept. Of course the interface needs to be pinned down at some point, but it’s an independent design choice and basically all interface aspects discussed in PEP 842-844 (decorator or keyword, mark internals or mark the public API, …) could be implemented for the namespace approach. I didn’t want to pin one design choice down up front because that would run the danger of distracting from the fundamental idea that we should have a dedicated namespace.
No matter whether you prefer a new keyword or not, I want people first to agree that distinction between public and internal is relevant and that holding this information in separate namespaces is a fitting implementation.

As for a concrete proposal, as written in A two-namespace model for Python modules to distinguish public/internal - #39 by timhoffm I’m inclined to go for

  • using the current module as the public one and making a second internal namespace.
  • declaring names internal is opt-in
  • using a new internal keyword:
    • internal import foo, from foo internal import bar
    • internal count = 0
    • internal def func():

This allows for gradual adoption. But it’s really up for discussion. Just for illustration, you could also make a completely different interface:

  • the current namespace is considered the implementation namespace
  • from __future__ import privatemodule results in setting up a second namespace __public_dict__
  • A __public__ variable at the top of the module declares the names that should be visible from the outside. (Preference to declare the public parts in a single place)
  • When a name is declared (such as defining a function), we check whether it is in __public__ and then add it to __public_dict__. When parsing the module is complete, we issue a warning if the keys in __public_dict__ do not match the __public__ declaration.
  • External access always runs through __public_dict__

This should illustrate that we have a lot of design freedom using the namespaces approach. Thus, with the idea of two namespaces in the back of our head, we can really think broadly how to best declare public/internal.

2 Likes

That would be one solution. Alternatively, __internal_dict__ could exist next to __dict__ and __internal__ would be a special accessor: Just like module.foo looks up in __dict__, module.__internal__.foo would look up in __internal_dict__. These are implementation details that will need careful consideration, but don’t substantially alter the overall idea.

Sort of yes, but I’d like to rephrase: The main idea is to split public/internal and “hide” internals in the sense that they are not accessible via the standard way from the outside (module.foo), but only through a different a different interface (such as __internal__).

Whether we want to mark the internals or declare the public interface is up for discussion. See also A two-namespace model for Python modules to distinguish public/internal - #44 by timhoffm

This is the topic on whether other modules in a package should still have access to each others internals.This is certainly feasible and worth discussing at some point. But as written in A two-namespace model for Python modules to distinguish public/internal - #13 by timhoffm I’d rather postpone this topic to keep the focus on the main idea.

1 Like

So in this case __internal__ would be inaccessible from __dict__ and only the only way to access it is via an explicit import.

>>> import foo
>>> foo.__internal__
# Attribute Error or something special

>>> import foo.__internal__ as ifoo
# okay

I do like that the separation comes at the import level, which means you need to be very explicit about including the internal namespace.

Alright alright, I’m starting to understand what you’re going for here! Thank you for your patience on this. Personally I like the export from PEP842 better, but I agree now that the syntax for how to create the separate namespaces is somewhat orthogonal to which namespaces exist and how they’re related.

Okay I think I’m warming up to that idea as well. So basically we would say that for the time being, library authors have to use __internal__ to access internal symbols from other modules in their own package, just like their users. The have to “pay” with this small inconvenience for the benefit of clearly communicating to users and tooling that these symbols are not part of the package’s public API.

Then later, we can devise a scheme that removes this inconvenience for package authors, but this changes nothing to external consumers of a package. Is that right?

The main issue I have with this approach is that realistically, if package authors adopt this approach in, say 3.18, it gets adopted, and we ship the inconvenience fix in, say 3.19, it will realistically take 5 or more years until package authors can actually remove all the __internal__ occurrences from their code, lest they break support for one python version. Maybe that’s fine, package authors could also just wait to adopt the feature until it’s complete.

Okay, but we should at least align on the semantics, of this, right? Because Python is so dynamic and inspectable, people will rely on whatever we come up with here, so we should be as certain as possible that these are not going to change.

Personally, I would argue for the simplest possible solution, which in my opinion, would be this:

  • __dict__ is the default namespace in every module, it always exists and always contains all symbols just like it currently does
  • If the module defines any private/internal/exported/public symbols (however that is done), the two namespaces __exported__ and __internal__ will also be defined. They are mutually exclusive and neither contains the module’s imports by default
  • Any access, whether via import, __getattr__ or whatever else, if __exported__ is defined, looks up symbol in that. If not, it’s looked up in __dict__; dir() and help() follow the same logic

That seems like all the rules that are needed, but I’m noticing that this makes the semantics of imports of submodules and symbols somewhat inconsistent. Not sure how to reconcile that.

The 2-namespace model is close to what Java offers when it comes to protected: Controlling Access to Members of a Class (The Java™ Tutorials > Learning the Java Language > Classes and Objects). The idea is that only your package is allowed to use something that is protected and not the outside world (hence the distinction between “internal user” (the library itself) and “external user” (the people installing the library in their projects)).

From a C++ perspective, that’s identical to have a details/impl namespace. Such namespaces are not meant to be accessed from the outside and only from inside the library. And then the non-details/impl namespaces expose the public API (to the outside world; it can also be used by the library itself).


I’m in favor of having this. But as Barry said, it’s also fine to have something called def _foo() and use it in your own library independently of how it’s exported. As a library author, you can decide “whta is public is public for everyone, what’s private is private for everyone or public for the library (i.e. can’t be public for the outside world)”. If users aren’t happy with that, they can be blamed! (pytest has passes a special keyword argument a bit everywhere to ensure that the function isn’t used at runtime; it forces users that want to use the internal API that maintainers are no longer responsible for how they use the API).


Again, on those kind of topics, I would rather see a way to cheaply enforce those rules at a language level rather than making this less obvious how to access internals from the outside world. But since Python doesn’t offer this (and likely won’t be offering it since it’s not the philosophy of every Python programmers), I would also support a way to make the distinction between what’s allowed in the outside world more clearly.

2 Likes

While one could do this, it’s not what I envisioned. Accessing __internal__ would be more like a built-in getattr; i.e. some hard-coded access logic not going through __dict__.

I’d rather discourage the import pattern. Using __internal__ (or however we want to call the accessor) at the usage site explicitly seems better than hiding it behind a renamed ifoo, which is less obvious to access internals.

2 Likes

So something like from a_package.__internal__ import a_function shouldn’t work in your opinion? I feel that’s pretty clear about what the contract is.

Yes.

My desire to start minimal is primarily for the design phase. Let’s not get lost on additional bells and whistles before there is a reasonable support for the main idea. But if that part is one year later, it’s also not the end of the world.

However, you are touching an important topic: Is there a way for adoption in newer python versions while still keeping backward compatibility for older versions? Worst case this is something that needs several years to be adopted because libraries would need to first drop pythons versions that don’t work with it - but that woud be no worse than other syntax changes. Maybe there are clever ways around. Overall, it’s not a deal breaker but also an aspect to be considered when refining the design.

Absolutely right. And since it’s such an important decision, I’m taking it slow to prosing exact semantics. In fact, I’m still in the phase of collecting the needs of users and library authors, which will inform a concrete proposal. I’m right now planning to broader input on this.

Thanks for the idea. So far I have only considered to namespaces: either __dict__ being publically accessible plus a new internal namespace, or __dict__ being internal plus a new public namespace. Is your threfold namespace __dict__, exported, internal adding the package-level visibility to the mix? Also you say

if __exported__ is defined, looks up symbol in that. If not, it’s looked up in __dict__

That looks like __exported__ and __dict__ have the same access level? Is that true, and if so what is the difference between them?

2 Likes

It should work. As well as import a_package; a_package.__internal__.a_function. In the end I would leave this up to the user as both are reasonable and it may depend on the context which one is more appropriate.

1 Like

The exact semantics is still to be determined. In the minimal form, I’ve original seen this - translated to Java terms - as public and package-private with an opt-in mechanism to access package-private from the outside (because we are consenting adults). This minimal form is motivated by the current import logic not distinguishing between imports from the same package or from a completely different namespace.
But the package-private level has come up multiple times and it will be part of the discussion when refining the semantics.

Leading underscore will remain a valid convention. I’m not intending to change anything about it.

1 Like

what if that was done just at the linter level?
like - a static checker can “know” what is defined in the module and what has been imported, and things that have been imported and are absent of __all__ would naturally be “internal”. No changes needed to the language (“simple is better than complex” and I can’t think of “two parallel namespaces that are mostly equal but not quite” being simple), but a PEP orientating the desired linter behavior and terminology could be good.

1 Like

AFAIK, linter-level already (partly?) works with __all__. It’s a question to be answered whether that’s enough. Added benefits of distinct namespaces would be

  • the possibilitly for runtime feedback/enforcement
  • obvious distinction at usage: With separate namespaces the call pattern would make the internal usage explicit (e.g. though something like mymodule.__internal__.internal_name - exact spelling t.b.d.) whereas currently mymodule.internal_name looks exactly like mymodule.public_name.
  • possibly better internal management than __all__ as a separatly maintained list of names. - though some optimizations are still possible with __all__, e.g. PEP 844.
1 Like

Poll: Public/internal handling — what behavior do we want?

The recent discussions show that there are several different expectations for how public and internal should work. I’ve created a poll to collect a broader view of these preferences before we settle on a particular approach.

Please take a moment to vote here: Poll: Public/internal handling — what behavior do we want?

1 Like

I’m not saying anything about whether the conventions will remain or not. I’m just saying that I understand why people don’t want yet another change. That’s why for such proposals, in general, I’d be much more convinced if runtime checks were considered rather than conventions.

Yes, and I think that’s what should be preferred IMO. I would rather see changes in imports (import $package instead of import package to access internals for instance) rather than having more namespacing. Adding namespacing is tricky and really annoying in the interpreter. Because you will need to also implement this functionalits on ModuleType itself. I don’t know how easy it’s to always add an __internal__ field to it, or some other magical field on the object.

Now it’s me thinking loud:

Dedicated import syntax

I suggest from module import $my_internal_function (with the $), e.g.:

import module  # regular import
module.$my_internal_function()  # access internal function
  
import $module  # expose internals automatically
$module.my_internal_function()
  
from module import $my_internal_function
$my_internal_function() 

I don’t have a better syntax suggestion than that and I understand it can be weird. I used $ to avoid confusion with ‘@’ possibly, and I can’t use ‘#’ as it’s for comments. But feel free to propose a better syntax here (we could use ! as well to stress that "this object must exists so don’t complain about it unless it doesn’t) or accept any local currency symbol).

Dedicated declaration syntax

In general, objects have a single dictionary, namely __dict__, and I think this assumption is spreaded around the interpreter’s code so it may not be as a transition.

Currently __dict__ is expected to be a Mapping[str, Any]. However, I’d like to offer another built-in type that would be a “scoped” dictionary. That scoped dictionary is also a way to achieve C++/Java-like scopes from an inheritance perspective. More precisely, I suggest adding a __attributes__ field as follows:

class DictByScope(NamedTuple):
    public: Mapping[str, Any]
    private: Mapping[str, Any]

When accessing something regularly, you would access __attributes__.public. If it’s package private, you would query __attributes__.private.

The above approach could be extended to classes and inheritance towers by also adding a protected field so that protected declared fields can only by accessed by subclasses (same as C++/Java).

When accessing __dict__ directly, it would simply be the merge of __attributes__.public | __attributes__.private so that we don’t break anything (i.e., it’s still possibe to bypass any accesses when directly accessing __dict__, but not via a simple x.y where the . is implicitly "query __attributes__.public only and x.$y is "query __attributes__.public or __attributes__.private, starting with __attributes__.private). I don’t know if it’s possible to have a mappingproxytype based on multiple dicts (for instance public’s and private’s keys should have empty intersections).

1 Like

Almost. The reason is backwards-compatibility; all existing code that uses __dict__ in whatever cursed ways will continue to work exactly like before. The difference is that __external__ will only contain the symbols that have been declared as such. So, if a module actually uses __external__ and __internal__ we get the runtime checks on symbol access that @picnixz talked about; direct access of an internal symbol will fail and will require using .__internal__.

In my mind, the “union” of __external__ and __internal__ would be __dict__. Whether __dict__["__external__"] should exist I’m still uncertain about, but both __dict__["__internal__"] and __external__.__internal__ seem kinda logical for the access syntax of mymodule.__internal__.symbol.

My assuption was that you basically keep backward compatibility by having __dict__ as the default. Only when one explicitly marks elements as internal, they are instead added to __internal__ so that all internal stuff is opt-in and new so that backward compatibility is not an issue. Are there any use cases that would benefit from the additional structure?

2 Likes

I agree. There is no problem to be solved. The leading underscore at variable, class or module level, as well as __all__ imo are good enough, they work and they are widely accepted.

As far as conventions go for a common place/naming convention to put the public api, I would be in favor of a (optional) .api module as a central place. Linters and IDEs could use this as the prioritized lookup namespace if it exists, and otherwise fallback on __all__

1 Like