Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 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
376e3fb
merge
adqm Oct 12, 2026
8c16306
fixes for checking whether a string should look up special dunder help
adqm Oct 12, 2026
f6c4f8c
add sphinx reference to news entry
adqm Oct 12, 2026
8991bb2
fix typo in __set_name__ dunder
adqm Oct 12, 2026
7f92fbe
add class-customization to pydoc topics
adqm Oct 12, 2026
d419cad
what's new entry
adqm Oct 12, 2026
f3f2f4d
add symbols to html help from pydoc
adqm Oct 12, 2026
b826b60
remove duplicate customization label from Doc/using/windows.rst
adqm Oct 12, 2026
e1c7fab
Merge branch 'main' into pydoc_dunders
adqm Oct 13, 2026
58de00a
add more built-in modules to SKIP_DUNDER_HELP
adqm Oct 13, 2026
f4b6f5c
also unquote url in error page
adqm Oct 13, 2026
cac3a02
merge
adqm Oct 14, 2026
d2f4627
replace allowlist with a check for module type
adqm Nov 1, 2026
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 @@ -2991,6 +2991,8 @@ automatic property creation, proxies, frameworks, and automatic resource
locking/synchronization.


.. _customize-instance-subclass-checks:

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

Expand Down
4 changes: 4 additions & 0 deletions Doc/tools/extensions/pydoc_topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@
"callable-types",
"calls",
"class",
"class-customization",
"comparisons",
"compound",
"context-managers",
"continue",
"conversions",
"customization",
"customize-instance-subclass-checks",
"debugger",
"del",
"dict",
Expand All @@ -70,6 +72,7 @@
"lambda",
"lazy",
"lists",
"name_equals_main",
"match",
"naming",
"nonlocal",
Expand Down Expand Up @@ -102,6 +105,7 @@
"while",
"with",
"yield",
"__main__",
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved
})


Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--
Expand Down
171 changes: 156 additions & 15 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1712,6 +1713,14 @@ def locate(path, forceload=0):
object = getattr(object, part)
except AttributeError:
return None
if (
_is_dunder_name(path)
and not isinstance(object, (type, type(__import__), type(__future__)))
):
Comment on lines +1716 to +1719

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than an allowlist of things to skip, we shouldn't skip any module that is successfully imported. There are some legitimate names for modules that are dunders, such as __about__ or __init__. The rest of this PR looks good otherwise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for reviewing, and sorry for being slow here. I'll be able to get back to this sometime this weekend.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry again that it took a while for me to get back to this.

I just pushed a change that handles modules like __future__ a bit more elegantly. That said, when poking around, I'm noticing that there are still a bunch of dunder methods (__abs__, __enter__, etc.) that don't currently have entries here. Is it worth adding those?

There are also other things that could probably use specialized help (like, for example, __file__).

If you think those are worth adding here, I can have a go at adding them sometime this week.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they're worth adding, but that can happen in a follow-up. This PR is large enough as it is :)

# if we're looking up a special variable and we don't find a class,
# function, or module, it's probably not what the user wanted (if it
# is, they can look up builtins.whatever)
return None
return object

