简体   繁体   中英

How to declare a class variable without a value in a way that suppresses Pylance warning

I love the typechecker in Pylance (VS Code), but there seems to be a situation that I must choose between ignoring a Pylance warning and best practice for class variable declaration. Many times, class variables are initialized using a None type in the class constructor, and the variable is set later. For example:

class Person:
    def __init__(self) -> None:
        self.name:str = None

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

In this case, Pylance gives the following error on the assignment self.name:str = None :

Cannot assign member "name" for type "Person"
Expression of type "None" cannot be assigned to member "name" of class "Person"
Type "None" cannot be assigned to type "str"

Is there any way to declare self.name in the constructor in such a way that a value is not needed and Pylance is happy?

EDIT: Several have suggested the use of typing.Optional to suppress this Pylance warning. However, if another member function is created to return self.name and guarantee that it is returning an instance of str , another Pylance error is generated. See the following example:

class Person:
    def __init__(self) -> None:
        self._name:Optional[str] = None

    @property
    def name(self) -> str:
        return self._name

    @name.setter
    def name(self, name:str) -> None:
        self._name = name

In this case, Pylance generates the error:

(variable) _name: str | None
Expression of type "str | None" cannot be assigned to return type "str"
Type "str | None" cannot be assigned to type "str"
Type "None" cannot be assigned to type "str"

Ideally, there would be some way to initially "allocate" self._name in the constructor in such a way that its only allowed type is str but is not given any value. Is this possible?

You need to make it optional via typing.Optional :

#!/usr/bin/env python
from typing import Optional


class Person:
    def __init__(self) -> None:
        self.name: Optional[str] = None

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

Python 3.10+ supports a simpler syntax via PEP 604 's Union type expressions 1 , eg:

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

update : This is so obvious I assume it won't work in your case, but it is the "natural thing to do"

If you arte annotating instance attributes, and don't want them to be able to read "None" or other sentinel value, simply do not fill in a sentinel value, just declare the attribute and its annotation. That is, do not try to fill in a value in __init__ , declare the annotation in the class body instead:

class Person:

    name: str

    # No need for this:
    # def __init__(self) -> None:
    #    self.name:str = None

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


Assuming you need to initialize it with an invalid value for whatever reason, the original answer applies:

Well, if this was certainly a case were the programmer knows better than the type-checker, I think it would be the case for using typing.cast : this is a no-operation in runtime, but which tells the static checkers that the value being cast is actually of that type, regardless of previous declarations:

(to be clear: don't do this :)

import typing as t

class Person:

    def __init__(self) -> None:
        self._name: t.Optional[str, None] = None

    @property
    def name(self) -> str:
        return t.cast(str, self._name)

    ...

However, upon writing this, I came to the realisation that "self._name" might actually be "None" at this time. The typechecker can do some state-checking by following the program steps, but not in such a generic way - trying to read instance.name before it was set should cause in Python a runtime error instead.

So, if the method is called to express exactly that, it works as the tools can follow parts of the code guarded by isinstance (and if one needs a more complicated check than isinstance , take a look on the docs for typing.TypeGuard ) - and we thank the tools for ensuring the program will run without a hard to debug error afterwards:

( this is the correct way )


import typing as t

class Person:

    def __init__(self) -> None:
        self._name: t.Optional[str, None] = None

    @property
    def name(self) -> str:
        if isinstance(name:=self._name, str):
            return name
        raise AttributeError()

    @name.setter
    def name(self, name:str) -> None:
        self._name = name

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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