Skip to content

Commit 431fdec

Browse files
authored
gh-137574: Add Support For Special Names in help (GH-137966)
1 parent 99799a9 commit 431fdec

9 files changed

Lines changed: 1019 additions & 26 deletions

File tree

‎Doc/library/__main__.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _`__main__`:
2+
13
:mod:`!__main__` --- Top-level code environment
24
===============================================
35

‎Doc/reference/datamodel.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2992,6 +2992,8 @@ automatic property creation, proxies, frameworks, and automatic resource
29922992
locking/synchronization.
29932993

29942994

2995+
.. _customize-instance-subclass-checks:
2996+
29952997
Customizing instance and subclass checks
29962998
----------------------------------------
29972999

‎Doc/tools/extensions/pydoc_topics.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@
4141
"callable-types",
4242
"calls",
4343
"class",
44+
"class-customization",
4445
"comparisons",
4546
"compound",
4647
"context-managers",
4748
"continue",
4849
"conversions",
4950
"customization",
51+
"customize-instance-subclass-checks",
5052
"debugger",
5153
"del",
5254
"dict",
@@ -70,6 +72,7 @@
7072
"lambda",
7173
"lazy",
7274
"lists",
75+
"name_equals_main",
7376
"match",
7477
"naming",
7578
"nonlocal",
@@ -102,6 +105,7 @@
102105
"while",
103106
"with",
104107
"yield",
108+
"__main__",
105109
})
106110

107111

‎Doc/whatsnew/3.16.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,9 @@ pydoc
614614
inherited from other classes are collapsed by default.
615615
(Contributed by Serhiy Storchaka in :gh:`153906`.)
616616

617+
* Add help for more special names to the built-in :func:`help` function.
618+
(Contributed by Adam Hartz in :gh:`137966`.)
619+
617620

618621
pickle
619622
------

‎Lib/pydoc.py‎

Lines changed: 156 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class or function within a module or module in a package. If the
6868
import textwrap
6969
import time
7070
import tokenize
71+
import urllib.parse
7172
import warnings
7273
from annotationlib import Format
7374
from collections import deque
@@ -1709,6 +1710,14 @@ def locate(path, forceload=0):
17091710
object = getattr(object, part)
17101711
except AttributeError:
17111712
return None
1713+
if (
1714+
_is_dunder_name(path)
1715+
and not isinstance(object, (type, type(__import__), type(__future__)))
1716+
):
1717+
# if we're looking up a special variable and we don't find a class,
1718+
# function, or module, it's probably not what the user wanted (if it
1719+
# is, they can look up builtins.whatever)
1720+
return None
17121721
return object
17131722

