5

プログラムに pylint を埋め込みたい。ユーザーは Python プログラムを入力し (Qt では、QTextEdit で、関連はありません)、バックグラウンドで pylint を呼び出して、入力したテキストをチェックします。最後に、エラーをメッセージ ボックスに出力します。

したがって、2 つの質問があります。まず、入力したテキストを一時ファイルに書き込んで 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: '<?>'

明日も掘り続けます。:)

4

2 に答える 2

2

私はそれを実行しました。

最初のもの (NoneType …) は非常に簡単で、コードのバグです:

例外に遭遇すると、get_astroid「失敗」する可能性があります。つまり、構文エラー メッセージを 1 つ送信して戻ります。

しかし、2 つ目については… 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)

その monkeypatching は、 を呼び出す直前に呼び出すことができますcheck_astroid_module。しかし、もう1つやらなければならないことがあります。ほら、もっと暗黙の振る舞いがあります: 一部のチェッカーはastroidfile_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 インターフェイスでラップしています。なぜ file_stream が内部的に存在するのか聞かないでください。Module はビルドされますが、ソース コードは忘れてしまいます。

PS: 私はあなたのコードで sth else を変更しなければなりませんでした:load_default_pluginsいくつかの他のものの前に来る必要があります (多分prepare_checkers、多分 sth.else)

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(先頭のアンダースコアなし)。

于 2013-11-23T22:13:47.490 に答える
1

実際にインポートされたモジュールを見つけるために場所が必要になるため、相対インポートの場合、場所を特定できないストリームを操作すると、間違いなく問題が発生する可能性があります。

Astroid は、ストリームから AST を構築することをサポートしていますが、これは、より高いレベルでファイルを操作するように設計された Pylint を介して使用/公開されていません。そのため、これを達成できるかもしれませんが、低レベルの API を少し掘り下げる必要があります。

最も簡単な方法は、バッファーをファイルに保存してから、必要に応じて SA の回答を使用してプログラムで pylint を開始することです (他の回答で見つかった私の他のアカウントを完全に忘れてしまいました;)。もう 1 つのオプションは、カスタム レポーターを作成してより詳細に制御することです。

于 2013-10-23T14:12:59.160 に答える