Instance Attribute Outside init

ID

python.instance_attr_outside_init

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Best Practice

Language

Python

Tags

best-practice, code-style

Description

Reports self.attr = …​ assignments that introduce a new instance attribute in a method other than init. Defining attributes lazily means the shape of an instance depends on the order of method calls; readers have to follow every code path to know which attributes exist on a given instance, and an exception while building the object can leave it half-formed.

class Bad:
    def __init__(self):
        self.x = 1

    def configure(self, y):
        self.y = y          # FLAW — y not declared in __init__

class Good:
    def __init__(self):
        self.x = 1
        self.y = None

    def configure(self, y):
        self.y = y          # OK — y is initialized in __init__

Rationale

Lazy attribute creation makes the class harder to reason about: hasattr(obj, "y") can return different values for different instances of the same class, and IDE / typecheck tooling cannot infer the attribute set from the constructor alone.

The rule reports only attributes that are not also assigned in the class’s init (or its constructor-equivalents new / post_init). An attribute defined in init and re-assigned later is plain mutation and is fine.

Remediation

Initialize the attribute in init (set it to None if the value is not yet known), and re-assign it from the configuring method:

class Good:
    def __init__(self):
        self.x = 1
        self.y = None       # placeholder

    def configure(self, y):
        self.y = y

For dataclasses, declare the attribute as a class-level annotated field; the dataclass decorator generates the init for you.