Skip to content

Commit 1e17df0

Browse files
committed
Add :subscript: option to :py:{function,method}
This can be used to document *subscript methods* and *subscript functions*, like `arr.vindex[1:2, [3, 4, 5]]`.
1 parent ad3f3cc commit 1e17df0

File tree

12 files changed

+142
-9
lines changed

12 files changed

+142
-9
lines changed

doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@
243243
('py:exc', 'sphinx.environment.NoUri'),
244244
('py:func', 'setup'),
245245
('py:func', 'sphinx.util.nodes.nested_parse_with_titles'),
246+
('py:class', 'IndexExpression'), # doc/usage/domains/python.rst
246247
# Error in sphinxcontrib.websupport.core::WebSupport.add_comment
247248
('py:meth', 'get_comments'),
248249
('py:mod', 'autodoc'),

doc/usage/domains/python.rst

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,45 @@ The following directives are provided for module and class contents:
120120
121121
.. versionadded:: 7.1
122122
123+
.. rst:directive:option:: subscript
124+
:type: no value
125+
126+
Indicate that the function's parameters should be displayed
127+
within square brackets instead of parentheses, in order to
128+
indicate that it must be invoked as a *subscript function*..
129+
130+
For example:
131+
132+
.. code-block:: rst
133+
134+
.. py:function:: numpy.s_(indexing_expr: tuple[int, ...]) -> IndexExpression
135+
:subscript:
136+
137+
Creates an index expression object.
138+
139+
This is rendered as:
140+
141+
.. py:method:: numpy.s_(indexing_expr: tuple[int, ...]) -> IndexExpression
142+
:no-contents-entry:
143+
:no-index-entry:
144+
:subscript:
145+
146+
Creates an index expression object.
147+
148+
A *subscript function* can be implemented as follows:
149+
150+
.. code-block:: python
151+
152+
class _S:
153+
@staticmethod
154+
def __getitem__(indexing_expr: tuple[int, ...]) -> IndexExpression:
155+
...
156+
157+
s_ = _S()
158+
159+
.. versionadded:: 8.3
160+
161+
123162
124163
.. rst:directive:: .. py:data:: name
125164
@@ -516,6 +555,46 @@ The following directives are provided for module and class contents:
516555
517556
.. versionadded:: 2.1
518557
558+
.. rst:directive:option:: subscript
559+
:type: no value
560+
561+
Indicate that the method's parameters should be displayed within
562+
square brackets instead of parentheses, in order to indicate
563+
that it must be invoked as a *subscript method*.
564+
565+
For example:
566+
567+
.. code-block:: rst
568+
569+
.. py:method:: Array.vindex(self, indexing_expr: tuple[int, ...]) -> list[int]
570+
:subscript:
571+
572+
Index the array using *vindex* semantics.
573+
574+
This is rendered as:
575+
576+
.. py:method:: Array.vindex(self, indexing_expr: tuple[int, ...]) -> list[int]
577+
:no-contents-entry:
578+
:no-index-entry:
579+
:subscript:
580+
581+
Index the array using *vindex* semantics.
582+
583+
A *subscript method* can be implemented as follows:
584+
585+
.. code-block:: python
586+
587+
class Array:
588+
class _Vindex:
589+
def __init__(self, parent: Array):
590+
self.parent = parent
591+
def __getitem__(self, indexing_expr: tuple[int, ...]) -> list[int]:
592+
...
593+
@property
594+
def vindex(self) -> Array._Vindex:
595+
return Array._Vindex(self)
596+
597+
.. versionadded:: 8.3
519598
520599
.. rst:directive:: .. py:staticmethod:: name(parameters)
521600
.. py:staticmethod:: name[type parameters](parameters)