# --------------------------------------- interactive interpreter interface
Expand All @@ -1725,10 +1734,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]))
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved
return object, thing
else:
name = getattr(thing, '__name__', None)
Expand Down Expand Up @@ -1819,10 +1833,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 @@ -1832,6 +1846,96 @@ def _introdoc():
enter "q", "quit" or "exit".
''')

def _is_dunder_name(x):
return isinstance(x, str) and x.isidentifier() and len(x) > 4 and x[:2] == x[-2:] == '__'

def collect_dunders(symbols):
dunders = {
'__name__': ('name_equals_main', ''),
'__main__': ('__main__', ''),
'__call__': ('callable-types', 'SPECIALMETHODS'),
}

basic_dunders = [
'__new__', '__init__', '__del__', '__repr__', '__str__', '__bytes__',
'__format__', '__hash__', '__bool__',
]
for bd in basic_dunders:
dunders[bd] = ('customization', 'SPECIALMETHODS')

attribute_dunders = [
'__getattr__', '__getattribute__', '__setattr__', '__delattr__',
'__dir__', '__get__', '__set__', '__delete__', '__objclass__',
]
for ad in attribute_dunders:
dunders[ad] = ('attribute-access', 'SPECIALMETHODS')

class_dunders = [
'__init_subclass__', '__set_name__', '__mro_entries__',
]
for cd in class_dunders:
dunders[cd] = ('class-customization', 'SPECIALMETHODS')

instance_dunders = [
'__instancecheck__', '__subclasscheck__'
]
for d in instance_dunders:
dunders[d] = ('customize-instance-subclass-checks', 'SPECIALMETHODS')

sequence_dunders = [
'__len__', '__length_hint__', '__getitem__', '__setitem__',
'__delitem__', '__missing__', '__iter__', '__reversed__',
'__contains__',
]
for sd in sequence_dunders:
dunders[sd] = ('sequence-types', 'SPECIALMETHODS')

comparison_dunders = {
'__lt__': '<',
'__le__': '<=',
'__eq__': '==',
'__ne__': '!=',
'__gt__': '>',
'__ge__': '>=',
}
for dunder, symbol in comparison_dunders.items():
dunders[dunder] = ('customization', f'{symbol} SPECIALMETHODS')
if symbol in symbols:
symbols[symbol] += f' {dunder}'

arithmetic_dunders = {
'__add__': '+',
'__sub__': '-',
'__mul__': '*',
'__truediv__': '/',
'__floordiv__': '//',
'__mod__': '%',
'__pow__': '**',
'__lshift__': '<<',
'__rshift__': '>>',
'__and__': '&',
'__or__': '|',
'__xor__': '^',
}
for dunder, symbol in arithmetic_dunders.items():
rname = "__r" + dunder[2:]
iname = "__i" + dunder[2:]
dunders[dunder] = ('numeric-types', f'{symbol} {rname} {iname} SPECIALMETHODS')
dunders[rname] = ('numeric-types', f'{symbol} {dunder} SPECIALMETHODS')
dunders[iname] = ('numeric-types', f'{symbol} {dunder} SPECIALMETHODS')
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved
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 @@ -1915,7 +2019,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 @@ -1965,8 +2070,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 @@ -2011,8 +2115,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 @@ -2086,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 @@ -2137,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 @@ -2190,6 +2310,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')
Expand Down Expand Up @@ -2563,14 +2686,14 @@ def onerror(modname):

# format page
def bltinlink(name):
return '<a href="%s.html">%s</a>' % (name, name)
return '<a href="%s.html">%s</a>' % (urllib.parse.quote(name), html.escape(name))

results = []
heading = html.heading('Search Results')
for name, desc in search_result:
results.append('<li>%s%s</li>' % (bltinlink(name), desc))
contents = heading + html.bigsection(
'key = %s' % key, 'index', '<ul>\n%s\n</ul>' % '\n'.join(results))
'key = %s' % html.escape(key), 'index', '<ul>\n%s\n</ul>' % '\n'.join(results))
return 'Search Results', contents

def html_topics():
Expand All @@ -2596,13 +2719,27 @@ def bltinlink(name):
contents = heading + html.multicolumn(names, bltinlink)
return 'Keywords', contents

def html_symbols():
"""Index of symbols available."""

def bltinlink(name):
return '<a href="topic?key=%s">%s</a>' % (urllib.parse.quote(name), html.escape(name))
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved

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()
htmlhelp = Helper(buf, buf)
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))
Expand Down Expand Up @@ -2631,7 +2768,7 @@ def html_error(url, exc):
heading = html.heading('Error')
contents = '<br>'.join(html.escape(line) for line in
format_exception_only(type(exc), exc))
contents = heading + html.bigsection(url, 'error', contents)
contents = heading + html.bigsection(html.escape(urllib.parse.unquote(url)), 'error', contents)
return "Error - %s" % url, contents

def get_html_page(url):
Expand All @@ -2646,8 +2783,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":
Expand Down Expand Up @@ -2828,7 +2968,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
2 changes: 1 addition & 1 deletion Lib/pydoc_data/_pydoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading