OOP

These checks ensures that you use Python’s version of OOP correctly.

There are different gotchas in Python to write beautiful classes and using objects correctly. That’s the place we collect these kind of rules.

Summary

BuiltinSubclassViolation

WPS600 — Forbid subclassing lowercase builtins.

ShadowedClassAttributeViolation

WPS601 — Forbid shadowing class level attributes with instance level attributes.

StaticMethodViolation

WPS602 — Forbid @staticmethod decorator.

BadMagicMethodViolation

WPS603 — Forbid certain magic methods.

WrongClassBodyContentViolation

WPS604 — Forbid incorrect nodes inside class definitions.

MethodWithoutArgumentsViolation

WPS605 — Forbid methods without any arguments.

WrongBaseClassViolation

WPS606 — Forbid anything other than a class as a base class.

WrongSlotsViolation

WPS607 — Forbid incorrect __slots__ definition.

WrongSuperCallViolation

WPS608 — Forbid super() with parameters or outside of methods.

DirectMagicAttributeAccessViolation

WPS609 — Forbid directly calling certain magic attributes and methods.

AsyncMagicMethodViolation

WPS610 — Forbid certain async magic methods.

YieldMagicMethodViolation

WPS611 — Forbid yield inside of certain magic methods.

UselessOverwrittenMethodViolation

WPS612 — Forbid useless overwritten methods.

WrongSuperCallAccessViolation

WPS613 — Forbid super() with incorrect method or property access.

WrongDescriptorDecoratorViolation

WPS614 — Forbids descriptors in regular functions.

UnpythonicGetterSetterViolation

WPS615 — Forbids to use getters and setters in objects.

BuggySuperContextViolation

WPS616 — Calling super() in buggy context.

Respect your objects

class BuiltinSubclassViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS600 — Forbid subclassing lowercase builtins.

We forbid to subclass builtins like int, str, bool, etc. We allow to subclass object and type, warnings, and exceptions.

See ALLOWED_BUILTIN_CLASSES for the whole list of whitelisted names.

Reasoning:

It is almost never a good idea (unless you do something sneaky) to subclass primitive builtins.

Solution:

Use custom objects around some wrapper. Use magic methods to emulate the desired behaviour.

Example:

# Correct:
class Some: ...
class MyValueException(ValueError): ...

# Wrong:
class MyInt(int): ...

Added in version 0.10.0.

Changed in version 0.11.0.

error_template: ClassVar[str] = 'Found subclassing a builtin: {0}'
code: ClassVar[int] = 600
previous_codes: ClassVar[Set[int]] = {426}
full_code: ClassVar[str] = 'WPS600'
summary: ClassVar[str] = 'Forbid subclassing lowercase builtins.'
class ShadowedClassAttributeViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS601 — Forbid shadowing class level attributes with instance level attributes.

Reasoning:

This way you will have two attributes inside your __mro__ chain: one from instance and one from class. It might cause errors. Needless to say, that this is just pointless to do so.

Also, if you ever want to optimise your code with a tool like mypyc, this rule is a requirement.

Solution:

Use either class attributes or instance attributes. Use ClassVar type on fields that are declared as class attributes.

Note, that we cannot find shadowed attributes that are defined in parent classes. That’s where ClassVar is required for mypy to check it for you.

Example:

# Correct:
from typing import ClassVar

class First:
    field: ClassVar[int] = 1

class Second:
    field: int

    def __init__(self) -> None:
        self.field = 1

# Wrong:
class Some:
    field = 1

    def __init__(self) -> None:
        self.field = 1

Added in version 0.10.0.

Changed in version 0.11.0.

Changed in version 0.14.0.

error_template: ClassVar[str] = 'Found shadowed class attribute: {0}'
code: ClassVar[int] = 601
previous_codes: ClassVar[Set[int]] = {427}
full_code: ClassVar[str] = 'WPS601'
summary: ClassVar[str] = 'Forbid shadowing class level attributes with instance level attributes.'
class StaticMethodViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS602 — Forbid @staticmethod decorator.

Reasoning:

Static methods are not required to be inside the class. Because they even do not have access to the current instance.

Solution:

Use instance methods, @classmethod, or functions instead.

Added in version 0.1.0.

Changed in version 0.11.0.

See also

