init Cannot Be a Generator

ID

python.init_not_generator

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Python

Tags

reliability, suspicious-construct

Description

Reports init methods that contain yield or yield from. The presence of a yield anywhere in a function turns it into a generator: the body does not execute when called, and the call returns a generator object. Inside init that means the constructor never runs the assignments to self, so the resulting instance is half-constructed; worse, init is contractually supposed to return None, and the implicit "return generator" raises TypeError once Python tries to assign the generator to nothing.

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

class BadFrom:
    def __init__(self, items):
        yield from items    # FLAW

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

Rationale

The combination of "constructor expected to mutate self`" and "generator that defers all work" is a category error. A class that wants to expose iteration should expose it through `iter, not by accident through init.

Remediation

Move the iteration logic into iter (or a dedicated method) and keep init as a regular function that initialises attributes:

class Good:
    def __init__(self, items):
        self.items = items

    def __iter__(self):
        yield from self.items