https://github.com/python/cpython/commit/431fdec842ae1460c8141ebdc27715d33bd1dade
commit: 431fdec842ae1460c8141ebdc27715d33bd1dade
branch: main
author: adam j hartz <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-11-03T12:22:48-05:00
summary:

gh-137574: Add Support For Special Names in `help` (GH-137966)

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2025-08-19-16-10-26.gh-issue-137966.nEgFAt.rst
M Doc/library/__main__.rst
M Doc/reference/datamodel.rst
M Doc/tools/extensions/pydoc_topics.py
M Doc/whatsnew/3.16.rst
M Lib/pydoc.py
M Lib/pydoc_data/_pydoc.css
M Lib/pydoc_data/topics.py
M Lib/test/test_pydoc/test_pydoc.py

diff --git a/Doc/library/__main__.rst b/Doc/library/__main__.rst
index 4407ba2f7714ddd..36b884f975804fa 100644
--- a/Doc/library/__main__.rst
+++ b/Doc/library/__main__.rst
@@ -1,3 +1,5 @@
+.. _`__main__`:
+
 :mod:`!__main__` --- Top-level code environment
 ===============================================
 
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index d2089724fa29572..02663ed465f424e 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -2992,6 +2992,8 @@ automatic property creation, proxies, frameworks, and 
automatic resource
 locking/synchronization.
 
 
+.. _customize-instance-subclass-checks:
+
 Customizing instance and subclass checks
 ----------------------------------------
 
diff --git a/Doc/tools/extensions/pydoc_topics.py 
b/Doc/tools/extensions/pydoc_topics.py
index 6c30a9c62626fb1..be5dfc9b805054c 100644
--- a/Doc/tools/extensions/pydoc_topics.py
+++ b/Doc/tools/extensions/pydoc_topics.py
@@ -41,12 +41,14 @@
     "callable-types",
     "calls",
     "class",
+    "class-customization",
     "comparisons",
     "compound",
     "context-managers",
     "continue",
     "conversions",
     "customization",
+    "customize-instance-subclass-checks",
     "debugger",
     "del",
     "dict",
@@ -70,6 +72,7 @@
     "lambda",
     "lazy",
     "lists",
+    "name_equals_main",
     "match",
     "naming",
     "nonlocal",
@@ -102,6 +105,7 @@
     "while",
     "with",
     "yield",
+    "__main__",
 })
 
 
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index d7fbc9cf1a1de4a..dcfa9ff5bdff127 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -614,6 +614,9 @@ pydoc
   inherited from other classes are collapsed by default.
   (Contributed by Serhiy Storchaka in :gh:`153906`.)
 
+* Add help for more special names to the built-in :func:`help` function.
+  (Contributed by Adam Hartz in :gh:`137966`.)
+
 
 pickle
 ------
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index e8a3fe1c4908f78..c0e26efa70e4314 100644
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -68,6 +68,7 @@ class or function within a module or module in a package.  If 
the
 import textwrap
 import time
 import tokenize
+import urllib.parse
 import warnings
 from annotationlib import Format
 from collections import deque
@@ -1709,6 +1710,14 @@ def locate(path, forceload=0):
             object = getattr(object, part)
         except AttributeError:
             return None
+    if (
+        _is_dunder_name(path)
+        and not isinstance(object, (type, type(__import__), type(__future__)))
+    ):
+        # if we're looking up a special variable and we don't find a class,
+        # function, or module, it's probably not what the user wanted (if it
+        # is, they can look up builtins.whatever)
+        return None
     return object
 
 # --------------------------------------- interactive interpreter interface
@@ -1722,10 +1731,15 @@ def resolve(thing, forceload=0):
     if isinstance(thing, str):
         object = locate(thing, forceload)
         if object is None:
+            if _is_dunder_name(thing):
+                special = "Use help('specialnames') for a list of special 
names for which help is available.\n"
+            else:
+                special = ""
             raise ImportError('''\
-No Python documentation found for %r.
-Use help() to get the interactive help utility.
-Use help(str) for help on the str class.''' % thing)
+No help entry found for %r.
+%sUse help() to get the interactive help utility.
+Use help(str) for help on the str class.
+Additional documentation is available online at 
https://docs.python.org/%s.%s/''' % (thing, special, *sys.version_info[:2]))
         return object, thing
     else:
         name = getattr(thing, '__name__', None)
@@ -1816,10 +1830,10 @@ def _introdoc():
         Python, you should definitely check out the tutorial at
         https://docs.python.org/{ver}/tutorial/.
 
-        Enter the name of any module, keyword, or topic to get help on writing
-        Python programs and using Python modules.  To get a list of available
-        modules, keywords, symbols, or topics, enter "modules", "keywords",
-        "symbols", or "topics".
+        Enter the name of any module, keyword, symbol, or topic to get help on
+        writing Python programs and using Python modules.  To get a list of
+        available modules, keywords, symbols, special names, or topics, enter
+        "modules", "keywords", "symbols", "specialnames", or "topics".
         {pyrepl_keys}
         Each module also comes with a one-line summary of what it does; to list
         the modules whose name or summary contain a given string such as 
