Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can python class variables become instance variables when altered in __init__?

As far as I understand var is a class variable here:

class MyClass:
    var = 'hello'

    def __init__(self):
        print(self.var)

And thats an instance variable:

class MyClass:

    def __init__(self, var):
        self.var = var
        print(self.var)

I had the problem, that I was looking for a method to make type hinting possible for instance variables. I can of course typehint the parameter with def __init__(self, var: str): but that would not effect the instance variable itself.

Then I noticed in some descriptions (like here) that they used the term instance variable for a var like this:

class MyClass:
    var : str = 'hello'

    def __init__(self, var : str = None):
        self.var = var if var
        print(self.var)

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. If you would use a list for var, all alterations to this list-var would be shared over the instances.

But in this case there would be no problem, because the string is replaced and would not be shared for other instances. 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.

like image 291
Asara Avatar asked Nov 28 '17 13:11

Asara


People also ask

Can a class be an instance variable?

Class methods cannot access instance variables or instance methods directly—they must use an object reference.

Can instance variables be changed?

An instance variable is a variable that is specific to a certain object. It is declared within the curly braces of the class but outside of any method. The value of an instance variable can be changed by any method in the class, but it is not accessible from outside the class.

How instance variable can be created inside the class in Python?

Instance variables are declared inside the constructor i.e., the __init__() method. Class variables are declared inside the class definition but outside any of the instance methods and constructors. It is gets created when an instance of the class is created. It is created when the program begins to execute.

Are instance variables and class variables same?

Following are the notable differences between Class (static) and instance variables. Instance variables are declared in a class, but outside a method, constructor or any block. Class variables also known as static variables are declared with the static keyword in a class, but outside a method, constructor or a block.


1 Answers

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...

  1. PEP 484 and 526 compliant type checkers will treat this class as if:
    1. It has an instance attribute named field1
    2. It has an instance attribute named field2 that has a default value of 'foo' (as per PEP 526).
  2. At runtime, ignoring type hints, Python will:
    1. Add a class annotation named field1 to Test, but not a class attribute. (Class annotations are not automatically turned into class attributes.)
    2. 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.)

like image 118
Michael0x2a Avatar answered Oct 20 '22 19:10

Michael0x2a