我想将pylint嵌入程序中。用户输入python程序(在Qt中,在QTextEdit中,尽管不相关),在后台,我调用pylint来检查他输入的文本。最后,我在消息框中打印错误。
因此,存在两个问题:首先,如何在不将输入的文本写入临时文件并将其提供给pylint的情况下执行此操作?我想在某个时候pylint(或astroid)处理流而不是文件了。
而且,更重要的是,这是一个好主意吗?会导致进口或其他物品出现问题吗?直觉上我不会说,因为它似乎产生了一个新的进程(带有epylint),但是我不是python专家,所以我真的不确定。如果我用这个来发射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
),所以……猴子补丁!
我们将ill-defined属性替换为一个属性,该属性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)
可以在调用之前调用该monkeypatching check_astroid_module
。但是还要做一件事。请参阅,还有更多隐式行为:一些检查器期望并使用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)
可以说没有多少毛刺就能创造出真正好的代码。不幸的是,pylint通过在文件上调用它的特殊性将巨大的复杂性结合在一起。真正好的代码具有很好的本机API,并使用CLI接口进行包装。不要问我,如果在内部存在file_stream,那么模块是从构建的,但是却忘记了源代码。
PS:我必须在代码中进行其他更改:load_default_plugins
必须先于其他内容(也许prepare_checkers
,也许其他)
PPS:我建议将BaseReporter子类化,并使用它代替您的 InMemoryMessagesBuffer
PPPS:这刚刚被撤消(2014年3月3日),并将解决此问题:https ://bitbucket.org/logilab/astroid/pull-request/15/astroidbuilderstring_build-was/diff
4PS:现在是正式版本,因此不需要修补猴子:astroid.scoped_nodes.Module
现在具有file_bytes
属性(不带下划线)。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句