I am trying to have a class with a class variable that represents an empty instance of the class. What I currently have is
from collections import namedtuple
# from typing import Optional
_Thing = namedtuple("_Thing", ["foo", "bar"])
class Thing(_Thing):
__slots__ = ()
def baz(self):
print("foo", self.foo)
# NameError: name 'Thing' is not defined
# EMPTY = Thing(None, None)
Thing.EMPTY = Thing(None, None)
if __name__ == '__main__':
thing = Thing.EMPTY
thing.baz()
print("Done")
I am also trying to run Mypy on the code. When I run python simple.py
, it runs as expected:
$ python simple.py && mypy simple.py
foo None
Done
simple.py:15: error: "Type[Thing]" has no attribute "EMPTY"
simple.py:18: error: "Type[Thing]" has no attribute "EMPTY"
Found 2 errors in 1 file (checked 1 source file)
but Mypy is unhappy because the declaration for Thing
does not define EMPTY
.
If I uncomment the definition of EMPTY
inside the class, I get a NameError
because I am trying to reference Thing
while it is being defined.
If I try to declare EMPTY
in the class as EMPTY = None
and assign it outside the class, Mypy is unhappy because it thinks the type of EMPTY
is None
.
If I try to annotate EMPTY
with Optional[Thing]
as a type, then I get back to using Thing
before it is defined.
Is there a solution to this, or do I just need to tell Mypy to ignore the EMPTY
field?
I am using python 3.9.
You can annotate a variable without assigning it a value. This is useful here because you can't use the name of the type Thing
to create an instance within its own class body, because the global name Thing
doesn't get defined until after the class object is created. So you want just an annotation, with the value to be defined later.
The lack of the global name Thing
is also why your attempts so far at annotating the attribute didn't work. The solution for this is to make a forward reference using a quoted string. You can use "Thing"
to annotate where an instance of your class will be, before Thing
is defined.
(Python 3.11, which comes out in fall 2022, will include PEP 673, which will offer an even better way to refer to "the current class" from within a class's body or methods: typing.Self
. It would be perfect for solving our forward reference problem, but it's not out yet.)
Annotating an attribute in a class body normally tells the type checker that the attribute will be an instance variable (this is the default assumption that type checkers will make). If you intend the attribute to be a class variable, you need to show that by using typing.ClassVar
in the annotation.
So, putting this all together, you probably want something like this:
import typing
class Thing(_Thing):
EMPTY: typing.ClassVar["Thing"]
#...
Thing.EMPTY = Thing(None, None)
If you ever upgrade this code to Python 3.11, you can replace "Thing"
in the annotation with typing.Self
.