Python 挂载在内存文件/流中

Python 挂载在内存文件/流中,python,editor,static-analysis,pylint,Python,Editor,Static Analysis,Pylint,我想在程序中嵌入pylint。用户输入python程序(在Qt中,在QTextEdit中,尽管不相关),在后台我调用pylint检查他输入的文本。最后,我在一个消息框中打印错误 因此,有两个问题:首先,我如何在不将输入的文本写入临时文件并将其提供给pylint的情况下执行此操作?我想在某个时刻,pylint(或astroid)处理一个流,而不再是一个文件 更重要的是,这是个好主意吗?它会给进口或其他东西带来问题吗?直觉上我会说不,因为它似乎产生了一个新的进程(使用epylint),但我不是pyt

我想在程序中嵌入pylint。用户输入python程序(在Qt中,在QTextEdit中,尽管不相关),在后台我调用pylint检查他输入的文本。最后,我在一个消息框中打印错误

因此,有两个问题:首先,我如何在不将输入的文本写入临时文件并将其提供给pylint的情况下执行此操作?我想在某个时刻,pylint(或astroid)处理一个流,而不再是一个文件

更重要的是,这是个好主意吗?它会给进口或其他东西带来问题吗?直觉上我会说不,因为它似乎产生了一个新的进程(使用epylint),但我不是python专家,所以我真的不确定。如果我使用pylint,也可以吗

编辑: 我试图修补派林的内部结构,但最终还是被卡住了

以下是迄今为止的代码:

from astroid.builder import AstroidBuilder
from astroid.exceptions import AstroidBuildingException
from logilab.common.interface import implements
from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker
from pylint.lint import PyLinter
from pylint.reporters.text import TextReporter
from pylint.utils import PyLintASTWalker

class Validator():
    def __init__(self):
        self._messagesBuffer = InMemoryMessagesBuffer()
        self._validator = None
        self.initValidator()

    def initValidator(self):
        self._validator = StringPyLinter(reporter=TextReporter(output=self._messagesBuffer))
        self._validator.load_default_plugins()
        self._validator.disable('W0704')
        self._validator.disable('I0020')
        self._validator.disable('I0021')
        self._validator.prepare_import_path([])

    def destroyValidator(self):
        self._validator.cleanup_import_path()

    def check(self, string):
        return self._validator.check(string)


class InMemoryMessagesBuffer():
    def __init__(self):
        self.content = []
    def write(self, st):
        self.content.append(st)
    def messages(self):
        return self.content
    def reset(self):
        self.content = []

class StringPyLinter(PyLinter):
    """Does what PyLinter does but sets checkers once
    and redefines get_astroid to call build_string"""
    def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None):
        super(StringPyLinter, self).__init__(options, reporter, option_groups, pylintrc)
        self._walker = None
        self._used_checkers = None
        self._tokencheckers = None
        self._rawcheckers = None
        self.initCheckers()

    def __del__(self):
        self.destroyCheckers()

    def initCheckers(self):
        self._walker = PyLintASTWalker(self)
        self._used_checkers = self.prepare_checkers()
        self._tokencheckers = [c for c in self._used_checkers if implements(c, ITokenChecker)
                               and c is not self]
        self._rawcheckers = [c for c in self._used_checkers if implements(c, IRawChecker)]
        # notify global begin
        for checker in self._used_checkers:
            checker.open()
            if implements(checker, IAstroidChecker):
                self._walker.add_checker(checker)

    def destroyCheckers(self):
        self._used_checkers.reverse()
        for checker in self._used_checkers:
            checker.close()

    def check(self, string):
        modname = "in_memory"
        self.set_current_module(modname)

        astroid = self.get_astroid(string, modname)
        self.check_astroid_module(astroid, self._walker, self._rawcheckers, self._tokencheckers)

        self._add_suppression_messages()
        self.set_current_module('')
        self.stats['statement'] = self._walker.nbstatements

    def get_astroid(self, string, modname):
        """return an astroid representation for a module"""
        try:
            return AstroidBuilder().string_build(string, modname)
        except SyntaxError as ex:
            self.add_message('E0001', line=ex.lineno, args=ex.msg)
        except AstroidBuildingException as ex:
            self.add_message('F0010', args=ex)
        except Exception as ex:
            import traceback
            traceback.print_exc()
            self.add_message('F0002', args=(ex.__class__, ex))


if __name__ == '__main__':
    code = """
    a = 1
    print(a)
    """

    validator = Validator()
    print(validator.check(code))
回溯如下:

Traceback (most recent call last):
  File "validator.py", line 16, in <module>
    main()
  File "validator.py", line 13, in main
    print(validator.check(code))
  File "validator.py", line 30, in check
    self._validator.check(string)
  File "validator.py", line 79, in check
    self.check_astroid_module(astroid, self._walker, self._rawcheckers, self._tokencheckers)
  File "c:\Python33\lib\site-packages\pylint\lint.py", line 659, in check_astroid_module
    tokens = tokenize_module(astroid)
  File "c:\Python33\lib\site-packages\pylint\utils.py", line 103, in tokenize_module
    print(module.file_stream)
