2012/05/30

条件をつけて作業内容を分けてみる ~if文~

前回は、作業を勝手にやってもらうときのカナメのひとつ「反復」を取り上げました。同じ作業を数限りなくやってくれるアレです。(同内容でまだ取り上げていないものにwhileループやrange()を使ったものなどもありますが。)
今回は、作業自動化のもう一つのカナメ、「分岐」についてとりあげたいと思います。
なんと、当方が敬愛してやまないtakkunさんのブログでも同様の内容が取り上げられていて、こちらより高度です(笑) より多くのリソースを求めたい方は、当ブログも併せてご利用くださいw





というわけで分岐です。
「ブンキと言われてもなにやら掴みづらいわー」ということでしたら『場合分け』というイメージでご了解ください。

つまり、
「この場合は~~する、あの場合は○○する、そうじゃないときはXXするです。

この「する」のなかに、すでに紹介した反復を入れますと、
いろんな種類にまたがる大量のオブジェクトをあれこれできるようになります。あるいは逆に、反復する内容の中に分岐を仕込んでもいいでしょう。
夢が膨らみますね。

余談ですが、
オンラインマニュアルではこんな感じ。melですが。
この反復、分岐といった内容をまとめて、プログラムの流れ(フロー)=制御フロー、制御構造(原:Control Flow) などと言ったりします。また制御フローを記述する際に使う単語は、コマンドでも関数でもなく「ステートメント」と呼ばれます。
Mayaのスクリプトエディタでは、書いたら太い黄緑色にハイライトされるやつらです。
MELで用いるステートメントでpythonでは使えないものなどは、書いてもハイライトされないので使えないことが分かります。逆も同様で、たとえばpythonの lambdaステートメント はMELでは使うことができず、書いても黄緑色になりません


まずは基本形↓


if (条件):
    (作業内容)



反復ではforで始めましたが、分岐はこのようにifで始めます。それに続けて『条件』を書き、forの時と同様、最初の行は「:」で締めます。それから、インデントして作業内容(本文=ボディ)を書きます。

「条件」というのは、「~~の場合には○○する」の『~~の場合には』にあたる文字列です。
まずは、こんな感じの例文を書いてみました↓


import pymel.core as pm

if pm.ls(sl=1):
    print "selecting!"


if とかが黄緑色になってます。

これを、新規シーンで実行してみましょう。
何も起きませんね。とくに何事も無く結果として出力されました。(初回実行時はpymel読み込みについての表示が出ますが。)
とりあえず実行しても、何も起きません。
では次に、なにかポリゴン球でも作ってから、実行してみましょう。

なにか選択した状態で実行
実行したスクリプトに続けて
「selecting!」
と出力されたとおもいます。

具体的にみて行きましょう。
一行目、ifに続けて、「pm.ls(sl=1)」と書かれています。今回は、これが条件になります。
これは、MELでいうところの「`ls -sl`」に同じです。
pyMelのなかの lsコマンド に slフラグ をつけて、選択中のオブジェクトが返ってくるようにしています(melでなら「-sl」のようにハイフンXXと書けばフラグが有効になっていましたが、pythonでは「XX=1」のように、0,1でスイッチを入れるようにしないと有効になりません。例えば sl=0 と書くと slフラグは有効になりませんし、0,1を指定せずslだけだとエラーになります)これによって、なにか選択していればそのリストが、そうでなければ空っぽのリストが返ってきます。
これをむまえて一行目は

「もし(条件式になにか中身があったら =)なにか選択中であれば」以下を実行する

くらいの意味になります。
何も選択していなければ、pm.ls(sl=1) はからっぽのリストを返しますので「もし」の条件に当てはまらず、したがってそれ以降も実行されません。

 
さて、
この例文だと、条件に該当しなかった場合 何も起きないため、なんだか味気ないですね。せめてなにかメッセージくらい表示させたいところです。
「~~の場合は○○、そうでない場合はXX
という感じで、該当した場合とそうじゃない場合とで別の内容が実行されるようにしたいと思います。


import pymel.core as pm

if pm.ls(sl=1):
    print "selecting!"
else:
    print "No Object Selected..."



