@@ -68,6 +68,7 @@ class or function within a module or module in a package. If the
6868import textwrap
6969import time
7070import tokenize
71+ import urllib .parse
7172import warnings
7273from annotationlib import Format
7374from 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+
18321936class 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):
21342247Here 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=''):
21872307Sorry, topic and keyword documentation is not available because the
21882308module "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.
0 commit comments