DEV Community

Cover image for IPython 的奇特 feature
codemee
codemee

Posted on

IPython 的奇特 feature

下午在 Colab 被一個奇妙的問題卡了好久,我把問題簡化成底下這個儲存格:

from IPython.display import Markdown
m = Markdown('sample_data')
Enter fullscreen mode Exit fullscreen mode

只要一執行,就會看到錯誤訊息:

---------------------------------------------------------------------------
IsADirectoryError                         Traceback (most recent call last)
<ipython-input-1-3e6438a6a511> in <cell line: 0>()
      1 from IPython.display import Markdown
----> 2 m = Markdown('sample_data')

1 frames
/usr/local/lib/python3.11/dist-packages/IPython/core/display.py in reload(self)
    660         """Reload the raw data from file or URL."""
    661         if self.filename is not None:
--> 662             with open(self.filename, self._read_flags) as f:
    663                 self.data = f.read()
    664         elif self.url is not None:

IsADirectoryError: [Errno 21] Is a directory: 'sample_data'
Enter fullscreen mode Exit fullscreen mode

從錯誤訊息就可以猜出來,它把我要以 Markdown 格式解譯的 "sample_data" 字串內容當成路徑名稱解譯,好死不死,Colab 預設就有一個名稱為 "sample_data" 的資料夾,正因為它是資料夾,所以當 IPython 底層的程式碼想要把它當成檔案讀取時,就會發生你看到的錯誤。

但為什麼為這樣?

DisplayObject 預設會從檔案更新內容

之所以會發生剛剛看到的問題,就是因為 IPython.display 模組中的 Markdown 等格式化顯示物件都是 DisplayObject 類別的子類別,這個類別的 __init__ 是這樣的(已刪除註解):

    def __init__(self, data=None, url=None, filename=None, metadata=None):
        if isinstance(data, (Path, PurePath)):
            data = str(data)

        if data is not None and isinstance(data, str):
            if data.startswith('http') and url is None:
                url = data
                filename = None
                data = None
            elif _safe_exists(data) and filename is None:
                url = None
                filename = data
                data = None

        self.url = url
        self.filename = filename
        self.data = data

        if metadata is not None:
            self.metadata = metadata
        elif self.metadata is None:
            self.metadata = {}

        self.reload()
        self._check_data()
Enter fullscreen mode Exit fullscreen mode

你會看到它做幾件事:

  1. 如果 data 參數是路徑類的物件,就先轉成字串。
  2. 如果 data 是字串,就會先判斷是不是網址,如果是網址且沒有傳入網址給 url(預設就為 None),就設定網址給 url;若不是網址,而且沒有傳入檔案路徑給 filename(預設就是 None),會依照底下的 safe_exists 函式結果設定給 filename

    def _safe_exists(path):
        """Check path, but don't let exceptions raise"""
        try:
            return os.path.exists(path)
        except Exception:
            return False
    

    這個函式很簡單,就是檢查輸入的內容是不是一個合法的路徑,不過請注意,它只檢查路徑是否存在,但並不會管這個路徑是檔案還是資料夾。

  3. 最後會叫用 self.reload 嘗試從網址或是檔案讀取更新內容:

    def reload(self):
        """Reload the raw data from file or URL."""
        if self.filename is not None:
            encoding = None if "b" in self._read_flags else "utf-8"
            with open(self.filename, self._read_flags, encoding=encoding) as f:
                self.data = f.read()
        elif self.url is not None:
            # Deferred import
            from urllib.request import urlopen
            response = urlopen(self.url)
            data = response.read()
    ...
    

    就是這裡導致我遇到的問題,由於它會在 data 不是網址而且沒有傳入檔名給
    filename 參數時把 data 指派給 filename,所以在 reload 方法中就會嘗試去讀取檔案內容,並因為 "sample_data" 是資料夾而發生錯誤。

你的 feature 是我的 bug

我推想 IPython 這樣的設計,應該是想增加彈性,只要透過 data 參數,就可以依據需要傳單純的內容,或者是傳入可以載入內容的網址或檔案路徑,因為在 IPython 的文件上,data 參數就是多用途的:

data (unicode, str or bytes) – The raw data or a URL or file to load the data from

所以這應該是個 feature,可是我認為你都另外獨立有 urlfilename 參數了,這個彈性只是會增加意外的驚嚇!

目前唯一的解法,就是在建立這些 DisplayObject 家族的物件時,必須自己檢查傳入的字串會不會剛好是某個檔案或是資料夾的路徑,如果是,就要自己用其他方式避開,例如變成 inline code:

m = Markdown('`sample_data`')
Enter fullscreen mode Exit fullscreen mode

就不會有事,或者是在字串開頭隨意加個空白字元之類的,總之,這錯誤就是要在剛好的狀況下才會發生,但突然遇到就會覺得真是怪,好特別的 feature。

對了,由於這是 IPython 的問題,所以使用 Jupyter 也會遇到類似的狀況。

Warp.dev image

The best coding agent. Backed by benchmarks.

Warp outperforms every other coding agent on the market, and gives you full control over which model you use. Get started now for free, or upgrade and unlock 2.5x AI credits on Warp's paid plans.

Download Warp

Top comments (0)

👋 Kindness is contagious

Explore this practical breakdown on DEV’s open platform, where developers from every background come together to push boundaries. No matter your experience, your viewpoint enriches the conversation.

Dropping a simple “thank you” or question in the comments goes a long way in supporting authors—your feedback helps ideas evolve.

At DEV, shared discovery drives progress and builds lasting bonds. If this post resonated, a quick nod of appreciation can make all the difference.

Okay