I have a common problem of referencing a class attribute type in a functions signature. 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.
I have gotten around it in a couple ways.
PersonName = str
class Person:
name: PersonName
def __init__(self, name) -> None:
self.name = name
def greet(name: PersonName):
return "Hello " + name
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. I'm trying to understand if there is any syntax for accessing the type of a class attribute. It appears reveal_type works but it feels like there should be a better way? For instance this is how it would be done in 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. The right hand side is attribute access, meaning you are accessing the value of the name attribute of Person.
But Person is a class and the way you defined it, that class has no attribute name. It just has an annotation for 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'.
You could create that class attribute by assigning a value to name in the class namespace:
class Person:
name: str = "foo"
Now Person.name actually exists as an attribute.
But that would not solve your problem because that value is a string i.e. an instance of the str class. Yet you want to annotate a variable with that, specifically you are doing this:
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. 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).
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. It does not know (or care) that the value at that point is always "foo". The only thing it sees is that Person.name is a str instance and that is obviously not valid for type annotations.
In your third code snippet you are technically just implicitly creating what is called a type alias by assigning 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.
To drive this point home, starting with Python 3.10 you could also do the following:
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. Which means, by the time we get to the function definition, we can use that attribute to annotate the parameter. (Though I find this approach quite ugly.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With