"spam",
@@ -1829,6 +1843,96 @@ def _introdoc():
         enter "q", "quit" or "exit".
     ''')
 
+def _is_dunder_name(x):
+    return isinstance(x, str) and x.isidentifier() and len(x) > 4 and x[:2] == 
x[-2:] == '__'
+
+def collect_dunders(symbols):
+    dunders = {
+        '__name__': ('name_equals_main', ''),
+        '__main__': ('__main__', ''),
+        '__call__': ('callable-types', 'SPECIALMETHODS'),
+    }
+
+    basic_dunders = [
+        '__new__', '__init__', '__del__', '__repr__', '__str__', '__bytes__',
+        '__format__', '__hash__', '__bool__',
+    ]
+    for bd in basic_dunders:
+        dunders[bd] = ('customization', 'SPECIALMETHODS')
+
+    attribute_dunders = [
+        '__getattr__', '__getattribute__', '__setattr__', '__delattr__',
+        '__dir__', '__get__', '__set__', '__delete__', '__objclass__',
+    ]
+    for ad in attribute_dunders:
+        dunders[ad] = ('attribute-access', 'SPECIALMETHODS')
+
+    class_dunders = [
+        '__init_subclass__', '__set_name__', '__mro_entries__',
+    ]
+    for cd in class_dunders:
+        dunders[cd] = ('class-customization', 'SPECIALMETHODS')
+
+    instance_dunders = [
+        '__instancecheck__', '__subclasscheck__'
+    ]
+    for d in instance_dunders:
+        dunders[d] = ('customize-instance-subclass-checks', 'SPECIALMETHODS')
+
+    sequence_dunders = [
+       '__len__', '__length_hint__', '__getitem__', '__setitem__',
+       '__delitem__', '__missing__', '__iter__', '__reversed__',
+       '__contains__',
+    ]
+    for sd in sequence_dunders:
+        dunders[sd] = ('sequence-types', 'SPECIALMETHODS')
+
+    comparison_dunders = {
+        '__lt__': '<',
+        '__le__': '<=',
+        '__eq__': '==',
+        '__ne__': '!=',
+        '__gt__': '>',
+        '__ge__': '>=',
+    }
+    for dunder, symbol in comparison_dunders.items():
+        dunders[dunder] = ('customization', f'{symbol} SPECIALMETHODS')
+        if symbol in symbols:
+            symbols[symbol] += f' {dunder}'
+
+    arithmetic_dunders = {
+        '__add__': '+',
+        '__sub__': '-',
+        '__mul__': '*',
+        '__truediv__': '/',
+        '__floordiv__': '//',
+        '__mod__': '%',
+        '__pow__': '**',
+        '__lshift__': '<<',
+        '__rshift__': '>>',
+        '__and__': '&',
+        '__or__': '|',
+        '__xor__': '^',
+    }
+    for dunder, symbol in arithmetic_dunders.items():
+        rname = "__r" + dunder[2:]
+        iname = "__i" + dunder[2:]
+        dunders[dunder] = ('numeric-types', f'{symbol} {rname} {iname} 
SPECIALMETHODS')
+        dunders[rname] = ('numeric-types', f'{symbol} {dunder} SPECIALMETHODS')
+        dunders[iname] = ('numeric-types', f'{symbol} {dunder} SPECIALMETHODS')
+        if symbol in symbols:
+            symbols[symbol] += f' {dunder}'
+
+    # __matmul__ isn't included above because help('@') doesn't talk about
+    # matrix multiplication, so we shouldn't list it here as a related topic.
+    dunders['__matmul__'] = ('numeric-types', f'__rmatmul__ __imatmul__ 
SPECIALMETHODS')
+    dunders['__rmatmul__'] = ('numeric-types', f'__matmul__ SPECIALMETHODS')
+    dunders['__imatmul__'] = ('numeric-types', f'__matmul__ SPECIALMETHODS')
+
+    dunders['__divmod__'] = ('numeric-types', 'divmod')
+
+    return dunders
+
 class Helper:
 
     # These dictionaries map a topic name to either an alias, or a tuple
@@ -1912,7 +2016,8 @@ class Helper:
         '(': 'TUPLES FUNCTIONS CALLS',
         ')': 'TUPLES FUNCTIONS CALLS',
         '[': 'LISTS SUBSCRIPTS SLICINGS',
-        ']': 'LISTS SUBSCRIPTS SLICINGS'
+        ']': 'LISTS SUBSCRIPTS SLICINGS',
+
     }
     for topic, symbols_ in _symbols_inverse.items():
         for symbol in symbols_:
@@ -1962,8 +2067,7 @@ class Helper:
         'BASICMETHODS': ('customization', 'hash repr str SPECIALMETHODS'),
         'ATTRIBUTEMETHODS': ('attribute-access', 'ATTRIBUTES SPECIALMETHODS'),
         'CALLABLEMETHODS': ('callable-types', 'CALLS SPECIALMETHODS'),
-        'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS '
-                             'SPECIALMETHODS'),
+        'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SPECIALMETHODS'),
         'MAPPINGMETHODS': ('sequence-types', 'MAPPINGS SPECIALMETHODS'),
         'NUMBERMETHODS': ('numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT '
                           'SPECIALMETHODS'),
@@ -2008,8 +2112,15 @@ class Helper:
         'TRUTHVALUE': ('truth', 'if while and or not BASICMETHODS'),
         'DEBUGGING': ('debugger', 'pdb'),
         'CONTEXTMANAGERS': ('context-managers', 'with'),
+        'DUNDERMETHODS': 'SPECIALMETHODS',
+        'MAINMODULE': '__main__',
     }
 
+    # add dunder methods
+    dunders = collect_dunders(symbols)
+    topics |= dunders
+
+
     def __init__(self, input=None, output=None):
         self._input = input
         self._output = output
@@ -2083,6 +2194,8 @@ def help(self, request, is_cli=False):
             if request == 'keywords': self.listkeywords()
             elif request == 'symbols': self.listsymbols()
             elif request == 'topics': self.listtopics()
+            elif request == 'specialnames':
+                self.listdunders()
             elif request == 'modules': self.listmodules()
             elif request[:8] == 'modules ':
                 self.listmodules(request.split()[1])
@@ -2134,7 +2247,14 @@ def listtopics(self):
 Here is a list of available topics.  Enter any topic name to get more help.
 
 ''')
-        self.list(self.topics.keys(), columns=3)
+        self.list([k for k in self.topics if k not in self.dunders], columns=3)
+
+    def listdunders(self):
+        self.output.write('''
+Here is a list of special names for which help is available.  Enter any one to 
get more help.
+
+''')
+        self.list(self.dunders.keys(), columns=3)
 
     def showtopic(self, topic, more_xrefs=''):
         try:
