のにっき

【python】tryの中で呼び出した関数がエラーになった場合の挙動

pythonの組み込みで必ず使用するtryを使った例外処理の話です。

tryの中で呼び出した関数でエラー

tryの中で外部関数を呼び出した場合、
・外部関数でエラーになった時は呼び出し元のtryが動いてくれるのか?
・ちゃんとexcept以下の処理が動くのか?
・外部関数で外部関数を呼び出した時も同じ挙動になるのか?
・外部関数の中でtryが組み込まれていた場合はどうなるのか?

これらの挙動を簡単なソースで確認してみました。

【外部にtryなし】外部でエラーが起きた場合の挙動

A )外部の外部の外部でエラーが起きた場合

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    print("01")
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    print('02')
    SetDisp_03()
#******************************************************************************
# 呼び出す外部関数_3rd
def SetDisp_03():
    print('03')
    print(1+"")# こいつがエラーになる行( int+str でエラー)   
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("エラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01を呼ぶ→SetDisp_02を呼ぶ→SetDisp_03を呼ぶ→エラー
という挙動になるように組み込みました。
実行結果

01
02
03
エラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12308>)

結果を見る限り、
外部でエラーになってもちゃんとtryが機能していて処理が中止されることはない。
ちゃんとエラー発生後にexcept以下の処理が実行されている
という事がわかりました。
B )外部でエラーが起きた場合、その後の処理は中断されるのか?

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    print("01")
    print(1+"")# こいつがエラーになる行( int+str でエラー)
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    print('02')
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("エラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01を呼ぶ→エラー(の後、SetDisp_02を呼ぶ)
という挙動になるように組み込みました。
実行結果

01
エラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB126C8>)

結果を見る限り、
外部でエラーになってもエラー発生後にすぐ
except以下の処理が実行されているから、エラー後の処理は行われない。
という事がわかりました。

まとめ
・外部でエラーになってもちゃんとtryが機能していて処理が中止されることはない。
・ちゃんとエラー発生後にexcept以下の処理が実行されている
・外部でエラーになってもエラー発生後にすぐ
 except以下の処理が実行されているから、エラー後の処理は行われない。

【外部にtryあり】外部でエラーが起きた場合の挙動

A )外部のtryの中でエラーが起きた場合

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    try:
        print("01")
        print(1+"")# こいつがエラーになる行( int+str でエラー)
    except:
        print("子01のエラー")
        print(sys.exc_info())
        pass
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    try:
        print("02")
        print(1+"")# こいつがエラーになる行( int+str でエラー)
    except:
        print("子02のエラー")
        print(sys.exc_info())
        pass    
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("親のエラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01(tryの中でエラー発生)→SetDisp_02(tryの中でエラー発生)
という挙動になるように組み込みました。
実行結果

0101のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12788>)
0202のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B08>)
正常

B )外部のtryの外でエラーが起きた場合

import sys
#******************************************************************************
# 呼び出す外部関数_1st
def SetDisp_01():
    try:
        print("01")
        print(1+"")# こいつがエラーになる行( int+str でエラー)
    except:
        print("子のエラー")
        print(sys.exc_info())
        pass
    SetDisp_02()
#******************************************************************************
# 呼び出す外部関数_2nd
def SetDisp_02():
    print('02')
    print(1+"")# こいつがエラーになる行( int+str でエラー)
#******************************************************************************
# メインルーチン
try:
    SetDisp_01()
    print("正常")
except:
    print("親のエラー")
    print(sys.exc_info())
    pass

このソースでは、メインルーチンのtryの中で
SetDisp_01(tryの中でエラー発生)→SetDisp_02(tryの外でエラー発生)
という挙動になるように組み込みました。
実行結果

01
子のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B48>)
02
親のエラー
(<class 'TypeError'>, TypeError("unsupported operand type(s) for +: 'int' and 'str'",), <traceback object at 0x000001DEFBB12B88>)

まとめ
A)とB)の結果を比較して、
・親tryの中で呼んだ外部関数のtry(以下、子try)の中でエラーが起きたら、
そのエラーは子tryの中で完結するので親tryから見たら正常扱いとなる。
・子tryはその中で完結しているので、子try外でのエラーは親tryのエラーとなる。
そのため、エラー発生時に処理は中断されて親tryのexcept以下の処理が実行される。

