示例变量的类型提示约定Python

a1o7rhls  于 2023-03-20  发布在  Python
关注(0)|答案(4)|浏览(133)

我不确定Python中类型提示示例变量的约定--我已经在__init__构造函数参数中做了,如下所示:

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value`

但我也看到了PEP的惯例,即对示例变量进行注解(下面的片段),然后在__init__参数中进行类型提示:

class BasicStarship:
    captain: str = 'Picard'               # instance variable with default
    damage: int                           # instance variable without default
    stats: ClassVar[Dict[str, int]] = {}  # class variable`

    def __init__(self, damage: int, captain: str = None):
        self.damage = damage
        if captain:
            self.captain = captain  # Else keep the default

最后,在PEP 526文章的后面,他们说为了方便和约定,可以做以下事情:

class Box(Generic[T]):
    def __init__(self, content):
        self.content: T = content

(Both以上代码片段的%来自此处。)
那么,这些约定中的一个是否比我应该坚持的其他约定更好/更广泛地被接受(更好的可读性等)?

xmq68pz9

xmq68pz91#

我建议使用第一个版本,在大多数情况下,您可以为__init__方法的参数分配类型。
这个方法的冗余度最小,同时仍然允许类型检查器验证您是否在代码的其他地方正确调用了__init__方法。
我建议使用第二个或第三个版本,当__init__方法变得足够复杂,以至于出现以下一个或多个情况时,可以显式地注解字段(__init__内部或外部):
1.您的字段从哪里开始就不再那么简单了
1.参数和字段之间不再是一对一的Map
1.您有复杂的初始化逻辑,它使字段的赋值方式变得模糊不清。
然而,我不清楚第二个版本还是第三个版本更好--我个人更喜欢第三个版本,因为它在概念上更清晰,似乎没有混合示例与类属性的概念,但我不能否认第二个版本看起来更清晰。
我在gitter的“typing”频道上问了这个问题,得到了Guido的如下回复(你可能不知道,他制作了Python,目前正在研究mypy和typing相关的东西):
无论哪种方式,似乎都有强烈的意见。我确实更喜欢把属性注解放在类体中,而不是把它们散布在__init__和其他方法中。我还认为,对于PEP 526,这将是未来的趋势(对于基于类的NamedTuple声明和可能的https://github.com/ericvsmith/dataclasses,也是如此)。
link to quote
因此,第二个版本似乎比第三个版本更值得推荐,而且以这种方式定义类将在未来的某个时候更深入地集成到Python语言本身中!

编辑:PEP 557, data classes原来是recently accepted,看起来像是在Python 3.7中包含的。

gab6jxml

gab6jxml2#

@阿萨拉
似乎从Python 3.8.10 / Mypy 0.910开始(2021年9月),当谈到类定义中示例变量的类型注解和类的声明之间的区别时类定义中的一个(静态)变量,则默认值的赋值会产生很大的差异。如果不赋值默认值(例如,x: int),Python将表达式视为类型注解;如果你赋值了一个默认值(例如,x: int = 42),Python会把表达式当作一个类(静态)变量声明。
可以使用ClassVar语法为类定义中的类(静态)变量创建类型注解。如果不分配默认值(例如y: ClassVar[int]),则实际类(静态)变量不会被创建;如果你指定一个默认值(例如y: ClassVar[int] = 69),一个事实类(静态)变量 * 将 * 被创建。

ssm49v7z

ssm49v7z3#

我会坚持你在LoggedVar中所做的,它遵循Python中其他地方相同的规则,所以没有那么多混乱。
BasicStarShip类通过将变量移出__init__函数来改变变量的作用域。在声明了captain之后,BasicStarShip.captain将返回'Picard'
PEP526注解读起来很不错,但它是针对__init__函数这一特定情况的新规则。

  • “特例不足以打破常规"*
wgeznvg7

wgeznvg74#

@加里和那些困惑的人,
我认为区别在于使用selfClassName
请尝试运行以下两个示例:(两者都使用默认值cnt = 0,因此应为类变量)
live code

  1. self.cnt += 1-〉的工作方式类似于示例变量,尽管它被赋值(cnt = 0)。
class Test:
    cnt = 0 
    def __init__(self):
        # Test.cnt+=1 
        self.cnt+=1 
    def p(self):
        print(f'self.cnt:{self.cnt}, Test.cnt:{Test.cnt}') 

t1 = Test() 
t2 = Test() 
t1.p() 
t2.p() 

# outputs:
# self.cnt:1, Test.cnt:0
# self.cnt:1, Test.cnt:0
  1. Test.cnt += 1-〉作为类变量工作,正如预期的那样。(因为已赋值)
class Test:
    cnt = 0 
    def __init__(self):
        Test.cnt+=1 
        # self.cnt+=1 
    def p(self):
        print(f'self.cnt:{self.cnt}, Test.cnt:{Test.cnt}') 

t1 = Test() 
t2 = Test() 
t1.p() 
t2.p() 

# outputs:
# self.cnt:2, Test.cnt:2
# self.cnt:2, Test.cnt:2

(No类型注解-结果与cnt: int = 0相同)

相关问题