webucator.com/article/when-to-use-static-methods-in-python-never

error_template: ClassVar[str] = 'Found using `@staticmethod`'
code: ClassVar[int] = 602
previous_codes: ClassVar[Set[int]] = {433}
full_code: ClassVar[str] = 'WPS602'
summary: ClassVar[str] = 'Forbid ``@staticmethod`` decorator.'
class BadMagicMethodViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS603 — Forbid certain magic methods.

Reasoning:

We forbid to use magic methods related to the forbidden language parts. Likewise, we forbid to use del keyword, so we forbid to use all magic methods related to it.

Solution:

Refactor your code to use custom methods instead. It will give more context to your app.

See MAGIC_METHODS_BLACKLIST for the full blacklist of the magic methods.

Added in version 0.1.0.

Changed in version 0.11.0.

error_template: ClassVar[str] = 'Found using restricted magic method: {0}'
code: ClassVar[int] = 603
previous_codes: ClassVar[Set[int]] = {434}
full_code: ClassVar[str] = 'WPS603'
summary: ClassVar[str] = 'Forbid certain magic methods.'
class WrongClassBodyContentViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS604 — Forbid incorrect nodes inside class definitions.

Reasoning:

Python allows us to have conditions, context managers, and even infinite loops inside class definitions. On the other hand, only methods, attributes, and docstrings make sense. So, we discourage using anything except these nodes in class bodies.

Solution:

If you have complex logic inside your class definition, most likely that you do something wrong. There are different options to refactor this mess. You can try metaclasses, decorators, builders, and other patterns.

Example:

# Wrong:
class Test:
    for _ in range(10):
        print('What?!')

We also allow some nested classes, check out NestedClassViolation for more information.

Added in version 0.7.0.

Changed in version 0.11.0.

error_template: ClassVar[str] = 'Found incorrect node inside `class` body'
code: ClassVar[int] = 604
previous_codes: ClassVar[Set[int]] = {452}
full_code: ClassVar[str] = 'WPS604'
summary: ClassVar[str] = 'Forbid incorrect nodes inside ``class`` definitions.'
class MethodWithoutArgumentsViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS605 — Forbid methods without any arguments.

Reasoning:

Methods without arguments are allowed to be defined, but almost impossible to use. Furthermore, they don’t have an access to self, so cannot access the inner state of the object. It might be an intentional design or just a typo.

Solution:

Move any methods with arguments to raw functions. Or just add an argument if it is actually required.

Example:

# Correct:
class Test:
    def method(self): ...

# Wrong:
class Test:
    def method(): ...

Added in version 0.7.0.

Changed in version 0.11.0.

error_template: ClassVar[str] = 'Found method without arguments: {0}'
code: ClassVar[int] = 605
previous_codes: ClassVar[Set[int]] = {453}
full_code: ClassVar[str] = 'WPS605'
summary: ClassVar[str] = 'Forbid methods without any arguments.'
class WrongBaseClassViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS606 — Forbid anything other than a class as a base class.

We only check base classes and not keywords. They can be anything you need.

Reasoning:

In Python you can specify anything in the base classes slot. In runtime this expression will be evaluated and executed. We need to prevent dirty hacks in this field.

Solution:

Use only attributes, names, and types to be your base classes. Use annotation future import in case you use strings in base classes.

Example:

# Correct:
class Test(module.ObjectName, MixinName, keyword=True): ...
class GenericClass(Generic[ValueType]): ...

# Wrong:
class Test((lambda: object)()): ...

Added in version 0.7.0.

Changed in version 0.7.1.

Changed in version 0.11.0.

Changed in version 0.12.0.

error_template: ClassVar[str] = 'Found incorrect base class'
code: ClassVar[int] = 606
previous_codes: ClassVar[Set[int]] = {454}
full_code: ClassVar[str] = 'WPS606'
summary: ClassVar[str] = 'Forbid anything other than a class as a base class.'
class WrongSlotsViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS607 — Forbid incorrect __slots__ definition.

Things that this rule checks:

  • That __slots__ is a tuple, name, attribute, star, or call

  • That __slots__ do not have duplicates

  • That __slots__ do not have empty strings or invalid python names

Reasoning:

__slots__ is a very special attribute. It completely changes your class. So, we need to be careful with it. We should not allow anything rather than tuples to define slots, we also need to check that fields defined in __slots__ are unique.

Solution:

Use tuples with unique elements to define __slots__ attribute. Use snake_case to define attributes in __slots__.

Example:

# Correct:
class Test:
    __slots__ = ('field1', 'field2')

class Other(Test):
    __slots__ = (*Test.__slots__, 'child')

# Wrong:
class Test:
    __slots__ = ['field1', 'field2', 'field2']

Note, that we do ignore all complex expressions for this field. So, we only check raw literals.

Added in version 0.7.0.

Changed in version 0.11.0.

Changed in version 0.12.0.

error_template: ClassVar[str] = 'Found incorrect `__slots__` syntax'
code: ClassVar[int] = 607
previous_codes: ClassVar[Set[int]] = {455}
full_code: ClassVar[str] = 'WPS607'
summary: ClassVar[str] = 'Forbid incorrect ``__slots__`` definition.'
class WrongSuperCallViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS608 — Forbid super() with parameters or outside of methods.

Reasoning:

super() is a very special function. It implicitly relies on the context where it is used and parameters passed to it. So, we should be very careful with parameters and context.

Solution:

Use super() without arguments and only inside methods.

Example:

# Correct:
super().__init__()

# Wrong:
super(ClassName, self).__init__()

Added in version 0.7.0.

Changed in version 0.11.0.

error_template: ClassVar[str] = 'Found incorrect `super()` call: {0}'
code: ClassVar[int] = 608
previous_codes: ClassVar[Set[int]] = {456}
full_code: ClassVar[str] = 'WPS608'
summary: ClassVar[str] = 'Forbid ``super()`` with parameters or outside of methods.'
class DirectMagicAttributeAccessViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS609 — Forbid directly calling certain magic attributes and methods.

Reasoning:

Certain magic methods are only meant to be called by particular functions or operators, not directly accessed.

Solution:

Use special syntax constructs that will call underlying magic methods.

Example:

# Correct:
super().__init__()
mymodule.__name__

# Wrong:
foo.__str__()  # use `str(foo)`
2..__truediv__(2)  # use `2 / 2`
d.__delitem__('a')  # use del d['a']

Note, that it is possible to directly use these magic attributes with self, cls, and super() as base names. We allow this because a lot of internal logic relies on these methods.

See ALL_MAGIC_METHODS for the full list of magic attributes disallowed from being accessed directly.

Added in version 0.8.0.

Changed in version 0.11.0.

Changed in version 0.16.0.

error_template: ClassVar[str] = 'Found direct magic attribute usage: {0}'
code: ClassVar[int] = 609
previous_codes: ClassVar[Set[int]] = {462}
full_code: ClassVar[str] = 'WPS609'
summary: ClassVar[str] = 'Forbid directly calling certain magic attributes and methods.'
class AsyncMagicMethodViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS610 — Forbid certain async magic methods.

We allow to make __anext__, __aenter__, __aexit__ async. We allow to make __aiter__ async if it is a generator (contains yield). We also allow custom magic methods to be async.

See ASYNC_MAGIC_METHODS_BLACKLIST for the whole list of blacklisted async magic methods.

Reasoning:

Defining the magic methods as async which are not supposed to be async would not work as expected.

Solution:

Do not make this magic method async.

Example:

# Correct:
class Test:
    def __lt__(self, other): ...

# Wrong:
class Test:
    async def __lt__(self, other): ...

Added in version 0.12.0.

error_template: ClassVar[str] = 'Found forbidden `async` magic method usage: {0}'
code: ClassVar[int] = 610
full_code: ClassVar[str] = 'WPS610'
summary: ClassVar[str] = 'Forbid certain async magic methods.'
class YieldMagicMethodViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS611 — Forbid yield inside of certain magic methods.

We allow to make __iter__ a generator. We allow to make __aiter__ an async generator. See YIELD_MAGIC_METHODS_BLACKLIST for the whole list of blacklisted generator magic methods.

Reasoning:

Python’s datamodel is strict. You cannot make generators from random magic methods. This rule enforces it.

Solution:

Remove yield from a magic method or rename it to be a custom method.

Example:

 # Correct:
class Example:
    def __init__(self):
        ...

