Set multiple inferred types based on arguments for pylint plugin

女生的网名这么多〃 提交于 2019-12-11 16:15:42

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!