@@ -2187,6 +2307,9 @@ def _gettopic(self, topic, more_xrefs=''):
 Sorry, topic and keyword documentation is not available because the
 module "pydoc_data.topics" could not be found.
 ''' , '')
+        if topic in self.symbols:
+            topic, _, xrefs = self.symbols[topic].partition(' ')
+            more_xrefs = ' '.join([xrefs, more_xrefs]).strip()
         target = self.topics.get(topic, self.keywords.get(topic))
         if not target:
             raise ValueError('could not find topic')
@@ -2559,14 +2682,14 @@ def onerror(modname):
 
         # format page
         def bltinlink(name):
-            return '<a href="%s.html">%s</a>' % (name, name)
+            return '<a href="%s.html">%s</a>' % (urllib.parse.quote(name), 
html.escape(name))
 
         results = []
         heading = html.heading('Search Results')
         for name, desc in search_result:
             results.append('<li>%s%s</li>' % (bltinlink(name), desc))
         contents = heading + html.bigsection(
-            'key = %s' % key, 'index', '<ul>\n%s\n</ul>' % '\n'.join(results))
+            'key = %s' % html.escape(key), 'index', '<ul>\n%s\n</ul>' % 
'\n'.join(results))
         return 'Search Results', contents
 
     def html_topics():
@@ -2592,6 +2715,18 @@ def bltinlink(name):
         contents = heading + html.multicolumn(names, bltinlink)
         return 'Keywords', contents
 
+    def html_symbols():
+        """Index of symbols available."""
+
+        def bltinlink(name):
+            return '<a href="topic?key=%s">%s</a>' % 
(urllib.parse.quote(name), html.escape(name))
+
+        heading = html.heading('Symbols')
+        names = sorted(Helper.symbols.keys())
+
+        contents = heading + html.multicolumn(names, bltinlink)
+        return 'Symbols', contents
+
     def html_topicpage(topic):
         """Topic or keyword help page."""
         buf = io.StringIO()
@@ -2599,6 +2734,8 @@ def html_topicpage(topic):
         contents, xrefs = htmlhelp._gettopic(topic)
         if topic in htmlhelp.keywords:
             title = 'KEYWORD'
+        elif topic in htmlhelp.symbols:
+            title = 'SYMBOL'
         else:
             title = 'TOPIC'
         heading = html.heading('%s %s' % (title.capitalize(), topic))
@@ -2627,7 +2764,7 @@ def html_error(url, exc):
         heading = html.heading('Error')
         contents = '<br>'.join(html.escape(line) for line in
                                format_exception_only(type(exc), exc))
-        contents = heading + html.bigsection(url, 'error', contents)
+        contents = heading + 
html.bigsection(html.escape(urllib.parse.unquote(url)), 'error', contents)
         return "Error - %s" % url, contents
 
     def get_html_page(url):
@@ -2642,8 +2779,11 @@ def get_html_page(url):
                 title, content = html_topics()
             elif url == "keywords":
                 title, content = html_keywords()
+            elif url == "symbols":
+                title, content = html_symbols()
             elif '=' in url:
                 op, _, url = url.partition('=')
+                url = urllib.parse.unquote(url).strip()
                 if op == "search?key":
                     title, content = html_search(url)
                 elif op == "topic?key":
@@ -2824,7 +2964,8 @@ class BadUsage(Exception): pass
     reference to a class or function within a module or module in a
     package.  If <name> contains a '{sep}', it is used as the path to a
     Python source file to document. If name is 'keywords', 'topics',
-    or 'modules', a listing of these things is displayed.
+    'symbols', 'specialnames', or 'modules', a listing of these things is
+    displayed.
 
 {cmd} -k <keyword>
     Search for a keyword in the synopsis lines of all available modules.
diff --git a/Lib/pydoc_data/_pydoc.css b/Lib/pydoc_data/_pydoc.css
index 9e5c5c2f6f320dc..c696a2954282186 100644
--- a/Lib/pydoc_data/_pydoc.css
+++ b/Lib/pydoc_data/_pydoc.css
@@ -127,7 +127,7 @@ section section {
     margin: 0.3em 0;
 }
 
-pre.topic, pre.keyword {
+pre.topic, pre.keyword, pre.symbol {
     background-color: var(--block-background-color);
     border: 1px solid var(--block-border-color);
     border-radius: 3px;
diff --git a/Lib/pydoc_data/topics.py b/Lib/pydoc_data/topics.py
index 91e3cccb20ca6f9..7278c2dba6d219a 100644
--- a/Lib/pydoc_data/topics.py
+++ b/Lib/pydoc_data/topics.py
@@ -1,7 +1,357 @@
-# Autogenerated by Sphinx on Tue Oct 13 08:05:49 2026
+# Autogenerated by Sphinx on Wed Oct 14 15:38:08 2026
 # as part of the release process.
 
 topics = {
+    '__main__': r'''"__main__" — Top-level code environment
+***************************************
+
+======================================================================
+
+In Python, the special name "__main__" is used for two important
+constructs:
+
+1. the name of the top-level environment of the program, which can be
+   checked using the "__name__ == '__main__'" expression; and
+
+2. the "__main__.py" file in Python packages.
+
+Both of these mechanisms are related to Python modules; how users
+interact with them and how they interact with each other.  They are
+explained in detail below.  If you’re new to Python modules, see the
+tutorial section Modules for an introduction.
+
+
+"__name__ == '__main__'"
+========================
+
+When a Python module or package is imported, "__name__" is set to the
+module’s name.  Usually, this is the name of the Python file itself
+without the ".py" extension:
+
+   >>> import configparser
+   >>> configparser.__name__
+   'configparser'
+
+If the file is part of a package, "__name__" will also include the
+parent package’s path:
+
+   >>> from concurrent.futures import process
+   >>> process.__name__
+   'concurrent.futures.process'
+
+However, if the module is executed in the top-level code environment,
+its "__name__" is set to the string "'__main__'".
+
+
+What is the “top-level code environment”?
+-----------------------------------------
+
+"__main__" is the name of the environment where top-level code is run.
+“Top-level code” is the first user-specified Python module that starts
+running. It’s “top-level” because it imports all other modules that
+the program needs. Sometimes “top-level code” is called an *entry
+point* to the application.
+
+The top-level code environment can be:
+
+* the scope of an interactive prompt:
+
+     >>> __name__
+     '__main__'
+
+* the Python module passed to the Python interpreter as a file
+  argument:
+
+     $ python helloworld.py
+     Hello, world!
+
+* the Python module or package passed to the Python interpreter with
+  the "-m" argument:
+
+     $ python -m tarfile
+     usage: tarfile.py [-h] [-v] (...)
+
+* Python code read by the Python interpreter from standard input:
+
+     $ echo "import this" | python
+     The Zen of Python, by Tim Peters
+
+     Beautiful is better than ugly.
+     Explicit is better than implicit.
+     ...
+
+* Python code passed to the Python interpreter with the "-c" argument:
+
+     $ python -c "import this"
+     The Zen of Python, by Tim Peters
+
+     Beautiful is better than ugly.
+     Explicit is better than implicit.
+     ...
+
+In each of these situations, the top-level module’s "__name__" is set
+to "'__main__'".
+
+As a result, a module can discover whether or not it is running in the
+top-level environment by checking its own "__name__", which allows a
+common idiom for conditionally executing code when the module is not
+initialized from an import statement:
+
+   if __name__ == '__main__':
+       # Execute when the module is not initialized from an import statement.
+       ...
+
+See also:
+
+  For a more detailed look at how "__name__" is set in all situations,
+  see the tutorial section Modules.
+
+
+Idiomatic Usage
+---------------
+
+Some modules contain code that is intended for script use only, like
+parsing command-line arguments or fetching data from standard input.
+If a module like this was imported from a different module, for
+example to unit test it, the script code would unintentionally execute
+as well.
+
+This is where using the "if __name__ == '__main__'" code block comes
+in handy. Code within this block won’t run unless the module is
+executed in the top-level environment.
+
+Putting as few statements as possible in the block below "if __name__
+== '__main__'" can improve code clarity and correctness. Most often, a
+function named "main" encapsulates the program’s primary behavior:
+
+   # echo.py
+
+   import shlex
+   import sys
+
+   def echo(phrase: str) -> None:
+      """A dummy wrapper around print."""
+      # for demonstration purposes, you can imagine that there is some
+      # valuable and reusable logic inside this function
+      print(phrase)
+
+   def main() -> int:
+       """Echo the input arguments to standard output"""
+       phrase = shlex.join(sys.argv)
+       echo(phrase)
+       return 0
+
+   if __name__ == '__main__':
+       sys.exit(main())  # next section explains the use of sys.exit
+
+Note that if the module didn’t encapsulate code inside the "main"
+function but instead put it directly within the "if __name__ ==
+'__main__'" block, the "phrase" variable would be global to the entire
+module.  This is error-prone as other functions within the module
+could be unintentionally using the global variable instead of a local
+name.  A "main" function solves this problem.
+
+Using a "main" function has the added benefit of the "echo" function
+itself being isolated and importable elsewhere. When "echo.py" is
+imported, the "echo" and "main" functions will be defined, but neither
+of them will be called, because "__name__ != '__main__'".
+
+
+Packaging Considerations
+------------------------
+
+"main" functions are often used to create command-line tools by
+specifying them as entry points for console scripts.  When this is
+done, pip inserts the function call into a template script, where the
+return value of "main" is passed into "sys.exit()". For example:
+
+   sys.exit(main())
+
+Since the call to "main" is wrapped in "sys.exit()", the expectation
+is that your function will return some value acceptable as an input to
+"sys.exit()"; typically, an integer or "None" (which is implicitly
+returned if your function does not have a return statement).
+
+By proactively following this convention ourselves, our module will
+have the same behavior when run directly (i.e. "python echo.py") as it
+will have if we later package it as a console script entry-point in a
+pip-installable package.
+
+In particular, be careful about returning strings from your "main"
+function. "sys.exit()" will interpret a string argument as a failure
+message, so your program will have an exit code of "1", indicating
+failure, and the string will be written to "sys.stderr".  The
+"echo.py" example from earlier exemplifies using the
+"sys.exit(main())" convention.
+
+See also:
+
+  Python Packaging User Guide contains a collection of tutorials and
+  references on how to distribute and install Python packages with
+  modern tools.
+
+
+"__main__.py" in Python Packages
+================================
+
+If you are not familiar with Python packages, see section Packages of
+the tutorial.  Most commonly, the "__main__.py" file is used to
+provide a command-line interface for a package. Consider the following
+hypothetical package, “bandclass”:
+
+   bandclass
+     ├── __init__.py
+     ├── __main__.py
+     └── student.py
+
+"__main__.py" will be executed when the package itself is invoked
+directly from the command line using the "-m" flag. For example:
+
+   $ python -m bandclass
+
+This command will cause "__main__.py" to run. How you utilize this
+mechanism will depend on the nature of the package you are writing,
+but in this hypothetical case, it might make sense to allow the
+teacher to search for students:
+
+   # bandclass/__main__.py
+
+   import sys
+   from .student import search_students
+
+   student_name = sys.argv[1] if len(sys.argv) >= 2 else ''
+   print(f'Found student: {search_students(student_name)}')
+
+Note that "from .student import search_students" is an example of a
+relative import.  This import style can be used when referencing
+modules within a package.  For more details, see Intra-package
+References in the Modules section of the tutorial.
+
+
+Idiomatic Usage
+---------------
+
+The content of "__main__.py" typically isn’t fenced with an "if
+__name__ == '__main__'" block.  Instead, those files are kept short
+and import functions to execute from other modules.  Those other
+modules can then be easily unit-tested and are properly reusable.
+
+If used, an "if __name__ == '__main__'" block will still work as
+expected for a "__main__.py" file within a package, because its
+"__name__" attribute will include the package’s path if imported:
+
+   >>> import asyncio.__main__
+   >>> asyncio.__main__.__name__
+   'asyncio.__main__'
+
+This won’t work for "__main__.py" files in the root directory of a
+".zip" file though.  Hence, for consistency, a minimal "__main__.py"
+without a "__name__" check is preferred.
+
+See also:
+
+  See "venv" for an example of a package with a minimal "__main__.py"
+  in the standard library. It doesn’t contain a "if __name__ ==
+  '__main__'" block. You can invoke it with "python -m venv
+  [directory]".
+
+  See "runpy" for more details on the "-m" flag to the interpreter
+  executable.
+
+  See "zipapp" for how to run applications packaged as *.zip* files.
+  In this case Python looks for a "__main__.py" file in the root
+  directory of the archive.
+
+
+"import __main__"
+=================
+
+Regardless of which module a Python program was started with, other
+modules running within that same program can import the top-level
+environment’s scope (*namespace*) by importing the "__main__" module.
+This doesn’t import a "__main__.py" file but rather whichever module
+that received the special name "'__main__'".
+
+Here is an example module that consumes the "__main__" namespace:
+
+   # namely.py
+
+   import __main__
+
+   def did_user_define_their_name():
+       return 'my_name' in dir(__main__)
+
+   def print_user_name():
+       if not did_user_define_their_name():
+           raise ValueError('Define the variable `my_name`!')
+
+       print(__main__.my_name)
+
+Example usage of this module could be as follows:
+
+   # start.py
+
+   import sys
+
+   from namely import print_user_name
+
+   # my_name = "Dinsdale"
+
+   def main():
+       try:
+           print_user_name()
+       except ValueError as ve:
+           return str(ve)
+
+   if __name__ == "__main__":
+       sys.exit(main())
+
+Now, if we started our program, the result would look like this:
+
+   $ python start.py
+   Define the variable `my_name`!
+
+The exit code of the program would be 1, indicating an error.
+Uncommenting the line with "my_name = "Dinsdale"" fixes the program
+and now it exits with status code 0, indicating success:
+
+   $ python start.py
+   Dinsdale
+
+Note that importing "__main__" doesn’t cause any issues with
+unintentionally running top-level code meant for script use which is
+put in the "if __name__ == "__main__"" block of the "start" module.
+Why does this work?
+
+Python inserts an empty "__main__" module in "sys.modules" at
+interpreter startup, and populates it by running top-level code. In
+our example this is the "start" module which runs line by line and
+imports "namely". In turn, "namely" imports "__main__" (which is
+really "start"). That’s an import cycle! Fortunately, since the
+partially populated "__main__" module is present in "sys.modules",
+Python passes that to "namely". See Special considerations for
+__main__ in the import system’s reference for details on how this
+works.
+
+The Python REPL is another example of a “top-level environment”, so
+anything defined in the REPL becomes part of the "__main__" scope:
+
+   >>> import namely
+   >>> namely.did_user_define_their_name()
+   False
+   >>> namely.print_user_name()
+   Traceback (most recent call last):
+   ...
+   ValueError: Define the variable `my_name`!
+   >>> my_name = 'Jabberwocky'
+   >>> namely.did_user_define_their_name()
+   True
+   >>> namely.print_user_name()
+   Jabberwocky
+
+The "__main__" scope is used in the implementation of "pdb" and
+"rlcompleter".
+''',
     'assert': r'''The "assert" statement
 **********************
 
