我想在程序中嵌入 pylint。用户输入 python 程序(在 Qt 中,在 QTextEdit 中,虽然不相关)并在后台我调用 pylint 来检查他输入的文本。最后,我在消息框中打印错误。
因此有两个问题:首先,如何在不将输入的文本写入临时文件并将其提供给 pylint 的情况下执行此操作?我想在某个时候 pylint(或 astroid)处理流而不是文件了。
而且,更重要的是,这是个好主意吗?它会导致进口或其他东西的问题吗?直觉上我会说不,因为它似乎产生了一个新进程(使用 epylint),但我不是 Python 专家,所以我真的不确定。如果我使用 this 来启动 pylint,也可以吗?
编辑:
我尝试修补 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: '<?>'
明天继续挖。 :)
最佳答案
我让它运行。
第一个 (NoneType ...) 非常简单,并且是您代码中的一个错误:
遇到异常会使 get_astroid
“失败”,即发送一条语法错误消息并返回!
但是对于第二个……pylint/logilab 的 API 中的这些废话……让我解释一下:这里的 astroid
对象是 astroid.scoped_nodes.Module
类型。
它也是由工厂 AstroidBuilder
创建的,它设置 astroid.file = '<?>'
。
不幸的是, Module
类具有以下属性:
@property
def file_stream(self):
if self.file is not None:
return open(self.file, 'rb')
return None
除了子类化(这会使我们无法使用
AstroidBuilder
中的魔法)之外,没有办法跳过它,所以......猴子补丁!我们将错误定义的属性替换为在进行上述默认行为之前检查实例以获取对我们的代码字节(例如
astroid._file_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)
可以在调用
check_astroid_module
之前调用monkeypatching。但还必须做一件事。看,还有更多隐性行为:一些检查器期望并使用 astroid
的 file_encoding
字段。所以我们现在在 check
中间有这个代码: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 接口(interface)包装它。如果在内部,不要问我为什么 file_stream 存在,Module 是从中构建的,但忘记了源代码。
PS:我不得不改变你的代码中的其他东西:
load_default_plugins
必须放在其他一些东西之前(也许是 prepare_checkers
,也许是……其他)PPS:我建议子类化 BaseReporter 并使用它而不是你的
InMemoryMessagesBuffer
PPPS:这只是被拉(3.2014),并将解决这个问题:https://bitbucket.org/logilab/astroid/pull-request/15/astroidbuilderstring_build-was/diff
4PS:现在是正式版,所以不需要猴子补丁:
astroid.scoped_nodes.Module
现在有一个 file_bytes
属性(没有前导下划线)。关于python - 内存文件/流上的 pylint,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/19542329/