简体   繁体   English

如何在 function 签名中引用 python class 属性的类型

[英]How to reference a python class attribute's type in a function signature

I have a common problem of referencing a class attribute type in a functions signature.我有一个在函数签名中引用 class 属性类型的常见问题。 Take for instance:举个例子:

class Person:
    name: str 

    def __init__(self, name) -> None:
        self.name = name 

def greet(name: str):
    return "Hello " + name

In the snippet above I'd expect that I could do:在上面的代码片段中,我希望我能做到:

class Person:
    name: str 

    def __init__(self, name) -> None:
        self.name = name 

def greet(name: Person.name):
    return "Hello " + name

But this gives me an error pyright error Expected class type but received str .但这给了我一个错误 pyright error Expected class type but received str

I have gotten around it in a couple ways.我已经通过几种方式解决了这个问题。

  1. Define a type and use it in both instances.定义一个类型并在两个实例中使用它。
PersonName = str

class Person:
    name: PersonName 

    def __init__(self, name) -> None:
        self.name = name 

def greet(name: PersonName):
    return "Hello " + name
  1. use reveal_type .使用reveal_type
class Person:
    name: str

    def __init__(self, name) -> None:
        self.name = name 

def greet(name: reveal_type[Person.name]):
    return "Hello " + name

This feels like a simple thing to do but I've struggle to find a clean way.这感觉像是一件简单的事情,但我一直在努力寻找一种干净的方法。 What am I missing?我错过了什么?

Edit: Thanks for the answers so far.编辑:感谢您到目前为止的回答。 I understand python typing doesn't parse Person.name .我了解 python 键入不解析Person.name I'm trying to understand if there is any syntax for accessing the type of a class attribute.我试图了解是否有任何语法可用于访问 class 属性的类型。 It appears reveal_type works but it feels like there should be a better way?看起来reveal_type有效,但感觉应该有更好的方法? For instance this is how it would be done in typescript.例如,这是在 typescript 中完成的方式。

class Person {
  name: string;
}

function greet(name: Person["name"]) {
  console.log(`hello ${name}`)
}

I think the other answers here are not wrong, but missing the actual point.我认为这里的其他答案并没有错,但没有抓住实际要点。

It seems to me that you just misunderstand, what it is you are actually doing, when you write name: Person.name .在我看来,你只是误解了,当你写name: Person.name时,你实际上在做什么。 The right hand side is attribute access , meaning you are accessing the value of the name attribute of Person .右侧是属性访问,这意味着您正在访问Personname属性的

But Person is a class and the way you defined it, that class has no attribute name .但是Person是 class 并且您定义它的方式是 class没有属性name It just has an annotation for name .它只有name注释 You can see that if you just try and do the following:您可以看到,如果您只是尝试执行以下操作:

class Person:
    name: str

print(Person.__annotations__)
print(Person.name)

The first print will give you {'name': <class 'str'>} , whereas the second will not even execute because Person.name will raise an AttributeError: type object 'Person' has no attribute 'name' .第一个print会给你{'name': <class 'str'>} ,而第二个甚至不会执行,因为Person.name会引发AttributeError: type object 'Person' has no attribute 'name'


You could create that class attribute by assigning a value to name in the class namespace:可以通过为 class 命名空间中的name分配一个值来创建 class 属性:

class Person:
    name: str = "foo"

Now Person.name actually exists as an attribute.现在Person.name实际上作为一个属性存在。

But that would not solve your problem because that value is a string ie an instance of the str class. Yet you want to annotate a variable with that, specifically you are doing this:但这不会解决您的问题,因为该值是一个字符串,即str class 的一个实例。但是您想要用它来注释一个变量,具体来说,您正在这样做:

def greet(name: Person.name): ...

Which would be equivalent to this:这相当于:

def greet(name: "foo"): ...

While syntactically correct, this makes no sense to any type checker because a variable annotation must be done with a type , not an instance.虽然在语法上是正确的,但这对任何类型检查器都没有意义,因为变量注释必须使用类型而不是实例来完成。 That is what pyright is telling you: Expected class type but received str .就是 pyright 告诉您的内容: Expected class type but received str It sees that you are trying to annotate the name function parameter with an instance of str , but you should use an instance of type (or a special typing construct such as Union ).它看到您正在尝试使用str的实例注释name function 参数,但您应该使用type的实例(或特殊类型构造,例如Union )。

By the way, the type checker does not see what specific instance of str is used in that annotation because it does not execute your code, it just reads it.顺便说一句,类型检查器不到该注释中使用了str的哪个特定实例,因为它不执行您的代码,它只是读取它。 It does not know (or care) that the value at that point is always "foo" .它不知道(或不关心)此时的值始终是"foo" The only thing it sees is that Person.name is a str instance and that is obviously not valid for type annotations.它唯一看到的是Person.name是一个str实例,这显然对类型注释无效。


In your third code snippet you are technically just implicitly creating what is called a type alias by assigning PersonName = str .在您的第三个代码片段中,您在技术上只是通过分配PersonName = str隐式创建所谓的类型别名 Those are also totally fine to use in annotations, which is why there is no problem there.这些也完全可以用于注释,这就是为什么那里没有问题的原因。 The type checker just resolves both name: PersonName annotations (in the Person class namespace and in the greet function signature) to name: str as in your very first snippet.类型检查器只是将name: PersonName注释(在Person class 命名空间和greet function 签名中)解析为name: str ,就像在您的第一个片段中一样。

To drive this point home, starting with Python 3.10 you could also do the following:要明确这一点,从 Python 3.10开始,您还可以执行以下操作:

from typing import TypeAlias


class Person:
    name_type: TypeAlias = str
    name: name_type


def greet(name: Person.name_type) -> None:
    print("Hi", name)

Notice again that the name_type attribute actually exists after the Person class is fully defined.再次注意,在Person class 完全定义之后, name_type属性实际存在。 Which means, by the time we get to the function definition, we can use that attribute to annotate the parameter.这意味着,当我们到达 function 定义时,我们可以使用该属性来注释参数。 (Though I find this approach quite ugly.) (虽然我觉得这种方法很丑陋。)

Like @Karl Knechtel states in the comment, it is not how typing is designed to work.就像@Karl Knechtel 在评论中指出的那样,这并不是设计打字的方式。 You are describing an interface using typing, so if I import your package/class and try to call a function, I should know how to do so.你正在使用类型描述一个接口,所以如果我导入你的包/类并尝试调用 function,我应该知道该怎么做。 It makes sense for custom classes you could define ( np.ndarray or pd.DataFrame are all over my code,), but if the interface for interacting with the class field is just str you should be using that, and the variable name should tell me the rest. If you have specific additional restrictions for Person.name making it not quack like a str , it is a separate class. If not, use str .这对于您可以定义的自定义类是有意义的( np.ndarraypd.DataFrame遍布我的代码,),但是如果与 class 字段交互的接口只是str您应该使用它,并且变量名应该告诉我是 rest。如果您对Person.name有特定的附加限制,使其不像str那样嘎嘎作响,则它是一个单独的 class。如果不是,请使用str

On that note, if what you really want is defining a schema with possible additional validation and whatnot, you should look towards something like pydantic or marshmallow .关于这一点,如果您真正想要的是定义一个带有可能的额外验证的模式等等,您应该看看像pydanticmarshmallow之类的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM