Repository navigation
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
base: main
Are you sure you want to change the base?
Changes from all commits
b36972d
93ac0fa
0c0f04c
255b603
da7f55c
3294f92
2aee131
bff3522
d1959d2
1bc0227
4ed0e72
5800535
587740b
69265aa
101fff3
35b4470
e196818
d11fb77
2c34fba
595b95c
0a49cfb
394e401
685b627
376e3fb
8c16306
f6c4f8c
8991bb2
7f92fbe
d419cad
f3f2f4d
b826b60
e1c7fab
58de00a
f4b6f5c
cac3a02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| .. _`__main__`: | ||
|
|
||
| :mod:`!__main__` --- Top-level code environment | ||
| =============================================== | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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])) | ||
|
ZeroIntensity marked this conversation as resolved.
|
||
| 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') | ||
|
ZeroIntensity marked this conversation as 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 | ||
|
|
@@ -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 '<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(): | ||
|
|
@@ -2596,13 +2726,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)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was actually originally worried about I hadn't noticed any broken pages in my original testing, though I did just now test searching for The change I just pushed tries to remedy that by unquoting the given URL on the error page (I was already unquoting when actually doing the lookup, so, e.g., searching for If you'd prefer, though, I can switch it to use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would show up if you had a module with some weird Unicode characters in the name. I suppose another fix would be to call
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I already have the I could be missing something, though; is there a case that line doesn't cover that I'm not seeing? |
||
|
|
||
| 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)) | ||
|
|
@@ -2631,7 +2775,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): | ||
|
|
@@ -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 <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. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.