17141723
# --------------------------------------- interactive interpreter interface
@@ -1722,10 +1731,15 @@ def resolve(thing, forceload=0):
17221731
if isinstance(thing, str):
17231732
object = locate(thing, forceload)
17241733
if object is None:
1734+
if _is_dunder_name(thing):
1735+
special = "Use help('specialnames') for a list of special names for which help is available.\n"
1736+
else:
1737+
special = ""
17251738
raise ImportError('''\
1726-
No Python documentation found for %r.
1727-
Use help() to get the interactive help utility.
1728-
Use help(str) for help on the str class.''' % thing)
1739+
No help entry found for %r.
1740+
%sUse help() to get the interactive help utility.
1741+
Use help(str) for help on the str class.
1742+
Additional documentation is available online at https://docs.python.org/%s.%s/''' % (thing, special, *sys.version_info[:2]))
17291743
return object, thing
17301744
else:
17311745
name = getattr(thing, '__name__', None)
@@ -1816,10 +1830,10 @@ def _introdoc():
18161830
Python, you should definitely check out the tutorial at
18171831
https://docs.python.org/{ver}/tutorial/.
18181832
1819-
Enter the name of any module, keyword, or topic to get help on writing
1820-
Python programs and using Python modules. To get a list of available
1821-
modules, keywords, symbols, or topics, enter "modules", "keywords",
1822-
"symbols", or "topics".
1833+
Enter the name of any module, keyword, symbol, or topic to get help on
1834+
writing Python programs and using Python modules. To get a list of
1835+
available modules, keywords, symbols, special names, or topics, enter
1836+
"modules", "keywords", "symbols", "specialnames", or "topics".
18231837
{pyrepl_keys}
18241838
Each module also comes with a one-line summary of what it does; to list
18251839
the modules whose name or summary contain a given string such as "spam",
@@ -1829,6 +1843,96 @@ def _introdoc():
18291843
enter "q", "quit" or "exit".
18301844
''')
18311845

1846+
def _is_dunder_name(x):
1847+
return isinstance(x, str) and x.isidentifier() and len(x) > 4 and x[:2] == x[-2:] == '__'
1848+
1849+
def collect_dunders(symbols):
1850+
dunders = {
1851+
'__name__': ('name_equals_main', ''),
1852+
'__main__': ('__main__', ''),
1853+
'__call__': ('callable-types', 'SPECIALMETHODS'),
1854+
}
1855+
1856+
basic_dunders = [
1857+
'__new__', '__init__', '__del__', '__repr__', '__str__', '__bytes__',
1858+
'__format__', '__hash__', '__bool__',
1859+
]
1860+
for bd in basic_dunders:
1861+
dunders[bd] = ('customization', 'SPECIALMETHODS')
1862+
1863+
attribute_dunders = [
1864+
'__getattr__', '__getattribute__', '__setattr__', '__delattr__',
1865+
'__dir__', '__get__', '__set__', '__delete__', '__objclass__',
1866+
]
1867+
for ad in attribute_dunders:
1868+
dunders[ad] = ('attribute-access', 'SPECIALMETHODS')
1869+
1870+
class_dunders = [
1871+
'__init_subclass__', '__set_name__', '__mro_entries__',
1872+
]
1873+
for cd in class_dunders:
1874+
dunders[cd] = ('class-customization', 'SPECIALMETHODS')
1875+
1876+
instance_dunders = [
1877+
'__instancecheck__', '__subclasscheck__'
1878+
]
1879+
for d in instance_dunders:
1880+
dunders[d] = ('customize-instance-subclass-checks', 'SPECIALMETHODS')
1881+
1882+
sequence_dunders = [
1883+
'__len__', '__length_hint__', '__getitem__', '__setitem__',
1884+
'__delitem__', '__missing__', '__iter__', '__reversed__',
1885+
'__contains__',
1886+
]
1887+
for sd in sequence_dunders:
1888+
dunders[sd] = ('sequence-types', 'SPECIALMETHODS')
1889+
1890+
comparison_dunders = {
1891+
'__lt__': '<',
1892+
'__le__': '<=',
1893+
'__eq__': '==',
1894+
'__ne__': '!=',
1895+
'__gt__': '>',
1896+
'__ge__': '>=',
1897+
}
1898+
for dunder, symbol in comparison_dunders.items():
1899+
dunders[dunder] = ('customization', f'{symbol} SPECIALMETHODS')
1900+
if symbol in symbols:
1901+
symbols[symbol] += f' {dunder}'
1902+
1903+
arithmetic_dunders = {
1904+
'__add__': '+',
1905+
'__sub__': '-',
1906+
'__mul__': '*',
1907+
'__truediv__': '/',
1908+
'__floordiv__': '//',
1909+
'__mod__': '%',
1910+
'__pow__': '**',
1911+
'__lshift__': '<<',
1912+
'__rshift__': '>>',
1913+
'__and__': '&',
1914+
'__or__': '|',
1915+
'__xor__': '^',
1916+
}
1917+
for dunder, symbol in arithmetic_dunders.items():
1918+
rname = "__r" + dunder[2:]
1919+
iname = "__i" + dunder[2:]
1920+
dunders[dunder] = ('numeric-types', f'{symbol} {rname} {iname} SPECIALMETHODS')
1921+
dunders[rname] = ('numeric-types', f'{symbol} {dunder} SPECIALMETHODS')
1922+
dunders[iname] = ('numeric-types', f'{symbol} {dunder} SPECIALMETHODS')
1923+
if symbol in symbols:
1924+
symbols[symbol] += f' {dunder}'
1925+
1926+
# __matmul__ isn't included above because help('@') doesn't talk about
1927+
# matrix multiplication, so we shouldn't list it here as a related topic.
1928+
dunders['__matmul__'] = ('numeric-types', f'__rmatmul__ __imatmul__ SPECIALMETHODS')
1929+
dunders['__rmatmul__'] = ('numeric-types', f'__matmul__ SPECIALMETHODS')
1930+
dunders['__imatmul__'] = ('numeric-types', f'__matmul__ SPECIALMETHODS')
1931+
1932+
dunders['__divmod__'] = ('numeric-types', 'divmod')
1933+
1934+
return dunders
1935+
18321936
class Helper:
18331937

18341938
# These dictionaries map a topic name to either an alias, or a tuple
@@ -1912,7 +2016,8 @@ class Helper:
19122016
'(': 'TUPLES FUNCTIONS CALLS',
19132017
')': 'TUPLES FUNCTIONS CALLS',
19142018
'[': 'LISTS SUBSCRIPTS SLICINGS',
1915-
']': 'LISTS SUBSCRIPTS SLICINGS'
2019+
']': 'LISTS SUBSCRIPTS SLICINGS',
2020+
19162021
}
19172022
for topic, symbols_ in _symbols_inverse.items():
19182023
for symbol in symbols_:
@@ -1962,8 +2067,7 @@ class Helper:
19622067
'BASICMETHODS': ('customization', 'hash repr str SPECIALMETHODS'),
19632068
'ATTRIBUTEMETHODS': ('attribute-access', 'ATTRIBUTES SPECIALMETHODS'),
19642069
'CALLABLEMETHODS': ('callable-types', 'CALLS SPECIALMETHODS'),
1965-
'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS '
1966-
'SPECIALMETHODS'),
2070+
'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SPECIALMETHODS'),
19672071
'MAPPINGMETHODS': ('sequence-types', 'MAPPINGS SPECIALMETHODS'),
19682072
'NUMBERMETHODS': ('numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT '
19692073
'SPECIALMETHODS'),
@@ -2008,8 +2112,15 @@ class Helper:
20082112
'TRUTHVALUE': ('truth', 'if while and or not BASICMETHODS'),
20092113
'DEBUGGING': ('debugger', 'pdb'),
20102114
'CONTEXTMANAGERS': ('context-managers', 'with'),
2115+
'DUNDERMETHODS': 'SPECIALMETHODS',
2116+
'MAINMODULE': '__main__',
20112117
}
20122118

2119+
# add dunder methods
2120+
dunders = collect_dunders(symbols)
2121+
topics |= dunders
2122+
2123+
20132124
def __init__(self, input=None, output=None):
20142125
self._input = input
20152126
self._output = output
@@ -2083,6 +2194,8 @@ def help(self, request, is_cli=False):
20832194
if request == 'keywords': self.listkeywords()
20842195
elif request == 'symbols': self.listsymbols()
20852196
elif request == 'topics': self.listtopics()
2197+
elif request == 'specialnames':
2198+
self.listdunders()
20862199
elif request == 'modules': self.listmodules()
20872200
elif request[:8] == 'modules ':
20882201
self.listmodules(request.split()[1])
@@ -2134,7 +2247,14 @@ def listtopics(self):
21342247
Here is a list of available topics. Enter any topic name to get more help.
21352248
21362249
''')
2137-
self.list(self.topics.keys(), columns=3)
2250+
self.list([k for k in self.topics if k not in self.dunders], columns=3)
2251+
2252+
def listdunders(self):
2253+
self.output.write('''
2254+
Here is a list of special names for which help is available. Enter any one to get more help.
2255+
2256+
''')
2257+
self.list(self.dunders.keys(), columns=3)
21382258

