1

I'm trying to catch up on Python variable annotations. According to PEP-0526 we now have something like:

class BasicStarship:
    captain: str = 'Picard'               # instance variable with default
    damage: int                           # instance variable without default
    stats: ClassVar[Dict[str, int]] = {}  # class variable 

It's been a rough weekend, and my Python is a bit rusty, but I thought variables declared without assignment to self were class variables. Here are some interesting examples:

class BasicStarship:
        captain: str = 'Picard'               # instance variable with default

        def __init__(self,name):
            self.captain = name

wtf = BasicStarship('Jim')
BasicStarship.captain = 'nope'

print(wtf.captain) #=> Jim

The above code works as I would expect. Below however confuses me a bit.

class BasicStarship:
        captain: str = 'Picard'               # instance variable with default

wtf = BasicStarship()
BasicStarship.captain = 'nope'

print(wtf.captain) #=> 'nope'

I would have expected 'Picard' instead of 'nope' in the second example. I feel like I am missing some rules about class variables versus instance variables. To some extent I would have thought doing BasicStarship.captain would have resulted in a class error since the captain is an instance variable (in the first example, not sure in the second example). Have you always been able to define instance variables after the class declaration (outside of methods)? Is there some interplay between class and instance variables that would make this clearer?

Code run with Python 3.6.3

9
  • Does this answer your question? What is the python attribute get and set order? Commented Jan 6, 2020 at 3:23
  • Until a value is assigned to that attribute of the instance then the class attribute is the instance attribute. What happens when you then doe wtf.captain = 'foo'? Commented Jan 6, 2020 at 3:32
  • @wwii it would for the first time assign the captain instance attribute, previously a reference to it would just be a reference to the underlying class attribute - the documentation is not very clear. Commented Jan 6, 2020 at 3:34
  • Read through the Custom classes and Class instances sections of 3.2 The standard type heiarchy. - attribute lookup and assignment. Commented Jan 6, 2020 at 3:40
  • 1
    @ventaquil No, OP knows (even though they are unsure of themselves) how to declare a "static attribute". The problem is a comment in documentation on typing.ClassVar that seems to suggest that captain: str = 'Picard' defines an instance variable, causing confusion. Commented Jan 6, 2020 at 4:01

2 Answers 2

1

I share some of your confusion about the documentation, since it seems that captain in your example is a class attribute instead of an instance attribute.

Consider this:

class BasicStarship:
    captain = 'Picard'

    def __init__(self, captain=None):
        if captain:
            self.captain = captain


wtf1 = BasicStarship()
wtf2 = BasicStarship('Me!')

BasicStarship.captain = 'Riker'

print(wtf1.captain)
print(wtf2.captain)

As you would expect (based on your question), this prints:

Riker
Me!

However, this is interesting:

print(wtf1.__dict__)
print(wtf2.__dict__)
print(BasicStarship.__dict__)

Results in:

{}
{'captain': 'Me!'}
{'__module__': '__main__', 'captain': 'Riker', '__init__': <etc.> }

So, wtf1 does not have an attribute called captain and therefore uses the class attribute called captain (explains why this changes when you change it). wtf2 does have an attribute called captain, so that overrides the class attribute. BasicStarship shows the class attribute.

This all makes sense and is similar to the example given in the actual documentation, the only confusing part is the description as instance variable with default, as it seems more correct as class variable with default.

(on Python 3.7.5, same otherwise)

Sign up to request clarification or add additional context in comments.

Comments

0

It is the normal interplay of class variables and instance variables. It has nothing to do with typing.

class Quux:
    foo = 1
    bar = 2

    def __init__(self):
        self.bar = 3
        self.baz = 4

quux = Quux()
Quux.foo    # => 1   - class variable
Quux.bar    # => 2   - class variable
quux.foo    # => 1   - class variable, because no instance variable
quux.bar    # => 3   - instance variable shadowing the class variable
quux.baz    # => 4   - instance variable

Your error is the wording here:

captain: str = 'Picard'               # instance variable with default

captain is not an instance variable here. This defines a class variable, which acts as a default when a corresponding instance variable is not set. But the class variable and the instance variable are two separate things.

Note that typing is only ever evaluated by static type checkers, never by Python interpreter itself. Thus, there cannot be any runtime semantic difference between these two:

class CheckedQuux:
    foo: Dict[str, int] = {}
    bar: ClassVar[Dict[str, int]] = {}

At runtime, they are both assignments of an empty dictionary to a class variable. If the annotation made it so that one of them defined an instance variable and the other a class variable, it would violate the rule that typing can't have runtime effect. The comment under ClassVar is misleading, though the text gives hints about what is meant: the annotation serves to indicate the intent of how the variable will be used. The type checker is to assume that foo above will be accessed from the instance (checked_quux.foo) and will presumably be shadowed over by an instance variable, while bar should not be accessed on the instance, but only class (CheckedQuux.bar) and should raise a type check error if we tried to access it on an instance (checked_quux.bar)

1 Comment

I think the OP is not missing the distinction, they are confused by the documentation stating that foo = 1 is an instance variable. (understandably so, in my opinion)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.