sphinx/addnodes.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,20 @@ class desc_parameterlist(nodes.Part, nodes.Inline, nodes.FixedTextElement):
246246
In that case each parameter will then be written on its own, indented line.
247247
A trailing comma will be added on the last line
248248
if ``multi_line_trailing_comma`` is True.
249+
250+
By default, it is surrounded by parentheses ``("(", ")")``, but this may be
251+
overridden by specifying a ``brackets`` attribute.
249252
"""
250253

251254
child_text_separator = ', '
252255

256+
@property
257+
def brackets(self) -> tuple[str, str]:
258+
return self.get('brackets', ('(', ')'))
259+
253260
def astext(self) -> str:
254-
return f'({super().astext()})'
261+
open_punct, close_punct = self.brackets
262+
return f'{open_punct}{super().astext()}{close_punct}'
255263

256264

257265
class desc_type_parameter_list(nodes.Part, nodes.Inline, nodes.FixedTextElement):

sphinx/domains/python/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class PyFunction(PyObject):
8888
option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
8989
option_spec.update({
9090
'async': directives.flag,
91+
'subscript': directives.flag,
9192
})
9293

9394
def get_signature_prefix(self, sig: str) -> Sequence[nodes.Node]:
@@ -235,6 +236,7 @@ class PyMethod(PyObject):
235236
'classmethod': directives.flag,
236237
'final': directives.flag,
237238
'staticmethod': directives.flag,
239+
'subscript': directives.flag,
238240
})
239241

240242
def needs_arglist(self) -> bool:
@@ -293,6 +295,10 @@ class PyClassMethod(PyMethod):
293295
"""Description of a classmethod."""
294296

295297
option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
298+
option_spec.update({
299+
'async': directives.flag,
300+
'subscript': directives.flag,
301+
})
296302

297303
def run(self) -> list[Node]:
298304
self.name = 'py:method'
@@ -305,6 +311,10 @@ class PyStaticMethod(PyMethod):
305311
"""Description of a staticmethod."""
306312

307313
option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
314+
option_spec.update({
315+
'async': directives.flag,
316+
'subscript': directives.flag,
317+
})
308318

309319
def run(self) -> list[Node]:
310320
self.name = 'py:method'

sphinx/domains/python/_object.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,10 @@ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]
384384
# for callables, add an empty parameter list
385385
signode += addnodes.desc_parameterlist()
386386

387+
if 'subscript' in self.options:
388+
for node in signode.findall(addnodes.desc_parameterlist):
389+
node['brackets'] = '[', ']'
390+
387391
if retann:
388392
children = _parse_annotation(retann, self.env)
389393
signode += addnodes.desc_returns(retann, '', *children)

sphinx/texinputs/sphinxlatexobjects.sty

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@
124124
\setlength\sphinxsignaturelistskip{0pt}
125125
\newcommand{\pysigtypelistopen}{\hskip\sphinxsignaturelistskip\sphinxcode{[}}
126126
\newcommand{\pysigtypelistclose}{\sphinxcode{]}}
127-
\newcommand{\pysigarglistopen}{\hskip\sphinxsignaturelistskip\sphinxcode{(}}
128-
\newcommand{\pysigarglistclose}{\sphinxcode{)}}
127+
\newcommand{\pysigarglistopen}{\hskip\sphinxsignaturelistskip\sphinxcode{\pysigarglistopenpunct}}
128+
\newcommand{\pysigarglistclose}{\sphinxcode{\pysigarglistclosepunct}}
129129
%
130130
% Use a \parbox to accommodate long argument list in signatures
131131
% LaTeX did not imagine that an \item label could need multi-line rendering

sphinx/writers/html5.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,10 @@ def _depart_sig_parameter_list(self, node: Element) -> None:
186186
self.body.append(f'<span class="sig-paren">{sig_close_paren}</span>')
187187

188188
def visit_desc_parameterlist(self, node: Element) -> None:
189-
self._visit_sig_parameter_list(node, addnodes.desc_parameter, '(', ')')
189+
open_punct, close_punct = node.brackets # type: ignore[attr-defined]
190+
self._visit_sig_parameter_list(
191+
node, addnodes.desc_parameter, open_punct, close_punct
192+
)
190193

191194
def depart_desc_parameterlist(self, node: Element) -> None:
192195
self._depart_sig_parameter_list(node)

sphinx/writers/latex.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,16 @@ def depart_desc(self, node: Element) -> None:
816816
else:
817817
self.body.append(CR + r'\end{fulllineitems}' + BLANKLINE)
818818

819+
def _define_parameterlist_brackets(self, open_punct: str, close_punct: str) -> None:
820+
self.body.append(
821+
r'\def\pysigarglistopenpunct{'
822+
+ self.escape(open_punct)
823+
+ '}'
824+
+ r'\def\pysigarglistclosepunct{'
825+
+ self.escape(close_punct)
826+
+ '}'
827+
)
828+
819829
def _visit_signature_line(self, node: Element) -> None:
820830
def next_sibling(e: Node) -> Node | None:
821831
try:
@@ -837,6 +847,7 @@ def has_multi_line(e: Element) -> bool:
837847
if isinstance(arglist, addnodes.desc_parameterlist):
838848
# tp_list + arglist: \macro{name}{tp_list}{arglist}{retann}
839849
multi_arglist = has_multi_line(arglist)
850+
self._define_parameterlist_brackets(*arglist.brackets)
840851
else:
841852
# orphan tp_list: \macro{name}{tp_list}{}{retann}
842853
# see: https://github.com/sphinx-doc/sphinx/issues/12543
@@ -867,6 +878,7 @@ def has_multi_line(e: Element) -> bool:
867878
break
868879

869880
if isinstance(child, addnodes.desc_parameterlist):
881+
self._define_parameterlist_brackets(*child.brackets)
870882
# arglist only: \macro{name}{arglist}{retann}
871883
if has_multi_line(child):
872884
self.body.append(CR + r'\pysigwithonelineperarg' + CR + '{')

sphinx/writers/manpage.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,13 @@ def depart_desc_returns(self, node: Element) -> None:
188188
pass
189189

190190
def visit_desc_parameterlist(self, node: Element) -> None:
191-
self.body.append('(')
191+
open_punct, _ = node.brackets # type: ignore[attr-defined]
192+
self.body.append(open_punct)
192193
self.first_param = 1
193194

194195
def depart_desc_parameterlist(self, node: Element) -> None:
195-
self.body.append(')')
196+
_, close_punct = node.brackets # type: ignore[attr-defined]
197+
self.body.append(close_punct)
196198

197199
def visit_desc_type_parameter_list(self, node: Element) -> None:
198200
self.body.append('[')

sphinx/writers/texinfo.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,11 +1480,13 @@ def depart_desc_returns(self, node: Element) -> None:
14801480
pass
14811481

14821482
def visit_desc_parameterlist(self, node: Element) -> None:
1483-
self.body.append(' (')
1483+
open_punct, _ = node.brackets # type: ignore[attr-defined]
1484+
self.body.append(f' {open_punct}')
14841485
self.first_param = 1
14851486

14861487
def depart_desc_parameterlist(self, node: Element) -> None:
1487-
self.body.append(')')
1488+
_, close_punct = node.brackets # type: ignore[attr-defined]
1489+
self.body.append(close_punct)
14881490

14891491
def visit_desc_type_parameter_list(self, node: Element) -> None:
14901492
self.body.append(' [')

0 commit comments

Comments
 (0)