以上です。
組み込んでて色々気になることを検証しているのですが、
説明がめんどくさくて記事に出来ない事ばっかりなので難しいですね。
今回もこんなに記事が長くなるとは思いませんでした。。。まとめ力が成長しない。。。

【Unity】バージョンアップ時にプロジェクトが開けなくなった

昨日、unityを起動したら急に発生した現象です。

アホみたいなことを繰り返してようやく復旧できたので

対処法をまとめておきます。

(ネットに全く参考記事が無くて孤独死しそうでした・・・)

 

 

 バグった現象

Unityプロジェクト起動後に、
今まで問題なかったプロジェクトのコンポーネントがほぼエラー状態になっていた。
※自作スクリプトではなくUnityに元からあるものもエラーになっていた

f:id:apuridasuo:20191129153335p:plain

図:壊れたプロジェクトのエディター表示

f:id:apuridasuo:20191129153416p:plain

図:VSでスプリクトを開いた時のエラー内容(一部抜粋)

色々確認していったのですが、

何かしらが原因でUnityEngineのdll参照が壊れているのかなー?

ぐらいしか分かりませんでした。

原因(予想)

原因は分からないのですが、心当たりがいくつかあるのでリスト化

・UnityHubの方のバージョンアップ通知があったので更新して

 更新後にプロジェクトを開いた。

 ※これが原因だと思ってます。

 でも、エディターでもないHubの更新でプロジェクトが壊れるとは思えない・・・

・当該PJは、当初2017バージョンのUnityエディタで制作していて

 1ヶ月前ぐらいに2019バージョン(2019.2.11)に切り替えていた

 ※切り替え後も上手く動いていて、2019バージョンで

 アプリリリースも行ってますので関係ないとは思う

上記の2つの要因を掛け合わせて予想した原因は、

UnityHubの更新を行ったことで

2017verエディタで作成したプロジェクトの2019verへの互換性がなくなった!

という事だと思ってます。

対応したこと(失敗編)

結局直りませんでしたが、行ったことをまとめておきます

・PCを再起動

 →変わらず・

・UnityHub、Unityエディターを再インストール

 →変わらず・・

・最新のエディター(2019.2.14)をインストールしてPJ(プロジェクト)を開く

 →変わらず・・・(*_*;

・壊れたPJのAssetsフォルダ全てをエクスポートして新規作成PJにインポート

 →新規作成PJは問題なく動くが、インポートしたとたん

 「'csc'は、内部コマンドまたは外部コマンド、
 操作可能なプログラムまたはバッチ ファイルとして認識されていません」

 というエラーが出てビルドできなかった・・・(; ・`д・´)

・PJのバックアップ(2017のエディターに対応)を引っ張り出してきて

 2019バージョンのエディタで起動

 →起動したPJが壊れていた・・・(*´з`)

 この対応で、

 「もしかしてHubの更新が原因で

  2019verのエディタと2017verエディタの互換性がなくなったんじゃ・・・?」

 と思いつきました。

対応したこと(解決編)

・PJのバックアップ(2017のエディターに対応)を2018.4.13のエディタで起動

2019バージョンのエディタで起動したら壊れたPJですが、

2018バージョンなら壊れずに正常に起動できました。

※2017バージョンでも起動できましたが、

 APIレベル28以上の基準が満たせないので2018バージョンで起動

 

あとは、エディター周りの設定を一度削除してしまっていたので

AndroidSDK、NDKの環境を整えて上手くビルドできる様になりました。

 

 分かったこと(大事!)

・UnityHubの更新でもバージョンの互換性は変化する

・バージョンごとに互換性があるため、安易に対応エディタのver更新をしない

・バックアップは偉大・・・( ..)φメモメモ

・2017verのプロジェクトと2018verのプロジェクトは互換性がある

 

いじょうです。

同じ境遇の方がいましたらご参考にしてみてください!

usingクラス取得ツール作った

namespaseを利用してクラス名を書かずに

別クラスのメソッド等を呼び出す方法がとても便利なのですが、

「using static ○○○」をスクリプトファイルごとに書くのが面倒くさい!

ということで、簡易的にツール化したのでご紹介します。

 

  

クラス名を書かずに別クラスのメソッド等を呼び出す方法

下記URLで説明してますので、ぜひご一読ください・・・

【C#6】別スクリプトの関数・定数を簡潔に呼び出す方法 - のにっき

 

ツール置き場 

仕様書も置いているので一緒に落として確認してみてください。

github.com

 

使い方 ※仕様書抜粋

1)まずは、Assetsフォルダのパスを記入(ファイルのD&DでOK!)