@@ -1613,6 +1963,260 @@ class with multiple inheritance will raise an error if 
one of the
    Traceback (most recent call last):
      ...
    TypeError: multiple bases have instance lay-out conflict
+''',
+    'class-customization': r'''Customizing class creation
+**************************
+
+Whenever a class inherits from another class, "__init_subclass__()" is
+called on the parent class. This way, it is possible to write classes
+which change the behavior of subclasses. This is closely related to
+class decorators, but where class decorators only affect the specific
+class they’re applied to, "__init_subclass__" solely applies to future
+subclasses of the class defining the method.
+
+classmethod object.__init_subclass__(cls)
+
+   This method is called whenever the containing class is subclassed.
+   *cls* is then the new subclass. If defined as a normal instance
+   method, this method is implicitly converted to a class method.
+
+   Keyword arguments which are given to a new class are passed to the
+   parent class’s "__init_subclass__". For compatibility with other
+   classes using "__init_subclass__", one should take out the needed
+   keyword arguments and pass the others over to the base class, as
+   in:
+
+      class Philosopher:
+          def __init_subclass__(cls, /, default_name, **kwargs):
+              super().__init_subclass__(**kwargs)
+              cls.default_name = default_name
+
+      class AustralianPhilosopher(Philosopher, default_name="Bruce"):
+          pass
+
+   The default implementation "object.__init_subclass__" does nothing,
+   but raises an error if it is called with any arguments.
+
+   Note:
+
+     The metaclass hint "metaclass" is consumed by the rest of the
+     type machinery, and is never passed to "__init_subclass__"
+     implementations. The actual metaclass (rather than the explicit
+     hint) can be accessed as "type(cls)".
+
+   Added in version 3.6.
+
+When a class is created, "type.__new__()" scans the class variables
+and makes callbacks to those with a "__set_name__()" hook.
+
+object.__set_name__(self, owner, name)
+
+   Automatically called at the time the owning class *owner* is
+   created. The object has been assigned to *name* in that class:
+
+      class A:
+          x = C()  # Automatically calls: x.__set_name__(A, 'x')
+
+   If the class variable is assigned after the class is created,
+   "__set_name__()" will not be called automatically. If needed,
+   "__set_name__()" can be called directly:
+
+      class A:
+         pass
+
+      c = C()
+      A.x = c                  # The hook is not called
+      c.__set_name__(A, 'x')   # Manually invoke the hook
+
+   See Creating the class object for more details.
+
+   Added in version 3.6.
+
+
+Metaclasses
+===========
+
+By default, classes are constructed using "type()". The class body is
+executed in a new namespace and the class name is bound locally to the
+result of "type(name, bases, namespace)".
+
+The class creation process can be customized by passing the
+"metaclass" keyword argument in the class definition line, or by
+inheriting from an existing class that included such an argument. In
+the following example, both "MyClass" and "MySubclass" are instances
+of "Meta":
+
+   class Meta(type):
+       pass
+
+   class MyClass(metaclass=Meta):
+       pass
+
+   class MySubclass(MyClass):
+       pass
+
+Any other keyword arguments that are specified in the class definition
+are passed through to all metaclass operations described below.
+
+When a class definition is executed, the following steps occur:
+
+* MRO entries are resolved;
+
+* the appropriate metaclass is determined;
+
+* the class namespace is prepared;
+
+* the class body is executed;
+
+* the class object is created.
+
+
+Resolving MRO entries
+=====================
+
+object.__mro_entries__(self, bases)
+
+   If a base that appears in a class definition is not an instance of
+   "type", then an "__mro_entries__()" method is searched on the base.
+   If an "__mro_entries__()" method is found, the base is substituted
+   with the result of a call to "__mro_entries__()" when creating the
+   class. The method is called with the original bases tuple passed to
+   the *bases* parameter, and must return a tuple of classes that will
+   be used instead of the base. The returned tuple may be empty: in
+   these cases, the original base is ignored.
+
+See also:
+
+  "types.resolve_bases()"
+     Dynamically resolve bases that are not instances of "type".
+
+  "types.get_original_bases()"
+     Retrieve a class’s “original bases” prior to modifications by
+     "__mro_entries__()".
+
+  **PEP 560**
+     Core support for typing module and generic types.
+
+
+Determining the appropriate metaclass
+=====================================
+
+The appropriate metaclass for a class definition is determined as
+follows:
+
+* if no bases and no explicit metaclass are given, then "type()" is
+  used;
+
+* if an explicit metaclass is given and it is *not* an instance of
+  "type()", then it is used directly as the metaclass;
+
+* if an instance of "type()" is given as the explicit metaclass, or
+  bases are defined, then the most derived metaclass is used.
+
+The most derived metaclass is selected from the explicitly specified
+metaclass (if any) and the metaclasses (i.e. "type(cls)") of all
+specified base classes. The most derived metaclass is one which is a
+subtype of *all* of these candidate metaclasses. If none of the
+candidate metaclasses meets that criterion, then the class definition
+will fail with "TypeError".
+
+
+Preparing the class namespace
+=============================
+
+Once the appropriate metaclass has been identified, then the class
+namespace is prepared. If the metaclass has a "__prepare__" attribute,
+it is called as "namespace = metaclass.__prepare__(name, bases,
+**kwds)" (where the additional keyword arguments, if any, come from
+the class definition). The "__prepare__" method should be implemented
+as a "classmethod". The namespace returned by "__prepare__" is passed
+in to "__new__", but when the final class object is created the
+namespace is copied into a new "dict".
+
+If the metaclass has no "__prepare__" attribute, then the class
+namespace is initialised as an empty ordered mapping.
+
+See also:
+
+  **PEP 3115** - Metaclasses in Python 3000
+     Introduced the "__prepare__" namespace hook
+
+
+Executing the class body
+========================
+
+The class body is executed (approximately) as "exec(body, globals(),
+namespace)". The key difference from a normal call to "exec()" is that
+lexical scoping allows the class body (including any methods) to
+reference names from the current and outer scopes when the class
+definition occurs inside a function.
+
+However, even when the class definition occurs inside the function,
+methods defined inside the class still cannot see names defined at the
+class scope. Class variables must be accessed through the first
+parameter of instance or class methods, or through the implicit
+lexically scoped "__class__" reference described in the next section.
+
+
+Creating the class object
+=========================
+
+Once the class namespace has been populated by executing the class
+body, the class object is created by calling "metaclass(name, bases,
+namespace, **kwds)" (the additional keywords passed here are the same
+as those passed to "__prepare__").
+
+This class object is the one that will be referenced by the zero-
+argument form of "super()". "__class__" is an implicit closure
+reference created by the compiler if any methods in a class body refer
+to either "__class__" or "super". This allows the zero argument form
+of "super()" to correctly identify the class being defined based on
+lexical scoping, while the class or instance that was used to make the
+current call is identified based on the first argument passed to the
+method.
+
+**CPython implementation detail:** In CPython 3.6 and later, the
+"__class__" cell is passed to the metaclass as a "__classcell__" entry
+in the class namespace. If present, this must be propagated up to the
+"type.__new__" call in order for the class to be initialised
+correctly. Failing to do so will result in a "RuntimeError" in Python
+3.8.
+
+When using the default metaclass "type", or any metaclass that
+ultimately calls "type.__new__", the following additional
+customization steps are invoked after creating the class object:
+
+1. The "type.__new__" method collects all of the attributes in the
+   class namespace that define a "__set_name__()" method;
+
+2. Those "__set_name__" methods are called with the class being
+   defined and the assigned name of that particular attribute;
+
+3. The "__init_subclass__()" hook is called on the immediate parent of
+   the new class in its method resolution order.
+
+After the class object is created, it is passed to the class
+decorators included in the class definition (if any) and the resulting
+object is bound in the local namespace as the defined class.
+
+When a new class is created by "type.__new__", the object provided as
+the namespace parameter is copied to a new ordered mapping and the
+original object is discarded. The new copy is wrapped in a read-only
+proxy, which becomes the "__dict__" attribute of the class object.
+
+See also:
+
+  **PEP 3135** - New super
+     Describes the implicit "__class__" closure reference
+
+
+Uses for metaclasses
+====================
+
+The potential uses for metaclasses are boundless. Some ideas that have
+been explored include enum, logging, interface checking, automatic
+delegation, automatic property creation, proxies, frameworks, and
+automatic resource locking/synchronization.
 ''',
     'comparisons': r'''Comparisons
 ***********
