From 9f08b64bcd3cb90b5432db7560d5ab5d594556e6 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 8 May 2022 11:09:48 -0700 Subject: [PATCH] Added documentation for class and instance variables. --- docs/type-concepts.md | 160 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/docs/type-concepts.md b/docs/type-concepts.md index 307b61a70..43899230c 100644 --- a/docs/type-concepts.md +++ b/docs/type-concepts.md @@ -416,3 +416,163 @@ Some functions or methods can return one of several different types. In cases wh 6. If no overloads remain and all unions have been expanded, a diagnostic is generated indicating that the supplied arguments are incompatible with all overload signatures. + +### Class and Instance Variables + +Most object-oriented languages clearly differentiate between class variables and instance variables. Python allows an object to overwrite a class variable with an instance variable of the same name. + +```python +class A: + my_var = 0 + + def my_method(self): + self.my_var = "hi!" + +a = A() +print(A.my_var) # Class variable value of 0 +print(a.my_var) # Class variable value of 0 + +A.my_var = 1 +print(A.my_var) # Updated class variable value of 1 +print(a.my_var) # Updated class variable value of 1 + +a.my_method() # Writes to the instance variable my_var +print(A.my_var) # Class variable value of 1 +print(a.my_var) # Instance variable value of "hi!" + +A.my_var = 2 +print(A.my_var) # Updated class variable value of 2 +print(a.my_var) # Instance variable value of "hi!" +``` + +Pyright differentiates between the following three types of variables: + +#### Pure Class Variables +If a class variable is declared with a `ClassVar` annotation as described in [PEP 526](https://peps.python.org/pep-0526/#class-and-instance-variable-annotations), it is considered a “pure class variable” and cannot be overwritten by an instance variable of the same name. + +```python +from typing import ClassVar + +class A: + x: ClassVar[int] = 0 + + def instance_method(self): + self.x = 1 # Type error: Cannot overwrite class variable + + @classmethod + def class_method(cls): + cls.x = 1 + +a = A() +print(A.x) +print(a.x) + +A.x = 1 +a.x = 2 # Type error: Cannot overwrite class variable +``` + +#### Regular Class Variables +If a class variable is declared without a `ClassVar` annotation, it can be overwritten by an instance variable of the same name. The declared type of the instance variable is assumed to be the same as the declared type of the class variable. + +Regular class variables can also be declared within a class method using a `cls` member access expression, but declaring regular class variables within the class body is more common and generally preferred for readability. + +```python +class A: + x: int = 0 + y: int + + def instance_method(self): + self.x = 1 + self.y = 2 + + @classmethod + def class_method(cls): + cls.z: int = 3 + +A.y = 0 +A.z = 0 +print(f"{A.x}, {A.y}, {A.z}") # 0, 0, 0 + +A.class_method() +print(f"{A.x}, {A.y}, {A.z}") # 0, 0, 3 + +a = A() +print(f"{a.x}, {a.y}, {a.z}") # 0, 0, 3 +a.instance_method() +print(f"{a.x}, {a.y}, {a.z}") # 1, 2, 3 + +a.x = "hi!" # Error: Incompatible type +``` + +#### Pure Instance Variables +If a variable is not declared within the class body but is instead declared within a class method using a `self` member access expression, it is considered a “pure instance variable”. Such variables cannot be accessed through a class reference. + +```python +class A: + def __init__(self): + self.x: int = 0 + self.y: int + +print(A.x) # Error: 'x' is not a class variable + +a = A() +print(a.x) + +a.x = 1 +a.y = 2 +print(f"{a.x}, {a.y}") # 1, 2 + +print(a.z) # Error: 'z' is not an known member +``` + +#### Inheritance of Class and Instance Variables +Class and instance variables are inherited from parent classes. If a parent class declares the type of a class or instance variable, a derived class must honor that type when assigning to it. + +```python +class Parent: + x: int | str | None + y: int + +class Child(Parent): + x = "hi!" + y = None # Error: Incompatible type +``` + +The derived class can redeclare the type of a class or instance variable. If `reportIncompatibleVariableOverride` is enabled, the redeclared type must be the same as the type declared by the parent class or a subtype thereof. + +```python +class Parent: + x: int | str | None + y: int + +class Child(Parent): + x: int # This is OK because 'int' is a subtype of 'int | str | None' + y: str # Type error: 'y' cannot be redeclared with an incompatible type +``` + +If a parent class declares the type of a class or instance variable and a derived class does not redeclare it but does assign a value to it, the declared type is retained from the parent class. It is not overridden by the inferred type of the assignment in the derived class. + +```python +class Parent: + x: object + +class Child(Parent): + x = 3 + +reveal_type(Parent.x) # object +reveal_type(Child.x) # object +``` + +If neither the parent nor the derived class declare the type of a class or instance variable, the type is inferred within each class. + +```python +class Parent: + x = object() + +class Child(Parent): + x = 3 + +reveal_type(Parent.x) # object +reveal_type(Child.x) # int +``` +