Skip to content

gh-137574: Add Support For Special Names in help #137966

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b36972d
first steps: don't show help output for dunders we've grabbed from bu…
adqm Aug 16, 2025
93ac0fa
better help for built-in help function
adqm Aug 16, 2025
0c0f04c
add __main__ and DUNDERMETHODS to help
adqm Aug 16, 2025
255b603
try to associate all dunder methods with relevant pages from datamode…
adqm Aug 16, 2025
da7f55c
show help for, e.g., __import__
adqm Aug 17, 2025
3294f92
bugfix for sequence-related dunders
adqm Aug 17, 2025
2aee131
list 'specialnames' as a special topic
adqm Aug 17, 2025
bff3522
add help for __name__
adqm Aug 18, 2025
d1959d2
change title of added reference
adqm Aug 17, 2025
1bc0227
faster check for dunder name
adqm Aug 18, 2025
4ed0e72
suggest online help as well if we can't find something
adqm Aug 18, 2025
5800535
fix bad reference (SPECIALNAMES doesn't exist)
adqm Aug 18, 2025
587740b
change help(help) output
adqm Aug 18, 2025
69265aa
clarify message when an entry is not found
adqm Aug 18, 2025
101fff3
update test cases for new message
adqm Aug 18, 2025
35b4470
revert changes to help(help); those should probably be a separate PR
adqm Aug 19, 2025
e196818
new test cases for help() about dunder methods
adqm Aug 19, 2025
d11fb77
remove extra whitespace
adqm Aug 19, 2025
2c34fba
__matmul__ shouldn't reference @ because of where help('@') currently…
adqm Aug 19, 2025
595b95c
fixes for missing dunder doctest
adqm Aug 19, 2025
0a49cfb
attempt to fix failing test case on windows
adqm Aug 19, 2025
394e401
add news blurb
adqm Aug 19, 2025
685b627
roll back changes to pydoc_data not relevant to the new topics
adqm Aug 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Doc/library/__main__.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _`__main__`:

:mod:`!__main__` --- Top-level code environment
===============================================

Expand Down
2 changes: 2 additions & 0 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2911,6 +2911,8 @@ automatic property creation, proxies, frameworks, and automatic resource
locking/synchronization.


.. _customize-instance-subclass-checks:

Customizing instance and subclass checks
----------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions Doc/tools/extensions/pydoc_topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"continue",
"conversions",
"customization",
"customize-instance-subclass-checks",
"debugger",
"del",
"dict",
Expand All @@ -69,6 +70,7 @@
"integers",
"lambda",
"lists",
"name_equals_main",
"naming",
"nonlocal",
"numbers",
Expand Down Expand Up @@ -100,6 +102,7 @@
"while",
"with",
"yield",
"__main__",
})


Expand Down
141 changes: 129 additions & 12 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1721,6 +1721,11 @@ 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__))):
# 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
Expand All @@ -1734,10 +1739,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)
Expand Down Expand Up @@ -1827,10 +1837,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",
Expand All @@ -1840,6 +1850,96 @@ def _introdoc():
enter "q", "quit" or "exit".
''')

def _is_dunder_name(x):
return isinstance(x, str) 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_names__', '__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
Expand Down Expand Up @@ -1920,7 +2020,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_:
Expand Down Expand Up @@ -1970,8 +2071,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'),
Expand Down Expand Up @@ -2016,8 +2116,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
Expand Down Expand Up @@ -2090,6 +2197,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])
Expand Down Expand Up @@ -2141,7 +2250,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:
Expand Down Expand Up @@ -2845,7 +2961,8 @@ class BadUsage(Exception): pass
reference to a class or function within a module or module in a
package. If <name> contains a '{sep}', it is used as the path to a
Python source file to document. If name is 'keywords', 'topics',
or 'modules', a listing of these things is displayed.
'symbols', 'specialnames', or 'modules', a listing of these things is
displayed.

{cmd} -k <keyword>
Search for a keyword in the synopsis lines of all available modules.
Expand Down
Loading
Loading