问题
I have a class that implements some typechecking via class variables. Then, when the class is instantiated, the defined variables become required arguments for the class, with required types. The pattern looks something like this:
class MyClass(MagicBaseClass):
arg1 = ArgumentObj(allowedTypes=(basestring, ))
arg2 = ArgumentObj(allowedTypes=(list, tuple))
def myMethod(self):
print type(self.arg1) # a basestring
print type(self.arg2) # a list
mc = MyClass(arg1='test', arg2=())
mc.myMethod()
Pylint does not like this. It sees arg1
and arg2
as instances of ArgumentObj
. So I want to write a plugin that reads the passed types, and treat those objects as instances of those types inside my MagicBaseClass
.
So, I've been able to figure out how to dig down to the correct nodes for the class transform, and I can access the all the data I need, but I don't really know what to do with it. The kicker is the multiple allowed types. I can't find any example now to handle that, and the docs I can find are basically useless.
from astroid import MANAGER
from astroid import nodes, node_classes
def transform_myClass(node):
for key, value in node.locals.items():
val = value[0]
try:
s = val.statement().value
if s.func.name != 'ArgumentObj':
continue
except AttributeError:
continue
for child in s.get_children():
if not isinstance(child, node_classes.Keyword):
continue
if child.arg == 'allowedTypes':
typeNames = child.value
#### And here is where I have no idea what to do next ####
MANAGER.register_transform(nodes.ClassDef, transform_myClass)
回答1:
There are Name
objects. These are basically the strings in your file before anything is done to them. To get what they will possibly be, you must .infer()
them. This turns something like the word in file "basestring" into a astroid class-ish object basestring
(well, actually it returns a generator ... but broad strokes here).
Then, given these astroid class-ish objects, you must "instantiate" the class to an astroid instance-ish object.
Finally (the important part), node.locals.items()
is a dictionary of {name: list of instance-ish objects}
. Updating that dictionary lets you set the inferred types.
So my broad-strokes code from above would turn into this:
from astroid import MANAGER
from astroid import nodes, node_classes
def transform_myClass(node):
updater = {}
for key, value in node.locals.items():
val = value[0]
try:
s = val.statement().value
if s.func.name != 'ArgumentObj':
continue
except AttributeError:
continue
# Collect all the inferred types in this list
typeList = []
for child in s.get_children():
if not isinstance(child, node_classes.Keyword):
continue
# What I needed to do was here:
# Infer the child classes, and return the instantiated class
if child.arg == 'allowedTypes':
for tc in child.value.get_children():
for cls in tc.infer():
typeList.append(cls.instantiate_class())
updater[key] = typeList
# Finally, I needed to update the locals
# which sets the inferred types of the class members
node.locals.update(updater)
MANAGER.register_transform(nodes.ClassDef, transform_myClass)
来源:https://stackoverflow.com/questions/46801978/set-multiple-inferred-types-based-on-arguments-for-pylint-plugin