21392259
def showtopic(self, topic, more_xrefs=''):
21402260
try:
@@ -2187,6 +2307,9 @@ def _gettopic(self, topic, more_xrefs=''):
21872307
Sorry, topic and keyword documentation is not available because the
21882308
module "pydoc_data.topics" could not be found.
21892309
''' , '')
2310+
if topic in self.symbols:
2311+
topic, _, xrefs = self.symbols[topic].partition(' ')
2312+
more_xrefs = ' '.join([xrefs, more_xrefs]).strip()
21902313
target = self.topics.get(topic, self.keywords.get(topic))
21912314
if not target:
21922315
raise ValueError('could not find topic')
@@ -2559,14 +2682,14 @@ def onerror(modname):
25592682

25602683
# format page
25612684
def bltinlink(name):
2562-
return '<a href="%s.html">%s</a>' % (name, name)
2685+
return '<a href="%s.html">%s</a>' % (urllib.parse.quote(name), html.escape(name))
25632686

25642687
results = []
25652688
heading = html.heading('Search Results')
25662689
for name, desc in search_result:
25672690
results.append('<li>%s%s</li>' % (bltinlink(name), desc))
25682691
contents = heading + html.bigsection(
2569-
'key = %s' % key, 'index', '<ul>\n%s\n</ul>' % '\n'.join(results))
2692+
'key = %s' % html.escape(key), 'index', '<ul>\n%s\n</ul>' % '\n'.join(results))
25702693
return 'Search Results', contents
25712694

25722695
def html_topics():
@@ -2592,13 +2715,27 @@ def bltinlink(name):
25922715
contents = heading + html.multicolumn(names, bltinlink)
25932716
return 'Keywords', contents
25942717

2718+
def html_symbols():
2719+
"""Index of symbols available."""
2720+
2721+
def bltinlink(name):
2722+
return '<a href="topic?key=%s">%s</a>' % (urllib.parse.quote(name), html.escape(name))
2723+
2724+
heading = html.heading('Symbols')
2725+
names = sorted(Helper.symbols.keys())
2726+
2727+
contents = heading + html.multicolumn(names, bltinlink)
2728+
return 'Symbols', contents
2729+
25952730
def html_topicpage(topic):
25962731
"""Topic or keyword help page."""
25972732
buf = io.StringIO()
25982733
htmlhelp = Helper(buf, buf)
25992734
contents, xrefs = htmlhelp._gettopic(topic)
26002735
if topic in htmlhelp.keywords:
26012736
title = 'KEYWORD'
2737+
elif topic in htmlhelp.symbols:
2738+
title = 'SYMBOL'
26022739
else:
26032740
title = 'TOPIC'
26042741
heading = html.heading('%s %s' % (title.capitalize(), topic))
@@ -2627,7 +2764,7 @@ def html_error(url, exc):
26272764
heading = html.heading('Error')
26282765
contents = '<br>'.join(html.escape(line) for line in
26292766
format_exception_only(type(exc), exc))
2630-
contents = heading + html.bigsection(url, 'error', contents)
2767+
contents = heading + html.bigsection(html.escape(urllib.parse.unquote(url)), 'error', contents)
26312768
return "Error - %s" % url, contents
26322769

26332770
def get_html_page(url):
@@ -2642,8 +2779,11 @@ def get_html_page(url):
26422779
title, content = html_topics()
26432780
elif url == "keywords":
26442781
title, content = html_keywords()
2782+
elif url == "symbols":
2783+
title, content = html_symbols()
26452784
elif '=' in url:
26462785
op, _, url = url.partition('=')
2786+
url = urllib.parse.unquote(url).strip()
26472787
if op == "search?key":
26482788
title, content = html_search(url)
26492789
elif op == "topic?key":
@@ -2824,7 +2964,8 @@ class BadUsage(Exception): pass
28242964
reference to a class or function within a module or module in a
28252965
package. If <name> contains a '{sep}', it is used as the path to a
28262966
Python source file to document. If name is 'keywords', 'topics',
2827-
or 'modules', a listing of these things is displayed.
2967+
'symbols', 'specialnames', or 'modules', a listing of these things is
2968+
displayed.
28282969
28292970
{cmd} -k <keyword>
28302971
Search for a keyword in the synopsis lines of all available modules.

‎Lib/pydoc_data/_pydoc.css‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ section section {
127127
margin: 0.3em 0;
128128
}
129129

130-
pre.topic, pre.keyword {
130+
pre.topic, pre.keyword, pre.symbol {
131131
background-color: var(--block-background-color);
132132
border: 1px solid var(--block-border-color);
133133
border-radius: 3px;

0 commit comments

Comments
 (0)