久々にC#プログラミングをやろうかな、ということで始めたネタ探し。

「【無駄話】趣味プロのネタ探し over againシリーズ」

 

 

 

思考系ゲームだし、懐かしさもあって標題に決定しました。

 

が、

 

オセロが「白黒チップを五目並べ的に盤において、陣地争いをしながら勝敗を決する」ゲームということは(中学生くらいの時に流行ったので)知っていましたが、詳細はよく知らず、改めて調べてみました。(オセロReversiオセロ専用HP

:いやぁ、昔はWindowsに標準装備だったReversiとオセロの確執等、

オセロの歴史はドラマ

なんですね。Wikiには、

「イギリスで19世紀後半に考案されたリバーシReversi)の一形態が1973年に日本でオセロとして発売され、爆発的な人気を呼んだ。オセロゲーム(Othello Game)とも呼ぶ。(オセロ

"Reversi is a strategy board game for two players, played on an 8×8 uncheckered board. It was invented in 1883. Othello, a variant with a fixed initial setup of the board, was patented in 1971.(Reversi"

とありますが、長谷川さんとその継承者はそうは認めていないようです。

 

また、

 

コンピューターゲームはOthello専用機やPCプログラム()などもありますが、プログラミングの際に変数の名前等、英語表記する必要があるのですが、これもこんなのとかあんなのがあり、あまり統一感がないのでReversiを参考にさせていただきました。プログラムの基本アルゴリズムは↑のブログに書いたこれを参考にしています。

:因みにWindowsに同梱されていたReversiも基本Othelloなんですが、Windows Meで初めて同梱され、Windows XPまで続いたとのこと。(出典

 

さて、

 

今後の進め方ですが、実際に私がこれを基にMSComAss用にプログラムを整えていった経過が、コード説明()になりますので、ドキュメンタリー的に記述してゆこうと思っています。

:オリジナルと私流双方の。

 

前回までの「Python初体験」の続きです。

表題が付いていましたが、単にリストの合計を求める処理でした。最終形は以下の通り。

#オリジナルの例題
cases = [100, 125, 110, 135, 93, 95, 93]  #想定は「1週間の感染患者数のリスト」です。

index = 0  #インデックス値の初期値 0 を設定
len_cases = len(cases)  #最初は要素数の絶対値7を代入していました。
total = 0  #合計感染者数の初期値 0 を設定

while index < len_cases:  #インデックス値 < len_cases の間、ループを繰り返す
    cases_of_day = cases[index]
    total = total + cases_of_day  #totalに、cases[index]を加算
    index = index + 1  #index に 1 を加算
    
print("合計感染者数は:", total)

出力結果はこうなります。
合計感染者数は: 751

「while文を使ったlistのループ処理」というよりは、len関数の紹介ですね。これだけでは何にも面白い所がないので、少しいじってみます。

#オリジナルの例題
cases = [100, 125, 110, 135, 93, 95, 93]  #想定は「1週間の感染患者数のリスト」です。

index = 0  #インデックス値の初期値 0 を設定
len_cases = len(cases)  #リストcasesの要素数
total = 0  #合計感染者数の初期値 0 を設定

while index < len_cases:  #インデックス値 < len(cases)の間、ループを繰り返す
    total += cases[index]  #totalに、cases[index]を加算。なお、オリジナルはtotal = total + cases_of_dayだが、+=は使える。
    index += 1  #index に 1 を加算(オリジナルは"index = index + 1"。なお、++indexやindex++は使えない。)

cases.sort()  #casesの要素を並び替える
print("合計感染者数:", total, "平均値:", total / len(cases), "中央値:", cases[int(len(cases) / 2) + 1])

出力結果はこうなります。
合計感染者数: 751 平均値: 107.28571428571429 中央値: 110

この次の回では、↑の処理をfor文を使って書き換えています。

#最終形は次の通り。
cases = [100, 125, 110, 135, 93, 95, 93]

total = 0 #合計感染者数の初期値 0 を設定
for cases_of_day in cases:
    total = total + cases_of_day

print("合計感染者数は:", total)

出力結果はこうなります。
合計感染者数は: 751

改造した処理もfor文に書き換えます。

#ちくっと変更
total = 0  #合計感染者数の初期値 0 を設定
for num in cases:  #Pythonの"for"は、C#の"foreach"の様に使える
    total += num  #totalに、cases[index]を加算

cases.sort()  #casesの要素を並び替える
print("合計感染者数:", total, "平均値:", round(total / len(cases), 2), "中央値:", cases[int(len(cases) / 2) + 1])

出力結果はこうなります。
合計感染者数: 751 平均値: 107.29 中央値: 110

 

まぁ、なんということもなかったですね。ではまた次回。

 

ネタ元のオセロゲームのコードの一部(注)をMSCompassでコンパイルできるように改造し、オリジナルのアルゴリズムを解説する、というネタで行くことに決定しました。

注:このページは、このサイトにあったのですが、Copyright条件が書かれていなかったので、「Topへ」を押したら"404 Page Not Foundエラー"、「【書籍等の購入はこちら⇒】」も中古車販売のCMだけ、しょうがないので、このサイトの上と思われるURLを調べたらここにたどり着きました。矢張りCopyright文言は無く、最終更新日が2012/03/09となっており、所謂「廃墟サイト」なのかなー、と思われます。なおこのサイトは「ソースプログラム 公開」とあったので参照という形で使わせていただきます。

 

ということで、

 

色々とオリジナルコードを読んで分析し、自分なりにコメントを入れ(またはオリジナルのコメントを書き換え)、

 

(1)"InitializeComponent();"で隠されたコードは、オリジナルのコメント文から推定して、"InitControls();"で追加する。

(2)ラジオボタンコントロールはChekedプロパティの初期状況が分からず、「先攻・後攻」がユーザーにより決定されるまでは、関連コントロールを無効にし、ゲームの仕方や操作等のUIが全くないので、簡単にMessageBoxで表示し、結果もやりっぱなしでは不味だろうと、戦績情報の記録、表示機能などを追加する。

(3)(恐らく構想段階で書かれたが、結局実現されずに)使われない不要な変数(gameStart)、コントロール(timer1)に関わるコードは削除する。

(4)オリジナルのアルゴリズム(特に手<move>の評価システム)はそのままとする(注)も、コーディングは意味の分かるような変数の名称を使ったり、関連メソッドの配置をまとめたり等可読性を高める。

(5)(実は最後に引っ掛かったのですが)オセロ盤の座標の表記で、配列、メソッド引数共に(i, j)、(II, JJ)等iとjで、その順で書かれており、(x, y)のことかと思ったのですが、実は(y, x)であり、メソッドだけは(x, y)の形式に直しました。

(6)その他、イレギュラーな条件式や不要な条件式などは手直しして、簡素な形にしました。

 

その結果、

 

このような形で元気に動いております。

 

後はじっくりと解説してゆきましょう。

 

一昨日、元の職場の飲み会に誘っていただき、赤を飲みすぎて、昨日は久々のアセトアルデヒド。で、書く気が起こらずダウン。

 

多くの人にあるでしょう?そんなこと。

 

ん?じゃ、それをネタにしては?

 

「二日酔い解消アプリ」!

 

なーんちゃって。

 

あるわけねーだろ!(PC画面見ているだけで、Uuuuuppppp!)

 

くだらないことを書いていないで、真面目にネタ探ししないと。

 

実用アプリは限界が来たので、(余り気が進まないけど)やっぱりゲームかな?

しかし、ゲームって現在は非常に高度で、何か一点でも飛び抜けていないと「くそゲー」と化す危険性大。第一、書くとなるとC#、C#と言えばUnity。そんな現代で今更小ネタゲームが受け入れられるのか?等とつらつら考えていたら、ネットでオセロゲームを公開していました。(勿論、Visual Studio版です。)

 

ウ~ン、懐かしいかも。

 

取り敢えずこれをダウンロードして、中身を覗いて、「解説するだけでもネタ」にならないか?

 

ということで、この線でちょっと検討しましょう。

 

ps. 「ダウンロードして、中身を覗い」たら、結構その気になり、Visual Studio版から全コード版にして、オリジナルのアルゴリズムを生かしながら「私流」に改造しています。

 

前回までの「Python初体験」の続きです。

前回、Pythonの標準配列としてのリスト(list)を知りましたが、今回はそれの使い方(method)について学びましょう。(
:などと、殊勝に書きましたが、どのようなプログラミング言語でも単純な配列(Pythonではリスト)である、一次元配列のデータ処理は大体次の通りとなります。
(1)追加、値の代入、削除
(2)検索、置換
(3)並び替え
最もプリミティブな配列は単純にデータをメモリーに並べてゆくだけになりますが、そういう単純なデータ処理は検索や並び替えの際に時間やリソースを浪費します。
現在は配列は配列データに対するポインター(メモリー上のデータ位置を指すアドレス)となり、検索や並び替えではデータそのものは参照するだけになりますし、置き換えの場合には元のデータを削除し、新しいデータを作って、そのアドレスデータを入れるだけになります。
OOPでデータと処理が統合されている場合には、配列に対するこのような操作が最初から配列オブジェクトにビルトインされています。

(1)追加、値の代入と削除
Pythonでは改めてList等の宣言は行わず、単純に配列で初期化すれば自動的にリストオブジェクトになります。

values = ['A', 'B', 'C']  #valueという変数(というか、オブジェクト)に配列であることを示す[]による初期化を行う。

この段階で、

print("修正前:", values, "(要素数:", len(values), ")")  #リストには要素数を取得するメソッドにはないですね。"len(<リスト>)"関数を使います。

とすると、出力は、

修正前: ['A', 'B', 'C'] (要素数: 3 )

になります。次にデータを追加するには「insert(<位置>, <データ>)」メソッドを使います。最初の要素に111、ブービー(最後の一つ前)に999を入れると、

values.insert(0, 111)
print("修正後1:", values, "(要素数:", len(values), ")")
values.insert(3, 999)
print("修正後:", values, "(要素数:", len(values),  ")")

こうなります。

修正後1: [111, 'A', 'B', 'C'] (要素数: 4 )
修正後: [111, 'A', 'B', 999, 'C'] (要素数: 5 )


データの追加にはinsertの他、append()(末尾に要素を追加)やextend()または '+' 演算子による要素の結合も可能です。

values.append(["私の", "名前は", "ysamaです"])
print("要素追加後:", values, "(要素数:", len(values),  ")")
values += [16, 32, 64]
print("要素追加後:", values, "(要素数:", len(values),  ")")

追加後はこうなります。

要素追加後: [111, 'A', 'B', 999, 'C', ['私の', '名前は', 'ysamaです']] (要素数: 6 )
要素追加後: [111, 'A', 'B', 999, 'C', ['私の', '名前は', 'ysamaです'], 16, 32, 64] (要素数: 9 )


ここで注意されたいのは、Pythonでは配列(リスト)に異なる型のデータを要素として混在させられることです。出力される"[]"の括りからすると、ベースは数値データで、文字列データの際に別の配列として中に入れているようです。(いずれにしてもリストオブジェクトの実体は矢張りポインターの配列のようですね。)

要素はリストオブジェクトに"[]"で、要素番号を指定すれば抽出、値の代入等が出来ます。

values[2] = 'b'  #'B'を'b'に置き換えます
print("要素置換後:", values, "(要素数:", len(values),  ")")

とすると、

要素置換後: [111, 'A', 'b', 999, 'C', ['私の', '名前は', 'ysamaです'], 16, 32, 64] (要素数: 9 )

となります。

最後にリストの要素の削除ですが、矢張りオブジェクトのメソッドではなく、delというコマンド文(注)を使うようです。
注:これは関数でもないです。なんか一貫していませんが...

del values[1]  #削除コマンドで、valuesの2番目の要素'A'を削除します。
print("要素削除後:", values, "(要素数:", len(values),  ")")

出力は、

要素削除後: [111, 'b', 999, 'C', ['私の', '名前は', 'ysamaです'], 16, 32, 64] (要素数: 8 )

となります。(なお、リストから特定の値の要素を削除する<リスト>.remove(<特定の値>)というメソッドがあるにはありますが。)

(2)検索と置換
↑で「リストオブジェクトの要素数を求めるメソッドに"count"があるのではないか?」、と最初試したらエラーになりました。
"count"メソッドはあるのですが、引数にデータを与えて合致する要素を検索し(<リスト>.count(<検索対象>))、その数を返すメソッドでした。単なる検索だけなら、その添字を教えてくれる<リスト>.index(<検索対象>)があるので、両方を組み合わせるとリスト中の検索対象のインデックスを求められます。

print("要素(999)の検索:", values.count(999))

この出力は次の通りになります。

要素(999)の検索数: 1

検索と置換は関数を定義してもよいですが、内包表記([式 for 任意の変数名 in イテラブルオブジェクト if 条件式])という方法でもできます。

def replace_array(array, s1, s2):  #s1をs2に変更する関数の定義です。
  for i in range(len(array)):
    array[i] = array[i].replace(s1, s2)

list_string = ["私の", "名前は", "ysamaです"];  #replace()は要素がすべて文字列でないとエラーになるので
print("置換前:", list_string, "(要素数:", len(list_string), ")")
replace_array(list_string, '名前', 'あだ名')
print("置換後:", list_string, "(要素数:", len(list_string), ")")

print("置換前:", list_string, "(要素数:", len(list_string), ")")
list_string = [s.replace('あだ名', '名前') for s in list_string]
print("置換後:", list_string, "(要素数:", len(list_string), ")")

出力はこのようになります。
置換前: ['私の', '名前は', 'ysamaです'] (要素数: 3 )
置換後: ['私の', 'あだ名は', 'ysamaです'] (要素数: 3 )
置換前: ['私の', 'あだ名は', 'ysamaです'] (要素数: 3 )
置換後: ['私の', '名前は', 'ysamaです'] (要素数: 3 )


(3)並び替え
最後は要素の並び替えです。一番分かり易いのがsort(key, reverse)メソッドでしょう。keyは比較関数を指定し、reverseは正順逆順を指定します。(既定値はkey = None、reverse = False。)
この他reverse()は要素を逆順にひっくり返します。

では、では。
 

プログラミングネタ探しをして、Webを徘徊していると、C#(Formベース)のコントロール一覧のページがあり、そこに

 

BackgroundWorkクラス

 

が出ていた。

 

なんだ、これ???

 

ということで調べてみると、ボタンやラベル等のよくあるUIのWindows Controlではなく、時間を要する処理を行う為の(別)スレッドユーティリティのようです。これはまだ試したことがないので、関連サイトを調べ、似たようなサンプルを参考に、(実に久しぶりに)"InitializeCOmponent"を使わない、MSCompAss用の「全コード」サンプルを作ってみました。

 

細かい解説は↓のサンプルのコードのコメントを参照されたいのですが、大まかなポイントは、

 

(1)参照DLLとして"C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.ComponentModel.EventBasedAsync\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.ComponentModel.EventBasedAsync.dll"を加え、コードには"using System.ComponentModel;"を追加します。

(2)System.ComponentModel.BackgroundWorkerのインスタンスを作成します。この際に、

         DoWork
         ProgressChanged
         RunWorkerCompleted

という3つのイベントにメソッドを追加しておきます。

(3)RunWorkerAsyncメソッドで別スレッドの処理(DoWorkイベントで追加したメソッド)を開始します。進捗はProgressChangedイベントのProgressChangedEventArgs eで報告され、完了時に終了の仕方がのRunWorkerCompletedイベントのRunWorkerCompletedEventArgs eで確認できます。

 

以下のサンプルでは、お約束の0.5秒Sleepを10回繰り返すだけですが、実際の処理では完了までの時間、処理量を100として、0から定量化する必要があるようです。ネットワークからのダウンロード等、環境で処理時間が変わり、進捗が変更されるものもありますが、そこは(ユーザーサイドで)ロジックを組む必要がある、ということだと思います。

 

ご参考まで。

 

【BackgroundWorkSample.cs】

//-------------------------
// BackgroundWorker Sample
//-------------------------

using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;    //BackgroundWorkerを使う為
using System.Reflection;        //Assemblyを使う為

namespace BWSample
{
    ///////////////////////////
    //エントリーポイントクラス
    ///////////////////////////

    class MainApp
    {
        [STAThread]
        public static void Main()
        {
            Application.Run(new MainForm());
        }
    }

    public partial class MainForm : Form
    {
        //メンバーコントロール
        BackgroundWorker backgroundWorker;    //但しMainFormの子になることはない
        Label resultLabel;
        ProgressBar    progressBar;
        Button startAsyncBtn;
        Button cancelAsyncBtn;

        public MainForm()
        {
            InitControls();                    //MainFormとコントロールの初期化
            backgroundWorker.WorkerReportsProgress = true;        //進行状況の更新を報告可否
            backgroundWorker.WorkerSupportsCancellation = true;    //キャンセルのサポート可否
        }

        //BWFormとコントロールの初期化
        private void InitControls()
        {
            //フォームの初期化
            Assembly myOwn = Assembly.GetEntryAssembly();
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);    //プログラムアイコンをフォームにつける
            this.Text = "BackgropundWorker Sample";
            this.ClientSize = new Size(480, 80);
            this.MinimumSize = new Size(480, 80);
            this.BackColor = SystemColors.Window;
            //ボタンの初期化
            startAsyncBtn = new Button();
            startAsyncBtn.Location = new Point(ClientSize.Width - startAsyncBtn.Width - 10, 10);
            startAsyncBtn.Text = "開始";
            startAsyncBtn.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            startAsyncBtn.Click += startAsyncBtn_Click;
            this.Controls.Add(startAsyncBtn);
            cancelAsyncBtn = new Button();
            cancelAsyncBtn.Location = new Point(ClientSize.Width - cancelAsyncBtn.Width - 10, startAsyncBtn.Height + 20);
            cancelAsyncBtn.Text = "中止";
            cancelAsyncBtn.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            cancelAsyncBtn.Click += cancelAsyncBtn_Click;
            this.Controls.Add(cancelAsyncBtn);
            //ラベルの初期化
            resultLabel = new Label();
            resultLabel.Location = new Point(10, 10);
            resultLabel.Anchor = (AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Left);
            resultLabel.BorderStyle = BorderStyle.Fixed3D;
            resultLabel.Width = this.ClientSize.Width - startAsyncBtn.Width - 30;
            resultLabel.Text = "ここに進捗を表示する";
            this.Controls.Add(resultLabel);
            //プログレスバーの初期化
            progressBar = new ProgressBar();
            progressBar.Location = new Point(10, startAsyncBtn.Height + 20);
            progressBar.Anchor = (AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Left);
            progressBar.Width = this.ClientSize.Width - startAsyncBtn.Width - 30;
            progressBar.Minimum = 0;
            progressBar.Maximum = 100;
            this.Controls.Add(progressBar);
            //BackgroundWorkerの初期化
            backgroundWorker = new BackgroundWorker();
            backgroundWorker.DoWork += backgroundWorker_DoWork;  //別スレッド処理
            backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);  //進捗変化があった場合の処理
            backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);  //処理が完了した場合の処理
            /* UIコントロールの様にControls.Addによって子供にする必要はない */
        }

        //startAsyncBtnを押した場合の処理
        private void startAsyncBtn_Click(object sender, EventArgs e)
        {
            if(backgroundWorker.IsBusy != true)
            {
                //非同期処理開始
                backgroundWorker.RunWorkerAsync();
            }
        }

        //cancelAsyncBtnを押した場合の処理
        private void cancelAsyncBtn_Click(object sender, EventArgs e)
        {
            if(backgroundWorker.WorkerSupportsCancellation == true)
            {
                //非同期処理キャンセル
                backgroundWorker.CancelAsync();
            }
        }

        //BackgroundWorkerによるバックグラウンド処理
        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;    //"as"はobjectのキャスト演算子
            for(int i = 1; i <= 10; i++)
            {
                if(worker.CancellationPending == true)    //キャンセルされた場合
                {
                    e.Cancel = true;
                    break;
                }
                else
                {
                    //時間のかかる処理をここで行う
                    System.Threading.Thread.Sleep(500);
                    worker.ReportProgress(i * 10);    //MinimumとMaximumに合致するように設定する
                }
            }
        }

        //バックグラウンド進捗処理
        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            resultLabel.Text = (e.ProgressPercentage.ToString() + "%が処理されました。");
            //プログレスバーの値を変更する
            if(e.ProgressPercentage < this.progressBar.Minimum)
            {
                this.progressBar.Value = this.progressBar.Minimum;
            }
            else if(this.progressBar.Maximum < e.ProgressPercentage)
            {
                this.progressBar.Value = this.progressBar.Maximum;
            }
            else
            {
                this.progressBar.Value = e.ProgressPercentage;
            }
        }

        //バックグラウンド終了処理
        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if(e.Cancelled == true)
            {
                resultLabel.Text = "中止されました。";
            }
            else if(e.Error != null)
            {
                resultLabel.Text = "エラー:"  + e.Error.Message;
            }
            else
            {
                resultLabel.Text = "完了しました。";
            }
        }
    }
}

 

 

