[Tutor] Function annotations
Steven D'Aprano
steve at pearwood.info
Sat Feb 4 22:23:55 EST 2017
On Sat, Feb 04, 2017 at 08:50:00PM -0600, boB Stepp wrote:
> I just finished looking at
> https://docs.python.org/3/tutorial/controlflow.html#function-annotations
> and skimming through PEP 484--Type Hints
> (https://www.python.org/dev/peps/pep-0484/). My initial impression is
> that the purpose of function annotations is to enable static code
> analysis tools like linters to be more effective.
That's exactly what they're for.
> Aesthetically, to
> my eye it makes the function definition line more cluttered looking
> and more difficult to interpret at a glance.
Obviously they take up more room, but they also provide more
information: the intended type of the argument,
> Of course, these are
> apparently optional. I now wonder if I should be endeavoring to add
> these to my code?
Do you run a linter? If not, there doesn't seem much point in adding
annotations.
> It is not clear to me how to implement function annotations in all
> instances. Starting with a rather useless function:
>
> def prt_stupid_msg():
> print('Duh!')
>
> This takes no arguments and returns None. While this particular
> function is rather useless, many others of some utility may take no
> arguments and return None. How should these types of functions be
> annotated?
Since it take no arguments, you cannot annotate the arguments it
doesn't have.
You could annotate the return value:
def prt_stupid_msg() -> None:
print('Duh!')
but that's unlikely to be useful. I wouldn't bother.
> What about:
>
> def print_stuff(stuff):
> print(stuff)
>
> Should this be annotated as:
>
> def print_stuff(stuff: Any) -> None:
>
> ?
Probably not. If you don't annotate the function, the linter should just
assume it takes anything as argument.
> What about a function from the tutorial:
>
> def make_incrementor(n):
> return lambda x: x + n
>
> n might always be an int or might always be a float or might be
> either, depending on the author's intent. Therefore, how should n be
> annotated? In accordance with the author's intentions?
Of course.
> And what
> about the case where n is sometimes an integer and sometimes a float?
That's a Union of two types. Think of Union as meaning "this OR that".
> And the return in this instance is a function. Should the return be
> annotated "function"?
from typing import Union
from types import FunctionType
def make_incrementor(n: Union[int, float]) -> FunctionType:
return lambda x: x + n
> And what about functions that return different types depending on
> conditional statements?
Probably a bad design...
> How would these varying return types be annotated? Say:
>
> def return_typed_value(desired_type, value):
> if desired_type = 'string':
> return str(value)
> elif desired_type = 'integer':
> return int(value)
> elif desired_type = 'float':
> return float(value)
>
> What should I do with this?
Redesign it.
def return_typed_value(
desired_type: str, value: Any) -> Union[str, int, float]:
...
> As written "desired_type" will always be
> a string, but "value" will be potentially several different types.
If you want to restrict the type of `value` depending on the value of
`desired_type`, you cannot expect the compiler or linter or type-checker
to do this at compile time. In general, it doesn't know what the value
of `desired_type` is, so it can't tell whether the type of `value` is
valid or not.
You would have to use a *dynamic* type check, rather than static:
def return_typed_value(desired_type, value):
if desired_type == 'string':
if isinstance(value, (int, float)):
return str(value)
elif desired_type == 'integer':
if isinstance(value, (float, str)):
return int(value)
elif desired_type == 'float':
if isinstance(value, (int, str)):
return float(value)
raise TypeError
When your type checking rules become this intricate, it's probably a bad
idea.
--
Steve
More information about the Tutor
mailing list