# Wrong:
class Example:
    def __init__(self):
        yield 10

Added in version 0.3.0.

Changed in version 0.11.0.

Changed in version 0.12.0.

error_template: ClassVar[str] = 'Found forbidden `yield` magic method usage: {0}'
code: ClassVar[int] = 611
previous_codes: ClassVar[Set[int]] = {435, 439}
full_code: ClassVar[str] = 'WPS611'
summary: ClassVar[str] = 'Forbid ``yield`` inside of certain magic methods.'
class UselessOverwrittenMethodViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS612 — Forbid useless overwritten methods.

Reasoning:

Overwriting method without any changes does not have any positive impact.

Solution:

Do not overwrite method in case you do not want to do any changes inside it.

Example:

# Correct:
class Test(Base):
    def method(self, argument):
        super().method(argument)
        return argument  # or None, or anything!

# Wrong:
class Test:
    def method(self, argument):
        return super().method(argument)

Added in version 0.12.0.

error_template: ClassVar[str] = 'Found useless overwritten method: {0}'
code: ClassVar[int] = 612
full_code: ClassVar[str] = 'WPS612'
summary: ClassVar[str] = 'Forbid useless overwritten methods.'
class WrongSuperCallAccessViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS613 — Forbid super() with incorrect method or property access.

Reasoning:

Can only use super() method that matches the following context. super().some() and super().some in Child.some(), and super().prop and super().prop() in Child.prop

Solution:

Use super() methods and properties with the correct context.

Example:

# Correct:
class Child(Parent):
    def some_method(self):
        original = super().some_method()

# Wrong:
class Child(Parent):
    def some_method(self):
        other = super().other_method()

Added in version 0.13.0.

error_template: ClassVar[str] = 'Found incorrect `super()` call context: incorrect name access'
code: ClassVar[int] = 613
full_code: ClassVar[str] = 'WPS613'
summary: ClassVar[str] = 'Forbid ``super()`` with incorrect method or property access.'
class WrongDescriptorDecoratorViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS614 — Forbids descriptors in regular functions.

Forbids using @staticmethod, @classmethod and @property for functions not in class.

Reasoning:

Descriptors like @staticmethod, @classmethod and @property do magic only as methods. We would want to warn users if the descriptors are used on regular functions.

Solution:

Do not use @staticmethod, @classmethod and @property on regular functions or wrap the functions into a Class.

Example:

# Correct:
class TestClass:
    @property
    def my_method():
        ...

# Wrong:
@property
def my_function():
    ...

Added in version 0.15.0.

error_template: ClassVar[str] = 'Found descriptor applied on a function'
code: ClassVar[int] = 614
full_code: ClassVar[str] = 'WPS614'
summary: ClassVar[str] = 'Forbids descriptors in regular functions.'
class UnpythonicGetterSetterViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS615 — Forbids to use getters and setters in objects.

Reasoning:

Python does not need this abstraction.

Solution:

Either use @property or make the attribute public and change it directly.

Example:

# Correct:
class Example:
    def __init__(self):
        self.attribute = None

# Wrong:
class Example:
    def __init__(self):
        self._attribute = None

    def set_attribute(self, value):
        ...

    def get_attribute(self):
        ...

Added in version 0.15.0.

error_template: ClassVar[str] = 'Found unpythonic getter or setter: {0}'
code: ClassVar[int] = 615
full_code: ClassVar[str] = 'WPS615'
summary: ClassVar[str] = 'Forbids to use getters and setters in objects.'
class BuggySuperContextViolation(node, text=None, baseline=None)[source]

Bases: ASTViolation

WPS616 — Calling super() in buggy context.

Reasoning:

Call to super() without arguments will cause unexpected TypeError in a number of specific contexts, e.g. dict/set/list comprehensions and generator expressions.

Read more: https://bugs.python.org/issue46175

Solution:

Use super(cls, self) instead in those cases.

Example:

# Correct
(super(cls, self).augment(it) for it in items)

# Wrong
(super().augment(it) for it in items)

Added in version 0.18.0.

error_template: ClassVar[str] = 'Found incorrect form of `super()` call for the context'
code: ClassVar[int] = 616
full_code: ClassVar[str] = 'WPS616'
summary: ClassVar[str] = 'Calling super() in buggy context.'