この記事を作った動機

 2025/10/2 から、2025/10/9 に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。

動作の様子 (フロントエンド)

TODOs

 2025/10/9 になる間やる項目たち。やりきれなかった分は次に持ち越し。

主な項目

  • ノートブック作成機能のバグチェックと修正
  • ノートブック、ページ関連における作成、削除、コピー名前の変更があった時、selector の情報やページviewを更新するようにする
  • ページ作成機能の実装
    • フロントエンド
    • バックエンド
    • 新しい階層構造を指定できるようにする
    • linux だけでなく windows 環境において、再帰的にフォルダを作成できるようにする。
  • ノートブック削除機能の実装
    • フロントエンド
    • バックエンド
  • ページ削除機能の実装
    • フロントエンド
    • バックエンド
  • markdown における編集機能、データセーブ(更新)の実装
    • フロントエンド (buggy)
    • バックエンド
    • Selector に未保存の表示を行う
    • 別のページを開いても未保存のデータのバッファが管理されるようにする
  • 内部構造を説明する図を作る
    • フロントエンド
    • バックエンド
  • free ページの実装
    • フロントエンド
    • バックエンド

他の事項

  • File API 周りの実装
  • マルチメディアの対応
  • messagebox の実装

やったことの詳細

ページ作成機能の実装

  • フォームを実装した。
    createpage
  • とりあえずフロントエンド周りのネットワーク関連も実装して、あとはバックエンドのみ。
  • バックエンド、フロントエンド両方とも、とりあえず動くところまでできた。

ノートブック削除機能の実装

  • フォームを実装した。
    deletenotebook
  • とりあえずフロントエンド周りのネットワーク関連も実装して、あとはバックエンドのみ。
  • バックエンド、フロントエンド両方とも、とりあえず動くところまでできた。

ページ削除機能の実装

  • フォームを実装した。
    deletepage
  • とりあえずフロントエンド周りのネットワーク関連も実装して、あとはバックエンドのみ。
  • バックエンド、フロントエンド両方とも、とりあえず動くところまでできた。

編集機能、データセーブ(更新)の実装

全体

  • 編集機能は、各ページで独自実装するようにした。
  • ToggleToolsBaredit 項目を追加し、各ページの独自実装の機能を呼び出せるようにした。
  • page.tsx にて、新しいページを開いたときは、ToggleToolsBaredit は新しいページが新たにツールを登録するために toggleable のクリーンアップだけするようにした。 -> 不具合の温床になっているっぽかったので、toggleable を設定したコンポーネントが自分で、コンポーネントが消されるときに toggleable を外すようにした。
  • データサーバー側は、単純にフロントエンドが送りつけた文字列をセーブデータとして扱う簡易的な仕様にすることにした。一応最低限のメタデータが存在するかはチェックするが、データ本体の整合性に関しては、フロントエンドに責任があることと現状ではしている。
  • とりあえず、markdown 形式のページについては動作するところまでupdatePageコマンドを実装し終えた。ただ、freeページに代表される、json 形式のページも実装はあるがまだ動くかテストできるほどフロントエンドが完成していないため、それについてはあとからテストする必要がある。

markdown ページ

  • preview splitView editorView を選択できるようにした。
  • 取得した markdown データは直接扱わず、markdownBuffer という状態変数によって中間的に管理することで、編集時に変更が反映できる仕組みにした。
  • highlight.js を使って、マークダウンをハイライトしようとした。
  • とりあえず、ctrl + S を押したり、セーブボタンを押せば、本当にページが保存されるところまではできた。

内部構造を説明する図を作る

  • とりあえずバックエンドは、現状の範囲で書き出した。
    architecture-Backend

ページの基本情報を表示

作成当時

 ページに付いているタグやファイルなどを管理するUIを作った。まだボタンがあるだけで、ファイルのプレビューを開いたり、タグの追加や削除、タグに付随するページをリスティングする機能は付いていないが、一応形は作ってみた。

ページの基本情報を表示するUI

変更点1

 UUID の代わりに、ページの作成日時と更新日時を表示するようにしてみた。

PageInfoのUIを変更

ページのメタデータ情報を追加

 作成日と作成時を記録するようにした。