@@ -4127,6 +4731,43 @@ def __hash__(self):
    considered true if its result is nonzero.  If a class defines
    neither "__len__()" nor "__bool__()" (which is true of the "object"
    class itself), all its instances are considered true.
+''',
+    'customize-instance-subclass-checks': r'''Customizing instance and 
subclass checks
+****************************************
+
+The following methods are used to override the default behavior of the
+"isinstance()" and "issubclass()" built-in functions.
+
+In particular, the metaclass "abc.ABCMeta" implements these methods in
+order to allow the addition of Abstract Base Classes (ABCs) as
+“virtual base classes” to any class or type (including built-in
+types), including other ABCs.
+
+type.__instancecheck__(self, instance)
+
+   Return true if *instance* should be considered a (direct or
+   indirect) instance of *class*. If defined, called to implement
+   "isinstance(instance, class)".
+
+type.__subclasscheck__(self, subclass)
+
+   Return true if *subclass* should be considered a (direct or
+   indirect) subclass of *class*.  If defined, called to implement
+   "issubclass(subclass, class)".
+
+Note that these methods are looked up on the type (metaclass) of a
+class.  They cannot be defined as class methods in the actual class.
+This is consistent with the lookup of special methods that are called
+on instances, only in this case the instance is itself a class.
+
+See also:
+
+  **PEP 3119** - Introducing Abstract Base Classes
+     Includes the specification for customizing "isinstance()" and
+     "issubclass()" behavior through "__instancecheck__()" and
+     "__subclasscheck__()", with motivation for this functionality in
+     the context of adding Abstract Base Classes (see the "abc"
+     module) to the language.
 ''',
     'debugger': r'''"pdb" — The Python Debugger
 ***************************