こんな感じですね。
if~~のかたまりに続けて、else:のかたまりを付け加えています。
今回も、「:」を忘れないこと、作業内容はインデントすること、に気をつけてください。
これで、「なにかを選択している場合」と「そうじゃない(=なにも選択していない)場合」とで内容をわけられます。
ifで指定した条件に当てはまらない全ての場合において、elseの内容が実行されます。
「選択していない状態」に対応できました。
さらにいろんな場合分けをしてみましょう。


import pymel.core as pm
import pymel.core.nodetypes as nt


if type(pm.ls(sl=1)[0]) == nt.Mesh:
    print "it is mesh."
elif type(pm.ls(sl=1)[0]) == nt.Lambert:
    print "it is lambert."
elif type(pm.ls(sl=1)[0]) == nt.Transform:
    print "it is Transform."
elif type(pm.ls(sl=1)[0]) == nt.File:
    pass
else:
    print "what is this ?"


今までの例文に比べて急に長いですが、といってなかみが複雑なわけではありません。5つに場合分けしていて、似たような内容が繰り返されています。

一つ上の例で else: による「それ以外の場合」を書きましたが、別の条件をあれこれと指定する場合は elif を使います。
elif はもちろん「else if」を略したもので、同じような構造を表現するとき他の言語ではelse if ~~ と書く場合もあります。pythonでは、else if と書いても通じません。

これまでのif、else、elifを加味して基本形を書くと↓このようになります。


if (条件A):
    (作業内容A)
elif (条件B):
    (作業内容B)
elif (条件C):
    (作業内容C)

…場合分けの数だけelif文

elif (条件X):
    (作業内容X)
else:
    (どれにも当てはまらない場合の作業内容)



最初はif、途中のelifはいくつでも、最後のelseは条件なしで、それぞれ記述します。
例文を見ていってみましょう。


import pymel.core as pm
import pymel.core.nodetypes as nt
 


ちょっとあこぎなimportをしています。pm.nodetypes なり毎回書けばいい気もしますが、ちょっと横着して別途 nt としてimport。

if type(pm.ls(sl=1)[0]) == nt.Mesh:

最初の条件はifで開始です。
それにつづく条件は、最初の例文から手を加えて  type(pm.ls(sl=1)[0]) == nt.Mesh  としています。やや長いですね。
詳しく見ると、左辺はさっきまで使っていた pm.ls(sl=1) を type() のなかに入れたものだと気づきます。
MELで `ls -sl` が配列を返していたように、pyMelでの pm.ls(sl=1) もlistを返します
となると、ただ単に type() の中に pm.ls(sl=1) を入れたのでは、タイプを何度調べても「これはlistだ」という返事になってしまいます。
配列の中身を見る時のように、~~[0] とすることでリストの中身を番号指定で見ることができます。

順を追ってみていくと…

pm.ls(sl=1) ←選択中のオブジェクトのリスト

pm.ls(sl=1)[0] ←…の一番目にはいってるオブジェクト

type(pm.ls(sl=1)[0]) ←…のタイプ

という意味になります。
さらに条件式は続きます。  == nt.Mesh  ですが、「==」となっているのは間違いではありません。==は、いわゆる数学で出てくるイコールにイメージの近い、「等しい」を表す記号です。プログラミング一般では単体の「=」は代入するための記号として扱われていますので、そうじゃなくて普通にイコールを表したいんだというときのための記号として「==」が用意されています。こうした条件を表すときの記号を比較演算子や論理演算子(※「==」は比較演算子)といいますが、これらについては追って解説したいところです。

nt.Mesh の nt は、importしたときに名前を変えた「pymel.core.nodetypes」です。なので全体を書き表すと、「pymel.core.nodetypes.Mesh」となります。
これに先ほどの「==(等しい)」がついてif文に入れて、「もし~~に等しければ○○する」となり、この場合は

「もし選択中のオブジェクトがメッシュなら、○○する」

くらいの意味になります。
ほかの条件も、elifになっている以外は同様。

elif type(pm.ls(sl=1)[0]) == nt.Lambert:
は、
選択しているのがランバート(マテリアル)ならば

elif type(pm.ls(sl=1)[0]) == nt.Transform:
トランスフォームノードならば

elif type(pm.ls(sl=1)[0]) == nt.File:
ファイルノード(=テクスチャ)ならば

else~~ はこれまでどおり、「どれにも当てはまらないならば○○する」


