Is there a tool that can check if the arguments listed in the docstring match the signature of the function call? It should be able to deal with numpy-style docstrings.
I am regularly using R CMD CHECK, which finds documentation/code mismatches in R and this is quite helpful. It would be very good to have something similar in Python, but I did not find anything yet.
I just created a tool to achieve this, called pydoctest.
It will attempt to infer the types in your docstrings (not just lexically compare) and report back on mismatches between number of arguments, argument-names, argument-types, return-types, (optionally) throw error if lacking docstring and more.
It currently supports google, sphinx and numpy docstring format, but can rather easily be extended with other formats.
Example:
def func_type_mismatch(self, a: int) -> int:
"""[summary]
Args:
a (float): [description] <-- float is not int
Returns:
int: [description]
"""
pass
Running pydoctest on this function, gives this output:
Function: <function IncorrectTestClass.func_type_mismatch at 0x7f9a8b52c8c8> FAIL | Argument type differ. Argument 'a' was expected (from signature) to have type '<class 'int'>', but has (in docs) type '<class 'float'>'
Edit (June 2021): I've started the development of a vscode-extension that uses and highlights the errors.
https://marketplace.visualstudio.com/items?itemName=JeppeRask.pydoctest
I was trying to find the same, so I wrote docsig
pip install docsig
then just run docsig . and it will check this for you
/path/to/project
-----------------------
def function(✖*args) -> ✓List[str]:
"""...
:param None: ✖
:return: ✓
"""
E103: parameters missing
There are 8 other errors so far
Related
After looking at this question I learned that the type hints are, by default, not enforced whilst executing Python code.
One can detect some discrepancies between the type hints and actual argument types using a slightly convoluted process of running pyannotate to generate stubs whilst running Python code, and scanning for differences after applying these stubs to the code.
However, it would be more convenient/faster to directly raise an exception if an incoming argument is not of the type included in the type hint. This can be achieved by manually including:
if not isinstance(some_argument, the_type_hint_type):
raise TypeError("Argument:{argument} is not of type:{the_type_hint_type}")
However, that is quite labour intensive. Hence, I was curious, is it possible to make Python raise an error if a type-hint is violated, using an CLI argument or pip package or something like that?
Hope this helps you - https://typeguard.readthedocs.io/en/latest/userguide.html#using-the-decorator
Thanks..
The edit queue for the Answer by #Surya_1897 is full, hence I will include a more detailed description of the solution here.
Typeguard does exactly what I was looking for. The following requirements apply:
Install typeguard with:
pip install typeguard
Import typeguard into each script, and add the #typechecked property above each function.
Example:
Change:
"""Some file description."""
def add_two(x:int):
"""Adds two to an incoming int."""
return x+2
somevar:float=42.1
add_two(somevar)
To:
"""Some file description."""
from typeguard import typechecked
#typechecked
def add_two(x:int):
"""Adds two to an incoming int."""
return x+2
somevar:float=42.1
add_two(somevar)
The latter will than throw an err:
TypeError: type of argument "x" must be int; got float instead
Let's consider the following function:
def f(x: int, y: int) -> int:
"""Get sum of two integers.
Parameters
----------
x : int
first integer
y : int
second integer
Returns
-------
int
sum of the provided integers
"""
return x + y
While documenting with Sphinx (v3.2.1), the documentation is generated in the following form:
However, I don't see a point to show type hints as in f(x: int, y:int) -> int in the heading of function documentation, and also in the Parameters section. In this case, it doesn't really matter, but it makes it very unreadable with functions with 4-5 arguments. Is there a way to skip the type hint? Like, I'll prefer if the heading is just f or f(x, y).
I expected this has something to do with add_function_parentheses, but setting it as False in conf.py didn't have any effect that I've noticed.
A related issue is that if type hint is long, may be like typing.Union with multiple long descriptions, where I do not want to use typing.Any, i often write those in the docstring across multiple lines, adhering to maximum line limit. In those cases, the Parameters section shows that the type is what is in the first line, and next lines are just descriptions. I'm not attaching an example of this issue, as I am not sure whether these are same or not. If they are, please let me know and I'll update with an example.
There is an option autodoc_typehints for sphinx.ext.autodoc. This has 3 options: none, description and signature (default). Using either of none or description will hide type hints in the header line. The only difference I can find in between these two is that if docstrings do not contain type suggestions, description will still show them in the documentation collecting from type hints.
To use this, following changes are required in conf.py:
add sphinx.ext.autodoc in extensions (if not done already)
set autodoc_typehints = "none"
A handler for the autodoc-process-signature event can change signatures and return annotations of functions and methods.
Below is a simple example. If you add this code to conf.py, all signatures and return annotations are removed from the output.
def fix_sig(app, what, name, obj, options, signature, return_annotation):
return ("", "")
def setup(app):
app.connect("autodoc-process-signature", fix_sig)
I've recently noticed something interesting when looking at Python 3.3 grammar specification:
funcdef: 'def' NAME parameters ['->' test] ':' suite
The optional 'arrow' block was absent in Python 2 and I couldn't find any information regarding its meaning in Python 3. It turns out this is correct Python and it's accepted by the interpreter:
def f(x) -> 123:
return x
I thought that this might be some kind of a precondition syntax, but:
I cannot test x here, as it is still undefined,
No matter what I put after the arrow (e.g. 2 < 1), it doesn't affect the function behavior.
Could anyone familiar with this syntax style explain it?
It's a function annotation.
In more detail, Python 2.x has docstrings, which allow you to attach a metadata string to various types of object. This is amazingly handy, so Python 3 extends the feature by allowing you to attach metadata to functions describing their parameters and return values.
There's no preconceived use case, but the PEP suggests several. One very handy one is to allow you to annotate parameters with their expected types; it would then be easy to write a decorator that verifies the annotations or coerces the arguments to the right type. Another is to allow parameter-specific documentation instead of encoding it into the docstring.
These are function annotations covered in PEP 3107. Specifically, the -> marks the return function annotation.
Examples:
def kinetic_energy(m:'in KG', v:'in M/S')->'Joules':
return 1/2*m*v**2
>>> kinetic_energy.__annotations__
{'return': 'Joules', 'v': 'in M/S', 'm': 'in KG'}
Annotations are dictionaries, so you can do this:
>>> '{:,} {}'.format(kinetic_energy(12,30),
kinetic_energy.__annotations__['return'])
'5,400.0 Joules'
You can also have a python data structure rather than just a string:
rd={'type':float,'units':'Joules',
'docstring':'Given mass and velocity returns kinetic energy in Joules'}
def f()->rd:
pass
>>> f.__annotations__['return']['type']
<class 'float'>
>>> f.__annotations__['return']['units']
'Joules'
>>> f.__annotations__['return']['docstring']
'Given mass and velocity returns kinetic energy in Joules'
Or, you can use function attributes to validate called values:
def validate(func, locals):
for var, test in func.__annotations__.items():
value = locals[var]
try:
pr=test.__name__+': '+test.__docstring__
except AttributeError:
pr=test.__name__
msg = '{}=={}; Test: {}'.format(var, value, pr)
assert test(value), msg
def between(lo, hi):
def _between(x):
return lo <= x <= hi
_between.__docstring__='must be between {} and {}'.format(lo,hi)
return _between
def f(x: between(3,10), y:lambda _y: isinstance(_y,int)):
validate(f, locals())
print(x,y)
Prints
>>> f(2,2)
AssertionError: x==2; Test: _between: must be between 3 and 10
>>> f(3,2.1)
AssertionError: y==2.1; Test: <lambda>
In the following code:
def f(x) -> int:
return int(x)
the -> int just tells that f() returns an integer (but it doesn't force the function to return an integer). It is called a return annotation, and can be accessed as f.__annotations__['return'].
Python also supports parameter annotations:
def f(x: float) -> int:
return int(x)
: float tells people who read the program (and some third-party libraries/programs, e. g. pylint) that x should be a float. It is accessed as f.__annotations__['x'], and doesn't have any meaning by itself. See the documentation for more information:
https://docs.python.org/3/reference/compound_stmts.html#function-definitions
https://www.python.org/dev/peps/pep-3107/
As other answers have stated, the -> symbol is used as part of function annotations. In more recent versions of Python >= 3.5, though, it has a defined meaning.
PEP 3107 -- Function Annotations described the specification, defining the grammar changes, the existence of func.__annotations__ in which they are stored and, the fact that it's use case is still open.
In Python 3.5 though, PEP 484 -- Type Hints attaches a single meaning to this: -> is used to indicate the type that the function returns. It also seems like this will be enforced in future versions as described in What about existing uses of annotations:
The fastest conceivable scheme would introduce silent deprecation of non-type-hint annotations in 3.6, full deprecation in 3.7, and declare type hints as the only allowed use of annotations in Python 3.8.
(Emphasis mine)
This hasn't been actually implemented as of 3.6 as far as I can tell so it might get bumped to future versions.
According to this, the example you've supplied:
def f(x) -> 123:
return x
will be forbidden in the future (and in current versions will be confusing), it would need to be changed to:
def f(x) -> int:
return x
for it to effectively describe that function f returns an object of type int.
The annotations are not used in any way by Python itself, it pretty much populates and ignores them. It's up to 3rd party libraries to work with them.
This means the type of result the function returns, but it can be None.
It is widespread in modern libraries oriented on Python 3.x.
For example, it there is in code of library pandas-profiling in many places for example:
def get_description(self) -> dict:
def get_rejected_variables(self, threshold: float = 0.9) -> list:
def to_file(self, output_file: Path or str, silent: bool = True) -> None:
"""Write the report to a file.
def f(x) -> 123:
return x
My summary:
Simply -> is introduced to get developers to optionally specify the return type of the function. See Python Enhancement Proposal 3107
This is an indication of how things may develop in future as Python is adopted extensively - an indication towards strong typing - this is my personal observation.
You can specify types for arguments as well. Specifying return type of the functions and arguments will help in reducing logical errors and improving code enhancements.
You can have expressions as return type (for both at function and parameter level) and the result of the expressions can be accessed via annotations object's 'return' attribute. annotations will be empty for the expression/return value for lambda inline functions.
def function(arg)->123:
It's simply a return type, integer in this case doesn't matter which number you write.
like Java :
public int function(int args){...}
But for Python (how Jim Fasarakis Hilliard said) the return type it's just an hint, so it's suggest the return but allow anyway to return other type like a string..
def f(x) -> str:
return x+4
print(f(45))
Will give the result : 49.
Or in other words '-> str' has NO effect on return type:
print(f(45).__class__)
<class 'int'>
-> is introduced in python3.
In simpler words, the content after the -> denotes the return type of the function.
The return type is optional.
It's just telling the user what it expects or return the value
funcname.__annotations__ will print the details
like
def function(name:str ,age:int) -> "printing the personal details ":
print(f"name is {name} age is {age}")
function("test",20)
print(function.__annotations__)
The Output
name is test age is 20
{'name': <class 'str'>, 'age': <class 'int'>, 'return': 'printing the personal details '}
even when you return the values it display nothing.
Please refer to the PEP3107 specification. These are function annotations. Python 2.x has docstrings. Similarly, Python 3 introduced the use of -> as function annotations. Python uses these while generating documentation.
I am currently trying to implement automatic documentation creation with Sphinx (using the extensions sphinx-apidoc and napoleon). This works quite well, but it would be even better if the typehints (PEP484 convention) are added automatically to the params list.
I was wondering whether this is possible.
More concretely: (from the napoleon example)
def function_with_pep484_type_annotations(param1: int, param2: str) -> bool:
"""Example function with PEP 484 type annotations.
Args:
param1: The first parameter.
param2: The second parameter.
Returns:
The return value. True for success, False otherwise.
"""
This renders as follows:
The parameters list has all the parameters, but does not attach the types. It is possible to add them manually, but this might introduce future problems when is decided to change the signature.
Example with manual type addition:
def function_with_pep484_type_annotations(param1: int, param2: str) -> bool:
"""Example function with PEP 484 type annotations.
Args:
param1 (int): The first parameter.
param2 (str): The second parameter.
Returns:
The return value. True for success, False otherwise.
"""
which renders as:
You can now use the sphinx-autodoc-typehints extension. It will automatically add the types to the sphinx docstrings when you write in the former example above.
To install, just do:
$ pip install sphinx-autodoc-typehints
Add 'sphinx_autodoc_typehints' to the extensions list in conf.py after 'sphinx.ext.napoleon', and make sure you also add napoleon_use_param = True to conf.py.
I have seen several standards for writing comments about the kind of data a function expects and returns in Python. Is there a consensus on which one is best-practice?
Is the new functionality in http://www.python.org/dev/peps/pep-3107/ something I should start using for this?
Function annotations are not for a specific use, they can be used for anything.
Tools can be written to extract information from the annotations and do anything you want, including checking types or generating documentation. But python itself does not do anything with the information. You could use to a completely different purpose, i.e. to provide a function that will be called on the parameter or to declare a string of possible return values.
Annotations can be any object:
def somefunc(param1: "string annotation",
param2: 151631,
param3: any_object): -> "some information here":
and you can retrieve the objects using:
print (somefunc.func_annotations)
{'param1': "string annotation",
'param2': 151631,
'param3': <object any_object>,
'return': "some information here"}
Use case suggestions provided by the PEP:
Providing typing information
Type checking
Let IDEs show what types a function expects and returns
Function overloading / generic functions
Foreign-language bridges
Adaptation
Predicate logic functions
Database query mapping
RPC parameter marshaling
Other information
Documentation for parameters and return values
Since function annotation syntax is too new, it is really not used for any production tools.
I suggest using other methods to document that. I use epydoc to generate my documentation, and it can read parameter typing information from docstrings:
def x_intercept(m, b):
"""
Return the x intercept of the line M{y=m*x+b}. The X{x intercept}
of a line is the point at which it crosses the x axis (M{y=0}).
This function can be used in conjuction with L{z_transform} to
find an arbitrary function's zeros.
#type m: number
#param m: The slope of the line.
#type b: number
#param b: The y intercept of the line. The X{y intercept} of a
line is the point at which it crosses the y axis (M{x=0}).
#rtype: number
#return: the x intercept of the line M{y=m*x+b}.
"""
return -b/m
This example is from epydoc's website. It can generate documentation in a variety of formats, and can generate good graphs from your classes and call profiles.
If you use epydoc to produce API documentation, you have three choices.
Epytext.
ReStructuredText, RST.
JavaDoc notation, which looks a bit like epytext.
I recommend RST because it works well with sphinx for generating overall documentation suite that includes API references. RST markup is defined here. The various epydoc fields you can specify are defined here.
Example.
def someFunction( arg1, arg2 ):
"""Returns the average of *two* (and only two) arguments.
:param arg1: a numeric value
:type arg1: **any** numeric type
:param arg2: another numeric value
:type arg2: **any** numeric type
:return: mid-point (or arithmetic mean) between two values
:rtype: numeric type compatible with the args.
"""
return (arg1+arg2)/2
Python 3.5 official typing
https://docs.python.org/3/library/typing.html
This update makes types an actual part of the language.
Example:
#!/usr/bin/env python3
from typing import List
def f(x: int, ys: List[float]) -> str:
return "abc"
# Good.
f(1, [2.0, 3.0])
# Bad.
f("abc", {}) # line 12
x = 1
x = "a" # line 15
y = [] # type: List[int]
y.append("a") # line 18
This code runs normally: Python 3.5 does not enforce typing by default.
But it can however be used by static linters to diagnoze problems, e.g.:
sudo pip3 install mypy
mypy a.py
gives:
a.py:12: error: Argument 1 to "f" has incompatible type "str"; expected "int"
a.py:12: error: Argument 2 to "f" has incompatible type Dict[<nothing>, <nothing>]; expected List[float]
a.py:15: error: Incompatible types in assignment (expression has type "str", variable has type "int")
a.py:18: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int"
Existing static analyzers like Pycharm's can already parse Sphinx documentation types, but this language update gives an official canonical way that will likely prevail.
Sphinx 1.8.2 does not seem to support it yet, but it is only a matter of time: Python 3: Sphinx doesn't show type hints correctly