函数调用作为Python字段注释
我正在开发一个小模块,利用注释包含额外的 通过将函数调用用作注释来获取有关类字段的数据(请参见代码 下)。我正在尝试一种方法来实现这一点,同时保持与静态类型检查的兼容性。(旁注:我在充分了解PEP 563和推迟的注释评估的情况下这样做) 我已经通过mypy 0.670和pycharm 2019.2.4运行了以下代码。mypy在函数调用作为Python字段注释,python,annotations,pycharm,static-analysis,mypy,Python,Annotations,Pycharm,Static Analysis,Mypy,我正在开发一个小模块,利用注释包含额外的 通过将函数调用用作注释来获取有关类字段的数据(请参见代码 下)。我正在尝试一种方法来实现这一点,同时保持与静态类型检查的兼容性。(旁注:我在充分了解PEP 563和推迟的注释评估的情况下这样做) 我已经通过mypy 0.670和pycharm 2019.2.4运行了以下代码。mypy在值的声明中报告“错误:无效的类型注释或批注”。但是,pycharm推断值字段应该是int pycharm似乎已确定函数调用的结果 its_an_int() 用于静态类型检查
值的声明中报告“错误:无效的类型注释或批注”。但是,pycharm推断值字段应该是int
pycharm似乎已确定函数调用的结果
its_an_int()
用于静态类型检查和其他IDE功能的整数。这是理想的,也是可行的
我希望Python类型检查能够完成的事情
我主要依靠pycharm,不使用mypy。然而,我很谨慎
关于使用此设计,如果它与所考虑的设计冲突
“sane”用于类型注释,特别是如果其他类型检查器喜欢
mypy会在这方面失败的
正如PEP 563所说,“与上述PEP不兼容的注释的使用应被视为不推荐使用。”。我认为这意味着注释主要用于指示类型,但我在任何PEP中都看不到阻止在注释中使用表达式的内容。据推测,可以静态分析的表达式是可以接受的注释
下面的值字段可以
通过静态分析可以推断为一个整数,正如当前为Python定义的那样
3.8(至4.0)?mypy的分析是否过于严格或有限?或者是
是自由派吗
from __future__ import annotations
import typing
def its_an_int() -> typing.Type[int]:
# ...magic stuff happens here...
pass
class Foo:
# This should be as if "value: int" was declared, but with side effects
# once the annotation is evaluted.
value: its_an_int()
def __init__(self, value):
self.value = value
def y(a: str) -> str:
return a.upper()
f = Foo(1)
# This call will fail since it is passing an int instead of a string. A
# static analyzer should flag the argument type as incorrect if value's type
# is known.
print(y(f.value))
以下内容可能会满足您的需要;我不确定。基本上,存在一个函数test
,这样用户在编写obj.memvar=y
时就会出现错误,除非test(y)
返回True
。例如,foo
可以测试y
是否是int
类的实例
import typing
import io
import inspect
import string
class TypedInstanceVar:
def __init__(self, name:str, test:typing.Callable[[object], bool]):
self._name = name
self._test = test
def __get__(descriptor, instance, klass):
if not instance:
with io.StringIO() as ss:
print(
"Not a class variable",
file=ss
)
msg = ss.getvalue()
raise ValueError(msg)
return getattr(instance, "_" + descriptor._name)
@classmethod
def describe_test(TypedInstanceVar, test:typing.Callable[[object], bool]):
try:
desc = inspect.getsource(test)
except BaseException:
try:
desc = test.__name__
except AttributeError:
desc = "No description available"
return desc.strip()
@classmethod
def pretty_string_bad_input(TypedInstanceVar, bad_input):
try:
input_repr = repr(bad_input)
except BaseException:
input_repr = object.__repr__(bad_input)
lamby = lambda ch:\
ch if ch in string.printable.replace(string.whitespace, "") else " "
with io.StringIO() as ss:
print(
type(bad_input),
''.join(map(lamby, input_repr))[0:20],
file=ss,
end=""
)
msg = ss.getvalue()
return msg
def __set__(descriptor, instance, new_val):
if not descriptor._test(new_val):
with io.StringIO() as ss:
print(
"Input " + descriptor.pretty_string_bad_input(new_val),
"fails to meet requirements:",
descriptor.describe_test(descriptor._test),
sep="\n",
file=ss
)
msg = ss.getvalue()
raise TypeError(msg)
setattr(instance, "_" + descriptor._name, new_val)
下面,我们看到正在使用的TypedInstanceVar
:
class Klass:
x = TypedInstanceVar("x", lambda obj: isinstance(obj, int))
def __init__(self, x):
self.x = x
def set_x(self, x):
self.x = x
#######################################################################
try:
instance = Klass(3.4322233)
except TypeError as exc:
print(type(exc), exc)
instance = Klass(99)
print(instance.x) # prints 99
instance.set_x(44) # no error
print(instance.x) # prints 44
try:
instance.set_x(6.574523)
except TypeError as exc:
print(type(exc), exc)
第二个例子是:
def silly_requirement(x):
status = type(x) in (float, int)
status = status or len(str(x)) > 52
status = status or hasattr(x, "__next__")
return status
class Kalzam:
memvar = TypedInstanceVar("memvar", silly_requirement)
def __init__(self, memvar):
self.memvar = memvar
instance = Kalzam("hello world")
第二个示例的输出为:
TypeError: Input <class 'str'> 'hello world'
fails to meet requirements:
def silly_requirement(x):
status = type(x) in (float, int)
status = status or len(str(x)) > 52
status = status or hasattr(x, "__next__")
return status
TypeError:输入“hello world”
不符合要求:
def\U要求(x):
状态=输入(x)(浮点,整数)
状态=状态或len(str(x))>52
状态=状态或hasattr(x,“\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
返回状态
您正在使用的语法似乎不太可能符合中定义的类型提示
这部分是因为PEP从未声明允许使用任意表达式作为类型提示,部分是因为我不认为您的示例真正符合PEP 484试图实现的精神
特别是,Python类型化生态系统的一个重要设计目标是在“运行时世界”和“静态类型”世界之间保持相当严格的划分。特别是,在运行时完全忽略类型提示应该是可能的,但如果类型提示在计算时有时会产生副作用,则这是不可能的
有人最终会设计一个政治公众人物,允许你尝试去做的事情,并成功地说服你接受它,这不是不可能的,但我认为没有人在做这样的政治公众人物,或者如果有很多人需要这样的政治公众人物
附加或记录元数据的更规范的方法可能是通过如下操作使副作用操作显式:
#或者,如果需要,将其设置为描述符类
#甚至更奇特的东西:https://docs.python.org/3/howto/descriptor.html
def magic()->任何:
#这里有魔力
Foo类:
值:int=magic()
定义初始值(自身,值):
自我价值=价值
…或使用新的注释的
类型descripe in,它允许类型提示和任意非类型提示信息共存:
#注意:最终应该可以直接从中的“键入”导入
#Python的未来版本,但现在您需要pip安装
#键入扩展,“键入”后端口。
从键入扩展名导入带注释的
def magic():
#这里有魔力
Foo类:
值:带注释的[int,magic()]
定义初始值(自身,值):
自我价值=价值
最后一种方法的主要警告是,我认为Pycharm还不支持带注释的类型提示,因为它是非常新的
抛开所有这些不谈,值得注意的是,拒绝PEP 484并继续使用Pycharm所理解的任何东西并不一定是错误的。Pycharm显然能够理解您的示例(也许这是Pycharm如何实现类型分析的一个实现工件?),这让我有点困惑,但如果它对您有效,并且如果将代码库调整为符合PEP 484太痛苦,那么只使用您现有的代码可能是合理的
而且,如果您希望代码仍能供其他使用PEP 484类型提示的开发人员使用,您可以随时决定将pyi存根文件与包一起分发,如中所述
生成这些存根文件需要相当多的工作,但存根确实提供了一种方法,让选择不使用PEP 484的代码与不使用PEP 484的代码进行互操作。很抱歉,但是我不确定这与我关于静态类型的问题有什么关系。我认为你对运行时和静态世界之间的区别的观察是对我问题的最好回答。我需要静态类型检查,根据PEP 484,我对注释的使用可能会超出范围。我不熟悉PEP593。这肯定对我有用