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.