古い形式

++++
{
    "files":["なんかよくわかんない絵ジト目口開き3.png"], 
    "tags":[],
    "UUID":"6428f4c7-7622-40b9-a6bc-f895b3f9ada8"
}
++++

新しい形式

++++
{
    "files":["なんかよくわかんない絵ジト目口開き3.png"], 
    "tags":[],
    "createDate": "2025/10/6",
    "updateDate": "2025/10/6",
    "UUID":"6428f4c7-7622-40b9-a6bc-f895b3f9ada8"
}
++++

バックエンドのヘルパー関数を追加

 特定のノートブックのメタデータを更新するための専用の関数を作った。主に以下のことを確認した後に、データを更新する。

  • メタデータ自体が存在しているか
  • バックアップは作成できたか
  • メタデータは更新できたか
# arg:
#   notebookName     : notebook name
#   notebookMatadata : entire updated notebook metadata
# return value
#   OK      : False
#   Error   : True will be returned when there are no notebooks or other type error is occured.
def updateNotebookMatadata(notebookName:str,notebookMatadata:dict):
    notebookMatadata['updateDate'] = timeString()
    notebookStoreRoot = loadSettings.settings["NotebookRootFolder"][0]
    notebookMetadataPath = notebookStoreRoot + "/" + notebookName + "/metadata.json"

    print("updateNotebookMatadata info: ")
    print(notebookMatadata)
    print(notebookName)
    print(notebookMetadataPath)
    
    if(not os.path.exists(notebookMetadataPath)):
        print("updateNotebookMatadata ERROR: the metadata file does not exist.")
        return True

    # https://stackoverflow.com/questions/123198/how-do-i-copy-a-file
    # update metadata
    try:
        # create metadata backup
        shutil.copyfile(notebookMetadataPath,notebookMetadataPath + ".backup")
        
        with open(notebookMetadataPath,"wt") as metadata:
            metadata.write(json.dumps(notebookMatadata))
            pass
    except:
        print("updateNotebookMatadata ERROR: Unable to create metadata backupfile or to update metadata.json of the notebook.")
        return True

    return False

バックエンドで一定期間ごとにタスクを実行できるように変更

 multiprocessingasyncio の組み合わせで、ただフロントエンドからの websocket の接続ややり取りを待ち受けるだけでなく、一定時間ごとに、タスクを実行できるようにした。

 例えば、特定のノートブックのメタデータから、ページの参照が一定期間削除されたままになっているページを完全に削除するタスクなどが考えられる。

  • main.py
# main server scirpt
# serve dataserver function with websocket. 

import asyncio
import multiprocessing
# from modules.reqestParser import parser
from websockets.asyncio.server import serve
from helper import loadSettings
from helper.netwrok import receiveLoop 
import tasks
import controller


async def mainLoop(websocket):
    await receiveLoop(websocket,controller.controller)    

async def dataserver():
    async with serve(mainLoop, loadSettings.settings["ip"], loadSettings.settings["port"]) as server:
        await server.serve_forever()

def dataserverThread():
    asyncio.run(dataserver())

def serviceThread():
    asyncio.run(tasks.taskController(loadSettings.settings["taskInterval"]))

if __name__ == "__main__":
    multiprocessing.Process(target=dataserverThread).start()
    multiprocessing.Process(target=serviceThread).start()

バックエンドにおいてエラーレスポンスを返すためのヘルパー関数を実装

 直接エラーレスポンスを返すコードをいちいち書いていると冗長になり、ミスが増えることがわかったので、共通で使える関数errorResponseを用意してみた。

