Reputation: 3806
I'm writing a Python tool that walks an AST to extract some data.
To improve its usability, I want to extend the ast.AST
class, so every node that is based on it has the same ability, for example getting the parent node, the children, the siblings, etc.
On Python 3.9+, I'm able to attach additional attributes, methods and cached properties to ast.AST
directly:
def _attach_to(cls, name=None):
def decorator(func):
func_name = name or func.__name__
cached = cached_property(func)
setattr(cls, func_name, cached)
# see https://bugs.python.org/issue38524
cached.__set_name__(cls, func_name) # noqa: WPS609
return func
return decorator
But on Python 3.8, which I'm willing to support, I cannot attach additional things to ast.AST
which is a builtin:
TypeError: can't set attributes of built-in/extension type '_ast.AST'
So I explored different things:
ast.AST
with a class of mine, using patch functions such as unittest.mock.patch
: it does not seem to affect the child classes which already inherited from the original ast.AST
ast.AST.__mro__
: __mro__
is readonlyast.AST.__bases__
: can't set attributes of built-inast.AST
to my own ExtendedAST
(that inherits from ast.AST
) in their __bases__
: complains about object layout differingIs there a way to dynamically add attributes/methods/properties to built-in classes?
Upvotes: 1
Views: 311
Reputation: 3806
I eventually found something that works: I declare an ExtendedAST
class, but instead of making it a subclass of ast.AST
, I leave it as a "mixin" and append it to the __bases__
of all the different nodes' classes:
for name, member in inspect.getmembers(ast):
if name != "AST" and inspect.isclass(member):
if ast.AST in member.__bases__:
new_bases = list(member.__bases__)
new_bases.append(ExtendedAST)
member.__bases__ = tuple(new_bases)
Upvotes: 1