Inheriting from type and typing.Mapping: "TypeError: descriptor '__subclasses__' of 'type' object needs an argument"

121 Views Asked by At

I am trying to define a class that is supposed to simultaneously do two things:

  • serve as the metaclass for a dataclass
  • act like a mapping

i.e., it will need to be derived from both type and typing.Mapping.

Defining such a class itself works, but I have encountered two different (but probably related) problems when actually trying to use it:

  1. TypeError when using it for its intended purpose as the metaclass of any dataclass (see error 1 below for details)
  2. TypeError as soon as an unrelated other class tries to register itself as a virtual subclass of Mapping (see error 2 below for details)

Minimum non-working example:

import dataclasses, typing


class MyDataclassMeta(type, typing.Mapping):
    # can imagine that the abstract methods of Mapping are implemented here (not relevant for the error)
    pass

# # either uncomment this class to get error 1
# @dataclasses.dataclass
# class MyDataclass(metaclass=MyDataclassMeta):
#     pass

# # or uncomment this class to get error 2
# @typing.Mapping.register
# class CompletelyUnrelatedClass:
#     pass


if __name__ == "__main__":
    pass

Traceback for error 1 (i.e., trying to use MyDataclassMeta as metaclass for any dataclass):

Traceback (most recent call last):
  File "foo.py", line 10, in <module>
    class MyDataclass(metaclass=MyDataclassMeta):
  File "D:\Python38\lib\dataclasses.py", line 1019, in dataclass
    return wrap(cls)
  File "D:\Python38\lib\dataclasses.py", line 1011, in wrap
    return _process_class(cls, init, repr, eq, order, unsafe_hash, frozen)
  File "D:\Python38\lib\dataclasses.py", line 991, in _process_class
    str(inspect.signature(cls)).replace(' -> None', ''))
  File "D:\Python38\lib\inspect.py", line 3105, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
  File "D:\Python38\lib\inspect.py", line 2854, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
  File "D:\Python38\lib\inspect.py", line 2307, in _signature_from_callable
    if _signature_is_builtin(obj):
  File "D:\Python38\lib\inspect.py", line 1847, in _signature_is_builtin
    obj in (type, object))
  File "D:\Python38\lib\_collections_abc.py", line 685, in __eq__
    if not isinstance(other, Mapping):
  File "D:\Python38\lib\abc.py", line 98, in __instancecheck__
    return _abc_instancecheck(cls, instance)
  File "D:\Python38\lib\abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
  File "D:\Python38\lib\abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: descriptor '__subclasses__' of 'type' object needs an argument

Traceback for error 2 (i.e., registering any class as virtual subclass of Mapping):

Traceback (most recent call last):
  File "foo.py", line 15, in <module>
    class CompletelyUnrelatedClass:
  File "D:\Python38\lib\abc.py", line 94, in register
    return _abc_register(cls, subclass)
  File "D:\Python38\lib\abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
  File "D:\Python38\lib\abc.py", line 102, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: descriptor '__subclasses__' of 'type' object needs an argument

Tried to define __subclasshook__ or __subclasscheck__, but I am not familiar enough with them to know how a proper implementation should look like, or whether that can even help with the problems.

1

There are 1 best solutions below

0
jsbueno On

As I stated above, the entry typing.Mapping is a tool used for static type checking alone, and is of no use when the program is executed. So much that it is deprecated - since collections.abc.Mapping can actually fit that role at the same time that it provides useful functionality for the actual execution of a program.

That said, collections.abc.Mapping is not designed to be used by a class that is itself a metaclass. It probably can be made to work, but not out of the box, and with knowledge of what is being done. just for starts, it would imply in the use of a abc.ABCMeta as a "meta meta class" - that is, it would police the instantiation of new classes themselves (because new classes are the instances of a metaclass) - I am not sure even if that would work.

The fact, and easier thing, is that a class do not need to inherit from collections.abc.Mapping to work as a mapping - it just has to implement the relevant special methods so that it acts as desired.

You don't post an example of the actual usage you want to make of your special class - you just get the errors of trying to declare the class - so I can't help you with a concrete example of what you'd need.

You just need to implement in your class the __getitem__, __setitem__, __delitem__ and __len__ methods, along with suitable implementations of get, setdefault, keys, values, items, __iter__: these are all optional, and will depend of what use you intend to do of the mapping capabilities of your class. (It might even work with only __getitem__ depending of what you want).

Them if you need typecheking of the classes using your metaclass, you can use the register call of collections.abc.Mapping itself: that will work out of the box:

In [9]: import collections.abc

In [10]: class A(type): pass

In [11]: collections.abc.Mapping.register(A)
Out[11]: __main__.A

In [12]: class B(metaclass=A): pass

In [13]: isinstance(B, collections.abc.Mapping)
Out[13]: True


Otherwise, just post (possibly in other questions) what do you actually want to achieve - i.e.: how do you intend to use this class, so that some working code can be given as an example.