@@ -7935,6 +8576,175 @@ def <lambda>(parameters):
   * **PEP 634** – Structural Pattern Matching: Specification
 
   * **PEP 636** – Structural Pattern Matching: Tutorial
+''',
+    'name_equals_main': r'''"__name__ == '__main__'"
+************************
+
+When a Python module or package is imported, "__name__" is set to the
+module’s name.  Usually, this is the name of the Python file itself
+without the ".py" extension:
+
+   >>> import configparser
+   >>> configparser.__name__
+   'configparser'
+
+If the file is part of a package, "__name__" will also include the
+parent package’s path:
+
+   >>> from concurrent.futures import process
+   >>> process.__name__
+   'concurrent.futures.process'
+
+However, if the module is executed in the top-level code environment,
+its "__name__" is set to the string "'__main__'".
+
+
+What is the “top-level code environment”?
+=========================================
+
+"__main__" is the name of the environment where top-level code is run.
+“Top-level code” is the first user-specified Python module that starts
+running. It’s “top-level” because it imports all other modules that
+the program needs. Sometimes “top-level code” is called an *entry
+point* to the application.
+
+The top-level code environment can be:
+
+* the scope of an interactive prompt:
+
+     >>> __name__
+     '__main__'
+
+* the Python module passed to the Python interpreter as a file
+  argument:
+
+     $ python helloworld.py
+     Hello, world!
+
+* the Python module or package passed to the Python interpreter with
+  the "-m" argument:
+
+     $ python -m tarfile
+     usage: tarfile.py [-h] [-v] (...)
+
+* Python code read by the Python interpreter from standard input:
+
+     $ echo "import this" | python
+     The Zen of Python, by Tim Peters
+
+     Beautiful is better than ugly.
+     Explicit is better than implicit.
+     ...
+
+* Python code passed to the Python interpreter with the "-c" argument:
+
+     $ python -c "import this"
+     The Zen of Python, by Tim Peters
+
+     Beautiful is better than ugly.
+     Explicit is better than implicit.
+     ...
+
+In each of these situations, the top-level module’s "__name__" is set
+to "'__main__'".
+
+As a result, a module can discover whether or not it is running in the
+top-level environment by checking its own "__name__", which allows a
+common idiom for conditionally executing code when the module is not
+initialized from an import statement:
+
+   if __name__ == '__main__':
+       # Execute when the module is not initialized from an import statement.
+       ...
+
+See also:
+
+  For a more detailed look at how "__name__" is set in all situations,
+  see the tutorial section Modules.
+
+
+Idiomatic Usage
+===============
+
+Some modules contain code that is intended for script use only, like
+parsing command-line arguments or fetching data from standard input.
+If a module like this was imported from a different module, for
+example to unit test it, the script code would unintentionally execute
+as well.
+
+This is where using the "if __name__ == '__main__'" code block comes
+in handy. Code within this block won’t run unless the module is
+executed in the top-level environment.
+
+Putting as few statements as possible in the block below "if __name__
+== '__main__'" can improve code clarity and correctness. Most often, a
+function named "main" encapsulates the program’s primary behavior:
+
+   # echo.py
+
+   import shlex
+   import sys
+
+   def echo(phrase: str) -> None:
+      """A dummy wrapper around print."""
+      # for demonstration purposes, you can imagine that there is some
+      # valuable and reusable logic inside this function
+      print(phrase)
+
+   def main() -> int:
+       """Echo the input arguments to standard output"""
+       phrase = shlex.join(sys.argv)
+       echo(phrase)
+       return 0
+
+   if __name__ == '__main__':
+       sys.exit(main())  # next section explains the use of sys.exit
+
+Note that if the module didn’t encapsulate code inside the "main"
+function but instead put it directly within the "if __name__ ==
+'__main__'" block, the "phrase" variable would be global to the entire
+module.  This is error-prone as other functions within the module
+could be unintentionally using the global variable instead of a local
+name.  A "main" function solves this problem.
+
+Using a "main" function has the added benefit of the "echo" function
+itself being isolated and importable elsewhere. When "echo.py" is
+imported, the "echo" and "main" functions will be defined, but neither
+of them will be called, because "__name__ != '__main__'".
+
+
+Packaging Considerations
+========================
+
+"main" functions are often used to create command-line tools by
+specifying them as entry points for console scripts.  When this is
+done, pip inserts the function call into a template script, where the
+return value of "main" is passed into "sys.exit()". For example:
+
+   sys.exit(main())
+
+Since the call to "main" is wrapped in "sys.exit()", the expectation
+is that your function will return some value acceptable as an input to
+"sys.exit()"; typically, an integer or "None" (which is implicitly
+returned if your function does not have a return statement).
+
+By proactively following this convention ourselves, our module will
+have the same behavior when run directly (i.e. "python echo.py") as it
+will have if we later package it as a console script entry-point in a
+pip-installable package.
+
+In particular, be careful about returning strings from your "main"
+function. "sys.exit()" will interpret a string argument as a failure
+message, so your program will have an exit code of "1", indicating
+failure, and the string will be written to "sys.stderr".  The
+"echo.py" example from earlier exemplifies using the
+"sys.exit(main())" convention.
+
+See also:
+
+  Python Packaging User Guide contains a collection of tutorials and
+  references on how to distribute and install Python packages with
+  modern tools.
 ''',
     'naming': r'''Naming and binding
 ******************
diff --git a/Lib/test/test_pydoc/test_pydoc.py 
b/Lib/test/test_pydoc/test_pydoc.py
index f57e596d67cf7de..896d961defe09df 100644
--- a/Lib/test/test_pydoc/test_pydoc.py
+++ b/Lib/test/test_pydoc/test_pydoc.py
@@ -229,10 +229,13 @@ class C(builtins.object)
                                       for s in expected_data_docstrings)
 
 # output pattern for missing module
-missing_pattern = '''\
-No Python documentation found for %r.
-Use help() to get the interactive help utility.
-Use help(str) for help on the str class.'''.replace('\n', os.linesep)
+def missing_pattern(name, dunder=False):
+    dunderhelp = "Use help('specialnames') for a list of special names for 
which help is available.\n" if dunder else ""
+    return ('''\
+No help entry found for %r.
+%sUse help() to get the interactive help utility.
+Use help(str) for help on the str class.
+Additional documentation is available online at 
https://docs.python.org/%s.%s/''' % (name, dunderhelp, 
*sys.version_info[:2])).replace('\n', os.linesep)
 
 # output pattern for module with bad imports
 badimport_pattern = "problem in %s - ModuleNotFoundError: No module named %r"
@@ -523,10 +526,34 @@ class B:
     def test_not_here(self):
         missing_module = "test.i_am_not_here"
         result = str(run_pydoc_fail(missing_module), 'ascii')