f:id:apuridasuo:20191108194043p:plain

図:初期設定

2)「検索開始」ボタン押下で「using static ○○○」リストを出力!

  ※そのまま貼り付けれる形式でリスト化されてます

f:id:apuridasuo:20191108194108p:plain

図:検索開始時の挙動

3)namespaseごとに、表示の切り替えができる様になってます

f:id:apuridasuo:20191108194128p:plain

図:チェックボックスの仕様

4)パスの記入内容は「保存する」にチェックしておくと、次回起動で

  パスが記入された状態で開かれるのでとても便利です。

f:id:apuridasuo:20191108194159p:plain

図:パスの保存チェック仕様

 

 以上です。

ぜひ使ってみてご意見・ご感想などいただけたら嬉しいです!

 

○進数を○進数に変換する関数

私の会社は16進数のデータをいじくりまわすことがある。
いちいち「Convert」を使って変換するのもめんどくさいので
各進数を別の進数に変換する関数を作成しました。

ソースコード

        /// <summary>
        /// 進数変換モード
        /// </summary>
        public enum Md_Conv
        { c10to2 = 0, c10to16, c16to2, c16to10, c2to10, c2to16 };

        /// <summary>
        /// ○進数を○進数に変換
        /// </summary>
        /// <param name="Mode">進数変換モード</param>
        /// <param name="SetWord">変換文字</param>
        /// <returns>変換後の文字※桁合わせしてる</returns>
        public static string Get_ConvertNum(Md_Conv Mode, string SetWord)
        {
            string setNumOff = SetWord.Trim();
            string cnv = "";
            switch (Mode)
            {
                case Md_Conv.c10to2:
                    cnv = Convert.ToString(int.Parse(setNumOff), 2);
                    cnv = cnv.PadLeft(8, '0');
                    break;
                case Md_Conv.c10to16:
                    cnv = Convert.ToString(int.Parse(setNumOff), 16);
                    cnv = "0x" + cnv.PadLeft(2, '0').ToUpper();
                    break;
                case Md_Conv.c16to10:
                    cnv = Convert.ToInt32(setNumOff, 16).ToString();
                    break;
                case Md_Conv.c2to10:
                    cnv = Convert.ToInt32(setNumOff, 2).ToString();
                    break;
                case Md_Conv.c16to2:
                    cnv = Convert.ToInt32(setNumOff, 16).ToString();
                    cnv = Convert.ToString(int.Parse(cnv), 2);
                    cnv = cnv.PadLeft(8, '0');
                    break;
                case Md_Conv.c2to16:
                    cnv = Convert.ToInt32(setNumOff, 2).ToString();
                    cnv = Convert.ToString(int.Parse(cnv), 16);
                    cnv = "0x" + cnv.PadLeft(2, '0').ToUpper();
                    break;
            }
            return cnv;
        }

使用例

▼ テストソース

            // テスト用変数作成
            int Num_10sin = 4095;
            string Num_2sin = "0111";
            string Num_16sin = "FFFF";
            string Num_16sin2 = "0x00FF";
            // 各進数表記に変換
            string Result = "";
            Result = "◆ 10進数「Num_10sin ("+ Num_10sin + ")」を変換\r\n";
            Result += "16進数:" + Get_ConvertNum(Md_Conv.c10to16, Num_10sin.ToString()) + "\r\n";
            Result += " 2進数:" + Get_ConvertNum(Md_Conv.c10to2, Num_10sin.ToString()) + "\r\n";
            Result += "◆  2進数「Num_2sin (" + Num_2sin + ")」を変換\r\n";
            Result += "16進数:" + Get_ConvertNum(Md_Conv.c2to16, Num_2sin) + "\r\n";
            Result += "10進数:" + Get_ConvertNum(Md_Conv.c2to10, Num_2sin) + "\r\n";
            Result += "◆ 16進数「Num_16sin (" + Num_16sin + ")」を変換\r\n";
            Result += "10進数:" + Get_ConvertNum(Md_Conv.c16to10, Num_16sin) + "\r\n";
            Result += " 2進数:" + Get_ConvertNum(Md_Conv.c16to2, Num_16sin) + "\r\n";
            Result += "◆ 16進数「Num_16sin2 (" + Num_16sin2 + ")」を変換\r\n";
            Result += "10進数:" + Get_ConvertNum(Md_Conv.c16to10, Num_16sin2) + "\r\n";
            Result += " 2進数:" + Get_ConvertNum(Md_Conv.c16to2, Num_16sin2) + "\r\n";
            MessageBox.Show(Result,    "出力結果");

