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.