3つ目のelifの行ですが、作業内容が pass となっています。
これは、「なにもしない」という指示です。
実際に特にすることがない場合のほか、あとで処理内容を書く予定だけど今はとにかく実行したい、という場合にも使います。こういうとき、elifと書いたものの中身を書かなかったり、#コメントだけ書いていたとしても、エラーになってしまって実行できません。とりあえずpassを書いて、さらに#コメントであとあと仕上げをするようメモなどすると良いのではないでしょうか。


かくして、
この例文を、何かを選択した状態で実行してみると、選択しているオブジェクトが
・メッシュオブジェクト
・ランバートマテリアル
・トランスフォームノード
・これらのどれでもない
の場合、それぞれにあったメッセージがprintされます。
ファイル(テクスチャ)の場合は、「なにもしない」という作業(笑)が行われます

トランスフォームノードを選択して実行した例


…さて、ではこの例文を、なにも選択しない状態で実行したらどうなるでしょうか?
この状態のスクリプトでなにも選択せずに実行すると…

なんと、なにも起きないどころかエラーになってしまいました!
これは、条件分けをすべて「いま選択しているのは何か」という内容にしたために起きた問題で、「いま選択しているかどうか」を判断する部分がそもそも無いのでエラーになってしまっています。
スクリプトが、意図なく「なにかを選択していることが前提」の内容になってしまっていて、「なにも選択してないんじゃタイプを判別しようにもお手上げ」という状態に陥ってのエラーです。

というわけで…
プログラムの流れを

なにか選択しているかどうか判断 → 選択しているもので作業内容分岐

というふうに変えてみましょう。
if文の入れ子です。

今回は構造の全体像も大したことないのでかまいませんが、実際にはもっと複雑な入れ子になることが多々あります。気軽に入れ子入れ子にしていると、いつの間にか判読困難修正至難脳内遭難なスクリプトになっていたりします。とても恐ろしいことです。
あまり複雑な事態に陥らないよう、見通しをたてて工夫しつつ構造を組み立てられるようになると、よいですね。。。

入れ子にすると、こんな感じになります↓


import pymel.core as pm
import pymel.core.nodetypes as nt

if len(pm.ls(sl=1)) == 1:
    if type(pm.ls(sl=1)[0]) == nt.Mesh:
        print "it is mesh (polyShape)."
    elif type(pm.ls(sl=1)[0]) == nt.Lambert:
        print "it is lambert."
    elif type(pm.ls(sl=1)[0]) == nt.Transform:
        print "it is Transform."
    elif type(pm.ls(sl=1)[0]) == nt.File:
        pass
    else:
        print "what is this ?"
else:
    print "please select ONE object."



記述面で重要なのは、入れ子にした内側のif文さらにインデントされているという点です。もとからif文なので本文はもちろんインデントなのですが、そいつをif文の中に入れたためもう一段インデントがかかっています。
すでに紹介したとおり、pythonではインデントは単なる文の整形にとどまらず、記述上の必要事項になっています。なのでこの例文を正しく動かすには、必要なだけのインデントをいれておく必要があります。
これによって文の構造を自分以外にも明示することになり、あとあと読み返すとき自分のためにもなります。
もちろん構造が見えやすくなるために、どれだけ入れ子にしているかも分かりやすくなり、「ちょっと深くしすぎ…?」などと構造を見直すきっかけにもなります。

最初の条件をさらにちょっと変えています。
 if len(pm.ls(sl=1)) == 1: の中の len(pm.ls(sl=1)) についてですが、
リストの長さ(要素数)を調べる組み込み関数 len() (※きっとlengthの略)に、選択中のオブジェクトのリスト pm.ls(sl=1) を入れています。 == 1 としているので「選択オブジェクトが1個の時は作業内容に進む、選択なしとか複数選択ならelseへ進む」となります。

無事エラー無く実行されるようになりました。

続けて条件式について書く予定でしたが、長くなりましたので次回。
もしくは、気になってしかたのない方は下記のリンクをたどってみてください!



■参考リンク

●承前


制御フロー @Python 2.7ja1 documentation » Python チュートリアル


pyMel  lsコマンド


組み込み関数 len @Python 2.7ja1 documentation » Python 標準ライブラリ


比較 @Python 2.7ja1 documentation » Python 言語リファレンス

0 件のコメント:

コメントを投稿