# arg:
#   websocket       : the connection to the frontend via websocket
#   request         : the frontend reqiuest entire JSON data
#   errorMessage    : an error message to show in the stdout and response to the frontend 
#   variablesList   : an error related vars list to show in the stdout and response to the frontend 
# return value
#   OK      : None
#   Error   : Error state does not exist.
async def errorResponse(websocket,request:dict,errorMessage:str,variablesList:list):
    print("{} ERROR: {}".format(request["command"],errorMessage))

    variableState = {}
    print("{} ERROR: variable state --------------------------".format(request["command"]))
    for aVar in variablesList:
        # print variable name and data
        # https://stackoverflow.com/questions/18425225/getting-the-name-of-a-variable-as-a-string
        # f'{aaa=}'.split("=")[0]
        varName = f'{aVar=}'.split("=")[0]
        print("{}:\n{}\n".format(varName,aVar))
        variableState[varName] = str(aVar)
    print("{} ERROR: variable state end --------------------------".format(request["command"]))

    responseString = json.dumps({
        "status"        : "error",
        "errorMessage"  : errorMessage,
        "UUID"          : request["UUID"],
        "command"       : request["command"],
        "data"          : variableState
    })
    await websocket.send(responseString)
    print(">>> " + responseString)

躓いたこと

状態変数 と ReactElement

 条件判定で挙動を変えたい UI (pageTypeList) があり、部分的に変数として切り出していたところ、適切にその UI 自体を更新しないと、正しく他の部分と提携して動作しないことがわかった。

 どうも、pageTypeListuseEffect 内で生成したときに、onChange に設定したコールバック関数がコピーされていて、かつコールバック関数の中身にある 状態変数の値なども、参照ではなくコピーされていることで、newPageInfo の古い値が pageTypeList ごと更新してやらないと残り続けてしまうようである。

問題のある状態

export function deletepage(){
    // 何かしらのコード...

    // データサーバーから取得した情報が格納される状態変数
    const [pageType,setPageType] = useState<pageType | null>(null)
    // 正しく更新されないデータ
    const [newPageInfo,setNewPageInfo]     = useState({
        "notebook": "",
        "pagename": "",
        "pageType": ""
    })

    // 何かしらのコード...

    // 切り出した UI が格納される変数
    const [pageTypeList,setPageTypeList] = useState<ReactElement[]>()

    // 何かしらのコード...

    // newPageInfo 状態変数が変更されても更新されない
    useEffect(() => {
        console.log(pageType)
        const optionStyle = "bg-gray-800 hover:bg-gray-700"
        
        const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
            console.log("type selected")
            console.log(newPageInfo)
            setNewPageInfo({
                notebook: newPageInfo.notebook, // newPageInfo の pageType が更新されても、それ以外は古い情報が反映され続ける
                pagename: newPageInfo.pagename, // newPageInfo の pageType が更新されても、それ以外は古い情報が反映され続ける
                pageType: event.target.value
            })
        }

        // こうしても駄目だった。
        // const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
        //     console.log("type selected")
        //     console.log(newPageInfo)
        //     setNewPageInfo({
        //         ...newPageInfo,  // newPageInfo の pageType が更新されても、それ以外は古い情報が反映され続ける
        //         pageType: event.target.value
        //     })
        // }

        if(pageType == null){
            setPageTypeList([
            <select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
                <option className={optionStyle} value="currently">No value exist.</option>
            </select>])
        }else if(pageType["data"] == null){
            setPageTypeList([
            <select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
                <option className={optionStyle} value="currently">Currently, no value exist.</option>
            </select>])
        }else{
            setPageTypeList([
            <select onChange={typeSelected} key="ValueExist" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
                {pageType.data.map((value,index) => <option className={optionStyle} value={value} key={index}>{value}</option>)}
            </select>])
        }
    },[pageType])
    // newPageInfo 状態変数が変更されても更新されない


    // 何かしらのコード...

    // return 内部では、newPageInfo 状態変数の内容は最新の内容が参照され、問題が起こらなかった。逆に言えば、UI を個別に変数として切り分けるまで問題に気付かなかった。
    return <OverlayWindow arg={args}> 
        <div className="m-[1rem] flex flex-col">
            <div className="item pagename flex">
                <div className="label">Name:</div>
                <input 
                    className="ml-[1rem] border-gray-700 soild border-[2px]" 
                    id="newPageName" 
                    type="text"
                    onChange={(event:ChangeEvent<HTMLInputElement>) => {
                        setNewPageInfo({
                            ...newPageInfo,
                            pagename:event.target.value})
                        }}></input>
            </div>
            <div className="item pageType flex mt-[0.5rem]">
                <div className="label">Type: </div>
                {pageTypeList}
            </div>
            <div className="item flex flex-col mt-[0.7rem]">
                <div className="label mr-auto">Location: </div>
                <div className="PathPreview border-l-4 border-l-gray-800 flex flex-col pl-[0.7rem] bg-gray-950">
                    <div className="notebook flex p-[0.5rem]">
                        <div className="mr-auto">Notebook: </div>
                        <div>{newPageInfo.notebook}</div>
                    </div>
                    <div className="page flex p-[0.5rem] pt-[0]">
                        <div className="mr-auto">Page:     </div>
                        <div>{newPageInfo.pagename}</div>
                    </div>
                </div>
            </div>
            <div className={submitButtonStyle}>Create New Page</div>
        </div>
    </OverlayWindow>
}