▼ 出力結果

f:id:apuridasuo:20191108153238j:plain
図:出力結果

関数概要

上記の感じで「16・10・2」進数の相互変換を行うことができます。
注意点としては、
10進数だろうが何だろうが文字列型でやり取りしている点です。
なので、数値型を引数として渡す場合はテストデータのように「ToString()」で文字列に直してください。
また、16進数の戻り値の表記は「0x」が頭について大文字になってます。
また、関数内の「cnv.PadLeft」の引数で0埋めの桁数を調節できます。

Scroll View内に高さが可変のノードを作る

ScrollViewの要素の高さを可変にしたい!

ScrollViewの要素にScrollViewを入れたい!という方にオススメ

設定を変えるだけで意外と簡単にできたので方法をまとめておきます

 

 

ヒエラルキーの内容

下図の感じでオブジェクトを設定します

「Scrl_Parent -> Content_P」の中にノード(P)を追加していくスクロールがあり、

そのノード(P)の中に、ノード(C)を追加していく。という仕様になります。

 

f:id:apuridasuo:20191029175150p:plain

図:オブジェクト構成

高さが可変のノードとは

ノード(C)が追加されるたびにノード(P)の高さが調節されるという仕様です。

スクロールのノードの高さがノードごとに可変であるという事がポイントです。

f:id:apuridasuo:20191029175711p:plain

図:実際の挙動

各オブジェクトの 設定

まずは、いつも通りScrollViewを作ってください。

その上で変更のある点は下記の2点です。

・親スクロールのContentオブジェクトの「○○ Layout Group」

「Control Child Size」にチェックを入れる

・親スクロールに追加するノードに「○○ Layout Group」を追加する

 

▼ 下図の設定は「ヒエラルキーの内容」項目のオブジェクト名と

照らし合わせて参考にしてください

f:id:apuridasuo:20191030093525p:plain

図:各オブジェクトのInspector内容

 

以上で、ScrollView内のノードに高さが可変のノードを作成することができます。

 

NCMBとGoogleAdMobの競合によるビルドエラー解決法【2019年10月】

何番煎じか分からない項目なのですが、

シャキッとした答えが見つからなかった。

バージョンの違いで対応が違っていたので

現バージョンのパッケージで解決した方法をまとめときます。

 

 

パッケージのバージョン

GoogleMobileAds・・・v4.0.0

NCMB・・・v4.0.3

ビルドエラー時の状況

・NCMBパッケージを先にインポートする

 →たぶんこれがビルドエラーの原因

・GoogleMobileAdsパッケージをインポートしてビルド

▼下図のエラー発生

UnityEditor.BuildPlayerWindow+BuildMethodException: 528 errors

CommandInvokationFailure: Gradle build failed.

f:id:apuridasuo:20191029113628p:plain

図:エラーメッセージ

今回行った対処法

NCMBパッケージでインポートした「Plugins -> Android」内のファイルを削除

ビルドエラーの原因は、ライブラリの競合にあるので

NCMBパッケージでインポートした

Pluginフォルダ内のファイルを削除して競合しないようにします。

◆削除するファイル名

・firebase-analytics-impl-16.0.0

・firebase-common-16.0.0

・firebase-iid-16.2.0

・firebase-iid-interop-16.0.0

・firebase-messaging-17.1.0

・play-services-base-15.0.1

・play-services-basement-15.0.1

・play-services-measurement-base-15.0.0

・play-services-tasks-15.0.1

・support-compat-26.0.2

・support-core-utils-26.0.2

・support-v4-26.0.2

f:id:apuridasuo:20191029115108p:plain

図:削除するファイル一覧

f:id:apuridasuo:20191029115712p:plain

図:削除前後の「Plugins -> Android」フォルダ

 

ビルドしてみる

これで上手くビルドが出来ているはずです。

ビルドエラーが発生したとき、

パッケージを複数インポートした場合は

ライブラリの競合が発生している可能性が多々あると思います。

そういった場合は、

1.検証用に新規プロジェクト(AAA)を作る