AttributeError: 'NoneType' object has no attribute 'file_stream'
# And sometimes this is added :
  File "c:\Python33\lib\site-packages\astroid\scoped_nodes.py", line 251, in file_stream
    return open(self.file, 'rb')
OSError: [Errno 22] Invalid argument: '<?>'
回溯(最近一次呼叫最后一次):
文件“validator.py”,第16行,在
main()
文件“validator.py”,第13行,在main中
打印(验证器检查(代码))
检查文件“validator.py”,第30行
self.\u验证程序检查(字符串)
文件“validator.py”,第79行,检查
自检查天体模块(天体,自走,自走,自走,自走,自走,自走)
文件“c:\Python33\lib\site packages\pylint\lint.py”,第659行,在check\u astroid\u模块中
令牌=令牌化_模块(astroid)
文件“c:\Python33\lib\site packages\pylint\utils.py”,第103行,在tokenize\u模块中
打印(模块文件\u流)
AttributeError:“非类型”对象没有属性“文件\u流”
#有时还加上:
文件“c:\Python33\lib\site packages\astroid\scoped\u nodes.py”,第251行,在文件\u流中
返回打开状态(self.file“rb”)
OSError:[Errno 22]无效参数:“”

我明天继续挖。:)

在相对导入的情况下,处理不可加载的流可能会导致问题,因为需要位置来查找实际导入的模块

Astroid支持从流中构建AST,但这不是通过Pylint使用/公开的,Pylint级别更高,设计用于处理文件。因此,尽管您可能已经实现了这一点,但仍需要深入了解底层API

最简单的方法是明确地将缓冲区保存到文件中,然后使用SA应答以编程方式启动pylint(完全忘记了在其他响应中找到的我的另一个帐户;)。另一个选择是编写自定义报告程序以获得更多控制。

我让它运行起来

第一个(非类型…)非常简单,代码中有一个bug:

遇到异常会使
get_astroid
失败,即发送一条语法错误消息并返回

但是对于secong one…pylint/logilab的API中的这些废话…让我解释一下:您的
astroid
对象属于
astroid.scoped_nodes.Module
类型

它也是由一个工厂创建的,
AstroidBuilder
,它设置
astroid.file='

不幸的是,
模块
类具有以下属性:

@property
def file_stream(self):
    if self.file is not None:
        return open(self.file, 'rb')
    return None
除了子类化(这将使我们无法使用
AstroidBuilder
中的魔法),没有办法跳过它,所以…猴子补丁

在执行上述默认行为之前,我们将用一个属性替换定义不正确的属性,该属性检查实例是否引用了我们的代码字节(例如,
astroid.\u file\u bytes

def _monkeypatch_module(module_class):
    if module_class.file_stream.fget.__name__ == 'file_stream_patched':
        return  # only patch if patch isn’t already applied

    old_file_stream_fget = module_class.file_stream.fget
    def file_stream_patched(self):
        if hasattr(self, '_file_bytes'):
            return BytesIO(self._file_bytes)
        return old_file_stream_fget(self)

    module_class.file_stream = property(file_stream_patched)
可以在调用
检查astroid\u模块之前调用monkeypatching。但还有一件事要做。看,还有更多的隐式行为:一些检查者期望并使用
astroid
文件编码
字段。所以我们现在有了这个代码在代码的中间>检查< /代码>:

astroid = self.get_astroid(string, modname)
if astroid is not None:
    _monkeypatch_module(astroid.__class__)
    astroid._file_bytes = string.encode('utf-8')
    astroid.file_encoding = 'utf-8'

    self.check_astroid_module(astroid, self._walker, self._rawcheckers, self._tokencheckers)
有人可能会说,再多的linting也不能创造出真正好的代码。不幸的是,pylint将巨大的复杂性与在文件上调用它的专门化结合在一起。真正好的代码有一个很好的本机API,并用CLI接口包装它。不要问我为什么文件流存在,如果模块是从内部生成的,但忘记了源代码

PS:我不得不更改代码中的其他内容:
load\u default\u plugins
必须先于其他内容(可能
prepare\u checkers
,可能是其他内容)

PPS:我建议对BaseReporter进行子类化,并使用它代替您的
InMemoryMessageBuffer

购买力平价:这刚刚被取消(2014年3月),并将解决这一问题:


4PS:这是现在的官方版本,所以不需要猴子补丁:
astroid.scoped_nodes.Module
现在有一个
file_bytes
属性(没有前导下划线)。

所以如果我远离相对导入,一切都会好起来吗?如果是的话,似乎很容易进入astroid的低级API。至少在使用临时文件之前我会先尝试一下。@ibizaman:有什么进展吗?我也一样,讨厌白蚁。这太棒了。不过我没有测试你的建议。您是在没有拉取请求的情况下使用了它,还是强制使用了它?如果您能发布您需要的要点或建议作为更改,我将不胜感激。(老实说,这个请求是50%的懒惰,50%的混乱):)monkeypatch与
文件编码
\u文件字节
字段的显式设置一致,取代了拉取请求,是的。一旦拉取请求,它就不会破坏代码。所以现在就使用它,在请求被拉出来并且一个新的astroid版本出来之后,或者删除
if astroid not None
后面的三行,或者忘记这样做:)正如所说的:添加
if astroid not None
可以修复代码中的错误。