# PEP 842 postmortem: how do we protect the standard library?

**URL:** <https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575>\
**Category:** Core Development\
**Created:** [August 15, 2026, 6:56pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575 "2026-08-15T18:56:54Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [August 15, 2026, 6:56pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/1 "2026-08-15T18:56:54Z")

</div>

Hi,

I recently wrote [a proposal](https://peps.python.org/pep-0842/) to hide private attributes from modules. My motivation for this was to prevent people from relying on stdlib internals. Now that the PEP has been withdrawn, I still think we need to find a way to achieve this goal, because the status quo of prefixing with `_` just isn’t working.

So – what should we do? From the discussion thread, the two ideas so far are:

1. Implement something similar to PEP 842, but only for the standard library.
2. Convert standard library modules to a “hub” model, where modules are separated into two parts: one that holds the implementation, and one that exports them. For example, `random.py` could become `random/ __init__.py` and `random/_impl.py`.

---

<div class="post-metadata">

**Author:** ![nedbat](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nedbat/32/8744_2.png) [@nedbat](https://discuss.python.org/u/nedbat)\
**Post date:** [August 15, 2026, 7:06pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/2 "2026-08-15T19:06:06Z")

</div>

Can describe the problem you are trying to solve? If people use \_-prefixed names from the stdlib, the core team is not obligated to ensure compatibility, right?

---

<div class="post-metadata">

**Author:** ![jorenham](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jorenham/32/27806_2.png) [@jorenham](https://discuss.python.org/u/jorenham)\
**Post date:** [August 15, 2026, 7:09pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/3 "2026-08-15T19:09:29Z")

</div>

Well, I don’t known much about the implementation details of the stdlib, but from a distance I can imagine that reorganizing module structure would be a lot easier than having to introduce complicated visibility management mechanics. So as a (probably naive) outsider, I think that something like @NeilGirdhar 's proposal from [PEP 842: Module Exports (new revision) - #129 by NeilGirdhar](https://discuss.python.org/t/pep-842-module-exports-new-revision/108460/129) indeed sounds like a good solution, viz. option 2.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [August 15, 2026, 7:17pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/4 "2026-08-15T19:17:47Z")

</div>

I wrote a lot about this in the motivation section of my PEP. In short: we’re not obligated to ensure compatibility, but in practice, [we choose to do so](https://peps.python.org/pep-0842/#real-world-cases), because the alternative is to break a lot of downstream users.

> [@jorenham](#):
>
> Well, I don’t known much about the implementation details of the stdlib, but from a distance I can imagine that reorganizing module structure would be a lot easier than having to introduce complicated visibility management mechanics.

It’s likely easier mechanically, but as I said on the original thread, I don’t think that’s a good long-term solution. We might as well make things nicer for ourselves if we can.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [August 15, 2026, 7:39pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/5 "2026-08-15T19:39:21Z")

</div>

I guess there are two releated questions.

1. Is there a better way to place the public API facing users looking at a module? (The policy of `_` prefixing isn’t uniform). From that perspective, I think the answer is a clear and resounding “yes”, we can create a dunder describing such.

2. Do we need enforcement if the intended public API is clearer, or is that enough to be willing to break people using things that fall outside of it in the future? If it’s not, then there’s an argument that the entire API is public by convention of unwillingness to break users.

---

<div class="post-metadata">

**Author:** ![bwoodsend](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bwoodsend/32/4027_2.png) [@bwoodsend](https://discuss.python.org/u/bwoodsend)\
**Post date:** [August 15, 2026, 8:21pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/6 "2026-08-15T20:21:21Z")

</div>

> [@ZeroIntensity](#):
>
> I wrote a lot about this in the motivation section of my PEP. In short: we’re not obligated to ensure compatibility, but in practice, [we choose to do so](https://peps.python.org/pep-0842/#real-world-cases), because the alternative is to break a lot of downstream users.

Although only 3 of the examples are specifically using `_`-prefixed names from the standard library. One was used in an example in the stdlib’s documentation and another was insinuated\[1\] as the way to do something by its repr. So really only one, fairly small example. This really sounds like cherry picked space bar heaters to me.

Personally, if I could change anything, it would be for the stdlib to have never used `_` prefixes for things people are actually supposed to use in the first place. Then no-one would have ever been given reason to doubt whether the underscore prefix convention applies to the stdlib. But I don’t have a time machine…

* * *

1. maybe insinuated is quite the right word but hopefully you know what I mean

---

<div class="post-metadata">

**Author:** ![warden](https://avatars.discourse-cdn.com/v4/letter/w/13edae/32.png) [@warden](https://discuss.python.org/u/warden)\
**Post date:** [August 15, 2026, 8:37pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/7 "2026-08-15T20:37:59Z")

</div>

To me, there is no doubt that a underscore prefixed name is not meant to be API, be it a PyPI package or some standard lib module. That is, unless the docs state otherwise, and even in those cases, I wish there simply were a non-underscore alias (say `logging.acquire_lock`) introduced soon that I could use instead.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [August 15, 2026, 8:51pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/8 "2026-08-15T20:51:49Z")

</div>

> [@bwoodsend](#):
>
> So really only one, fairly small example. This really sounds like cherry picked space bar heaters to me.

> [@warden](#):
>
> To me, there is no doubt that a underscore prefixed name is not meant to be API, be it a PyPI package or some standard lib module.

For both of you: For brevity’s sake, the PEP can’t be a complete list. I grabbed what I found or knew about and put it in the PEP. I don’t want to reopen the talk of whether prefixing is enough, because that was already discussed extensively in the other threads.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [August 15, 2026, 8:59pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/9 "2026-08-15T20:59:10Z")

</div>

> [@ZeroIntensity](#):
>
> I wrote a lot about this in the motivation section of my PEP. In short: we’re not obligated to ensure compatibility, but in practice, [we choose to do so](https://peps.python.org/pep-0842/#real-world-cases), because the alternative is to break a lot of downstream users.

Is it not an option (call it option 3) to simply _stop_ choosing to ensure compatibility?

Ultimately, if an API isn’t documented, it’s private. And if users are ignoring that fact, that’s their choice but also their risk. There are awkward cases where the documentation is incomplete (`importlib.resources`, I’m looking at you 🙁) but we can make exceptions without abandoning the general principle. And we can fix the docs…

If we implement some sort of stdlib-only protection, some people will still work around it (both PEP 842 and the hub model can be worked around fairly easily if a user wants to). Do we avoid breaking those users? Don’t we just end up with a never-ending arms race of “no really, it’s private, we mean it, we _will_ break you without warning unlike last time” assertions?

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [August 15, 2026, 9:18pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/10 "2026-08-15T21:18:55Z")

</div>

> [@pf\_moore](#):
>
> Ultimately, if an API isn’t documented, it’s private.

PEP 387 explicitly says “Note that if something is not documented at all, it is _not_ automatically considered private”.

> [@pf\_moore](#):
>
> And if users are ignoring that fact, that’s their choice but also their risk.

Again, PEP 842 (and this thread) is more about cases where users _accidentally_ reach for private APIs or are otherwise too tempted to use something private. I think by providing some sort of friction, we’ll see both less private API usage and more requests to properly promote private things to public (rather than just grabbing them and hoping for the best).

---

<div class="post-metadata">

**Author:** ![gdchinacat](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/gdchinacat/32/37254_2.png) [@gdchinacat](https://discuss.python.org/u/gdchinacat)\
**Post date:** [August 16, 2026, 12:51am UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/11 "2026-08-16T00:51:21Z")

</div>

> [@ZeroIntensity](#):
>
> PEP 387 explicitly says “Note that if something is not documented at all, it is _not_ automatically considered private”.

I’m not sure how relevant this quote is, in this context or in general, but I am not familiar with the deliberations that led to PEP 387, so anyone who has more insight please set me straight.

This does not mean that something that is “not automatically considered private” by virtue of it being undocumented is therefore public. At best I think it is undefined, and as such is not covered by 387. In fact, 387 says it is and example of something that is “explicitly not part of the public API. They can change or be removed at any time in any way.” Something that is undocumented is not necessarily private, but it is unambiguously not public and is categorized by PEP 387, by the same line item you partially quote, as receiving the same level of support as things that are explicitly declared private.

As a response to @pf_moore’s comment that “if an API isn’t documented, it’s private.” is splitting hairs. It receives the same level of support as something that is actually declared private, which I believe is the salient point.

---

<div class="post-metadata">

**Author:** ![jrivers](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jrivers/32/12134_2.png) [@jrivers](https://discuss.python.org/u/jrivers)\
**Post date:** [August 16, 2026, 4:52am UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/12 "2026-08-16T04:52:43Z")

</div>

I think before asking “how do we prevent someone from using a private name?”, we should ask “why did they use a private name in the first place?” If they thought that the private name was part of the public API, then, what led them to believe that? If they used a private name on purpose, then why wasn’t the public API sufficient?

You could guess and say, “they didn’t know that underscore names are private.” And somebody else could say (seems more likely to me), “they used an underscore name because the functionality wasn’t available in the public API.” But, I feel like if, instead of just finding examples of using private names, we could find out the reasons why they did that, it would probably help guide us to the solution to preventing using those names.

---

<div class="post-metadata">

**Author:** ![BrenBarn](https://avatars.discourse-cdn.com/v4/letter/b/74df32/32.png) [@BrenBarn](https://discuss.python.org/u/BrenBarn)\
**Post date:** [August 16, 2026, 6:28am UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/13 "2026-08-16T06:28:15Z")

</div>

> [@ZeroIntensity](#):
>
> > [@pf\_moore](#):
> >
> > Ultimately, if an API isn’t documented, it’s private.
> 
> PEP 387 explicitly says “Note that if something is not documented at all, it is _not_ automatically considered private”.

But we could change that policy. That seems easier than introducing a bunch of machinery to mark things private and public and so on.

---

<div class="post-metadata">

**Author:** ![DavidCEllis](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/davidcellis/32/9789_2.png) [@DavidCEllis](https://discuss.python.org/u/DavidCEllis)\
**Post date:** [August 16, 2026, 6:41am UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/14 "2026-08-16T06:41:07Z")

</div>

> [@ZeroIntensity](#):
>
> Again, PEP 842 (and this thread) is more about cases where users _accidentally_ reach for private APIs or are otherwise too tempted to use something private.

For the case of accidental use, the stdlib is actually in a special situation already in that most of the names editors provide come from the typeshed stubs and not the stdlib itself. Perhaps we can look at not exporting private names there?

I know this is already the case for `annotationlib`\[1\], trying to use `_Stringifier` will cause every checker I’ve tried to say it does not exist even though it does and it doesn’t show up on autocomplete. On the other hand `ctypes._CDataType` may be given as an option in autocomplete even though it doesn’t actually exist at runtime.

* * *

1. its private names aren’t even defined in the stub file

---

<div class="post-metadata">

**Author:** ![tmk](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/tmk/32/10935_2.png) [@tmk](https://discuss.python.org/u/tmk)\
**Post date:** [August 16, 2026, 11:27am UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/15 "2026-08-16T11:27:25Z")

</div>

> [@ZeroIntensity](#):
>
> 1. Convert standard library modules to a “hub” model,

Even the hub model isn’t super great. As the PEP pointed out, linters don’t like it (it’s unused exports and it might not be immediately clear that these imports are meant to be used by other packages; you essentially have to hard code the name ` __init__.py` to avoid these problems). For me, even a minimal version of PEP 842 with just

```python
from .module export symbol

```

would be useful.

And yes, there are workarounds and they work okay, but the language would be a bit nicer to use (for me at least) if this syntax existed.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [August 16, 2026, 12:31pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/16 "2026-08-16T12:31:49Z")

</div>

> [@gdchinacat](#):
>
> This does not mean that something that is “not automatically considered private” by virtue of it being undocumented is therefore public. At best I think it is undefined, and as such is not covered by 387.

Yeah, I believe that’s the interpretation. I wasn’t there when it was written, but when I’ve seen that line referenced, it’s been used to prevent core devs from writing off issues where breakage was caused by something that looked public, but was supposed to be private.

> [@jrivers](#):
>
> If they thought that the private name was part of the public API, then, what led them to believe that? If they used a private name on purpose, then why wasn’t the public API sufficient?

Refer to [“Library consumers use runtime introspection for documentation”](https://peps.python.org/pep-0842/#library-consumers-use-runtime-introspection-for-documentation).

> [@BrenBarn](#):
>
> But we could change that policy. That seems easier than introducing a bunch of machinery to mark things private and public and so on.

Refer to [“We want to be nice to users, not shrug them off”](https://peps.python.org/pep-0842/#we-want-to-be-nice-to-users-not-shrug-them-off).

> [@DavidCEllis](#):
>
> Perhaps we can look at not exporting private names there?

Good idea. Two things, though:

1. Why are they listed in the first place? Is there some relevant history here?
2. Is avoiding autocomplete enough? The `dir()`/`help()` problem may still be relevant.

---

<div class="post-metadata">

**Author:** ![DavidCEllis](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/davidcellis/32/9789_2.png) [@DavidCEllis](https://discuss.python.org/u/DavidCEllis)\
**Post date:** [August 16, 2026, 1:14pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/17 "2026-08-16T13:14:23Z")

</div>

> [@ZeroIntensity](#):
>
> Good idea. Two things, though:
> 
> 1. Why are they listed in the first place? Is there some relevant history here?
> 2. Is avoiding autocomplete enough? The `dir()`/`help()` problem may still be relevant.

1. I’m not entirely sure, some of the names just leak but are used for typevars like `_T`. Otherwise I’d be guessing. I’m also really not familiar with `ctypes` as a module, looking at the stub file I’m not completely sure what’s ‘real’ and what isn’t.

2. This is only for IDEs, the REPL still gets the real details. We can override ` __dir__ ` if we need to hide things there. Looking at `help()` (which I rarely do for modules) I’m more struck by how it shows _every_ inherited dunder method\[1\]. I kind of feel like if someone actually manages to _find_ some internals through `help()` then that’s an achievement in and of itself!

* * *

1. There’s about 4 pages of dunder methods inherited from `Enum` for `annotationlib.Format`.

---

<div class="post-metadata">

**Author:** ![mikeshardmind](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mikeshardmind/32/14381_2.png) [@mikeshardmind](https://discuss.python.org/u/mikeshardmind)\
**Post date:** [August 16, 2026, 2:50pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/18 "2026-08-16T14:50:59Z")

</div>

If the focus is on the standard library, would it be enough to do the following?

1. Define a new dunder that, when present, specifies the public api. By “Public api,” this means the api as intended for use by consumers of a library\[1\], not those developing the library. This also means that a user looking at what’s in a module doesn’t need to cross-reference the rendered documentation to determine if something was considered to be documented for public use.

2. Change `dir` and `help` to only list things in the above dunder when present _by default_, but add a kwarg to them for showing private details.

3. Don’t add any new syntax or enforcement mechanism, commit to a “willingness to break users of private names”. Make sure this is mentioned in the release notes for the first cpython version with this dunder as a means of communicating it, and encourage users in any such notices to reach out for public api promotion. That’s not the same as requiring those users be broken, but it frees up maintainers to do it when appropriate for the overall maintenance of the public api.

If pressed for a policy on point 3 there, I’d say that in cases where a private name became public due to there being a good reason for users to have reached for it anyway, having an alias to the old name for transitionary use for the duration of the lifetime of the python versions prior to the name becoming public avoids breaking. aliases are relatively easy to mechanically maintain and phase out.

* * *

1. This definition allows the dunder to also be used by third-party libraries, while allowing linters to have a consistently applicable definition they can refer back to for “should we warn on this use”, which overcomes one of the reasons why a warning for using a name not in ` __all__ ` would be too noisy. While the focus here is the standard library, I think having something consistently applicable ecosystem-wide may help the standard library see tools point out issues here to users.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [August 16, 2026, 2:52pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/19 "2026-08-16T14:52:17Z")

</div>

> [@DavidCEllis](#):
>
> This is only for IDEs, the REPL still gets the real details. We can override ` __dir__ ` if we need to hide things there. Looking at `help()` (which I rarely do for modules) I’m more struck by how it shows _every_ inherited dunder method

Just changing ` __dir__ ` seems reasonable to me. Perhaps we can just add an internal helper that constructs a ` __dir__ ` that hides ` __all__ `?

Even without enforcement, I think it would be a great improvement if we could hide internals from `dir()` and IDEs.

---

<div class="post-metadata">

**Author:** ![ZeroIntensity](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/zerointensity/32/10639_2.png) [@ZeroIntensity](https://discuss.python.org/u/ZeroIntensity)\
**Post date:** [August 16, 2026, 2:53pm UTC](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575/20 "2026-08-16T14:53:24Z")

</div>

I’d be fine with this too. This sounds like the initial revision of PEP 842, just without runtime enforcement.

[Next page](https://discuss.python.org/t/pep-842-postmortem-how-do-we-protect-the-standard-library/108575.md?page=2)