とりあえず動く状態

export function CreatePage(){
    // 何かしらのコード...

    // データサーバーから取得した情報が格納される状態変数
    const [pageType,setPageType] = useState<pageType | null>(null)
    // 正しく更新されないデータ
    const [newPageInfo,setNewPageInfo]     = useState({
        "notebook": "",
        "pagename": "",
        "pageType": ""
    })

    // 何かしらのコード...

    // 切り出した UI が格納される変数
    const [pageTypeList,setPageTypeList] = useState<ReactElement[]>()

    // 何かしらのコード...

    // newPageInfo 状態変数が変更されたら、useEffect がかかり newPageInfo 状態変数の最新の内容が反映される
    useEffect(() => {
        console.log(pageType)
        const optionStyle = "bg-gray-800 hover:bg-gray-700"
        
        const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
            console.log("type selected")
            console.log(newPageInfo)
            setNewPageInfo({
                notebook: newPageInfo.notebook, // newPageInfo の pageType が更新されると、他の値は最新の newPageInfo の内容になる
                pagename: newPageInfo.pagename, // newPageInfo の pageType が更新されると、他の値は最新の newPageInfo の内容になる
                pageType: event.target.value
            })
        }

        // const typeSelected = (event:ChangeEvent<HTMLSelectElement>) => {
        //     console.log("type selected")
        //     console.log(newPageInfo)
        //     setNewPageInfo({
        //         ...newPageInfo,  //  newPageInfo の pageType が更新されると、他の値は最新の newPageInfo の内容になる
        //         pageType: event.target.value
        //     })
        // }

        if(pageType == null){
            setPageTypeList([
            <select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
                <option className={optionStyle} value="currently">No value exist.</option>
            </select>])
        }else if(pageType["data"] == null){
            setPageTypeList([
            <select onChange={typeSelected} key="NoValueExistError" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
                <option className={optionStyle} value="currently">Currently, no value exist.</option>
            </select>])
        }else{
            setPageTypeList([
            <select onChange={typeSelected} key="ValueExist" className="ml-auto border-[2px] border-gray-700 solid" name="pageType" id="pageType">
                {pageType.data.map((value,index) => <option className={optionStyle} value={value} key={index}>{value}</option>)}
            </select>])
        }
    },[pageType,newPageInfo]) // newPageInfo 変更時に、pageTypeList ごと更新をかける
    // newPageInfo 状態変数が変更されたら、useEffect がかかり newPageInfo 状態変数の最新の内容が反映される


    // 何かしらのコード...

    // return 内部では、newPageInfo 状態変数の内容は最新の内容が参照され、問題が起こらなかった。逆に言えば、UI を個別に変数として切り分けるまで問題に気付かなかった。
    return <OverlayWindow arg={args}> 
        <div className="m-[1rem] flex flex-col">
            <div className="item pagename flex">
                <div className="label">Name:</div>
                <input 
                    className="ml-[1rem] border-gray-700 soild border-[2px]" 
                    id="newPageName" 
                    type="text"
                    onChange={(event:ChangeEvent<HTMLInputElement>) => {
                        setNewPageInfo({
                            ...newPageInfo,
                            pagename:event.target.value})
                        }}></input>
            </div>
            <div className="item pageType flex mt-[0.5rem]">
                <div className="label">Type: </div>
                {pageTypeList}
            </div>
            <div className="item flex flex-col mt-[0.7rem]">
                <div className="label mr-auto">Location: </div>
                <div className="PathPreview border-l-4 border-l-gray-800 flex flex-col pl-[0.7rem] bg-gray-950">
                    <div className="notebook flex p-[0.5rem]">
                        <div className="mr-auto">Notebook: </div>
                        <div>{newPageInfo.notebook}</div>
                    </div>
                    <div className="page flex p-[0.5rem] pt-[0]">
                        <div className="mr-auto">Page:     </div>
                        <div>{newPageInfo.pagename}</div>
                    </div>
                </div>
            </div>
            <div className={submitButtonStyle}>Create New Page</div>
        </div>
    </OverlayWindow>
}

