Skip to content

F841 and PLE0117 don’t understand __class__ bindings #18442

@dscorbett

Description

@dscorbett

Summary

By default, __class__ within a method definition is a free variable and its corresponding cell variable is always bound. unused-variable (F841) and nonlocal-without-binding (PLE0117) have false positives because they don’t understand this special case. This is probably a general problem with how Ruff resolves bindings which could affect other rules.

$ cat >f841_ple0117.py <<'# EOF'
class A:
    x = 1


class B:
    x = 2


class C(A, B):
    def set_class(self, cls):
        nonlocal __class__
        if cls not in type(self).__mro__:
            raise ValueError(f"Invalid superclass: {cls}")
        __class__ = cls

    def get_class(self):
        return __class__

    def __getattribute__(self, attr):
        if attr in vars(type(self)):
            return object.__getattribute__(self, attr)
        if attr in vars(__class__):
            mro = type(self).__mro__
            return getattr(super(mro[mro.index(__class__) - 1], self), attr)
        return getattr(super(), attr)


c = C()
print(c.x)
c.set_class(B)
print(c.x)
# EOF

$ python f841_ple0117.py
1
2

$ ruff --isolated check --select F841,PLE0117 f841_ple0117.py --output-format concise -q
f841_ple0117.py:11:18: PLE0117 Nonlocal name `__class__` found without binding
f841_ple0117.py:14:9: F841 Local variable `__class__` is assigned to but never used

$ ruff --isolated check --select F841,PLE0117 f841_ple0117.py -s --unsafe-fixes --fix

$ python f841_ple0117.py
1
1

That PLE0117 diagnostic is a false positive because __class__ does have a binding; it just isn’t explicit in the source code. That F841 diagnostic is a false positive because __class__ is not a local variable.

Version

ruff 0.11.12 (aee3af0 2025-05-29)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions