Reputation: 868
I have a collections.abc.MutableMapping
subclass which implements the required abstract methods through monkey patching :
from collections.abc import MutableMapping
def make_wrappers(cls, methods = []):
"""This is used to eliminate code repetition, this file contains around 12
classes with similar rewirings of self.method to self.value.method
This approach is used instead of overriding __getattr__ and __getattribute__
because those are bypassed by magic methods like add()
"""
for method in methods:
def wrapper(self, *args, _method=method, **kwargs):
return getattr(self.value, _method)(*args, **kwargs)
setattr(cls, method, wrapper)
class MySubclass(MutableMapping):
def __init__(self, value = None):
value = {} if value is None else value
self.value = value
make_wrappers(
MySubclass,
['__delitem__', '__getitem__', '__iter__', '__len__', '__setitem__']
)
When trying to instanciate MySubclass, I get this error :
>>> c = MySubclass({'a':a, 'b':b})
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
c = MySubclass({'a':1, 'b':2})
TypeError: Can't instantiate abstract class MySubclass with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__
Yet this works :
>>> MySubclass.__setitem__
<function make_wrappers.<locals>.wrapper at 0x0000020F76A24AF0>
How do I force instantiation ?
I know those methods work, because when I put an additional layer of inheritance between MySubclass
and collections.abc.MutableMapping
they magically do !
Upvotes: 2
Views: 296
Reputation: 50126
Creating an ABC
creates a set of missing abstract methods as soon as the class is created. This must be cleared to allow instantiating the class.
>>> # setup as before
>>> MySubclass.__abstractmethods__
frozenset({'__delitem__', '__getitem__', '__iter__', '__len__', '__setitem__'})
>>> MySubclass({'a':a, 'b':b})
# TypeError: Can't instantiate abstract class MySubclass with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__
>>> MySubclass.__abstractmethods__ = frozenset() # clear cache of abstract methods
>>> MySubclass({'a':a, 'b':b})
<__main__.MySubclass at 0x1120a9340>
Be aware that .__abstractmethods__
is not part of the Python Data Model or abc
specification. Consider it version and implementation specific – always test whether your target version/implementation uses it. It should work on CPython (testet on Py3.6 and Py3.9) and PyPy3 (testet on Py3.6), however.
The wrapper function can be adjusted to automatically remove monkey-patched methods from the abstract method cache. This makes the class eligible for instantiation if all methods are patched.
def make_wrappers(cls, methods = []):
"""This is used to eliminate code repetition, this file contains around 12
classes with similar rewirings of self.method to self.value.method
This approach is used instead of overriding __getattr__ and __getattribute__
because those are bypassed by magic methods like add()
"""
for method in methods:
def wrapper(self, *args, _method=method, **kwargs):
return getattr(self.value, _method)(*args, **kwargs)
setattr(cls, method, wrapper)
cls.__abstractmethods__ = cls.__abstractmethods__.difference(methods)
Upvotes: 1