Python class and instance variables

  • Class variables are shared by all instances of the class
    • Class variable’s are declared outside of any instance methods
  • Instance variables are owned by each instance

Good rule on decididing when to use class or instance variables:

  • Always use instance variables, unless there is a specific need to share data between instances.
  • With type annotations is good to use value-less annotions to declare instance variables

It is easy to make mistakes if instance and class variables are used in a same class.

class QueryClient:
    source = "www.google.com"  # class variable

    def __init__(self, id):
        self.id = id  # instance variable
        print(f"Init client {self.id} for: {self.source}") # self.source is a class variable

    def fetch(self, query):
        result = f"Fetching: {self.source} with query {query}"
        print(result)
        return result

    def change_source(self, new_source):
        self.source = new_source  # note: this makes self.source an instance variable
        print(f"Client {self.id} source changed to: {self.source}")

"""
1. create a new instance of the class
2. change class variable source
3. create 2nd instance
4. change 2nd instance source
5. change class variable source
"""

query = "python instance vs class variables"

# default class variable is www.google.com
print(QueryClient.source)

client_one = QueryClient(1)
client_one.fetch(query)  # www.google.com

# change the class variable
QueryClient.source = "www.yahoo.com"

client_one.fetch(query) # www.yahoo.com

# Create 2nd instance
client_two = QueryClient(2)
client_two.fetch(query) # www.yahoo.com

client_two.change_source("www.bing.com")
print(QueryClient.source)  # production.company.com

# change_url changed url to an instance variable
client_two.fetch(query) # www.bing.com
# client_one's url is still a class variable
client_one.fetch(query)  # www.yahoo.com

QueryClient.url = "www.duckduckgo.com"

client_one.fetch(query)  # www.duckduckgo.com
# 2nd instances url didn't change as it is now a class variable
client_two.fetch(query) # www.bing.com

Type annotations change the behaviour a little: pep-05269

Type annotations can also be used to annotate class and instance variables in class bodies and methods. In particular, the value-less notation a: int allows one to annotate instance variables that should be initialized in init or new. The proposed syntax is as follows:

Example from pep-05269:

from typing import ClassVar


class Starship:
    captain: str = "Picard"               # instance variable with default 
    damage: int                           # instance variable without default
    stats: ClassVar[dict[str, int]] = {}  # class variable

    def __init__(self, damage: int, captain: str | None = None):
        self.damage = damage
        if captain:
            self.captain = captain  # Else keep the default

    def hit(self) -> None:
        Starship.stats['hits'] = Starship.stats.get('hits', 0) + 1


# Note:
# captain is actually a class variable
# damage is an instance variable
enterprise_d = Starship(3000)
enterprise_d.hit()

enterprise_d.stats = {}  # Flagged as error by a type checker 
# (NOTE: This makes enterprise_d.stas an instance variable)
Starship.stats = {}  # This is OK

Starship.captain = "Kirk"  # This is also OK as captain is actually a class variable
print(enterprise_d.captain)  # captain is "Kirk"

enterprise_d.captain = "Sisko"

print(Starship.captain)  # captain is "Kirk"
print(enterprise_d.captain)  # captain in "Sisko"

Other way to define instance variable with default value:

class Starship:
    captain: str 

    def __init__(self, captain: str = "picard"):
        self.captain = captain 

Data Classes use type annotations to annotate instance variables. __init__ is generated automatically with instance variable arguments.

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int = 20


p = Person("Timmy")
print(p.name)
print(p.age)

# With dataclasses it is also possible to use class variables
Person.name = "Hello"
Person.age = 99

print(Person.name)
print(Person.age)

# But it won't affect default values set in the class definition
p2 = Person("Timmy 2nd")

print(p2.name)
print(p2.age)

Good Python Class Variables example.

Written on July 14, 2022