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 c860a3356628af1..d1c91709e32c81b 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -2991,6 +2991,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 a1d7d1eaae7d526..119f91a21dd92f1 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -546,6 +546,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`.)
+
re
--
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 6f3dab24d5bda33..87ef6fa6906518a 100644
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -71,6 +71,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
@@ -1712,6 +1713,15 @@ 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__)))
+ and path not in SKIP_DUNDER_HELP
+ ):
+ # if we're looking up a special variable and we don't find a class or a
+ # function, it's probably not what the user wanted (if it is, they can
+ # look up builtins.whatever)
+ return None
return object
# --------------------------------------- interactive interpreter interface
@@ -1725,10 +1735,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)
@@ -1819,10 +1834,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",
@@ -1832,6 +1847,102 @@ def _introdoc():
enter "q", "quit" or "exit".
''')
+SKIP_DUNDER_HELP = frozenset({
+ '__future__',
+ '__hello__',
+ '__phello__',
+})
+
+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
@@ -1915,7 +2026,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_:
@@ -1965,8 +2077,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'),
@@ -2011,8 +2122,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
@@ -2086,6 +2204,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])
@@ -2137,7 +2257,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:
@@ -2190,6 +2317,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')
@@ -2563,14 +2693,14 @@ def onerror(modname):
# format page
def bltinlink(name):
- return '%s' % (name, name)
+ return '%s' % (urllib.parse.quote(name), html.escape(name))
results = []
heading = html.heading('Search Results')
for name, desc in search_result:
results.append('
%s%s' % (bltinlink(name), desc))
contents = heading + html.bigsection(
- 'key = %s' % key, 'index', '' % '\n'.join(results))
+ 'key = %s' % html.escape(key), 'index', '' % '\n'.join(results))
return 'Search Results', contents
def html_topics():
@@ -2596,6 +2726,18 @@ def bltinlink(name):
contents = heading + html.multicolumn(names, bltinlink)
return 'Keywords', contents
+ def html_symbols():
+ """Index of symbols available."""
+
+ def bltinlink(name):
+ return '%s' % (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()
@@ -2603,6 +2745,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))
@@ -2631,7 +2775,7 @@ def html_error(url, exc):
heading = html.heading('Error')
contents = '
'.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):
@@ -2646,8 +2790,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":
@@ -2828,7 +2975,8 @@ class BadUsage(Exception): pass
reference to a class or function within a module or module in a
package. If 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
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 (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 006fbc8b6968f3b..8c4dacd3f803990 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.