功能注释:PEP-3107
我遇到了一段代码,演示了Python3的功能注释。这个概念很简单,但我想不出为什么这些在Python3中实现或者对它们有任何好用。也许SO可以启发我吗?
工作原理:
def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
... function body ...
参数后冒号后面的所有内容都是'注释',->
后面的信息是函数返回值的注释。
foo.func_annotations将返回字典:
{'a': 'x',
'b': 11,
'c': list,
'return': 9}
有这个有什么意义吗?
答案 0 :(得分:83)
我认为这实际上很棒。
从学术背景来看,我可以告诉你,注释已证明对于为Java这样的语言启用智能静态分析器非常宝贵。例如,您可以定义语义,如状态限制,允许访问的线程,体系结构限制等,并且有很多工具可以读取这些并处理它们以提供超出编译器的保证。你甚至可以编写检查前置条件/后置条件的东西。
我认为在Python中特别需要这样的东西,因为它的键入较弱,但实际上没有任何构造使这个直接成为官方语法的一部分。
除了保证之外,注释还有其他用途。我可以看到如何将基于Java的工具应用于Python。例如,我有一个工具,可以让你为方法分配特殊警告,并在你调用它们时给你指示你应该阅读他们的文档(例如,假设你有一个不能用负值调用的方法,但它是名字不直观)。使用注释,我可以为Python编写类似的东西。类似地,如果存在官方语法,则可以编写基于标记在大类中组织方法的工具。
答案 1 :(得分:83)
功能注释就是你对它们所做的。
它们可用于文档:
def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
...
它们可用于前置条件检查:
def validate(func, locals):
for var, test in func.__annotations__.items():
value = locals[var]
msg = 'Var: {0}\tValue: {1}\tTest: {2.__name__}'.format(var, value, test)
assert test(value), msg
def is_int(x):
return isinstance(x, int)
def between(lo, hi):
def _between(x):
return lo <= x <= hi
return _between
def f(x: between(3, 10), y: is_int):
validate(f, locals())
print(x, y)
>>> f(0, 31.1)
Traceback (most recent call last):
...
AssertionError: Var: y Value: 31.1 Test: is_int
另请参阅http://www.python.org/dev/peps/pep-0362/了解实现类型检查的方法。
答案 2 :(得分:35)
这是一种迟到的答案,但AFAICT,功能注释的当前最佳用途是PEP-0484和MyPy。
Mypy是Python的可选静态类型检查器。您可以使用即将推出的Python 3.5 beta 1(PEP 484)中引入的类型注释标准向Python程序添加类型提示,并使用mypy进行静态类型检查。
像这样使用:
command = r"elev.cmd {0}\data\nssm.exe install service {1}\data\service.exe".format(targetdir,targetdir)
答案 3 :(得分:23)
只是从我的答案here中添加一个好用的具体示例,再加上装饰器,就可以实现多方法的简单机制。
# This is in the 'mm' module
registry = {}
import inspect
class MultiMethod(object):
def __init__(self, name):
self.name = name
self.typemap = {}
def __call__(self, *args):
types = tuple(arg.__class__ for arg in args) # a generator expression!
function = self.typemap.get(types)
if function is None:
raise TypeError("no match")
return function(*args)
def register(self, types, function):
if types in self.typemap:
raise TypeError("duplicate registration")
self.typemap[types] = function
def multimethod(function):
name = function.__name__
mm = registry.get(name)
if mm is None:
mm = registry[name] = MultiMethod(name)
spec = inspect.getfullargspec(function)
types = tuple(spec.annotations[x] for x in spec.args)
mm.register(types, function)
return mm
以及使用示例:
from mm import multimethod
@multimethod
def foo(a: int):
return "an int"
@multimethod
def foo(a: int, b: str):
return "an int and a string"
if __name__ == '__main__':
print("foo(1,'a') = {}".format(foo(1,'a')))
print("foo(7) = {}".format(foo(7)))
这可以通过将类型添加到装饰器来完成,如Guido's original post所示,但注释参数本身更好,因为它避免了参数和类型错误匹配的可能性。
注意:在Python中,您可以访问注释function.__annotations__
而不是function.func_annotations
,因为在Python 3上删除了func_*
样式。
答案 4 :(得分:20)
Uri已经给出了正确答案,所以这里不太严肃:所以你可以缩短你的文档。
答案 5 :(得分:12)
我第一次看到注释时,我认为很棒!最后我可以选择进行某种类型检查!&#34;当然,我没有注意到注释实际上并没有被强制执行。
所以我决定write a simple function decorator to enforce them:
def ensure_annotations(f):
from functools import wraps
from inspect import getcallargs
@wraps(f)
def wrapper(*args, **kwargs):
for arg, val in getcallargs(f, *args, **kwargs).items():
if arg in f.__annotations__:
templ = f.__annotations__[arg]
msg = "Argument {arg} to {f} does not match annotation type {t}"
Check(val).is_a(templ).or_raise(EnsureError, msg.format(arg=arg, f=f, t=templ))
return_val = f(*args, **kwargs)
if 'return' in f.__annotations__:
templ = f.__annotations__['return']
msg = "Return value of {f} does not match annotation type {t}"
Check(return_val).is_a(templ).or_raise(EnsureError, msg.format(f=f, t=templ))
return return_val
return wrapper
@ensure_annotations
def f(x: int, y: float) -> float:
return x+y
print(f(1, y=2.2))
>>> 3.2
print(f(1, y=2))
>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>
我将其添加到Ensure库。
答案 6 :(得分:3)
很长一段时间以来被问到这个问题,但是问题中给出的示例片段(如那里所述)来自PEP 3107,并且在PEP示例结束时也给出了可以回答问题的用例。 PEPs的观点;)
以下内容来自PEP3107
使用案例
在讨论注释的过程中,提出了许多用例。其中一些在这里呈现,按照它们传达的信息进行分组。还包括可以使用注释的现有产品和包的示例。
有关特定点(及其参考资料)的更多信息,请参阅PEP
答案 7 :(得分:1)
作为一个延迟答案,我的一些软件包(marrow.script,WebCore等)使用注释来声明类型转换(即转换来自Web的传入值,检测哪些参数是布尔开关等)。 )以及执行额外的参数标记。
Marrow Script为任意函数和类构建了一个完整的命令行界面,允许通过注释定义文档,转换和回调派生的默认值,并使用装饰器来支持较旧的运行时。我使用注释的所有库都支持表单:
any_string # documentation
any_callable # typecast / callback, not called if defaulting
(any_callable, any_string) # combination
AnnotationClass() # package-specific rich annotation object
[AnnotationClass(), AnnotationClass(), …] # cooperative annotation
对文档字符串或类型转换函数的“Bare”支持允许更容易地与其他具有注释感知的库混合。 (即,有一个使用类型转换的Web控制器,它也恰好作为命令行脚本公开。)
编辑添加:我还开始使用TypeGuard包使用开发时断言进行验证。好处:在启用“优化”(-O
/ PYTHONOPTIMIZE
env var)的情况下运行时,省略了可能很昂贵(例如递归)的检查,并认为您已正确测试了应用程序开发所以在生产中不需要检查。
答案 8 :(得分:1)
Python 3.X(仅)也将函数定义概括为允许 用对象值注释的参数和返回值 用于扩展程序。
要解释的META数据,要更加明确函数值。
注释被编码为:value
之后
参数名称和默认值之前,以及->value
之后的
参数列表。
它们被收集到函数的__annotations__
属性中,但不会被Python本身视为特殊:
>>> def f(a:99, b:'spam'=None) -> float:
... print(a, b)
...
>>> f(88)
88 None
>>> f.__annotations__
{'a': 99, 'b': 'spam', 'return': <class 'float'>}
来源:Python袖珍参考,第五版
示例:强>
typeannotations
模块提供了一组用于类型检查和Python代码类型推断的工具。它还提供了一组用于注释函数和对象的类型。
这些工具主要用于静态分析器,如linters,代码完成库和IDE。另外,提供了用于进行运行时检查的装饰器。运行时类型检查在Python中并不总是一个好主意,但在某些情况下它可能非常有用。
答案 9 :(得分:1)
尽管此处描述了所有用法,但可强制执行且最有可能强制使用的注释将用于type hints。
目前没有以任何方式强制执行,但从PEP 484来看,未来的Python版本只允许使用类型作为注释的值。
引用What about existing uses of annotations?:
我们希望类型提示最终将成为注释的唯一用途,但这需要在使用Python 3.5初始推出输入模块后进行额外的讨论和弃用期。在Python 3.6发布之前,当前的PEP将具有临时状态(参见PEP 411)。最快的可想到的方案将引入静态弃用3.6中的非类型提示注释,3.7中的完全弃用,并声明类型提示作为Python 3.8中唯一允许使用的注释。
虽然我还没有看到3.6中的任何静默弃用,但很可能会碰到3.7。
所以,即使可能还有一些其他好的用例,如果你不想在未来存在这种限制的情况下改变所有内容,最好将它们仅用于类型提示。
答案 10 :(得分:-2)
如果你看一下Cython的好处列表,一个主要的是能够告诉编译器Python对象的类型。
我可以设想一个未来,Cython(或编译你的一些Python代码的类似工具)将使用注释语法来实现他们的魔力。