前回までの「Python初体験」の続きです。

今回はPythonの変数オブジェクトの一つであるリストをやります。


「初体験」の冒頭で、リストとは「複数の値を決まった順番に並べ、ひとまとめにした情報」という説明があります。まぁ、殆どのプログラミング言語で扱う配列や最近ではC#でやったList<型>などはその最たるものでしょう。
当然リストの構造は[要素1, 要素2, ...]となり、矢張り型宣言無しの変数に代入できます。

【例】stations = ["東京", "品川", "新横浜", "小田原", "熱海"]

他の言語と異なり、Pythonで面白いのは、仮にprint(stations)というメソッドを実行すると、

【出力】['東京', '品川', '新横浜', '小田原', '熱海']

となることです。またこのリスト変数の取り扱いはC系の配列ポインターのようで、print(stations[0], stations[4])とすると、

【出力】東京 熱海

になります。また(当たり前ですが)勿論配列添字(Pythonも'0ベース'です)がオーバーすると、所謂"Out of range"エラーになります。

【出力】IndexError: list index out of range

なお要素は代入されている値に応じた変数の型で使えるので、例えば数値型の値が入っている要素は算術演算等が可能です。

まぁ、こんなところで今回は手仕舞しましょう。

 

ps. 我ながらあっさりとしたブログに危機感を感じます。プログラミングネタ切れのみならず、ブログネタ切れ感濃厚です。実は一昨日、昨日と間に「【無駄話】趣味プロのネタ探し over again(2)」を書こうを思い、途中まで書きかけたのですが、用事があったり、気に入らなかったりでポイしました。でも、Stay tuned!

 

 

