Handling Python class redefinition warnings in pylint and mypy

Viewed 489

SQLAlchemy backrefs tend to cause circular imports as they get complex, so I came up with a way to "re-open" a Python class like in Ruby:

def reopen(cls):
    """
    Moves the contents of the decorated class into an existing class and returns that.

    Usage::

        from .other_module import ExistingClass

        @reopen(ExistingClass)
        class ExistingClass:
            @property
            def new_property(self):
                pass

    This is equivalent to::

        def new_property(self):
            pass

        ExistingClass.new_property = property(new_property)
    """

    def decorator(temp_cls):
        for attr, value in temp_cls.__dict__.items():
            # Skip the standard Python attributes, process the rest
            if attr not in ('__dict__', '__doc__', '__module__', '__weakref__'):
                setattr(cls, attr, value)
        return cls

    return decorator

This is a simplified version; the full implementation has more safety checks and tests.

This code works fairly well for inserting bi-directional SQLAlchemy relationships and helper methods wherever SQLAlchemy's existing relationship+backref mechanism doesn't suffice.

However:

  1. mypy raises error "name 'ExistingClass' already defined (possibly by an import)"
  2. pylint raises E0102 "class already defined"

I could ignore both errors by having an annotation on the line (# type: ignore and # skipcq), but overuse of those annotations can let bugs slip through. It'll be nice to tell mypy and pylint that this use is okay.

How can I do that?

1 Answers

As a workaround, I've switched to this pattern:

@reopen(ExistingClass)
class __ExistingClass:
    @property
    def new_property(self):
        pass

>>> assert __ExistingClass is ExistingClass
True

Pylint and Mypy no longer complain about redefinition. However, Mypy will not recognise ExistingClass.new_property as an addition to the class, so it kicks the problem downstream to code that uses it.

My impression is that such class extension hacks, while discussed multiple times, have not gained enough traction as a coding pattern, and in Mypy's case this will need a custom plugin.

Related