Python 再帰の限界

  Python では、再帰できる回数に制限があるようで、それでコケるということがあった。

問題のある状態

  • 問題の再帰コード
async def taskController(interval:int):   
    if(interval == 0 or interval < 1):
        print("taskController: The interval setting is too short.: {} sec".format(interval))    
        print("taskController: Please make it longer.")
        interval = 1

    for aJob in jobs:
        aJob()

    await asyncio.sleep(interval)
    await taskController(interval)
  • 限界に達すると出るエラー
Process Process-2:
Traceback (most recent call last):
  File "/usr/lib/python3.13/multiprocessing/process.py", line 313, in _bootstrap
    self.run()
    ~~~~~~~~^^
  File "/usr/lib/python3.13/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/main.py", line 25, in serviceThread
    asyncio.run(tasks.taskController(loadSettings.settings["taskInterval"]))
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "/usr/lib/python3.13/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/usr/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 24, in taskController
    await taskController(interval)
  File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 24, in taskController
    await taskController(interval)
  File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 24, in taskController
    await taskController(interval)
  [Previous line repeated 975 more times]
  File "/home/username/work/OneNoteAlternative/firstPrototype/backend/dataServer/tasks.py", line 23, in taskController
    await asyncio.sleep(interval)
  File "/usr/lib/python3.13/asyncio/tasks.py", line 714, in sleep
    h = loop.call_later(delay,
                        futures._set_result_unless_cancelled,
                        future, result)
  File "/usr/lib/python3.13/asyncio/base_events.py", line 799, in call_later
    timer = self.call_at(self.time() + delay, callback, *args,
                         context=context)
  File "/usr/lib/python3.13/asyncio/base_events.py", line 816, in call_at
    timer = events.TimerHandle(when, callback, args, self, context)
  File "/usr/lib/python3.13/asyncio/events.py", line 114, in __init__
    super().__init__(callback, args, loop, context)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/asyncio/events.py", line 45, in __init__
    if self._loop.get_debug():
       ~~~~~~~~~~~~~~~~~~~~^^
RecursionError: maximum recursion depth exceeded

とりあえず解決とした状態

 再帰しすぎるのが駄目ならということで、単純に無限ループするようにしてみた。

async def taskController(interval:int):   
    while(True):
        if(interval == 0 or interval < 1):
            print("taskController: The interval setting is too short.: {} sec".format(interval))    
            print("taskController: Please make it longer.")
            interval = 1

        for aJob in jobs:
            aJob()

        await asyncio.sleep(interval)

os.system() と platform.system()

 helper の中にある deleteDataSafely 関数において、プラットフォームを識別するために、情報を取得する関数を間違えていた。紛らわしいので、一応記録する。

os.system(command:str)

 大雑把には、指定されたコマンドを実行する関数。

platform.system()

 大まかにどの OS を動かしているか情報を得る。

  • ‘Windows’
  • ‘Linux’
  • ‘Darwin’ (mac os)
  • ‘Java’
  • ’’ <- 空文字 (識別不能のとき)

Windows と文字コード

 私は今回のプログラムを作っている中で当初は、Python 3 以降を使えば必然的に全部 UTF-8 でやり取りされるだろうと思い込んでいた。

 しかし、実際に linux 上で開発していたものを windows 上に持ち込むと、少なくとも anaconda 経由でインストールされた Python 3.13.2 では ShiftJIS がデフォルトになっていて、linux 上で開発していた時と文字コードが合わなくなる問題が起こった。

 結果として起こったことが、テストデータに日本語が含まれていると、UTF-8 で保存されたファイルを、ShiftJIS で読み込もうとして、以下のようなエラー(illegal multibyte sequence)となり、ページデータの内容を読み込めないということになった。ちなみにこの時、英語だけで構成されたデータは問題なく読み込めてしまう。