前回までの「Python初体験」の続きです。

今回は突然「PythonにおけるObjectとは何か?」というテーマでした。
Objectに関わる記述では、
Pythonでは、...整数や実数、文字列などのデータのことを、オブジェクト(Object) という用語で呼びます。
Pythonでは、Pythonが操作するいろいろな種類のデータやプログラムなどのことを、まとめて オブジェクト と呼びます。
数値などの値のように、変数に代入したり、関数のように関数名を指定したりと、プログラマが名前をつけられるものはオブジェクト、whileのようにつけられないものはオブジェクトではない、と考えると良いと思います。
というようなものが見当たりました。

だから何?

と言われるかもしれませんが、28歳から41年間も趣味でプログラミングをしてきた私には感慨深いものがあります。


ROM BASICの時代では、「プログラムは短ければ善」ということで行区切り(:)をいっぱいつけて「一行プログラム」なんてーのがはやっていました。しかし、何をやっているのかが非常に分かりずらいこと言うまでもありません。そんな「手続き型プログラミング」の中で、

 

「(データ)処理)手続きの構造」が視覚的に直観できるようにするプログラミングを「構造化プログラミング」

 

と呼び、一種のブームになりました。(私も当時Cを学び始め、インデントの重要性を確信していました。→それがPythonでは「ブロック(データ処理の纏まり)」を表す文法としてMUSTになっています。)


