That would be the solution indeed, but is that still an instance variable? Because it is defined in the class body, it would be a class variable in my understanding. [...snip...] However, it seems wrong to me if you call it an instance variable and I don't know if I should use it like this just to have the type hinting working.
For what it's worth, I also share the same discomfort. It seems like we're conceptually mixing two concepts there just for the sake of having cleaner type annotations.
However, I've asked Guido one or two times about this, and it seems like he does indeed prefers treating those class attributes as if they were instance attributes.
In any case, to answer your core question, if we do this:
class Test:
field1: int
field2: str = 'foo'
Then...
- PEP 484 and 526 compliant type checkers will treat this class as if:
- It has an instance attribute named
field1
- It has an instance attribute named
field2
that has a default value of 'foo' (as per PEP 526).
- At runtime, ignoring type hints, Python will:
- Add a class annotation named
field1
to Test, but not a class attribute. (Class annotations are not automatically turned into class attributes.)
- Add both a class annotation named
field2
to Test as well as a class attribute named field2
containing the value 'foo'.
So, it can get a bit muddled.
But regardless, this then begs the question: how do we indicate to a type checker that we want some field to genuinely be a class attribute?
Well, it turns out PEP 484 was amended semi-recently to contain the ClassVar
type annotation, which does exactly that.
So, if we wanted to add a new class attribute, we could do this:
from typing import ClassVar
class Test:
field1: int
field2: str = 'foo'
field3: ClassVar[int] = 3
So now, field3
should be treated as a class attribute with a default value of '3'.
(Note: ClassVar was added to typing
for Python 3.5.3 -- if you're using the older version of typing
bundled with Python 3.5, you can get a "backport" of the type by installing the typing_extensions
third part module via pip and importing ClassVar
from there instead.)
I think whether you decide to embrace this approach or not use it is a personal preference.
On one hand, Guido's opinion, pretty much by definition, defines what's "Pythonic" or not, so from that stance, there's no issue adopting this new idiom. Furthermore, the language itself is slowly but surely shifting to adopt this new idiom -- see the very recently accepted PEP 557, for example, which ends up following this same idiom of treating class attributes/class annotations as instance attributes.
On the other hand, it's difficult to shake off the nagging worry that this subtle difference will lead to issues down the line. In that case, you could stick with the standard approach of just setting all your fields inside __init__
. This approach also has the benefit of keeping your code compatible with Python 2 and 3.x - 3.5.
A middle ground might be to just simply never use class attributes, in any way, shape, or form, and just stick to using class annotations. This is slightly restrictive, since we can no longer give our instance variables default values, but we can now avoid conflating class attributes with instance attributes entirely. (As previously stated, and as pointed out in the comments, class annotations are not added as class attributes.)