admin管理员组

文章数量:1417720

Given the following Python code:

import ctypes
from collections.abc import Mapping

class StructureMeta(type(ctypes.Structure), type(Mapping)):
    pass

class Structure(ctypes.Structure, Mapping, metaclass=StructureMeta):
    pass

struct = Structure()
assert isinstance(struct, ctypes.Structure)
assert isinstance(struct, Mapping)

The metaclass is needed to avoid a metaclass conflict when deriving from both ctypes.Structure (metaclass _ctypes.PyCStructType) and Mapping (metaclass abc.ABCMeta).

This works fine when executed with Python 3.11. Alas, pylint 3.3.4 reports two errors:

test.py:5:0: E0240: Inconsistent method resolution order for class 'StructureMeta' (inconsistent-mro)
test.py:9:0: E1139: Invalid metaclass 'StructureMeta' used (invalid-metaclass)

How do I need to change the meta class to fix the error reported by pylint? Is it even a problem?

Given the following Python code:

import ctypes
from collections.abc import Mapping

class StructureMeta(type(ctypes.Structure), type(Mapping)):
    pass

class Structure(ctypes.Structure, Mapping, metaclass=StructureMeta):
    pass

struct = Structure()
assert isinstance(struct, ctypes.Structure)
assert isinstance(struct, Mapping)

The metaclass is needed to avoid a metaclass conflict when deriving from both ctypes.Structure (metaclass _ctypes.PyCStructType) and Mapping (metaclass abc.ABCMeta).

This works fine when executed with Python 3.11. Alas, pylint 3.3.4 reports two errors:

test.py:5:0: E0240: Inconsistent method resolution order for class 'StructureMeta' (inconsistent-mro)
test.py:9:0: E1139: Invalid metaclass 'StructureMeta' used (invalid-metaclass)

How do I need to change the meta class to fix the error reported by pylint? Is it even a problem?

Share Improve this question edited Jan 31 at 13:31 Markus asked Jan 31 at 9:25 MarkusMarkus 3,3972 gold badges26 silver badges37 bronze badges 3
  • Why do you use both inheritance and a metaclass? What happens if you use only the metaclass and no additional inheritance? – mkrieger1 Commented Jan 31 at 9:39
  • @mkrieger1 I added the metaclass because otherwise Python won't let me derive from both classes at the same time: TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. If I only uses the metaclass, both asserts fails. – Markus Commented Jan 31 at 10:00
  • If the code works, and the static analysis tool breaks, report a bug against the static analysis tool, and just ignore its "error". – jsbueno Commented Jan 31 at 12:12
Add a comment  | 

1 Answer 1

Reset to default 1

As you can attest, there is no "error" in this code.

Working with metaclasses is hard - and having to create a compatible metaclass in cases like this shows some of the side effects.

The problem is most of what metaclasses do are things that take effect as the code is executed (i.e. in runtime) - while analysis tools like pylint, pyright, mypy, all try to see Python as if it was a static language, and as they evolve, they incorporate patterns to understand ever a little bit more of the language's dynamism.

You simply reached a step that pylint (or whatever tool) haven't reached yet - the plain workaround is just to ignore its fake error message (by adding a # noQA or equivalent comment in the infringing line).

Do it without ignoring the the linting error

  • No, seriously, don't do this! The copying of the Mapping class in this way is subject to unforeseen side effects

But, for the sake of entertainment and completeness, this is the path to avoid the linter-error (involving much more experimental code practices and subject to surprise side effects than just silencing the QA tool):

A workaround there might be not to use the Mapping base class as it is, since it has a conflicting metaclass - it is possible for your resulting class to be registered as a Mapping (by using the Mapping.register call as a decorator) - so that isinstance(struct, Mapping) will still be true.

That would imply, of course, in reimplementing all the goodness collections.abc.Mapping have, in wiring up all of a mapping logic so that one just have to implement a few methods.

Instead of that, we might just create a "non-abstract" version of Mapping by copying all of its methods over to another, plain, class - and use that to the multiple-inheritance with ctypes.Struct

from collections import abc
import ctypes

ConcreteMapping = type("ConcreteMapping", (), {key: getattr(abc.Mapping, key) for key in dir(abc.Mapping)})

@abc.Mapping.register
class Struct(ctypes.Structure, ConcreteMapping):
    pass

 
s = Struct()

assert isinstance(s, abc.Mapping)
assert isinstance(s, ctypes.Structure)

(Needless to say, this throws away the only gain of abstractclasses in Python: the RuntimeError at the point one attempts to instantiate an incomplete "Mapping" class, without implementing one of the required methods as per the docs.)

Keep it simple: No Linter Error, no metaclass and no frankenstein clonning

Actually, there is a path forward, without potential side effects like either combining metaclasses (that, although working, may yield problems as Python versions change due to subtle modifications in how these metaclasses interact) - and without clonning collections.abc.Mapping as above - if you are willing to let go of the most of the benefficts of inheriting from Mapping, namely, getting the .get, __contains__, values, keys, items, __ne__, __eq__ methods for free: Just implement the methods your codebase will be actually using from Mapping (which might be just __getitem__, in this case), and register your class as a subclass of collections.abc.Mapping: the isinstance check will return True. No side effects - just that if one attempts to use one of the non-existing methods for your class, it will raise a RuntimeError:

@collections.abc.Mapping.register
class Struct(ctypes.Structure):
   _fields_ = ...
   def __getitem__(self, key):
       ...

本文标签: