AKAGI Rails

鉄道模型シミュレーターNXで遊んでいたはずが、気づいたらPythonなども。

Pythonの「dict(辞書)型」虎の巻

Pythonらしいデータ構造といえばdict(辞書型)です。

  • dict型の基本
  • dict型の操作

(本稿はPython3系を前提とします。)

dict型の基本

dict型は、「キー」から「要素」にアクセスできるのが特徴です。

Object
list, tuple インデクス(i番目)から要素にアクセスできる
dict キーから要素にアクセスできる

dict型は波カッコ{}で括り、{キー:要素,...}のように書きます。キーから値を参照したいときは、リストと似た感じで変数名[キー]でアクセスします。

キーは文字列ないし整数がいいと思います。floatでもプログラムは走りますが数値計算の問題があるのでうまくないです。

要素は何でもありです。リストなども可。ここをどうするかが工夫のしどころです。

person = {
    "name": "Alice",
    "age": 30,
    "city": "Tokyo"
}

print(person["name"])  # → Alice
続きを読む

Pythonの「配列オブジェクト」虎の巻

「配列オブジェクト」は正確なPython用語ではありませんが、大抵のプログラミング言語には「配列」ないし、配列の機能を持つものがあります。

(VRM5以前のVRMスクリプトには配列がなかったわけですけど・・・それはさておき。)

「配列オブジェクト」とは、何らかのオブジェクトが順番に並んでいて、ひとまとめにグループ化されたものです。

特定のi番目の要素を取り出す操作(インデクス)が可能なのが、配列オブジェクトの特徴です。

(本稿はPython3系を前提とします。)

基本のlist型

Pythonの配列オブジェクトの基本はlist型です。四角いカッコ[]で括ります。要素の間はカンマ,で区切ります。

abc = ['い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と']

配列型の基本操作

インデクス

i番目を取り出してくる操作です。abc[3]のように、変数名のうしろに四角いカッコ[]で何番目かを指定します。

先頭を0番目と数えます。これはプログラミング言語によくあるやり方です。

abc[0]  # -> 'い'
abc[3]  # -> 'に'

後ろからインデクスすることもできます。一番うしろは[-1]でインデクスします。

abc[-1]  # -> 'と'
abc[-2]  # -> 'へ'

スライス

配列の中から「i番目からj番目の手前まで」という区間指定をして、一部を切り出すことができます。

abc[0:3]  # ['い', 'ろ', 'は']
abc[2:6]  # ['は', 'に', 'ほ', 'へ']
abc[3:4]  # ['に']

abc[3:4]は「3番目から4番目の手前まで」ですから、3番目だけのリストが返ってきます。

先頭と末尾の指定は省略することができます。省略すると、もとのリストの先頭から・もとのリストの末尾までになります。

abc[:4]  # -> ['い', 'ろ', 'は', 'に']
abc[4:]  # -> ['ほ', 'へ', 'と']
abc[:]  # -> ['い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と']

配列の長さを知る

abc = ['い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と']
len(abc)  # -> 7

list型を後から操作する

list型は作った後に要素の追加などの操作ができるのが特徴です。

そういう操作は、list型の「メソッド」を使って行います。オブジェクト名.メソッド名(オプション)の形で使います。

"""リスト型の操作"""
abc = ['い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と']

# リストの特定の場所を取り替え
abc[2] = 'ハ'
abc  # -> ['い', 'ろ', 'ハ', 'に', 'ほ', 'へ', 'と']

# リストの最後に要素を1つだけ追加
abc.append('ち')  # ->  ['い', 'ろ', 'ハ', 'に', 'ほ', 'へ', 'と', 'ち']

# リストのi番目に要素を挟み込んで追加
i = 0
abc.insert(i, 'ん')  # -> ['ん', 'い', 'ろ', 'ハ', 'に', 'ほ', 'へ', 'と', 'ち']

# リストの最後に別のリストを追加して延長
abc.extend(['り', 'ぬ', 'る', 'を'])  # -> ['ん', 'い', 'ろ', 'ハ', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を']

ソートもできます。

abc.sort()
# --> ['い', 'ち', 'と', 'に', 'ぬ', 'へ', 'ほ', 'り', 'る', 'ろ', 'を', 'ん', 'ハ']
# この場合、元の順序は破壊される

元のリストに手を加えることなく、ソート済みのリストを別オブジェクトで受け取りたいときは、組み込み関数のsorted()を使います。

abc = ['ん', 'い', 'ろ', 'ハ', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を']
abc_2 = sorted(abc)

abc  # -> ['ん', 'い', 'ろ', 'ハ', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を']
abc_2  # -> ['い', 'ち', 'と', 'に', 'ぬ', 'へ', 'ほ', 'り', 'る', 'ろ', 'を', 'ん', 'ハ']

リスト型は、同じものを複数回入れてもいいのです。

abc = ['い', 'ろ', 'は']
abc.extend(['あ', 'い', 'う'])
abc  # -> ['い', 'ろ', 'は', 'あ', 'い', 'う']

# 要素数を数える
abc.count('い')  # -> 2

本当はlist型ももっと色々できるのですが、何でもかんでもやればいいというものではありませんので、いったんここまでを紹介しました。

tuple型(タプル)

タプルも配列のひとつで、こちらは丸括弧()で括ります。

タプルがリストと大きく違う点は、一度作ったらその後に編集(要素の差し替え、挟み込み、ソートなど)ができないという点です。

「〇〇ができない」は、裏を返せば「〇〇されることはない」ので、ポカヨケが必要な場面でリストと区別して使います。具体例で見てみましょう。

"""リストとタプルの使い分け"""
# 入れ替わっては困るものは、タプル
xy = (256, 32)  # 座標など
size = (83, 69.7, 91)  # 女性の平均スリーサイズだそうです。

# タプルには異なる型の要素を詰め込むことも多い
pokemon = ('ピカチュウ', 35, 55, 40, 50, 50, 90)  # 名前と種族値

# イテレーションに使うだけならlistで十分
order = ['pizza', 'soda', 'coke', 'pizza', 'banana']
for item in order:
    print(f"{item}を作って提供する")

Pythonの「算術演算」虎の巻

コンピュータプログラムが数値を扱う上では、「数値型」について少し意識する必要があります。

Python言語は、数値型への強い意識がなくとも、都合よく動いてくれるプログラムを書きやすいようにできてる方だと思います。しかし、思いがけないエラーに遭遇する原因が数値型にあることも少なくありません。ですから、はじめのうちに知っておくことが肝要です。

  • 主な数値型
  • 基本の計算
  • intとfloatを混ぜると・・・
  • 数値の比較
  • floatの計算は不正確?

(本稿はPython3系を前提とします。)

主な数値型

本稿では次の2つの数値型のみを扱います。

  • int型…整数を扱う
  • float型…小数を含む実数を扱う

floatというのは、コンピュータ内部での数値の表現方法の特徴に基づく名前で、「浮動小数点数」とも言われます。

続きを読む

Pythonの「超・基本文法」

Pythonに限らず、プログラミング言語を扱う上では、まず最初に知っておかなくてはいけない基本文法があります。

VRMNXでPythonスクリプトを扱う上でも必須の事項です。

本稿では形式張ったPython文法仕様は抜きにして、初心者がまず最初に知っておくべき「超・基本文法」を取り挙げます。その筋の方からすると「それって文法か?」と思うところもおありでしょうが、割り切って進めていきます。

(本稿はPython3系を前提とします。)

全角スペースは使わない

スペースには「半角スペース」と「全角スペース」があります。

Pythonでは、ほとんど 常に・いつも・必ず・絶対に、「半角スペース」を使います。全角スペースは使いません。

""" Example """
# OK
gauge = 1067

# NG
gauge = 1067

これは極端な例ではありますが、NG例では = の前後が全角スペースになっています。NG例では次のエラーが出てきます。

  File "<stdin>", line 1
    gauge = 1067
         ^
SyntaxError: invalid character in identifier

全角スペースの混入はイッパツでエラーになりますが、その後発見が難しい、厄介なものです。 半角スペースを常に使う という心構えが必要です。

誰しもがはじめに経験するエラーではありますが、慣れとともに遭遇することはなくなるでしょう。

インデントは半角スペース4つ

関数やif文、for文(それ自体の説明は別の機会に譲ります。)などでは、まとまった量のコードをグループ化する機会があります。

Pythonでは、コードのグループをインデントによって表します。(C言語のようにカッコは使いません。)

"""コードのグループ化"""
def absolute(x):
    """xの絶対値を返す"""
    if x >= 0:
        return x
    else:
        return -x

このとき、1段階のインデントは必ず、半角スペース4個です。

  • インデントに、Tabとスペースは混用不可
  • 個数の異なるスペースインデントは不可

であることに注意してください。

文字列はクオートで囲む

Pythonプログラムが扱うデータは、「データ型」によって分類され、それぞれ扱いが異なります。

| 数値 | 数値型(int型、float型など) | | 文字列 | str型 |

他にも、list型、tuple型、dict型などがPythonの組み込み型にはありますが、ここではまず文字列型とそれ以外の区別について。

文字列型は、クオートで囲みます。Pythonでは、シングルクオート'とダブルクオート"のどちらを使ってもOKです。

"""文字列はクオートで囲む"""
# OK
station = 'Shibuya'
station_ja = "渋谷"

数値と文字列は別物として扱われます。比較しても等しくないのです。

'109' == 109  # -> False

シングルクオート'とダブルクオート"のどちらがよいか、に定説はないようですが、できれば統一して使っている方が、検索の手間を考えると望ましいと思います。

私の好みでは、どちらかというとシングルクオート'ですが、あまり徹底して統一していません。

変数名、オブジェクト名のルール

  1. 変数名、オブジェクト名にはハイフン-は使えません。
    • かわりに、アンダースコア_は使えます。JISキーボードでは、[Shift]+[ろ]で出せます。
  2. 名前の先頭に数字の使用不可
"""変数名、オブジェクト名の絶対的なルール"""
# NG
gauge-z = 1067

# OK
gauge_s = 1435

# NG
1st = 'first'

AIを使って撮る夫くん開発

次期撮る夫くんでは,ゲームパッド入力時にBボタンを押すとスクリーンショットを撮影して自動的にPNG保存する機能が実装されます。(というか、もう公開してあるんですけどね。)

Gamebarによるネイティブ撮影よりは実行速度が遅く,まずまずの出来ではありますが,簡単な撮影は大概これで足りそうです。

さて,撮る夫くんのスクショ撮影機能には,mssライブラリを利用しています。mssまわりの実装は若干慣れないプログラミングの内容だったので,最近流行りのAIの力を借りることにしました。

AIは別になんでもよかったので,開発時点でお金がかからず,特に制限なく使えたX Grokを選びました。具体的な実装を聞いてみて帰ってきた回答(ソースコード)に多少間違いはあったものの,大まかな方針はつかむことができたので実装の助けにはなりました。ま,今回は引き分けかな。

また,依存ライブラリが増えることにより,撮る夫くんモジュールのインストールが難しくなってしまうことが予想されたため,pipコマンドで撮る夫くんを依存ライブラリと一緒にインストールができるような仕組みを整えました。

pipコマンドでインストールを可能とするためには,pyproject.tomlというファイルに色々とおまじないを書いておかなくてはいけないのですが,これにもGrokの手を借りました。 調べながら自分の手で書いたpyproject.tomlでインストールがうまくいかなかったので,丸ごとGrokに投げて相談したのですが,Grokの答えの通りに修正したらうまくいきました。うーんこれは完敗。

ちなみに、VRMNXのPythonエンジンでpipコマンドを叩くのにはちょっとしたコツがいるので、必要なコマンドはバッチファイルに仕立ててそのまま配布してあります。詳しくは撮るおくんクイックスタートで。

想像以上に賢いです。こんにちのAIは。

Pythonらしいコードとは

Pythonらしさ」とは何か,という問に,簡単な答えを一つ用意するとしたら,「Python言語の利点を最大限活かした実装のやり方やコーディングスタイル」とでも言えるのではないでしょうか。本日は,「Pythonの禅」とPython標準のコーディング規約を紹介します。どちらも,Python公式から出されている PEP (Python Enhancement Proposals) という文書の一部であり,世界中のPythonプログラマに共有されているものです。

[PEP 20] The Zen of Python (Pythonの禅) を読もう

PEP 20 The Zen of Pythonでは,Pythonのデザインに関する原則について19行が記されています。日本語訳も多数見つかりますが,以下の記事が読みやすいでしょう。

qiita.com

The Zen of Python は哲学的な指針が列挙されているばかりで,すぐに実践するのは難しいかもしれませんが,どのような実装をすべきか悩んだときにはここに立ち返るとヒントが得られるかもしれません。

[PEP 8] Pythonコードのスタイルガイドを読もう

PEP 8では,Python標準ライブラリのPythonコードが従うべきコーディング規約が示されています。ただし,拘束力はそこまで強くないので,パッと見ですがPEP 8どおりでないPython標準ライブラリも無いわけではないみたいです。しかし,Python標準ライブラリのみならず,世界中のPythonコードに対してデファクトスタンダードのコーディング規約となっているフシがありますので,従っておいて損はありません。

PEP 8の日本語版は以下で読むことができます。

pep8-ja.readthedocs.io

すぐにでも実践したいいくつかの事項は,ここで改めて引用しながら紹介したいと思います。

インデント

Pythonコード内でのインデントには,半角スペース4つを使います。タブは原則として一切使いません。

関数などの途中で改行する場合の指針も示されていますが,厳密に唯一のルールが決められているわけではないので,ここは読みやすければある程度適当でもいいと思っています。

空行の入れ方

トップレベルの関数やクラスは、2行ずつ空けて定義するようにしてください。

クラス内部では、1行ずつ空けてメソッドを定義してください。

関連する関数のグループを分けるために、2行以上空けても構いません(ただし控えめに)。 関連するワンライナーの場合は、空行を省略しても問題ありません。(例: ダミー実装)

関数の中では、ロジックの境目を示すために、空行を控えめに使うようにします。

あまり空行を連発するとPythonコードとして品がないような印象を持たれます。

import

import文は原則,1モジュール1行ずつ記述します。

import文 は常にファイルの先頭、つまり モジュールコメントや docstring の直後、そしてモジュールのグローバル変数や定数定義の前に置くようにします。

import文 は次の順番でグループ化すべきです:

  1. 標準ライブラリ
  2. サードパーティに関連するもの
  3. ローカルな アプリケーション/ライブラリ に特有のもの

上のグループそれぞれの間には、1行空白を置くべきです。

PEP 8にはこのように書いてあるので,例えばVRMNXで使うならこのようになるはずです。

# LAYOUT

import os   # 標準ライブラリ
import sys

import vrmapi # ローカルのアプリケーションに特有のもの

# この行はatenxaのimportの前にどうしても必要なのでここに来る
sys.path.insert(0, vrmapi.SYSTEM().GetLayoutDir())

import atenxa  # ローカルのライブラリに特有のもの

# グローバル変数,定数定義
somedict = {}
NXSYS = vrmapi.SYSTEM()

def vrmevent(obj, ev, param):
    # 以下省略
    pass

コメントの書き方

PEP 8 -jaの本文に色々書いてありますが,一例を示せばちょうどこの上に書いた例のようになるはずです。インラインコメントでは,「スペース2つ以上 + # + スペース1つ」とすることになっていますが,スペースの使い方を忘れている方が多いです。

ドキュメンテーション文字列

別名 "docstrings" と言われています。別途PEP 257に規定があるので詳しく後述します。

命名規約

これは結構重要だと思っています。が,少なくともVRMNX界隈では今のところ皆さん好き勝手に書いているように見受けられます。

いちばん重要な原則

公開されているAPIの一部としてユーザーに見える名前は,実装よりも使い方を反映した名前にすべきです。

「撮る夫くん」の activate 関数を例に挙げてみます。 activate の実装は他ならぬイベントハンドラの実体にすぎませんが,撮る夫くんユーザーにはむしろ「撮る夫くんを有効化してくれる一行」として認識して頂くほうが理解しやすいだろうと考えられますので,toruo.vrmevent とか toruo.eventhandler とせずに,toruo.activate としました。

守るべき命名規約

簡単にまとめると以下のようになります。(標準ライブラリや組み込み関数,組み込み型は古い経緯があるのでこれに従わないケースがありますが,現在以降はこれに従うほうがよいでしょう。)

  • パッケージ・モジュールの名前は すべて小文字の短い名前にする。モジュール名では,必要であれば,アンダースコア _ は使ってよいが,パッケージ名では不可。

  • クラスの名前は原則, CapWords 方式とする。

  • 関数と変数の名前は,小文字のみ。読みやすくするために必要であれば,英単語をアンダースコア _ で区切る。

  • 定数扱いの変数は大文字のみ。必要に応じてアンダースコアで区切る。

  • 非公開プロパティ(モジュールの内部でのみ使用する前提であり,外部に公開していないグローバル変数や関数など)は,先頭をアンダースコア _ で始める

PEP 8では,関数の名前はクラスメソッドも含み,全部小文字ということになっていますが,肝心のvrmapiがCapWords方式で実装されているので困り果てています。ですが,各部品のイベントハンドラ(関数)は vrmevent とか vrmevent_xxx のように全部小文字+アンダースコアで生成されるので,大本営はvrmapiの中と外で命名規則を使い分けることを意図しているのでしょう。(本当に?)

VRM4~VRM5時代のVRMスクリプトはghost氏の提唱でCapWordsのハンガリアン記法が推奨されていましたが最早古いので,私の書くコードではあくまでPEP 8側に従っています。Pythonでは特に,クラスと関数がひと目見て区別できるコーディングスタイルは重要だと思います。

しかし,PEP 8の冒頭に「一貫性にこだわりすぎるのは、狭い心の現れである」とあるので,必要以上にとやかくは言いません。

比較に関する注意

None との比較には == は使いません。is または is not を使います。

# 正しい:
if foo is None:
    pass

if foo is not None:
    pass
# 間違い:
if foo != None:
    pass

if foo == None:
    pass

if not foo is None:
    pass

docstringsを書こう

コメントを書き込んでプログラムを分かりやすくしておくことは最早当たり前になっていますが(かといって徹底できていないのでおま言うw),関数の使い方やクラスの役割などを説明するコメントの書き方はある一定のルールが設けてあります。このルールに従っておくと,例えばVS Codeのような高機能エディタがうまく気を利かせて,コーディング中にポップアップでその説明を見せてくれたり,Sphinxのように自動でオンラインドキュメント化してくれるツールが存在したりするので,実務的にも得することができます。

docstringsの基本事項はPEP 257に規定されていますが,もう少し細かく書式を規定したものに,numpyスタイルとgoogleスタイルの二大流派があります。縦方向に省スペースでも書けるという点で,私はgoogleスタイルが好みです。

googleスタイルのdocstringsの書き方は,以下の記事が読みやすいです。

qiita.com

atenxaや撮る夫くんのコードにもgoogleスタイルでdocstringを書いています。コードごとSphinxに読み込ませると,こうやってドキュメントがほぼ全自動で作ってもらえるので大変便利です。

よいPython実装を真似しよう

The Zen of Pythonには,

There should be one-- and preferably only one --obvious way to do it. 何かをするための明らかな方法(実装)が,一つは……あわよくば,ただ一つだけの方法があるはずだ。

とはありますが,実際問題,Pythonプログラムには,実装の上手い下手や,実行速度の早い遅いが出てくるものです。

できるだけ分かりやすく,パフォーマンスも悪くない,上手な実装は,教科書のような書籍もいくつかありますが,有名なライブラリのコードを読んで,参考にして勉強するのもおすすめです。

個人的なおすすめをいくつか紹介します。

  • 標準モジュールのdatetime
    • カレンダー上の日付等を扱うモジュールです。曜日やうるう年の計算など,内容が分かりやすく,実装がきれいなので勉強になります。
  • seaborn (matplotlib拡張のデータ可視化ライブラリ)
    • matplotlibを内部で使用してmatplotlibよりも気が利いてきれいなグラフをささっと書いてくれるライブラリです。matplotlibのマニュアルを脇に開いて,seabornの実装を読み解くとPythonプログラミングの勉強にはなります。ですが分量は多いしmatplotlib自体も結構難解なので,普通に難解ではあります。VRMNXでは使う機会はないでしょうが,Pythonでのグラフ描きは比較的ポピュラーな用途かと思いますので紹介してみました。中級以上の方向け。

github.com

github.com

おわりに

Pythonまわりで実践したいこと,あるいは皆さんに布教したいことを勝手につらつらと書いてみました。PEP 8のコーディング規約も,GoogleスタイルDocstringsも,デファクトスタンダードとしてかなり有効に機能していますので,ご存じなかった方はぜひ参考にしてみてください。

今見てみると撮る夫くんではPEP 8やGoogleスタイルdocstringsの決まりごとを守れていない点がちらほら見受けられはしたので,若干反省もしておるところです。

VRMNXpyで名前空間はどうなっているのか

本稿では、VRMNXpyにおける名前空間、すなわちあるオブジェクトが参照されうる範囲(見える範囲=スコープともいう)がどのようになっているのかについて述べていきます。また、その結果として,どのような実装パターンが良さそうか、についても考察を与えます。

なおAKAGIの理解の範囲内では、Pythonにおいて変数とオブジェクトの違いは曖昧である(ことにしておいたほうが都合がいい)ので、本稿においてもこれらの用語を混同して使用します。混同しているとハマる罠があるのは知っていますが,一旦これは棚上げにします。

ローカルとグローバル

関数 (function) の中で生成されたオブジェクトは「ローカル変数」となり、実行中のその関数の中でのみ参照や書き込みが許され,その関数の処理が終わると廃棄されます。別の関数からはもちろん、一度処理が終わった関数をもう一度呼び出したとしても、以前の実行時の変数の内容は破棄されていて知ることはできません。

一方で関数の外で生成されたオブジェクトはグローバル変数となり、削除しない限りはその後のランタイムのどこからでも参照することができます。ですから,なにか作った値を,将来,別の関数などから参照したいと思っているのなら,何らかのグローバル変数に保存しておく必要があります。

グローバル変数に関数内から何かを書き込む

前述のように,将来,なにかの値を今実行中の関数とは別の関数で使いたい場合には,グローバル変数に保存しておく必要があります。何らかの関数内からグローバル変数の内容を書換えたいときには一手間かかります。

some_global_var = None # これはグローバル変数

def some_function(arg):
    global some_global_var
    # グローバル変数 some_global_var をこの関数内から書き込み可能にする
    
    result = arg * 2
    some_global_var = result # グローバル変数に保存した

このように,関数内でglobal文を用いて,グローバル変数some_global_var と解釈するように明示する必要があります。

VRMNXpyの実践上,注意しておくべき事柄は,イベントハンドラも関数であるという点です。ですから,イベントハンドラ内で vrmapi を通じて取ってきた何らかの値を将来使えるように保存しておきたければ,保管用のグローバル変数イベントハンドラの外で定義する必要があります。

具体例の紹介は省略しますが,グローバル変数を使わない別の方法もあります。それはVRMNXの各オブジェクトが持つdictを使う方法です。

部品を跨ぐコードのスコープ

VRMNXでは,レイアウト上に配置されたセンサーや編成や車両や信号機といった「部品」ごとそれぞれに,Pythonスクリプトを書き込む「スペース」が用意されています。

バージョン5までのVRMスクリプトとは,部品とスコープの扱いがだいぶ変わりました。

早い話, すべての部品は1つのスコープを共有していて,全部品のPythonスクリプトから,全部品のグローバル変数を直接参照可能 です。ただし,いくつかの注意事項があります。なぜなら,VRMNXがビュワー起動までにPythonスクリプトをどう扱っているか,イメージできていないと若干トリッキーだからです。マニュアルにはちゃんと書いてありませんが,実験的なコードをいろいろ書いて試した範囲では,ビュワー起動時に以下のような手順を踏んでいることが推測されます。

  1. ビュワー起動時に,レイアウトオブジェクトのPythonスクリプトが実行される。
    • 注) イベントハンドラを含む関数は,名前が拘束されるだけで中身の実行はまだされない
  2. その他の部品のPythonスクリプトが実行される。(順番は不定らしい。)
  3. レイアウト→その他部品の順で,initイベントがキックされる。
  4. 画面の描画が始まる

(この程度に理解しておけば,今のところVRMNXpyをそれなりに思った通りに動かせます。)

この挙動の中で注意すべきなのは,1や2の最中で,まだ定義されていない別オブジェクトのグローバル変数を参照しようとすると参照エラーが起きる可能性がある点です。この場合,ビュワーがクラッシュすることも想定されますが,一方でVRMNXビュワーが完全に起動していないのでLOGウィンドウが使い物にならないかもしれません。ですから, 初期化部分では不用意な動作を書くべきではありません。 vrmapi も不用意に使用しないほうがいいと思っています。 せいぜい,モジュールのインポートや,初期化だけしておきたいカラの変数の定義をするに留めておくのが良いでしょう。これは,VRMNX用の自作モジュールにも当てはまるでしょう。

ちょっと古い作例ですが,自作踏切モジュールは,踏切部品(親)のPythonスクリプトの初期化部分で定義したグローバル変数を,子の踏切部品からも参照しちゃう作例になっています。(ただしこのような実装は可能ですが,私のVRMNXpyの理解の途上でやってみた実装に過ぎず,別の部品のコードで定義したPythonオブジェクトを参照するという分かりにくさがあるので,常におすすめできる方法ではないと今は考えています。)

VRMNXpyの実装パターンはどうなっていくか

ここまでグローバル変数の話をしましたが, グローバル変数を使わない 方法によりわかりやすい実装をすることができるのではないかと思っています。グローバル変数の代わりに使うのが,レイアウト外部の自作Pythonモジュールです。

撮る夫くんやATENXAは,いろいろなレイアウトに使い回せる汎用性を意識して作っているものですが,レイアウトごとに,ad hocPythonモジュールを用意することはできるでしょう。例えば,

(root)
├ MyLayout.vrmnx
└ mymodule.py

のように自作モジュール (mymodule.py) を用意します。中身は簡単に次のように想定します。

# mymodule.py

my_global_var = None # このグローバル変数をなにかに使おう

レイアウトスクリプトの中で,vrmnxの次にmymoduleをimportします。

# レイアウト
import vrmnx
import mymodule

def vrmevent(obj, ev, param):
    pass # 以下省略

すると,例のグローバル変数は他の部品からは mymodule.my_global_var で参照できます。

このようにして,

というルールを敷いておけば,別の部品のグローバル変数のつもりで参照したり書き込んだりという混乱の元を,いくぶんか減らせるのではないかと思います。

(小規模なレイアウトで小規模なギミックを実装する範囲に留まるのであれば,これに限りませんが。)

さらにグローバル変数に対するアクセスポリシーを厳しくするには,アンダースコア _ で始まる変数名を使うことです。Pythonでは,アンダースコアで始まる変数は「内部ONLY」の意味を持つと暗黙に約束されています。(たとえグローバル変数であっても。)アンダースコアで始まる内部ONLYのグローバル変数は,グローバル変数ながら,モジュールの外部からのアクセスをしてはいけないという明確な表示になります。詳しくは,PEP 8の「実践されている命名方法」などを参照されるといいでしょう。

撮る夫くんでは,内部ONLYのグローバル変数を多用しています。興味のある方は撮る夫くんのソースをご覧になってみてください。

まとめ

あまりまとまりがありませんが,VRMNXpyの変数のスコープについて語ってみました。VRMNXならではの重大事項として,各部品はスコープを共有しているということに注意が必要です。さらに,ビュワー起動時の内部動作がどうなっているかについて多少の想像を及ばせておく必要があります。このほかは,Python一般の知識がきちんと通用すると思いますので,分かりやすく,間違えにくいコードを書いていく努力をしていけばよいものと思われます。