-        expected = missing_pattern % missing_module
+        expected = missing_pattern(missing_module)
         self.assertEqual(expected, result,
             "documentation for missing module found")
 
+    def test_dunder_help(self):
+        def get_main_output(topic):
+            return run_pydoc(topic).split(b'Related help topics:')[0].strip()
+
+        # check that each dunder method maps to a help topic that includes its
+        # name
+        for name in pydoc.Helper.dunders:
+            self.assertIn(name.encode('ascii'), get_main_output(name))
+
+        # check that right-hand and in-place versions match
+        self.assertEqual(get_main_output('__add__'), 
get_main_output('__radd__'))
+        self.assertEqual(get_main_output('__add__'), 
get_main_output('__iadd__'))
+
+    def test_dunder_main(self):
+        self.assertEqual(run_pydoc('__main__').splitlines(False)[0],
+                         '"__main__" — Top-level code 
environment'.encode('utf-8'))
+        self.assertEqual(run_pydoc('__name__').splitlines(False)[0],
+                         '''"__name__ == '__main__'"'''.encode('utf-8'))
+
+    def test_dunder_not_here(self):
+        result = str(run_pydoc_fail("__dict__"), 'ascii')
+        expected = missing_pattern("__dict__", dunder=True)
+        self.assertEqual(expected, result)
+
     @requires_docstrings
     def test_not_ascii(self):
         result = run_pydoc('test.test_pydoc.test_pydoc.nonascii', 
PYTHONIOENCODING='ascii')
@@ -536,7 +563,7 @@ def test_not_ascii(self):
     def test_input_strip(self):
         missing_module = " test.i_am_not_here "
         result = str(run_pydoc_fail(missing_module), 'ascii')
-        expected = missing_pattern % missing_module.strip()
+        expected = missing_pattern(missing_module.strip())
         self.assertEqual(expected, result)
 
     def test_stripid(self):
@@ -690,10 +717,10 @@ def test_builtin_on_metaclasses(self):
         self.assertNotIn('Built-in subclasses', text)
 
     def test_fail_help_cli(self):
-        elines = (missing_pattern % 'abd').splitlines()
+        elines = (missing_pattern("abd")).splitlines()
         with spawn_python("-c" "help()") as proc:
             out, _ = proc.communicate(b"abd")
-            olines = out.decode().splitlines()[-9:-6]
+            olines = out.decode().splitlines()[-10:-6]
             olines[0] = olines[0].removeprefix('help> ')
             self.assertEqual(elines, olines)
 
@@ -701,7 +728,7 @@ def test_fail_help_output_redirect(self):
         with StringIO() as buf:
             helper = pydoc.Helper(output=buf)
             helper.help("abd")
-            expected = missing_pattern % "abd"
+            expected = missing_pattern("abd")
             self.assertEqual(expected, buf.getvalue().strip().replace('\n', 
os.linesep))
 
     @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
@@ -763,6 +790,8 @@ def run_pydoc_for_request(request, expected_text_part):
         run_pydoc_for_request('keywords', 'Here is a list of the Python 
keywords.')
         # test for "symbols"
         run_pydoc_for_request('symbols', 'Here is a list of the punctuation 
symbols')
+        # test for "specialnames"
+        run_pydoc_for_request('specialnames', 'Here is a list of special 
names')
         # test for "topics"
         run_pydoc_for_request('topics', 'Here is a list of available topics.')
         # test for "modules" skipped, see test_modules()
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-19-16-10-26.gh-issue-137966.nEgFAt.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-19-16-10-26.gh-issue-137966.nEgFAt.rst
new file mode 100644
index 000000000000000..6a2ce5e3318fdbb
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-19-16-10-26.gh-issue-137966.nEgFAt.rst
@@ -0,0 +1,2 @@
+Add support for additional special names to the built-in :func:`help` function.
+Patch by Adam Hartz.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to