函数调用作为Python字段注释

我正在研究一个小模块,以利用注释包括额外的内容 通过使用函数调用作为注释来获取有关类字段的数据(请参见代码 下面)。我正在努力做到这一点,同时保持与静态类型检查的兼容性。 (旁注:我这样做是在充分了解PEP 563和推迟评估批注的情况下完成的)

我已经通过mypy 0.670和pycharm 2019.2.4运行了以下代码。 mypy在value字段的声明中报告“ 错误:无效的类型注释或注释”。但是,pycharm推断value字段是一个int。

pycharm似乎已确定函数调用的结果 its_an_int()是类型int,因此可以将字段视为 用于静态类型检查和其他IDE功能的整数。这是理想的 我希望Python类型检查能够完成。

我主要依靠pycharm而不使用mypy。但是,我很谨慎 关于与该设计是否冲突的考虑 对于类型注释,“理智”,尤其是在其他类型检查器如 mypy对此将失败。

如PEP 563所述,“ 用于与上述PEP不兼容的注释的使用应视为已弃用。”。我想说的是,注释主要用于指示类型,但是我看不到任何PEP中的任何东西,否则会阻止在注释中使用表达式。据推测,可以静态分析的表达式本身就是可接受的注释。

是否可以期望下面的value字段可以 由当前为Python定义的静态分析推断为整数 3.8(至4.0)? mypy的分析过于严格还是有限?或者是 pycharm是自由主义者吗?

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))
sy20031983 回答:函数调用作为Python字段注释

您使用的语法似乎不太可能符合PEP 484定义的类型提示。

部分原因是PPE从未声明允许使用任意表达式作为类型提示,部分原因是我不认为您的示例确实符合PEP 484试图实现的精神。

特别是,Python类型化生态系统的一个重要设计目标是在“运行时世界”和“静态类型”世界之间保持相当严格的划分。特别是,应该始终可以在运行时完全忽略类型提示,但是如果在评估时类型提示有时会产生副作用,这将是不可能的。

有人最终会设计出允许您尝试做的PEP,并成功地为它的接受辩护,但这并非不可能,但我认为没有人在从事这种PEP的工作,或者是否有很大的需求

附加或记录元数据的更规范的方法可能是通过执行以下操作来明确显示副作用操作:

# Alternatively,make this a descriptor class if you want to do
# even fancier things: https://docs.python.org/3/howto/descriptor.html
def magic() -> Any:
    # magic here

class Foo:
    value: int = magic()

    def __init__(self,value):
        self.value = value

...或使用表面上刚被接受的PEP 593中描述的新Annotated类型,它允许类型提示和任意非类型提示信息共存:

# Note: it should eventually be possible to import directly from 'typing' in
# future versions of Python,but for now you'll need to pip-install
# typing_extensions,the 'typing' backport.
from typing_extensions import Annotated

def magic():
    # magic here

class Foo:
    value: Annotated[int,magic()]

    def __init__(self,value):
        self.value = value

最后一种方法的主要警告是,鉴于Pycharm非常新,我不认为Pycharm还支持Annotated类型提示。


将所有这些放在一边,值得注意的是,拒绝PEP 484并继续使用Pycharm碰巧理解的内容并不一定错误。 Pycharm显然可以理解您的示例(也许这是Pycharm如何实现类型分析的实现工件),这让我感到有些困惑,但是,如果它对您有用,并且如果将您的代码库调整为符合PEP 484标准,那将非常痛苦。随便摆放什么都是合理的。

如果您仍然希望让使用 的其他开发人员使用PEP 484类型提示来使用您的代码,则可以始终决定将pyi stub文件与软件包一起分发,如PEP 561

生成这些存根文件将花费大量工作,但是存根确实提供了一种方法,使选择退出使用PEP 484的代码与尚未使用PEP 484的代码互操作。

,

以下内容可能会满足您的要求;我不确定。基本上,将存在函数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,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,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,"__next__")
    return status
本文链接:https://www.f2er.com/3131436.html

大家都在问