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:
x
here, as it is still undefined,2 < 1
), it doesn't affect the function behavior.Could anyone familiar with this syntax style explain it?
This is function annotations. It can be use to attach additional information to the arguments or a return values of functions. It is a useful way to say how a function must be used.
The None keyword is used to define a null value, or no value at all. None is not the same as 0, False, or an empty string. None is a data type of its own (NoneType) and only None can be None.
Straight as an arrow Python doesn't use arrow notation, like JavaScript — so what is this arrow doing? This, friend, is a return value annotation, which is part of function annotation, and it's a feature of Python 3 - and has been here since 3.0!
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With