その後、C言語が発展して、Object Oriented Programing(略称"OOP"で、英語の"oops"に引っ掛けた?)を実現したC++では、データと構造化の対象となる処理を分別するのでなく、「データ(メンバー変数-後のフィールド)とそれを処理する方法(メンバー関数-後のメソッド)」が一つになったものがオブジェクトとされ、オブジェクトの抽象的な「型」をクラス、それをメモリー上に実体として置いた段階で「オブジェクト」ということを学びました。(

:OOPの特徴である、
(1)カプセル化
(2)抽象化
(3)継承
(4)ポリモーフィズム
等についてはここでは省略します。なお、余談ですがMicrosoft Learnのクラス、オブジェクトの説明はかえって初心者を混乱させてしまうのではないか、と懸念します。Pythonに関わるOOPの説明も、直ぐにプログラミング実務に飛ぶので「プログラマーの世界の住人」でないと俄かには理解しずらいのではないでしょうか?
 

更に、C++にもう二つ++をつけたC#では、クラス、オブジェクトは同様でも、「変数」はDB的に「フィールド」、アクセッサーでget;、set;する「プロパティ」、「関数」は「メソッド」と呼ばれるようになったこと、ご存じの通りです。
 

Pythonにおいては、オブジェクトはクラスやメソッドの概念にとらわれず、「プログラミング上、主語または目的語となるものが(メソッドが規定されていなくても)オブジェクト」と言っているように感じます。(述語がメソッドですか、ね。)

私だったらどう説明するか?A good question!

"例えば、新婚の夫婦がこれからの結婚生活についてプランを立てますよね。例えば「子供」については「男の子」や「女の子」が欲しい、とか、「子供」の「養育」「教育」「しつけ」「(トイレ等生活動作の)訓練」なども話し合うでしょう。実際に子供が生まれて、その子が「太郎」という名をつけ、その子が「食事」し、「成長」し、「学習」し、社会的に活動してゆくわけですが、そういう「主体または客体」と「行動」が一体となった「太郎」がオブジェクトの概念なんです。"

ウ~ン、却って分かりづらかったかも。難しいなぁ。

いずれにしてもPythonの公式文書では「Python公式サイトにあるドキュメント「オブジェクト、値、および型」では、Pythonにおけるオブジェクトとは『データを抽象的に表したもの』と表現されている。つまり、Pythonで書いたプログラムが操作したり、処理したりするデータのことを『オブジェクト』という。」とされているそうですので、今はそんな風に覚えておけばよいですね。
 

結局どんな言語を使おうが、最後は此処に帰ってきちゃうんですね。

 

で、

 

正直「またか」と感じながら、「人は何をやっているのだろう」ということでwebをググる。

 

そうすると、

 

商業的なプログラミング教室的サイトばかり現れる、という悪循環です。

 

昔はこんなことは余りありませんでした。

 

何故って?

 

ロハ(「只」を崩して読む隠語ですね)で使える開発環境、プログラミング言語、教本、ツール、コンポーネント等何もなかったので、

 

全部自分で作らなければならなかったから

 

です。要すれば「

 

やることがいっぱいあった!

 

」ということです。所が今は過去の遺産がいっぱいあり、GitHubのようなネットでそれを共有する仕組みもできて、わざわざ自分で一から作る必要がなくなりました。その結果、業務用ソフト開発は効率化が進みましたが、暇な趣味プログラマーはやることがなくなってしまいました。(注)

注:世の中にはこういう逆説的な問題がありますね。人間は色々なことをしたいと考えて、長生きをしたいと望みましたが、100歳が当たり前になってくるようになると、「することが無くて苦痛」ということもあり得るのです。(因みに私の母は存命で今年101歳を迎えます。)

 

そんな中で、この本のデータが未だあるのにちっと感激!前にもちょっと書きましたが、30歳で米国に研修に行き、帰ってきて購入したのが「8bit最速のZ80 BのSharp MZ-2500」。最初は同梱のBASICインタープリターでプログラミングしていましたが、どうしても速度の速い機械語を使いたくなり、(Sharpが格安でオプション販売していた)CP/MとCコンパイラー(BDS C)を買ってCを学習したときに、持っていた本です。この本でBDS Cの為に機械語(ニーモニック)で書いたファイルを一緒にコンパイルしてくれる"CASM"というツールのソースが載っており、これをどうしても使いたくて、(逆行するようですが、「低級」とはいえ高級言語の端くれの)CでZ80のアッセンブラー(ZASM)を書いたのが

 

初めての実用プログラム(お恥ずかしいので小さくなっています)

 

でした。そして、役20年前ロハのBorlans C++ コンパイラーの為のリソースエディター、Win32APIのライブラリーやツール一式まとめたBCCForm and BCCSkeltonを出すまで、

 

ずーっとプログラミングをするために作るべきものがあった

 

というのが、ネタだったんですねぇ。

 

ウ~ン、難しい世の中になった。(注)

注:Windows 11でロハのVisual Studioに様々な言語が使用できるようになった現在も、当時のBASICやZ80プログラミングを趣味にしている方がいるのは、矢張りそういった「モノを作る楽しさ」への郷愁なのかもしれません。

 

 

前回までの「Python初体験」の続きです。

今回は表題の通り、ローカル(局所)変数とグローバル(大域)変数、および関数の引数のお話です。まぁ、C系言語でプログラミングを学んだ方には目新しいことはあまりないと思います。

例題で興味を持ったのが次のプログラムです。
"""\
# global_value はグローバル変数
global_value = 100

def test_global(arg):
    # ローカル変数 arg と グローバル変数 global_value の積
    return arg * global_value

print(test_global(10)) # 10 * 100\
"""

このプログラムの関数test_globalで、グローバル変数global_valueと同名のローカル変数を2で初期化して宣言するとどうなるか(命題:グローバル変数とローカル変数の何れが優先するか?)実験してみました。

"""\
# global_value はグローバル変数
global_value = 100

def test_global(arg):
    #ローカル変数argとグローバル変数global_valueの積
    return arg * global_value

def test_global2(arg):
    #ローカル変数argと同名のローカル変数global_valueの積
    global_value = 2  #このように記述するとローカル変数宣言か、グローバル変数への代入か分からないが..
    return arg * global_value

print("test_global(10)", test_global(10)) # 10 * 100
print("test_global2(10)", test_global2(10)) # 10 * 100 または 10 * 2\
print("test_global(10)", test_global(10)) # 10 * 100
"""

結果は、

test_global(10) 1000
test_global2(10) 20
test_global(10) 1000

 

となり、


(1)test_global2関数内でグローバル変数と同名のローカル変数を宣言し、2で初期化した(つもり-見た目はグローバル変数に2を代入しているようにも見える)。結果は(「グローバル変数に2を代入した場合」でも「ローカル変数を2で初期化した場合」でも変わらないが)20(10 * 2)となった。
(2)その後、再度test_globalを実行すると、値100のグローバル変数が演算に使われ、結果が1000となったので、関数内のローカル変数はグローバル変数に優先することが分かる。(また、グローバル変数と同名のローカル変数の宣言、初期化がグローバル変数として認識されない、ことが分かる。)

 

と考えられます。

 

で、待てよ?

ということで同様のプログラムをC++で作って走らせました。
#include    <stdio.h>
#include    <conio.h>    //getch()使用の為
#include    <iostream>

using namespace std;

int g_int = 100;    //グローバル変数

int calc(int n) {   //グローバル変数を使った関数
    return g_int * n;
}

int calc2(int n) {  //ローカル変数を使った関数
    int g_int = 2;  //グローバル変数と同名のローカル変数
    return g_int * n;
}

int main(int argc, char** argv) {

    cout << "calc(10)の戻り値は、" << calc(10) << "です。" << endl;
    cout << "calc2(10)の戻り値は、" << calc2(10) << "です。" << endl;
    cout << "calc(10)の戻り値は、" << calc(10) << "です。" << endl;

    getch();
}

<コンソール出力>
calc(10)の戻り値は、1000です。
calc2(10)の戻り値は、20です。
calc(10)の戻り値は、1000です。

ということでC++(Embarcadero C++:bcc32c.exe)でもローカル変数が優先されますが、C++では(型宣言が付くので「ローカル変数の宣言」と「グローバル変数の値代入」とは)構文が明確に違いますので、間違えようがありません。まぁ、グローバル変数と同名のローカル変数を使うのは避け、知らずに一緒になった場合、大きな(そして見つけにくい)バグとなる可能性があることは肝に銘じましょう。

次に関数の引数ですが、C++と同じようにデフォルトの引数を渡すことが出来る、という話です。以下がその構文例です。
先ずサンプルとなる関数として引数が3つあるfooを宣言します。

def foo(arg1, arg,2, arg3):  #関数fooに3つの引数を設ける

この場合、呼び出しには"foo(16, 0, '相対値')"の様に引数を渡す必要があります。一方、

def foo(arg1 = 32, arg,2 = 0, arg3 = "絶対値"):  #関数fooに3つの引数を設け、32、0、"絶対値"というデフォルト引数値を設定する

の様に書くと、foo関数の3つの引数にデフォルト値を設けることが出来ます。ここまではC++と同じですね。

デフォルト値のままでよければ"foo()"と書けばよいのもC++と同じですが、arg1とarg2はデフォルト値でarg3のみ「相対値」でfoo関数を呼びたい時、

【C++】
    foo(32, 0, "相対値");    //arg1とarg2も書かなければならない

【Python】
foo(arg3 = "相対値");  #arg3だけを指定して書くことが出来る

という賢い対応が出来ます。

<テストプログラム>
def foo(arg1 = 32, arg2 = 0, arg3 = "絶対値"):
    print("関数fooの引数に",arg1, "、", arg2, "、と", arg3, "が与えられました")

print("foo()の場合↓")
foo()
print("foo(arg3 = \"相対値\")の場合↓")
foo(arg3 = "相対値")

<出力>
foo()の場合↓
関数fooの引数に 32 、 0 、と 絶対値 が与えられました
foo(arg3 = "相対値")の場合↓
関数fooの引数に 32 、 0 、と 相対値 が与えられました

ということで、C++よりも使いやすくなっていますね。では、また次回。