2.エラーの出るプロジェクトに入れたパッケージを(AAA)に入れる

3.インポートした「Plugins」フォルダのファイルを

 パッケージごとに消していきエラーが回復するか確認する

このやり方で、競合するファイルが何か判別できると思います。

 

teratailで質問する前にやっておくべき事

teratailを質問者として利用してばかりじゃ申し訳ないと思い、
若輩者なりに回答者としていくつか回答してみました。
その中で、質問者として最低限行ってほしいと思ったことをまとめます。
※必ずしろ!というわけではなく、してもらえると回答シヤスイナーです

teratailとは?

プログラム実装で詰まったら質問できるサイトです。
私のように教わる人のいない状況で勉強している方には非常に便利なサイトになります。
teratail.com

ソースコードを貼るときはタグで囲む

これは必ず行って下さい。利用する方でできていない方が結構います。
teratailは、ソースコードをタグで囲むことで見やすくする機能があります。

f:id:apuridasuo:20191019112223p:plain
図:タグ説明
記入方法を以下にまとめるので知らない方は必ず確認して下さい!
f:id:apuridasuo:20191019112350p:plain
図:ソースコード記入方法
この書き方でソースコードを記入すれば、背景色も変わり、
コメントや変数などの色付けもされてとても見やすい表示になります。
f:id:apuridasuo:20191019112755p:plain
図:プレビュー結果

エラーの内容でググる

バグが発生して何が間違っているのかわからない場合、
ログにエラーが出ていないか確認してみて下さい。
そして、エラーの文字をコピーしてググってみましょう。
意外とネットには解決方法が載っているので質問する前に解決できます。

自分の書いたソースを理解してから質問する

ソースコードをコピペしてバグって訳がわからない!ということがあった場合、
そのまま質問しても、回答者の回答が何を言っているのか分からないと思います。
コピペしたコードを部分的にググってみて、
自分の書いたソースの内容をある程度勉強して下さい。
そうすれば、バグの原因がわからないとしても、
回答者が何をいっているのか理解することができると思います。

質問したいこと、求めている回答を明確にした質問をする

何が分からないか分からないという状況で質問しないよう気をつけましょう
・何を目的としたコードを組んでいるか
・現在の挙動はどうなっているか
・何を回答してほしいか
以上の項目は自分で考えて、明確にしてから質問しましょう。

自分でソースの状況を把握しておく

ソースの組み込みを行う上で、
プログラムの実行中に一時停止を行って変数の内容などを確認たりします。
そういったデバッグ方法をいくつかご紹介します。
※実装環境でやり方は異なるので、「VisualStudio ブレーク 方法」
 のように検索していただけると方法が出てくると思います。

  • ブレーク(ポイント)

ブレークは、設定した場所で処理を一時停止させる機能です。
ブレークポイントを設定した状態でプログラムを実行すると
設定した場所に処理が到達した時点でプログラムが一時停止します。
ブレークポイントを設定することを「ブレークを貼る」ということがあります。
【使用例】
・if文等の分岐でブレークを貼ってどの分岐が通っているか調べる
・問題のある処理でブレークを貼って、その時の変数の値などを確認する
・反応しない関数を呼ぶ処理にブレークを貼ってその処理が通っているか調べる

  • ログ(メッセージ)

ログは、処理の中で指定した文字をコンソール等の、
実際には影響のない部分に出力させる機能です。
【使用例】
・if文等の分岐で通ってはいけない場所にログを実装して「ログ出力=バグ」とする
・変数設定後に変数の内容をログで出力する事で見えない変数の内容を把握する
これらのデバッグ方法を使って質問の前に
不具合の原因を探してみてある程度の目星をつけておくと
回答者とのやり取りが非常にスムーズになります。

質問しない!

上記のデバッグ方法とネットの力を駆使したら
メジャーな問題はたいてい解決できます。
まずは自力で解決する!これが良い質問をする最大のコツだと思います。
自力で解決する努力を行うことで問題への理解が深まり、
質問する際に相手に伝えるべき項目をまとめて質問することができます。
また、回答者は専門的な用語を踏まえて回答をくれるときが多いですが
調べることに慣れていると戸惑わずに対応できると思います。

以上になります。
回答者の方々はきちんと対応すれば良い方ばかりです。
変な感じになったな~と思ったら大体自分の対応が悪かったと思って
めげずに頑張って下さい。