IntelliJ IDEAでOpenPyXLの補完が効かなくなったときの対処法

Python

症状

しばらくぶりにOpenPyXLでExcelファイルをあれこれしようとIntelliJ IDEAでPythonスクリプトを書こうとしたら何か変です。たぶんPyCharmでも同じ現象が発生します。

  • openメソッド(load_workbookのエイリアス)が未定義扱いになる。
  • load_workbookのリターンを代入した変数で補完が効かない。

使い物になりません。

対処法

IntelliJ IDEAの設定項目は複雑怪奇を極めており「これ、開発者も全容を把握してないだろ・・・」疑惑まであるぐらい入り組んでいるので絶対にコレとは言えませんが、私の場合はこの方法で解決しました。それは

PythonStubがバグっているので削除する

でした。

PythonStub(スタブファイル)とは、Google先生に聞いたところIDEが型補完をより効かせるために使用するファイルらしいです。それが原因でバグってたら世話ないです。

ステートメントをポイントすると、どのファイルを参照しているか確認できます。補完が効かない状態のopenpyxlのimportは下のスクショのようなPythonのsite-packagesじゃないところを参照していることでしょう。拡張子が.pyiでタイプ:がPythonStubになっているはずです。

このパスをコピーしてエクスプローラーで開きます。stubsフォルダまで上ってからopenpyxlフォルダを消しましょう。心配ならどこかへコピーしておきましょう。

IDEを再起動します。

補完されなかったファイルを開いてみると、ちゃんとopenメソッドが認識されており、そのあとの補完も効くようになっています。

importの参照先がPythonのsite-packagesに変わっています。タイプ:がPythonになっています。このへんの何もしなくても自動的に参照を切り換えるうごきはさすがです。

補足

PythonStubがどのタイミングで生成されるのかナゾですが、補完できない状態の__init__.pyiは次のような内容でした。

from openpyxl.compat.numbers import NUMPY as NUMPY
from openpyxl.reader.excel import load_workbook as load_workbook
from openpyxl.workbook import Workbook as Workbook
from openpyxl.xml import DEFUSEDXML as DEFUSEDXML, LXML as LXML

from ._constants import (
    __author__ as __author__,
    __author_email__ as __author_email__,
    __license__ as __license__,
    __maintainer_email__ as __maintainer_email__,
    __url__ as __url__,
    __version__ as __version__,
)

ぱっと見でopenのエイリアスがないのがわかるので試しに

from openpyxl.reader.excel import load_workbook as open

に書き換えてみたところ、load_workbookが未定義となりopenが定義済みに変わりました。よって、このファイルの記述が補完の挙動に影響しているのが確定です。

load_workbookメソッドのヒントを見てみるとリターンがNoneになっています。なんでやねん。そりゃ補完せんわと。

スタブフォルダのopenpyxl.reader.excelを開くと次のような内容です。

from typing import Any

SUPPORTED_FORMATS: Any

class ExcelReader:
    archive: Any
    valid_files: Any
    read_only: Any
    keep_vba: Any
    data_only: Any
    keep_links: Any
    shared_strings: Any
    def __init__(self, fn, read_only: bool = ..., keep_vba=..., data_only: bool = ..., keep_links: bool = ...) -> None: ...
    package: Any
    def read_manifest(self) -> None: ...
    def read_strings(self) -> None: ...
    parser: Any
    wb: Any
    def read_workbook(self) -> None: ...
    def read_properties(self) -> None: ...
    def read_theme(self) -> None: ...
    def read_chartsheet(self, sheet, rel) -> None: ...
    def read_worksheets(self) -> None: ...
    def read(self) -> None: ...

def load_workbook(filename, read_only: bool = ..., keep_vba=..., data_only: bool = ..., keep_links: bool = ...): ...

リターンがことごとくNoneになっています。なんでやねん。

このファイルを書き換えてリターンをWorkbookにしてみたのですが、うまく反映されませんでした。キャッシュを消してもダメでした。何か書き方のルールがあるのかもしれないですが、調べる気もしないのでフォルダごと抹消します。

再起動後は

ちゃんとWorkbookオブジェクトが返ってくるのがわかるようになりました。

IntelliJ IDEAはたまにこういう不可解な事象が発生しますが、もはや他のIDEでは物足りなくて現状は一択なわけで。

IntelliJ IDEA: JetBrains の人間工学に基づく高機能 Java IDE
エンタープライズ Java、Scala、Kotlin向け、人間工学に基づいたインテリジェントなJava IDE
PyCharm:JetBrainsによるプロ開発者向けPython IDE
インテリジェントなコード補完、オンザフライのエラーチェックとクイックフィックスなどを備えたPython&Django用のIDE

おわり。

追記:発生原因

Pythonのプラグインがアップデートされたタイミングでまた全く補完が効かなくなりスタブが参照されるようになりました。詳しいしくみはわかりませんが、プラグインの中の人がスタブファイルを用意しているということでしょうか。

スタブファイルがないことで他のオブジェクトの補完が効かなくなっている可能性はありますが、初っぱなのWorkbookオブジェクトに効かないのは不便すぎるので、速攻でスタブファイルを削除しました。