----------------------
<<<{"command":"pageInfo","UUID":"25eca682-e30b-4562-8602-90891238c5a5","data":{"pageID":"test.json","notebook":"test"}}
connection handler failed
Traceback (most recent call last):
  File "C:\Users\username\miniconda3\Lib\site-packages\websockets\asyncio\server.py", line 376, in conn_handler
    await self.handler(connection)
  File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\main.py", line 15, in mainLoop
    await receiveLoop(websocket,controller.controller)
  File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\helper\netwrok.py", line 24, in receiveLoop   
    await callback(message,websocket)
  File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\controller.py", line 54, in controller        
    await commands[requestedCommand](request,websocket)
  File "C:\Users\username\work\OneNoteAlternative\firstPrototype\backend\dataServer\commands\notebookAndPage\pageInfo.py", line 35, in pageInfo
    contentString = aPage.read()
UnicodeDecodeError: 'cp932' codec can't decode byte 0x86 in position 430: illegal multibyte sequence

実際の対応

 以下のように全部ファイルPython built-in の open関数でを操作する際、全部文字コードを ”UTF-8” で統一した。

 open(pagePath,"wt",encoding="utf-8") 

Python ドキュメント

 ChatGPT にただ質問を投げて受け取ったのを真に受けるのは良くないので、どうしてそうなるのか公式ドキュメントを漁ってみたところ、なぜ Windows 環境で動いている Python 3 において、ShiftJIS を使おうとするか、見つかった。

 Built-in Functions — Python 3.13.7 documentationには次のような記述がある。

mode is an optional string that specifies the mode in which the file is opened. It defaults to ‘r’ which means open for reading in text mode. Other common values are ‘w’ for writing (truncating the file if it already exists), ‘x’ for exclusive creation, and ‘a’ for appending (which on some Unix systems, means that all writes append to the end of the file regardless of the current seek position). In text mode, if encoding is not specified the encoding used is platform-dependent: locale.getencoding() is called to get the current locale encoding. (For reading and writing raw bytes use binary mode and leave encoding unspecified.) The available modes are:

 特に重要そうだと思った部分は以下のところである。

if encoding is not specified the encoding used is platform-dependent: locale.getencoding() is called to get the current locale encoding.

 そこで、実際に Windows 環境で、Anaconda 経由でインストールされた、Python 3.13.2 を使って、locale.getencoding() をしてみると以下のような結果が得られた。

(base) PS C:\Users\username\work\OneNoteAlternative> python
Python 3.13.2 | packaged by Anaconda, Inc. | (main, Feb  6 2025, 18:49:14) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> import locale
>>> locale.getencoding()
'cp932'
>>> 

 また、Linux 環境において、同じことを試すと以下のようになった。

# python
Python 3.13.5 | packaged by Anaconda, Inc. | (main, Jun 12 2025, 16:09:02) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> import locale
>>> locale.getencoding()
'UTF-8'
>>>

 よって、少なくとも linux と Windows では、少なくとも私が持っている環境においては、open関数が参照しているlocale.getencoding()の返す値が異なり、異なる文字コード(ShiftJIS,UTF8)がファイルの読み書きにデフォルトで使われることが分かった。

 ただ気になる点としては、sys.getdefaultencoding()においては、UTF8 が linux と Windows では返ってきており、なぜlocale.getencoding()と挙動が違うのかはまだ分かっていない。

 確かに OS 事情を考慮すると、DOS 時代あたりから ShiftJIS が Windows では採用されていて、広く使われた結果、いまUTF8が幅を利かせてきても、まだ互換性の呪いが残っているのかな?みたいに勝手なことを思ったりはした。それに対して linux 環境は普通のデスクトップ環境としてはあまり広く使われていなかったことから、UTF8 が早い段階で事実上の標準になっていたのかななんて思ったりもした。

 

ChatGPT の痕跡

Record を使う提案

Open as a page

ShiftJIS の件

 以下はwindows-と文字コードに関連のあるものである。

Open as a page

参考にしたサイトとか