K-lab 春学期セミナー¶
このページは早稲田大学人間科学部菊池英明研究室における 学部三年生向け合宿の資料ページになります.
開発環境¶
ここではセミナーや今後のゼミ生活において必要になる 種々アプリケーションをインストールしたり, 種々サービスのアカウントを作成します.
目次
警告
事前準備
アプリケーションのインストールに関しては セミナー前に 対応しておいてください.
- インターネット接続が必須になります.
- 早稲田校内ではなく,自宅で行ってください.
- 時間の掛かる処理も多いので, 電源を繋いだ状態で作業を行うことをオススメします
サービスのアカウント設定に関しては セミナー中に 行うので事前準備は不要です.
アプリケーションのインストール¶
今後の研究生活に必要なアプリケーションを インストールします.
ここでインストールする多くのアプリケーションは,
CUI
アプリケーションと呼ばれるもので,
多分,皆様の思い浮かべるアプリケーションとは
異なるものだと思います.
CUI
アプリケーションとは,いわゆるコマンドです.
よくプログラマの映画などに出てくる黒い画面で操作をするものです.
はじめに¶
まずは,種々作業を行うために ターミナル(黒い画面)を開いてください.
これは以下のように行います.
ターミナルを開くには,
Finder
を開き,
アプリケーション > ユーティリティフォルダ > ターミナル.app
をクリックします.

すると, 白い画面 が出てきます.
以後の作業はこの白い画面に文字を入力していってください.
注釈
白いじゃん
映画に出てくるターミナルは基本的に黒いです. しかし,ここで紹介したターミナルは(何もしてなければ) 白いですね.
これを黒くするには, 画面上部から
ターミナル > 環境設定
を選択し,
プロファイル > Homebrew
をします.
気になる方はやってみてください.
- 好みの問題なので強制はしません.
- このページではあくまでも "黒い画面" という表現を使用します.
Homebrew¶
まずは, この画面から Homebrew
というアプリケーションをインストールします.
これは, パッケージマネージャーと呼ばれるツールです.
一般に何か新しいアプリケーションを導入する際には, わざわざ WEB ページにいってインストーラーをダウンロードして, それを実行して... といった手順が必要になります.
一方このツールはただ指定された文字を入力すれば, そのツールを正しく導入することができるのでとても楽です.
まずは, 以下のコマンドを実行してください:
$ which brew
注釈
コマンドを実行してくださいと書いている場合
ターミナルにそのコマンドを写してください.
- 基本的にコピペでよいです
- ただし, 先頭の
$
は入力不要です.
何も出てこない場合,問題ありません. このまま INSTALL を続けてください.
逆に何か文字が出て来た場合, UPDATE の処理を行ってください.
INSTALL¶
ターミナルに以下のコマンドを入力してみてください:
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 長いので,コピペすることをお勧めします.
コマンドを入力すると何か色々文字が流れて, RETURN を押してくださいと言われます.
上記メッセージは英語です.
多分以下のようなメッセージになるかと思います:
"Press RETURN to continue or any other key to abort"
RETURN を押すと, 今度はパスワードを聞かれます.
パスワードを入力してください.
Password:
という表示が出てきます.- このパスワードは普段 Mac にログインする時のパスワードです
- パスワード入力中は 画面が変化しない ので注意してください.
- パスワードの入力を終えたら RETURN を押します.
しばらく,色々な処理をして Homebrew のインストールが修了します.
USEAGE¶
試しに Homebrew を使ってみましょう.
Homebrew は CUI アプリケーションですので, ターミナルにコマンドを入力することで使用できます.
例えば以下のコマンドを入力してみてください:
$ brew install asciiquarium
このコマンドを実行すると以下のコマンドが使えるようになります:
$ asciiquarium
ターミナルがアクアリウムになって面白いですね.
このように
$ brew install <入れたいアプリケーション>
とすると,
入れたいアプリケーションを使うことができるようになります.
以後の作業では,すべてこの brew
コマンドを使用します.
注釈
アクアリウムが止まらない...
止めるには ctrl + c を押してください.
これは shift の上にあるやつです
- コピペで使用するものではないので注意してください.
- CUI アプリケーションを停止させる際に よく使うので覚えておいてください.
git¶
git
もコマンドです.
先程の brew
は何かアプリケーションを導入するツールでしたが,
git
はファイルを PC に持って来るツールです.
詳しい使い方は今回のセミナーや,その後の研究室活動で教わると思いますので, ここでは, ツールの導入だけ済ませます:
$ brew install git
python¶
python は今回のセミナーで教えるプログラム言語です. 実は, Mac には最初から入っているのですが, 少し古いものしかありません.
これでは大変不便なので, 最新版を入れておきます:
$ brew install tcl-tk
$ brew install python3
- ここは二行になっています.
- 必ずこの順番で実行してください.
- 今後, 図とか, GUI を作成したりする時に必要なので入れておいてください.
chrome¶
google chrome
は ブラウザ
(何かインターネットをみるやつ) です.
もしかしたら,もう既にインストール済みかも知れません. その場合,この章は飛ばしてよいです.
mac には saffari
というブラウザがありますが,
これは色々不都合があるので,
chrome
を使用してください.
これも Homebrew
からインストール可能です:
$ brew cask install google-chrome
注釈
cask って何??
今迄の brew コマンドとは異なりここでは cask オプションを使用しています.
このオプションは, GUI アプリケーションを入れるためのオプションです.
エラー
google-chrome をデフォルトにする
このように google chrome
を使用せよと
何度も行っているのですが,
何故か, 頑に :file:saffari を使用し,
文句を言い出す学生がいます.
- しらんがな
それを防ぐため, chrome をデフォルトのブラウザにしてしまいましょう.
まず google-chrome を開きます.
- これはコマンドではないので,普通のやり方で.
ここで既存のブラウザとして設定するという チェックボックスが出てきましたら,チェックをしてください.
しつこく,確認されますが,chrome にするを選択します.
続いて, dock を変更しましょう.
- dock とは Mac の下の方にあるアプリケーションを置いておく場所です.
まず, saffari を開いている場合 (あるいは, saffari のアイコンが見えている場合) 二つ指でタップをします.
すると,メニューが表示されると思いますので 閉じると書かれたものを選択します.
- × ボタンでは本当にアプリを閉じたことにならないので注意です.
これで saffari が消えました. 続いて, chrome のアイコンを両指タップし, オプションという部分に, マウスを合わせます.
すると Dock に追加という項目があると思いますので, それをクリックします.
これで,何時でも chrome が開けるようになりました.
lastpass¶
今後の研究室生活では, 色々な WEB サービスを利用すると思います.
WEB サービスを利用する際には必ず, アカウントというものを作る必要があり, 多くはパスワードの登録が必須になります.
ここで,パスワードは本来ランダムでかつ 12 桁位のものが, 良いとされていますが, なかなか,作成することが困難だったり, 覚えるのが難しかったりします.
中には"何時も使うパスワード"を一つ決めてしまって, それのみで色々なサイトに登録をしている方も多いのでは無いでしょうか?
しかし,これは本来ありえてはならないことです.
何故なら,一つのパスワードがバレると, 全部バレてしまうからです.
- 昨年度だけで 3 人以上のパスワードが筆者の知るところになりました.
- 別に態々, クラックをしかけたわけでなくです
- その程度にパスワードは良く流出するものなのだと思ってください.
しかし,パスワードが複雑で,色々あると, よく忘れてしまうことも多いです.
このような際に便利なツールが lastpass です.
これを利用すると,色々なサイトのパスワードを自動で作成したり, 覚えておいてくれるので,正しいパスワード運用が行いやすくなります.
はじめての Python¶
このページは早稲田大学人間科学部菊池英明研究室における 学部三年生向け合宿 python チュートリアルの資料となります.
このページに関してはプログラミング初学者用に作成しているので, 種々イントロダクションや,環境設定に関する記述もあります.
一方で,あくまでも導入以上の情報を記述する気がないことに注意してください.
また,執筆時の最新情報を記述する予定ですが, 情報が古くなってしまう可能性も多くあることに注意してください.
Introduction¶
目次
Why Python?¶
菊池研究室は,メインで教えるプログラムは python
です.
この言語は (大体は) オブジェクト指向プログラミング言語で, かつ,高水準言語です. だたし実用面に特化し,便利なものは何でも使うところがあるので, アカデミックな意味での定義付けはしにくいです.
この言語の(というよりもこの言語のコミュニティの)特徴は, PEP と呼ばれる種々規約をが存在する点にあると思います. つまり,速さや上手さより,読み易さ,美しさを重視する側面があり, 筆者はそれをとても気に入っています.
python は現在(でも)色々なサービスに利用されている言語です.
実例を挙げるなら,以下のサービスは python で書かれていることで有名です(AGFA の内,A 以外というのが面白いです).
- mozila
- dropbox
python という言語に関して,とくに dropbox の開発者が 2013 年に, 面白いことを言っていたので少し紹介します.
これは python という言語を使える人間の感想をうまく表していると思います. おそらくは,このチュートリアルのみでは, この丁度よさを感じてもらうことは難しいと思うのですが, 皆様は卒業をする頃には,雑談をするような気軽さで(雑談が気軽かどうかは置いておき), Python を使えるようになるとうれしいなと思います.
What is programming?¶
ではプログラミングとは何でしょうか?
哲学的な問いにも見えますが, ここは行動学的に考えて見ましょう.
プログラミングとは結局, スクリプトを書いて,実行することです. 結局はそれだけ.
別の言い方をするのなら, 台本を書いて,演じて貰うんです.
もう少し,細かく書くとこの作業は以下の手順が必要です.
- 筆箱を開く
- 鉛筆を出す
- 鉛筆で書く
- 役者に渡す
これを今からやってもらいます. そのために必要な道具は,3 つです.
筆箱と鉛筆と,それから役者です.
皆様は,Mac をお使いだと思いますから, この 3つ は既に用意済みです.
警告
ただし,Mac に最初から用意されている道具は基本的に安物ですし, ちょっと古くさすぎる気がします.
本当はもっと便利な道具はいっぱいありますが, それは,その内,機会があれば教えます.
- 別途環境設定用ページを記述するのでそちらを利用してください.
では早速,台本を書いて,演じてもらいましょう.
筆箱を開く¶
まずは筆箱を開きましょう. これは, Finder > アプリケーション > ユーティリティ > ターミナル で開けます.
ターミナルと呼ばれる筆箱は, 言ってみれば,ドラえもんの四次元ポケットみたいなものです. ここから,貴方のPCで使える全てのものを取り出すことができます.
そう,なんでもできるのです (筆者は,ターミナルと chrome 以外のアプリケーションを開くことは滅多にありません).
鉛筆も取り出せますし,役者も取り出すことができます.
鉛筆を用意する¶
では,ターミナルから鉛筆を取り出してみましょう.
今回使用する鉛筆の名前は vim
といいます.
取り出し方は簡単です.
ただ,ターミナルに vim
と入力してください.
注釈
emacs 派の方はどうぞそれで. 戦争をする気はありません.
台本を書く¶
では台本を書いてみましょう. ここでは,プログラムは,こうやって使うのだということを 試してみるだけなので,以下の内容をそのまま写していいですよ.
print("hello world")
書けましたか? 多分書けないと思います(書けたかたは偉い).
初めて文字を書くときには,鉛筆の持ち方を練習するように
vim
も,使い方を練習しないといけません.
注釈
考えてみればモノを書くって面白いですよね. まずは道具を練習し,文字を覚えて,文にする.
その内,文は書けるようになるのだけれど, そこから相手にうまく伝わる文章を書こうと思うと, 長い練習が必要になる.
その意味ではやっぱり,プログラミングって, "国語" の世界なのですよ.
vim
という鉛筆はキャップ付きです
(ボールペンみたいなものと思ってくれてもいいです).
最初はキャップがついたままなので,書けません. キャップを外すには i を押します で書き終わったら ctrl + [ もしくは etc です. これでもう一度,キャップが付きます.
なんで,キャップがついているのかといいますと, そっちの方が色々できるからです. 例えば,キャップをつけた状態(これのことをノーマルモードといいます)で, / を押すと検索ができたり, x を押すと削除ができたりします.
これは本当に多機能で dd を押すと一行まるまるを消せたり, u を押すと作業を元に戻せたりします.
注釈
vim
に関しては,それだけで,
筆者的には一ヶ月以上は話続けることができる自信があるのですが,
説明はこの程度にしておきましょう.
代わりにオススメの書籍を二冊紹介しておきます. どちらも菊池先生におねだりすればきっと,購入してくれます.
なお,どちらがより役にたつかと言われれば, 間違いなく前者です.
さて,そろそろ書けたでしょうか? 書けたかたは,これを紙に印刷する必要があります.
そのためには,ノーマルモードにして,
:w ./hello.py と入力してください.
で,一旦 vim
を筆箱にしまいます.
これは :q と入力します.
- もう一度,スクリプトを開くには
vim ./hello.py
とすればよいですよ.
役者に渡す¶
では最後に台本を役者に渡します.
ここで,台本の名前は ./hello.py
です.
それを python という名前の役者に渡せばよいのです.
$ python ./hello.py
hello
警告
先頭の $ は書いてはいけません.
- これは,ターミナルで実行してくださいという意味です.
- $ の無い部分は出力です.
感動してくれたかどうかは別にして, プログラミングとはどういうものなのかは分かっていただけたと思います.
ただ,やりたいことを書いて,それを役者に渡すだけなのですよ.
役者と話す¶
先程,最後に と書きましたがあれはウソです.
しっかりとしたやり取りをする場合には, 上記のように,一度文章を書いてあげる必要がありますが, 実は,役者と直接話をすることもできます.
まずは,ターミナルから,python を呼び出します. 機嫌がいい時には(基本,いつも機嫌がよいはずなのですが), 以下のような表示になります.
$ python
Python 2.7.15 (default, Oct 15 2018, 15:26:09)
[GCC 8.2.1 20180801 (Red Hat 8.2.1-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
警告
version はきちんと確認をしてください.
ヘビが大きくなっていませんか? つまり, ANACONDA とか書いていませんか? なっている場合今は良いですが後で困るかもです
上記のようになれば,python が話を聞いてくれます.
>>> 3 + 1
4
>>> 6 - 1
5
>>> 3 * 1
3
>>> 21 / 3
7
結構色々なことができます.
>>> len("this is a pen".split()) # 単語数を数える簡単な例
4
>>> "".join([x for x in reversed("rats")]) # ねずみを星に
'star'
ちょっとこむずかしい例を書きます. どんな結果になるのか少し考えてみてください.
>>> # 星と言えばクリスマス
>>> def mk_tree(n):
>>> for i in range(n):
>>> print(' ' * (n - (i + 1)) + '+' * (2 * i + 1))
>>> for i in range(n - 2):
>>> print(' ' * (n - 2) + '|' + ' ' + '|' + ' ' * (n - 2))
>>> mk_tree(5)
>>> # クリスマスに隠された本当の意味とは
>>> import itertools
>>> words = "christmas"
>>> ["".join(x) for x in itertools.permutations(words, len(words))]
- ちょっと最後のはわかりずらかったかも
- これは christmas という単語の並び換えをすべて表示している例で trims cash (現金をすり減らすという文が含まれています)
- なお,パスワードの突破などによく使います.
まとめ¶
このページでは python に関しての一寸した自慢から始まります. これは皆様がこれから学ぶものが,なんだかスゴイものであると思って貰うことが 主目的です(だって折角やるならスゴイものの方がいいじゃないですか).
で,プログラミングとは何かという話をしました. ここの本質はプログラミングを行うための手順を理解して貰うことが目的です. コミニュケーションの基本としてまずは python に挨拶をし, それから,簡単な実例を数点示しました. そうそう,実は python と直接やり取りができることも示したのでした.
実は,この実習で試していきたいことの殆どは,上の例で示されています. そのため,今後の座学では,上の例でやっていたことを理解することが主な目的になるでしょう.
課題¶
これは実習中にやる必要はありません. もっと言えばそもそもやる必要はありません(義務教育じゃないのです).
ただし,実際にやってみて見せたい方は筆者まで連絡をください. あるいはやろうとして躓いていたら,連絡をくれたら対応します.
課題2¶
例題中,"ねずみを星に" の例があります. 似たようなものとして,回文という文をご存知でしょうか(前から読んでも後ろから読ん でも同じ文になるやつです).
その例題を探し,実際に python 上で検証してみてください. もし, "星と言えばクリスマス" の例を上手く使い関数化することができれば, そうしてください.その場合には True or False で結果を返すことができるとよいですね.
First Python¶
実は上の例で,一通り python に関して, 最低限知っておくべきことの実例を示してします.
以後しばらくは,何を伝えたかったのかの説明をします.
目次
データモデル¶
プログラミングは一種の言葉です. 言葉の構成要素を考えると, 大雑把には二つに分けることができます.
つまり,単語と文です.
python では単語のことをデータモデル, 文のことを制御構文といいます.
注釈
この説明に関しては異論を認めます. つまり,正確な説明ではありません.
あくまでもここでの説明のための操作上の定義です.
よい文章を書くためには, できるだけ多くの単語を使いこなせる方がいいわけで, ここではまず,単語の話からしていきます.
Python においてデータモデルは, 基本的に 2 つに分けることができます.
- 1 つで意味をもつもの
- いっぱいで意味をもつもの
ここでは python を使う上で必要になる特に重要な単語の使い方を説明していきます.
注釈
基本的以外のものは,その両方(変数)か,その両方ではないもの(None)です.
この内,関数についてだけ先に説明をさせてください.
イントロダクションの例で変数を使用している部分を紹介します.
1 2 3 4 | >>> # クリスマスに隠された本当の意味とは
>>> import itertools
>>> words = "christmas"
>>> ["".join(x) for x in itertools.permutations(words, len(words))]
|
この例の内変数は 3 行目です.
イコール記号を使っていますが数学的な意味でのイコールとは少し意味が異なります.
どちらかというと,ラベル付けという意味です.
よく変数を "箱" であると説明される方がいるのですが,
"箱" というよりラベルです(だって箱であったら x=1
のあとに x=2
としたら x = [1,2] になりそうじゃないですか).
つまりこの例では,
christmas
という文字列に word
というラベルを貼っているのです.
それで次に word
というラベルを見た時には python は
頑張ってラベルを探し,その内容を返します.
いくつか例を見てみましょう.
>>> word = "christmas"
>>> print(word)
christmas
>>> number = 1
>>> print(number)
1
なんとなくお分かりいただけましたでしょうか? これは単なるラベルなので,左辺の文字は何でもよいです.
>>> a = "christmas"
>>> print(a)
christmas
だたし,このラベルには注意が必要です. 以下の結果が如何して,そうなるのか, 説明できるでしょうか?
>>> word = "christmas"
>>> print(word)
christmas
>>> word = "new year"
>>> print(word)
new year
数値型: int / float / bool¶
1 つで意味をもつものに関してを数値型といいます. 大雑把に言えば,数ですね. 代表的なものに以下の 3 つがあります.
- int: 整数
- float: 実数
- bool: 真偽値(True, False)
数値型は数値型同士において四則演算を行うことができます.
>>> 1 + 1
2
>>> 0 - 1
-1
>>> 2 * 5
10
>>> 10 / 2
5.0
数値型同士と言いました. つまり, bool と int も演算を行うことが可能です.
>>> 1 + True
2
>>> 1 + False
1
さて,では True や False は結局なにものでしょうか? 上の例から考えてみてください.
注釈
先の例を見て,あれ?文字は?と感じた方は, とても良いです.
実は文字は,複数で初めて意味を持つので, 文字列型となり,次の章での説明になります.
それが証拠に 文字列と int は四則演算できません.
>>> "a" + 1
----------------------------------------------
TypeError: can only concatenate str (not "int") to str
大抵の場合,四則演算が可能であれば, 比較を行うことができます.
比較とは以下の操作のことを指します.
演算子 | 意味 |
---|---|
< | より小さい |
<= | 以下 |
> | より大きい |
>= | 以上 |
== | 等しい |
!= | 等しくない |
is | 同一のオブジェクトである |
is not | 同一のオブジェクトでない |
例えば以下のような結果になります.
>>> 1 > 2
False
>>> 1 > 1
False
>>> 1 < 2
True
>>> 1 <= 1
True
>>> 1 > False
True
>>> 1 == 1
True
>>> 1 != 1
False
>>> 1 == True
True
>>> 1 != True
False
>>> 0 == False
False
ただし, is
や is not
は,
今までの説明とは異なる挙動を示します.
>>> 0 is False
False
>>> 1 is not True
True
もし,貴方がプログラミング経験豊富な方であれば, この特性が極めて理に適っていることに気が付くと思います.
初学者の方は,まだ,あまり考えなくていいです(いずれ問題が起きたときに思い知るでしょうから).
この章を通じて説明したかった内容には数値型の扱いがあります.
数値型は四則演算可能で,かつ, 比較可能(ハッシュ可能といいます)であるという特性を持っています.
もう一つこの章で何となく感じて欲しいことがあります. それは Python において, データ型は,どんなことができるのか(つまり動詞)との関係で, 定義されているということです.
逆に言えば,どんなことができるのかを決めることで, 全く新しい型を作成した時に,数値にしたり,その他にしたりすることができます(こう いうプログラミングの方法をダックタイプといいます).
シーケンス型: list, tuple, str¶
続いてはシークエンス型です. これはいっぱいで意味をもつものの内, 順番が大事なものです.
これには list, tuple, str が相当します.
ここで, list と tuple ですが, list は一度作った後に変更可能ですが, tuple は変更不可能という特徴があります.
- これは一見 list の方が便利だと考えられますが, 逆に tuple は演算速度が速いです.
一方で list と str を比較すると, str は文字限定の機能を使うことができます
例えば,こういう風に使います.
>>> # list
>>> print([1, 2, 3])
[1, 2, 3]
>>> # tuple
>>> print((1, 2, 3))
(1, 2, 3)
>>> # str
>>> print("apple")
apple
基本的には, データを何かの記号で囲っていることに注意してください.
順番を持っているので, 特定の順番のデータだけを取り出すこともできます.
>>> print([1, 2, 3][0])
1
>>> print((1, 2, 3)[1])
2
>>> print("hello"[-1])
o
ここで,順番(インデックス)は 0 から始まります. もちろん,空の箱を作ることもできます.
>>> print([])
[]
ただし,存在しないインデックスの内容を取り出すことはできません.
>>> print([1][1])
-------------------------------------
IndexError: list index out of range
データの取り出し方は色々あります.
- 以下のような操作をスライスと言います.
>>> [1,2,3][-1]
3
>>> [1,2,3,4][1:3]
[2, 3]
>>> [1,2,3,4][1:4:2]
[2, 4]
>>> [1,2,3,4][::-1]
[4, 3, 2, 1]
また, 長さを持っているため,以下のような関数を実行することができます.
>>> len([1,2,3,4])
4
>>> a = [1,2,3,4]
>>> a[1:len(a)]
[2, 3, 4]
list 限定操作¶
list は和と積が定義されています(差と商は定義されていません).
>>> [1,2,3] + [1,2]
[1, 2, 3, 1, 2]
>>> [1,2,3] * 2
[1, 2, 3, 1, 2, 3]
ここで二項目の型は非常に重要です. つまり,以下の演算はできません.
>>> [1,2,3] + 1
TypeError: can only concatenate list (not "int") to list
>>> [1,2,3] * [2]
TypeError: can't multiply sequence by non-int of type 'list'
また,以下のような操作を行うことができます.
>>> numbers = [1,2,3]
>>> numbers.append(1)
>>> print(numbers)
[1, 2, 3, 1]
>>> a = numbers.pop(-1)
>>> print(a)
1
>>> print(numbers)
[1, 2, 3]
>>> numbers.extend([1, 2])
>>> print(numbers)
[1, 2, 3, 1, 2]
>>> numbers.reverse()
>>> print(numbers)
[2, 1, 3, 2, 1]
>>> numbers.sort()
[1, 1, 2, 2, 3]
>>> numbers.index(3)
4
>>> numbers.count(1)
2
ここで,何か今までと違うと思った方は, とてもいいセンスをしています.
list の種々操作の多くは一度変数を使用すると,
その後別の変数に代入することはあまりありません( pop
の例とか面白いですよね).
何故かというと, list は可変オブジェクトだからです.
タプル型¶
list とはうって変わって tuple オブジェクトは非可変です. これは少し理解が難しいかもしれません.
例えば以下のようなことは可能です.
>>> tuples = (1, 2, 3, 4, 5)
>>> new_tuples = tuples + (6, 7)
>>> print(new_tuples)
(1, 2, 3, 4, 5, 6, 7)
また,これも可能です.
>>> tuples = (1, 2, 3, 4, 5)
>>> new_tuples = tuples[2:4]
>>> print(new_tuples)
(3, 4)
つまり, 何か新しい変数にするなら, リスト型と同じような操作ができるのです.
一方で, append
関数や, pop
関数のように
一度決まった変数そのものを変更することはできません.
>>> tuples = (1, 2, 3, 4, 5)
>>> tuples[0] = 1
TypeError: 'tuple' object does not support item assignment
この型は初学者の内では,むしろ,種々制御構文内で使用されることが多いです.
str型¶
文字列型と list 型はとても良く似ています. つまり,和と積が定義されています.
>>> x = "apple"
>>> print(x + "pen")
applepen
>>> print(x)
apple
>>> x * 2
'appleapple'
ただし,文字列に特化した様々な操作が用意されています.
>>> " ".join(["This", "is", "a", "pen"])
'This is a pen'
>>> "This is a pen".split()
['This', 'is', 'a', 'pen']
>>> "This is a pen".upper()
'THIS IS A PEN'
>>> "This is a pen".lower()
'this is a pen'
>>> "This is a pen".find("pen")
10
>>> "This is a pen".replace("pen", "pepar")
'This is a pepar'
>>> "1 + 1 = {}".format(1 + 1)
'1 + 1 = 2'
集合型: set, frozenset¶
シークエンス型は順番を大事にしますが, 集合型は順番を無視します.
これは同じ値は一つだけという意味を持ちます.
- 例えば,色の種類とか,何かの種類を決める時に便利です.
これは以下のように使います.
>>> pokemon_set = {"red", "green"}
>>> print(pokemon_set)
{'green', 'red'}
基本的な操作は以下の通りです.
>>> pokemon_set = {"red", "green"}
>>> pokemon_set.add("blue")
>>> print(pokemon_set)
{'red', 'blue', 'green'}
>>> pokemon_set.add("blue")
>>> print(pokemon_set)
{'blue', 'green', 'red'}
>>> pokemon_set.remove("blue")
>>> print(pokemon_set)
{'red', 'green'}
>>> version = pokemon_set.pop()
>>> print(version)
'red'
>>> print(pokemon_set)
{'green'}
また, set 型はつまり集合なので,以下の操作が可能です.
>>> k_pokemon_set = {"Bulbasaur", "Charmander", "Squirtle", "Zubat"}
>>> j_pokemon_set = {"Chikorita", "Cyndaquil", "Totodile", "Zubat"}
>>> print(k_pokemon_set | j_pokemon_set)
{'Bulbasaur', 'Chikorita', 'Zubat', 'Totodile', 'Squirtle', 'Cyndaquil', 'Charmander'}
>>> print(k_pokemon_set - j_pokemon_set)
{'Squirtle', 'Bulbasaur', 'Charmander'}
>>> print(k_pokemon_set & j_pokemon_set)
{'Zubat'}
>>> print(k_pokemon_set ^ j_pokemon_set)
{'Bulbasaur', 'Chikorita', 'Totodile', 'Squirtle', 'Cyndaquil', 'Charmander'}
この辺を使いこなせると,確立や,ニューラルネットワークに強くなります.
また,以下のような作業を行うことも多いです.
>>> k_pokemons = ["Bulbasaur", "Charmander", "Squirtle", "Zubat"]
>>> j_pokemons = ["Chikorita", "Cyndaquil", "Totodile", "Zubat"]
>>> pokemons = list(set(k_pokemons + j_pokemons))
マッピング型: dict¶
辞書型は, set 型と変数の組み合わせです. つまり,一つの key を持ち,それに対応する value を持ちます.
これは以下のように使います.
>>> b = {'one': 1, 'two': 2, 'three': 3}
>>> print(b)
これは辞書なので,あるインデックスで検索を行うことができます.
>>> b = {'one': 1, 'two': 2, 'three': 3}
>>> b["one"]
1
リスト型にとても良く似た指定方法ですが, インデックスは数字ではなく,文字列です.
当然,存在しないインデックスを指定するとエラーになります.
>>> b = {'one': 1, 'two': 2, 'three': 3}
>>> b["four"]
KeyError: 'four'
辞書型はとても作り込まれた型で, 様々なことができます.
>>> dict = {'one': 1, 'two': 2, 'three': 3}
>>> print(len(dict))
3
>>> dict["four"] = 4
>>> print(dict)
{'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> print("four" in dict)
True
>>> print("five" not in dict)
True
>>> print(dict.get("one"))
1
>>> print(dict.get("five"))
None
>>> print(dict.pop("one"))
1
>>> print(dict.pop("five"))
None
>>> dict.update({"one": 1, "five": 5})
>>> print(dict)
{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}
>>> print(list(dict.keys()))
['one', 'two', 'three', 'four']
>>> print(list(dict.values()))
[1, 2, 3, 4, 5]
>>> print(list(dict.items()))
[('two', 2), ('three', 3), ('four', 4)]
制御構文¶
さて,ここまでに色々な単語について説明をしてきました. ここからはそれらの単語を使った文章について説明をしていきます.
if 文¶
if 文とは読んで字の如し, xx たっだら oo するという意味の文章です. 例えば以下のように使用します.
>>> val = 100
>>> if (val > 10):
>>> print("いっぱい")
いっぱい
負例の場合はどうなるのでしょう.
>>> val = 100
>>> if (val > 1000):
>>> print("いっぱい")
その場合には何も起きません.
ここで else
というキーワードを使うと,
そうじゃなかったらを表現できます
>>> val = 100
>>> if (val > 1000):
>>> print("いっぱい")
>>> else:
>>> print("ちょっと")
ちょっと
xxx か否か以外にも条件を足すことが可能です.
>>> val = 100
>>> if (val > 1000):
>>> print("いっぱい")
>>> elif (val > 99):
>>> print("ふつう")
>>> else:
>>> print("ちょっと")
ちょっと
この elif はいくつでもよいです.
- 逆に言えば if や else は一回に一つしかありえません.
- elif だけということもありえません.
因みに,今回紹介する制御構文の多くはこの if 文から作成されています. つまり,色々なところに else が出てくるのです.
while 文¶
while 文に関しては基本的に, 初学者が使用することはないので単純な例だけを記述します.
while i < 3:
print(i)
i += 1
こうすると, while 以下が三回繰り返されます. ここにも else が登場します(本質が条件式なので).
while i < 3:
print(i)
i += 1
else:
print('!!FINISH!!')
でも実務上 while を使用する, 最も有意義な例はこれです.
from time import sleep
while True:
print('無限ループって怖くない?')
sleep(3)
これを実行すると'無限ループって怖くない?'と三秒ごとに永遠に, 表示されます.
- ctrl + c で停止されます.
注釈
無限ループはいつ使うのか
初学者に無限ループを教えると, 何故かみな怖がります.
でも,実はとても一般的に使用します. 例えば,何かアプリケーションをイメージしてください. word でもいいですし,chrome でもいいです.
これらは一度起動したら,閉じるボタンを押すまで, ずっと起動しています.
こういう風にずっと起動させておきたい何かを作る際に, 無限ループは使用されます.
for 文¶
Python において繰り返し処理を行う, 最も一般的な例は for 文でしょう.
これは以下のように使用します.
>>> text = "this is a pen."
>>> for word in text.split():
>>> print(word)
基本的にリストや辞書の中身を一つずつ見て行くときに便利です.
プログラミングにおいては,しばしば,いま何回目のデータを見ているのかが知りたくなります.そういうときには以下の記法を覚えておくと便利です.
>>> text = "this is a pen."
>>> for i, word in enumerate(text.split()):
>>> print("{}: {}".format(i, word))
この制御構文は list や tuple, dict と共に使用されることが多いので, 以下のような書き方も可能です.
>>> numbers = [i * i for i in range(10)]
>>> print(numbers)
>>> numbers = {i: i * i for i in range(10)}
>>> print(numbers)
また,この書き方は if 文と併用可能です.
>>> numbers = [i * i for i in range(10) if i % 2 == 0]
>>> print(numbers)
try 文¶
try 文は何かエラーが起きても頑張るようにする制御構文です. たとえば以下のコードはエラーが起きます.
>>> numbers = []
>>> print(numbers[0])
IndexError: list index out of range
当たり前ですね. しかし,以下のようにすると, エラーが起きた時も何とかすることが可能です.
>>> numbers = []
>>> try:
>>> print(numbers[0])
>>> except Exception:
>>> print(0)
except Exception
は何かのエラーが起きた場合には,
それ以下のものを実行してという意味です.
xxx ならば ooo なので,else 文が使用可能です.
>>> numbers = [1]
>>> try:
>>> v = numbers[0]
>>> except Exception:
>>> v = 0
>>> else:
>>> print(v)
関数¶
今から説明する関数と,クラスは, 今回のチュートリアルの中で特に難しく,かつ,大切なものになります.
これらは,単語であるという意味において, データモデルであり,文章を使うという意味において,制御構文です.
どういうことかというと,自分で新しい単語を作る方法であるという意味です.
今から説明をする関数は,実は今まで使用してきたものです.
>>> x = "Hello World"
>>> len(x)
この内の len() の部分がそうですね. この語は長さを出すという意味を持ちます.
プログラミングの主な仕事の一つは, このような操作に関係する単語を自分で決めることにあります.
これは以下のようにします.
>>> def get_word_num(x):
>>> return len(x.split())
このように決めた言葉を使うには以下のようにします.
>>> get_word_num("this is a pen.")
4
ここで,関数とは何かを, 良く理解しておきましょう.
関数とは, (多くの場合自分で決めた)言葉です.
今回の場合, get_word_num
という言葉です.
この部分は自分で自由に決めることができます(というか決めなくてはいけません).
その意味では変数に近いです.
それと同時に,関数とは動詞です. つまり,目的語が入ります. 今回の場合目的語は x ですね. この目的語も実は何でもいいです. 単なるラベル(変数)です.
つまり以下のようにしたっていいわけです.
>>> def x(y):
>>> return len(y.split())
>>> res = x("this is a pen.")
>>> print(res)
4
勿論,自動詞のように目的語の存在しない関数だって作れます.
>>> def x():
>>> return 1
>>> res = x()
>>> print(res)
1
すこし,不思議ですね.
じゃあ,今までの例にすべて出て来た return
は必要なのでしょうか?
実は必須ではありません.
>>> def x():
>>> 1
>>> res = x()
>>> print(res)
None
あれ?と思って頂けるとうれしいです. 今まで算数で扱ってきた,関数とは随分違うものですね.
関数とは例えるなら,トンネルです. それもドラえもんのガリバートンネルみたいなやつです.
入り口があってもいいし, 出口があってもいい(そしてなくてもいい).
ただ,そこを通すと,通ったものが何か変わる(時もある). そんなトンネルなのです.
これは日常でも沢山あります. 例えばどのようなものがあるのでしょうか? すこし考えてみてください(それは大いにプログラミングの上達を手助けします).
注釈
引数とは何者か
関数を教えていると, しばしば引数に関して混乱する方が多いです.
初学者の内は,引数とは定義されていない(ことの多い)変数なのだ,と 理解するのがよいです.
どういうことでしょうか?
例えば以下の例で考えてみます:
>>> def add(a, b):
>>> return a + b
>>>
>>> add(1, 2)
3
>>> add(10, -1)
9
関数 add
は変数 a
と変数 b
を
足しているだけです.
で実際に使用している際には, (1, 2)
や (10, -1)
を使っています.
では例えば, 1 はどこに入るのでしょうか?
あるいは -1 はどこに入るのでしょうか?
これを考えると, 引数とは,後で値が決まる変数の一種だと分かると思います.
引数が変数であるということを示す例をもう一つおみせします:
>>> def add(a, b=1):
>>> return a + b
>>>
>>> add(1)
2
>>> add(1, 2)
3
>>> add(1, b=4)
5
何が起きたのか考えてみましょう.
注釈
可変長引数
引数に関してはもう一つ, 面白い書き方があります:
>>> def show(*args):
>>> for arg in args:
>>> print(arg)
>>>
>>> show(1)
1
>>> show(1, 2)
1
2
このように引数の前に * をつけると, いくつでも後で値を決めることができる引数になります.
こういうのもあります:
>>> def show(**kwargs):
>>> return ["{}: {}".format(k, v) for k, v in kwargs.items()]
>>> show(a=1)
['a: 1']
>>> show(a=1, b=2)
['a: 1', 'b: 2']
何が起きたか考えてみてください.
- これら二つの例にはタプルや辞書型が関わってきます.
クラス¶
クラスとは,雑に説明すれば,型です. そう,型,そのものを自分で作ることができるのです.
型とは何かと考えると,基本的には名詞,つまりデータだったはずです. そして,python の型は, 何ができるのかによって,定義されます. つまり,その名詞が目的語になる関数をもっています.
TODO アプリ¶
例えば,簡単な TODO アプリを作ってみましょう.
TODO アプリには何が必要ですか? アプリを作成するときには,それが何をすることができるのかを まず言葉で説明をする習慣をつけましょう.
TODO アプリは何ができますか?
以下に,筆者が考える TODO アプリの最小限の説明を書きます.
- TODO LIST は TODO を管理する
- TODO LIST は TODO を登録できる
- TODO LIST は TODO を確認できる
- TODO LIST は TODO を修了できる
- TODO LIST は TODO を消せる
まあ,こんなところですかね. ここで,これらの文章を見てみると, 全て名詞 TODO LIST および TODO が出てきていることに気がつきます (というかそうしたのです).
つまり, 今作りたい TODO アプリは 2 つのクラスのみで作成することが出来そうです. そう, TodoList クラスと Todo クラスです.
TODO クラス¶
まずは Todo クラスに関して, もう少し詳しく考えてみましょうか.
TODO ってなんですか?
TODO は一般に何をやるのかの情報をまとめたものです.
これは以下のように書きます.
- この辺から,対話環境で記述することが難しくなると思います.
- 一度スクリプトに書いてから実行してみてください.
class Todo(object):
text = ""
def __init__(self, text):
self.text = text
if __name__ == "__main__":
todo = Todo("TODO")
print(todo.text)
これで,新しく Todo クラスが使えるようになりました. 使い方は以下の通りです.
$ python todo.py
TODO
さて,ここまでで何をやったのかを説明しましょう.
このスクリプトでは Todo クラスを作成しています.
class Todo
から始まる部分です.
ここにやりたいことを書いていきます.
TODO は "何をやるのかの情報" を保存します.
text=""
と書かれている部分がそれですね.
この text
に"何をやるのかの情報"が入ります.
インスタンス化¶
ん,でも text
には "" しか入らないじゃんと思った皆様は,
今までの話に付いて来れています.
でも,スクリプトを実行した結果は "TODO" となっています.
なんででしょう?
この謎を解く鍵が, __init__()
関数です.
上のコードには以下のように書かれています.
def __init__(self, text):
self.text = text
この関数は引数を二つ持ちます.
self
と text
です
(python の関数は引数名に制約を持たないので別の名前でもよいのです).
関数の中身をみると,text
の値を self.text
に入れています.
では self
とは何かというと,クラス Todo そのものです(自分自身だから self ).
つまりこの関数を実行すると, __init__(self, text)
の text
が
クラス todo
に登録されます.
実際に text
を登録している場所はどこでしょうか?
これは以下の部分ですね.
if __name__ == "__main__":
todo = Todo("TODO")
print(todo.text)
ここまでで質問ありますか?
... 無いと困ります.
- ん?
__init__
なんて使っていないけど? と思った皆様はとても,勘が良いです. - ん?
self
は? と思った皆様もここまでの話によくついて来れています. if __name__ == "__main__":
って何と思った方,後で説明します.
python のクラスにはいくつか特別な名前の関数が存在します.
__init__()
は正にそれで,クラスをインスタンス化する際に使います.
インスタンス化とは,具体化のことです.
今回作成している Todo クラスは, ユーザによって毎回異なる内容が登録されるはずです.
でも,今までやってきた用に変数に直接値を入れてしまうと, その値を変更することができません.
そのような時に(人類が古い歴史の中で編み出した)秘策が抽象化です.
例えば,我々人間は,一人一人,身長も違えば,体重も違います. 髪の色も,皮膚の色も,もしかしたら手の数だって違うのです.
それでも,どんな人間でも身長が存在するし,体重が存在します. このように,個別具体的なことは一旦わすれて,あるモノが, どのような属性を持っているのかを考えることをここでは抽象化と言っています.
言い換えれば, 先の Todo
クラスは,
TODO を一つの属性 text
を持つものだと抽象化したものです.
で,この属性 text
に具体的な値を入れることを,
プログラミングの世界では インスタンス化といいます.
注釈
プログラミングとギリシャ哲学
ここで,哲学に詳しいかたは, きっと,アリストテレスや,プラトンを思い出したことでしょう. そう,イディア論です.
クラス指向のプログラミングは,正しく,イディア論の実戦です. ある名詞を,どのように抽象化するのかこそが, プログラマの腕の見せ所なのですから.
だからそこ,プログラミングを行うには, それが何であるのかを常に言葉で説明する習慣をつけた方が良いです.
さて,次の疑問.
__init__()
の第一引数は self
でした.
しかし, todo=Todo("TODO")
には self
がありません.
これはなんででしょう.
これもクラス関数の特殊な事情です. Python のクラス関数は 第一引数が self である というルールが存在します.
ちょっと,試してみましょう.
class Todo(object):
text = ""
def __init__(text):
self.text = text
if __name__ == "__main__":
todo = Todo("TODO")
print(todo.text)
違いは, __init__(text):
の様に self を無くしただけです.
実行してみましょう.
$ python todo.py
Traceback (most recent call last):
File "todo.py", line 68, in <module>
todo = Todo("TODO")
TypeError: __init__() takes 1 positional argument but 2 were given
結果はエラーです. ここでエラーコードをよく読むと(プログラミングが上手くなる人間は何時もエラーを怖 れません.まず英語を読みましょう).
TypeError: __init__() takes 1 positional argument but 2 were given
どういう意味ですか? この結果に納得行きますか?
納得できるまで考えてください.
- そして,直すのを忘れないでください.
直したら,次に行きます.
実行カ所¶
最後の疑問は if __name__ == "__main__":
って何という疑問でした.
これは決まり事なので,簡単に説明します(本当は理由があるけど).
if __name__ == "__main__":
はここから先には,
文章を書くよという意味です.
より正確には python xxx.py
の形でスクリプトを実行した時に,
python さんに実行して貰うカ所になります.
今説明をしているクラスや,いままでに説明をした関数は, 自分で単語を決めている部分です. でも,誰かに説明をするときには,普通単語だけでやり取りをすることはありません(”んだけでやり取りができるほど,python とは仲良くなれないのです). 何処かで,自分の決めた単語を使って文として,何をしてほしいのかを書く必要があります.
その,ここから先が文ですよ.という宣言が,
if __name__ == "__main__":
です.
ここは以下のようになっています.
if __name__ == "__main__":
todo = Todo("TODO")
print(todo.text)
文としては以下のことが書かれていますね.
- まず,Todo クラスをインスタンス化してください.
- その時には,
"TODO"
という値を属性にいれてください.
- その時には,
- 次に,
todo
インスタンスのtext
という属性を表示してください
こういう文を python に伝えているので,実行結果は "TODO" になりました.
TODO クラスを拡張する¶
さて, ここまでで,
class Todo
を使うと,
何をやりたいのかを管理することができるようになりました.
ただ, TODO アプリというと, 普通はその仕事が終わったのかどうなのかを管理できるはずです.
では, それをできるようにしてみましょう.
class Todo(object):
text = ""
is_finished = False
def __init__(self, text):
self.text = text
def set_is_finish(self, x):
self.is_finished = x
if __name__ == "__main__":
todo = Todo("TODO")
print(todo.text)
print(todo.is_finished)
todo.set_is_finish(True)
print(todo.is_finished)
実行してみると以下のようになります:
$ python todo.py
'TODO'
False
True
先の例と同じように, if __name__ == "__main__":
以下が,
ユーザの動作です.
今回は, 最初に, Todo("TODO")
とすることで,
ユーザは TODO の内容を入力しています.
その上で, その内容と, 終わったかどうかを表示しています. これが一つ目の出力結果と二つ目の出力結果ですね.
それから時間が経って, 最終的にその TODO が終了したとします.
その際の挙動が todo.set_is_finish(True)
ですね.
そうすると, いままで False
であった todo.is_finished
が
True
に変わりました.
これが三つ目の出力結果です.
実習¶
さて,ここからは実習をしましょう.
このスクリプトでは, 一つの TODO を管理できるだけでした. これでは TODO LIST アプリとは言えないので, 複数の TODO を管理できるようにしてみましょう.
また, 今までは, if __main__ == __name__:
以下にユーザの
作業を書いていました.
これでは, 実際のアプリっぽくないので
以下の挙動になるように種々クラスや実行文を書き換えてください.
何も引数を与えずに実行すると今までに登録された全ての todo を表示します:
# TODO の登録がない場合
$ python todo.py
# TODO の登録がある場合 $ python todo.py - [False] TODO 1 (0) - [False] TODO 2 (1) - [False] TODO 3 (2)
--add "内容" を加えると新規 todo を追加します:
# TODO の登録がない場合
$ python todo.py --add "やること" - [False] やること (0)
# TODO の登録がすでにあった場合 $ python todo.py --add "やること" - [False] TODO (0) - [False] やること (1)
--fininish id を加えるとその ID の TODO を修了します:
$ python todo.py --finish 0
- [True] TODO 1 (0)
- [False] TODO 2 (1)
- [False] TODO 3 (2)
- [False] やること (3)
--delete id を加えるとその ID の TODO を削除します:
$ python todo.py --delete 1
- [True] TODO 1 (0)
- [False] TODO 3 (1)
- [False] やること (2)
- ヒント:
- 筆者は 1 つのクラスと 3 つの関数でこれを行いました.
- ユーザの入力部分に関しては, 以下のノートに記載しています.
- プログラミングではデータを保存する際には基本的にファイルへの入出力が必要です.
- この方法に関しては with 構文の説明で触れています.
- ただし, ファイルに保存を行うには, Todo オブジェクトの内容を一度文字列化する必要があります
- 同様に, ファイルを読み込むと, その内容は文字列型になります. これを何とかして Todo オブジェクトに変更する必要があります.
注釈
引数入力
この課題では, 実際にユーザに情報を入力させます. この方法に関しては今まで 説明していなかったので, ここで説明させてください.
class Todo(object):
text = ""
is_finished = False
def __init__(self, text):
self.text = text
def set_is_finish(self, x):
self.is_finished = x
if __name__ == "__main__":
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--add", type=str)
args = parser.parse_args()
if args.add:
todo = Todo(args.add)
print(todo.text)
実行してみましょう.
$ python todo.py
何も出ませんね. これは以下のように使用します.
$ python todo.py --add test
test
$ python todo.py --add 頑張る
頑張る
何が変わったのかというと,
python を実行する際に, --add "やりたい内容"
を付け加えているときと,
そうじゃない時とで実行する内容を変えることができたのです.
これを決めている部分は if __name__ -- "__main__":
以下の行,
上から4 行目までです.
特に重要な部分は parser.add_argument("--add", type=str)
です.
この関数は, 第一引数に, 実行時にどのようなオプションを使うのかを書きます.
第二引数は, そのオプションに与えられる値の型がなんなのかを書きます.
今回の場合では, 実行時に "--add" というオプションが書かれた時には,
Todo に登録する内容が, その後に続くと決めたいので,
parser.add_argument
の第二引数は str 型になります.
この parser.add_argument
は args = parser.parse_args()
を
書く前であれば何回でも使えます.
ここで Todo(args.add)
に注目してくさい.
ここには, やることの内容がはいるのでした.
つまり args.add
にはユーザの入力が入っていることがわかります.
どうように, 例えば parser.add_argument("--text", type=str)
とした場合にはユーザの入力は args.text
でとりだすことができます.
Combat python¶
最後に実例で何かを作ってみましょう. ここでは, 折角なので,グループワークをしましょう. 各グループはB3 のみで最低三人とし, 上級生(B3 以上,院生,菊池先生含む)は, それぞれ,適当にグループに散って,参加してください.
- 勿論,種々サポートをして下さっても構いませんし, 単純に参加者の一部として,協同作業をしていただいても構いません.
ここで作り上げたいものは簡単なポーカーゲームです. とはいいつつ,厳密にポーカーを作ると大変なので, インディアンポーカーにしましょう.
ジョーカーは抜きです. 基本的には,数字が大きい方が勝ちですが, 13 は 1 に負けます.
ゲームとしては,相手の手札のみが見え, 自分は勝てると思ったら yes, 負けると思ったら no を入力します. yes だった場合は,上記ルールに基づいて勝ち負けを決めます.
かったら 2 点貰えます. 負けたら -2 点です. そもそも,ユーザが no を入力した場合, 自動的に -1 点としましょう(その都度得点を表示するようにしましょう).
これを 5 回繰り返し,最後に総合得点を出してプログラミング修了です.
最終的に各班出来たところまでを簡単に発表していただくことにします.
皆様に相談して頂きたいことは,以下の通りです.
- 必要なクラス,関数は何か (最低でも一つ以上のクラスと3つ以上の関数を用意してください)
- どのように役割分担を行うか
- プログラムをどのように書くか
筆者の注目点は,第一項目です. これはプログラミング設計と呼ばれる部分で, これの上手さにより,第二項,第三項の出来が大きく変わります.
それでは皆様頑張ってみてください.
警告
こういう機能がないか?とか, こういう風にしたいのだけどどうすればよいか? などの質問には喜んで解答します.
そもそもどうすればよいか?という類の質問に関しては, グループワークを行っているので,班の内部で完結していただけると 幸いです.
What next¶
お疲れさまでした. 以上で, python の tutorial を修了します.
冒頭にも書きました通り, プログラミングは, 文章を書くことにとても良く似ています.
初学者は,まず道具の練習や単語の練習で, 一杯一杯になります. これを突破して,初めて文章を作ることができるようになります. 文章構成を考えたり,推敲したり, 色々な練習を経て,初めて,相手(それは python そもものかも知れませんし,他の開発 者かもしれません.あるいは貴方のサービスを使うユーザかもです)に伝わる文章を練り 上げることができるようになります.
ここには特効薬はありません. ひたすら,書くしか無いのです. ""次になにする"" という項目は多くの技術書に出てくる内容ではありますが, 答えは何時も一つです.
何かを作ろう. とにかく,書こうです.
フォースと共にあれ というヤツです.
さて,2 つの方針をお渡ししましょう.
ライブラリを極める: ダークサイド¶
python の良い部分の一つには, 質のよいライブラリコミュニティが複数存在することが挙げられます.
もし,貴方が WEB サービスに興味を持つとするのなら, django というライブラリを調べてみましょう.
WEB サービスではなく,GUI を作成したい場合, pyside2 のコミュニティが今は一番, 触りやすいと思います.
そういう物作りではなく,機械学習に興味のある方は, scikit-learn のドキュメントには一通り目を通すべきます.
あるいは統計に興味がある場合には, pandas, scipy, numpy そして Statsmodels などを押さえておくのがよいと思います.
- https://www.statsmodels.org/stable/index.html
- http://pandas.pydata.org/
- https://www.scipy.org://www.scipy.org/
- http://www.numpy.org/
注釈
R という言語を知っている方は pandas から始めるのかよいでしょう. このライブラリは,筆者が R に必要性を感じなくなった切っ掛けです.
また matlab という言語を知っているかたは scipy を見て驚くことになるでしょう. matlab でできることは全て scipy で実装されています.
python を極める: ライトサイド¶
一方で今回は python その物に関してもほんの一般的な触りだけしか触れていません. 本当に pythonic な部分は,あまり説明をできていないのが現状です. これら python そのものに関してを探求したい場合には, 何だかんだで公式のドキュメントが一番詳しいです.
例えば今回扱った範囲では,以下のページが役に立ちます
- https://docs.python.org/ja/3/reference/datamodel.html
- https://docs.python.org/ja/3/reference/compound_stmts.html
- https://docs.python.org/ja/3/library/index.html
その他,結局の処,最もよい資料は公式であることを, 筆者は絶対に譲りませんが,初学者はそもそも何を書こうとしているのが 意味不明な部分があることは否めません.
そのため,オススメの書籍を紹介しておきます.
- みんなの python
- https://www.amazon.co.jp/%E3%81%BF%E3%82%93%E3%81%AA%E3%81%AEPython-%E7%AC%AC4%E7%89%88-%E6%9F%B4%E7%94%B0-%E6%B7%B3/dp/479738946X
- 一番基本的な python に関する説明が書かれています.
- 個人的には,勝手まで読むなら,何か手を動かした方が速いと思いますが.
- 実践 Python 3
- https://www.amazon.co.jp/%E5%AE%9F%E8%B7%B5-Python-3-Mark-Summerfield/dp/4873117399/ref=sr_1_1?ie=UTF8&qid=1552503176&sr=8-1&keywords=%E5%AE%9F%E8%B7%B5python3
- デザインパターンに関する本です.
- 文が書けるようになった後のステップとして極めて良書です.
- Fluent Python
- https://www.amazon.co.jp/Fluent-Python-%E2%80%95Pythonic%E3%81%AA%E6%80%9D%E8%80%83%E3%81%A8%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E6%89%8B%E6%B3%95-Luciano-Ramalho/dp/4873118174/ref=sr_1_1?ie=UTF8&qid=1552503310&sr=8-1&keywords=fluent+python
- 筆者らが言っている通り,中級者以上の人間が読む本です.
- ただし,必要なことがとてもよく書いてあります.
- イメージ的には,文章の微妙な言い回しに関する指導書でしょうか?
- 公式ドキュメントに対する良質な解説書とも言えます.
計算機科学基礎¶
What is this¶
ここでは, python
を使った古典的な計算機科学問題に取り組んでいこうと思います.
古典的な計算機問題には例えばナップサック問題というものがあります. 皆様はこれから無人島に島流しに合います. ここで大きさの決まったナップサックを一つだけ持っていくことができます. 無人島生活には色々なものが必要ですが, ナップサックに入る量は決まっています. この際に, ある目標の価値を上回る品物の選び方はあるのか, ないのか, どの組み合わせが最もよいのか, こういうことを決める問題です.
一見すると, あまり面白い問題ではないようにも見えますが (無人島にナップザック一個 で行くとかそうそうない, どうぶつの森でもあるまいし...), この問題は色々な所で使え ます. 例えば, 年度の頭には予算決めがあるのですが, 限られた予算で欲しいものを最大 幸福的に受け取る計画を立てるなんて, よくある話です.
あるいは限られた時間の中で, どの講義には力を入れ, どの講義は手を抜く(切る)のかな んて皆様も一度は考えたことがあるのではないでしょうか?
世の中には典型的というか、よく出くわす問題 (先程の予算のように)というものがある のです. こうしたよくある問題をパズルの問題のようにしたものが古典的な計算機科学の 問題です. そして, 世のパズルがそうであるように, これらの問題には定石というか, 一般的な解法というものも存在します. こういう一般的な解法のことを, かっこよく言えばアルゴリズムというのです.
計算機科学とは何かいうと要はパズルです. パズル. 最初に頭を使って考えて, 答えに納得したらあとは身に付ければそれでよいと思います. どうぞ, 気楽に楽しんでみてください.
For whom?¶
この文章の中では然程 python
そのものに関しての説明を記述しません.
それは First Python で既に行っています.
それ以上の知識は不要です.
逆に, 上記チュートリアルをまだ行っていない場合には, 必ず一読しておいてください.
準備運動¶
フィボナッチ数列¶
まずは準備運動として フィボナッチ数列 を解いてみましょう. もしかしたら少しプログラミングに詳しい人にはお馴染の問題かも知れません.
ここで考え、慣れて欲しいことは以下の二つです.
- 数式が与えられた時にそれをプログラムに直す練習(あと関数の復習)
- 再帰処理の発想と練習
フィボナッチ数列とは, 先頭の2項 (二つの数字) を除いて, 一般項が前の 2 項の和で示されます.
... 何を言っているのでしょう.
まずは例で考えます.
以下の数列はフィボナッチ数列です:
0, 1, 1, 2, 3, 5, 8 ...
まず, 第一項のフィボナッチ数は 0 です. で, 次の項は 1 ですね.
第三項はというと, 第一項と第二項の和なので, 1 です. 以下, 第四項は第二項と第三項の和なので 2 です. では, ... で省略した次の項はなんでしょう(考えてみてください. 勿論 SLACK で皆様答え合わせしてくれてもいいですよ)?
さて, この数列の任意の n 項目のフィボナッチ数は次式で得られます.
数式の意味としては, フィボナッチ数列の第一項が 0 で, 第二項が 1 の時に, 2 より大きい n は n の一個前の場合の値と, 二個前の場合の値の和で決まると書いてあります. 言い換えれば, 先頭の2項 (二つの数字) を除いて, 一般項が前の 2 項の和で 第 n 項示のフィボナッチ数列が表現されるということですから, 実は定義をそのまま、数式にしたにすぎません.
再帰処理¶
なぜ, 言葉の通りの内容を態々小難しい数式なんぞに変えたかというと, 一度数式にすると, そのままコードに変換することができるからです.
ちょっとコードに書いてみましょう:
def fib(n):
return fib(n - 1) + fib(n - 2)
ほらね, そのままでしょ?
え, これで動くの? のビックリされた貴方. 試してみてください:
def fib(n):
return fib(n - 1) + fib(n - 2)
print(fib(3))
安心してください. 当然動きません. エラーがでますね.
そのエラーをよく読んでください. 以下のようになっているはずです:
RecursionError: maximum recursion depth exceeded
直訳すると, "'最大再帰深度を超えました" です. 今回のテーマである "再帰" という言葉がでましたね.
上記コードの問題は, 常に fib
関数が呼ばれ続けてしまうため,
いつまで立っても計算が終わらないことです
(こういうものを無限再帰と呼びます. 大まかには無限ループのようなものだと思ってくれて構いません).
要は計算ができないのではなく、計算が終わらないことが問題なのです. そのため、終了条件(基底部, 計算を終えるための条件)を用意してやれば, 上記関数は上手く動きます.
では fib
の基底部はなんでしょうか?
これも実は数式では定義されています.
そう, 最初の二項を除いて... の部分です.
素直にコードを書くと以下のようになります:
def fib(n):
if n < 2: # 最初の二項では
return n # そのまま n を返す
return fib(n - 1) + fib(n - 2) # それ以外では再帰的に自身を呼び出す
print([fib(n) for n in range(7)])
このコードを実行すると冒頭で示したフィボナッチ数列がそのまま得られます(内包表記, 覚えていますか?).
その次の値の答え合わせもできますね.
さて, この再帰式, とっても面白い形をしていませんか?
書いてあることは if
文なのにやっていることは for
文です.
First Python の中でチラッと, "制御構文の多くはこの if
文から作成されています" と書いていますが,
実は繰り返し系の制御構文は if
文で作成することができるのです.
計算時間¶
もう少し, この再帰について教えると, フィボナッチ数列の様な数式のことを数学の言葉では 漸化式 といいます. これは, 逆に言えば漸化式と言われれば、再帰を書けばとりあえず関数が作れるということを意味します. 例えば、デジタル信号処理なんかでは、この漸化式は非常に良く出てきますし、 最適化、機械学習などの実装にもこれはよく使います.
さて、先に作成した fib
関数に話を戻します.
この fib
関数には実は問題があるのです.
たとえば n = 35 のフィボナッチ数を計算させてみてください. 大分時間がかかるはずです.
では n = 50 だったら?
多分計算が終わらないでしょう.
これは何故かわかりますか?
ここには再帰の呼出し回数が関わってきます.
たとえば, fib(4)
の場合の呼出を考えてみると以下の通りです:
fib(4) -> fib(3) + fib(2)
-> fib(3) -> fib(2) + fib(1)
-> fib(2) -> fib(1) + fib(0)
-> fib(1) -> 1
-> fib(0) -> 0
-> fib(2) -> fib(1) + fib(0)
-> fib(1) -> 1
-> fib(0) -> 0
このように n=4 のときには fib
関数は 9 回呼び出されます.
では n=5 では何回でしょう. n=10 では?
この二つ位は頑張って数えてみましょうか (まだ 1000 は行かないので).
n = 20 くらいになると 20000 回を超える呼出し回数になります. これだけぐるぐると繰り返し処理をしていると, 計算が中々終わりません(n=50 くらいになると多分まず, 終わらないんじゃないかな?)
では, n = 50 際のフィボナッチ数は計算できないのか? というと, 実はそんなことはありません.
この節の最後には、 n = 50 の場合のフィボナッチ数を計算するための方法を二種類紹介します.
メモ化¶
一つの方法はメモ化です. もう一度フィボナッチ数列を眺めてみましょう:
0, 1, 1, 2, 3, 5, 8 ...
我々人間はこの数列をみれば次の値は直ぐにわかりますね. なんで、直ぐに分かるかというとその前の結果を記録して覚えておくことができるからです.
同じようにプログラムでも前の結果を記録させて置けば処理は大分早くなります。 このように前の処理結果を保存しておいて、必要になった時に保存された結果を使う技法をメモ化といいます.
早速メモ化を試してみましょう:
memo = {0:0, 1:1} # 基底部
def fib(n):
if n not in memo:
memo[n] = fib(n - 1) + fib(n - 2)
return memo[n]
print([fib(n) for n in range(7)])
print(fib(50))
このようにするとさっきまで何時迄立っても結果が出なかった fib(50)
が一瞬で出てきます.
上のコードでは関数の外に変数 memo
を用意します.
今回は辞書型(覚えています?)で値を決めています.
注釈
何故 dict 型なのか?
変数 memo
は別に dict 型である必要はありません.
list でも問題なく作ることが可能です(チャレンジしてみてください).
なぜ dict 型を選んだかというと key = n, value = 解答 の形でメモを整理したかったからです.
ただし 変数 memo
を宣言する場所は必ず fib
関数の外で無くてはいけません。
何故だかわかりますか?
一番最初に基底部を決めているので,
if
文の中では memo
に解答がない場合だけ, 結果を保存するようにすればよいです.
この処理をすると、どのような場合でも memo
の中には解答が記録されているので、
あとはそのまま、 memo
の情報を返せば関数は上手く動きます.
注釈
メモ化をもっと楽にする
上記メモ化のコードは内容がとても分かり易いですが少し面倒です. python という言語は簡単なことを簡単にやるのが好きな言語なので、 メモ化そのものはもっと手軽に実行できます:
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(50))
上記コードでは関数の内容そのものはメモ化をする前のものと同一です
( fib
関数が何をやるのかはこちらの方が分かり易いでしょう)
ただ, 上で少し不思議なことをしています.
まず一行目ではライブラリの読み込みを行っています.
ここで使用している functools
というライブラリは python が元々持っている便利ツールの一つです.
このツールの内, lru_cache
という関数を使いますよというのが、
一行目の意味です.
@ を使う記法は今回はじめて出てきましたね. これはデコレーターといいます(ほら、ケータイをデコるとかいうじゃないですか、あのデコです).
少し言葉の説明になりますが, 例えばケータイをデコるといったとき(通じる?)、 シールをはるのはデコるですよね. でも, 例えばなかのSIMカードを変えることをデコるとは余り言わないかと思います.
何がいいたいかというと、デコレータ, デコレーションという言葉は 何かに被せるとか, 上にのせるとかそういう行為をさしていて, 本体を変えるものじゃないということです(別のプログラミング用語としてラッパーという言い方をすることもあります).
同じように, @lru_cache
関数も, fib
関数を覆います.
つまり, fib
関数実行時に入力を監視して, もし計算したことがあれば、
その結果を返し、計算したことが無い場合だけ, 実際に fib
関数を実施します.
また, fib
関数が実行された際には自動でその結果を保存します.
つまり, fib
関数の開始と終了で何か決まった処理を行うわけです.
こういう関数のことをデコレータ関数(ラッパー関数)といいます.
デコレータ関数は自分で書けると凄く便利なので(実務ではよく使うのですよ. 例えば ログを取ったり, 計算させた後で可視化をさせたり, 結果はどうあれ何かをしたい時 というのは往々にしてあるものです), 興味のある方は調べてみるとよいですよ.
あと, @lru_cache
に関してですが, キャッシュという言葉を聞いたことがある方は居ませんか?
なんか, キャッシュが残って変な結果になっているとか.
メモ化とは、あのキャッシュを関数レベルで使いましょうという話です.
特にプログラミングに不慣れな方ほど、キャッシュという言葉、ものを毛嫌いする傾向があるのですが,
キャッシュを上手く使えると、今迄試したように、計算が終わらないものを一瞬で解決できるようになったりするのです.
反復処理化¶
さあ、準備運動第一節で、多分皆様大分お疲れかと思いますが, まだ続きます.
この節の最後に, フィボナッチを反復型(普通の for
文)で解いてみましょう:
def fib(n):
if n == 0:
return 0
last = 0 # 前の値を保存(初期値は fib(0) なので 0 )
next = 1 # 次の値を保存(初期値は fib(1) なので 1 )
for _ in range(1, n):
last, next = next , last + next
return next
fib(50)
さあ, これでも答えは出てきます.
そしてメモ化はしていないのに fib(50)
を計算することができます.
まず, このコードでもフィボナッチの計算が何故できるのか説明できますか? そして, なぜメモ化をしていないのに再帰で書く場合とは違い答えが出てくるのかわかりますか?
課題として, まずは n = 0 から n = 5 位までで各行の結果がどうなるのかを考えてみてください. そして、結果として公式通りの処理になっていることを確認してください.
その際に, for
文が何回実行されるのかを考えてみるとよいでしょう.
上記二つに解答できたら、この節は終了です(繰り返しですが SLACK 等で答え合わせとかしてもいいですよ)
ハノイの塔¶
さて, 続いては ハノイの塔 というパズルに挑戦していきましょう. ハノイの塔とは以下の写真のようなパズルです.

このパズルでは 3 つの塔(写真では棒) と n 個の円盤が出てきます. 初期状態では, 向かって左側の塔に全ての円盤が入っていますね. これを向かって右側の塔になるべく少ない回数で移動させると勝ちです.
ただし、円盤の移動には以下のルールが存在します.
- 一回に一枚の円盤だけが移動できる
- 移動できる円盤はそれぞれの塔の一番上だけ
- 大きな円盤を小さな円盤の上にのせてはいけない
例えば, 写真の例ですと, 最初に移動できる円盤は向かって左にある一番上の円盤1つだけです. これは, 真ん中か右の塔に移動できます.
2 回目に移動できる円盤は, 最初に移動した円盤か, 左の(この段階で)一番上の円盤のみです. ここで、左の円盤は, 最初に移動した円盤の上に置くことはできません(なぜなら二番目の方が大きいからです).
どうです? パズルの内容はご理解いただけたでしょうか?
モデリング¶
まずは, このパズルそのものをプログラムにしてみましょう. このパズルの登場人物は大きく二つ, つまり塔と円盤です.
塔はいくつかの円盤を持つことができます. 加えて塔は最後の円盤を取り出すことができて、 塔は最後に円盤を入れることができます.
このように問題に合わせて登場人物を決定し、 その関係を考え、その動きを決める作業のことをモデリングといいます.
さて, 上記モデリングが済んだわけですが、 この条件から塔をプログラム的に表現できますか?
実は First Python の中に正に上記条件に一致するクラスを紹介しています.
それは list
です.
- このなぜ塔を表現するのに list が最適か言葉で説明できますか?
- できない場合, First Python の list の説明をもう一度読み直してください.
例えば, 上記写真のハノイの塔は以下のように表現できます:
disk_n = 8
tower_a = [i + 1 for i in range(disk_n)] # [1, 2, 3, 4, 5, 6, 7, 8]
tower_b = []
tower_c = []
向かって左一番上の円盤 (上記例の場合 8
という数字です) を tower_b
に移動するには以下のようにすればよいです:
tower_b.push(tower_a.pop()) # tower_b = [8], tower_a = [1, 2, 3, 4, 5, 6, 7]
このように, pop
と append
のみで操作する配列構造を後入先出法(LIFO) などといいます.
ここまでで, とりあえずパズルそのものをコードにすることができました(不足はあるのですが).
- もしモデル化に不十分な点があることにお気付きの皆様は, きちんとクラス化をしてみるといいですよ.
- このままでは, できてはいけないことができてしまいます.
ソルバー¶
さて, ハノイの塔の問題そのものはモデル化できたとして, これをどのように解けばよいのでしょうか?
こういうときにはまず決まりきったこと(基底部)から考えます. 例えば, 円盤が 1 枚だけの場合はどうでしょうか? 簡単ですね. 左にある一枚の円盤を向かって右側の塔に移動させればよいのです:
disk_n = 1
tower_a = [i + 1 for i in range(disk_n)] # [1]
tower_b = []
tower_c = []
tower_c.append(tower_a.pop()) # tower_a = [], tower_b = [], tower_c = [1]
基底部が決まったら今度は再帰部を考えます. つまり, 円盤が2枚以上の場合を考えましょう. ここでは具体的に円盤が2枚の場合と、3枚の場合を考えてみましょう.
円盤が二枚の場合以下の手順になるはずです.
tower_a
の円盤をtower_b
に置きます.tower_a
の円盤をtower_c
に置きます.tower_b
の円盤をtower_c
に置きます.
では, 三枚の場合は? この場合には以下の手順になるはずです.
tower_a
の円盤をtower_c
に置きます.tower_a
の円盤をtower_b
に置きます.tower_c
の円盤をtower_b
に置きます.tower_a
の円盤をtower_c
に置きます.tower_b
の円盤をtower_a
に置きます.tower_b
の円盤をtower_c
に置きます.tower_a
の円盤をtower_c
に置きます.
ここまで, 大丈夫でしょうか? 一つずつ紙にかいてみるとよいです.
この手順を整理すると再帰部は以下の3つのステップに分解できます.
- n - 1 枚の円盤を
tower_a
からtower_b
にtower_c
を経由して移動 (1-3) - n 枚目の円盤(一番下の円盤) を
tower_a
からtower_c
に移動 (4) - n -1 枚の円盤を
tower_b
からtower_c
にtower_a
を経由して移動 (5-7)
ではこの再帰部と基底部を使ってハノイの塔を解く、ソルバー(何か問題を解くための関数をソルバーと呼びます) を作ってみましょう:
def hanoi(begin, end, tmp, n):
if n == 1:
end.append(begin.pop()) # 基底部
else:
hanoi(begin, tmp, end, n - 1) # 再帰部(a から b に c を経由して移動)
hanoi(begin, end, tmp, 1) # 再帰部 (n番目の円盤を a から c へ移動)
hanoi(tmp, end, begin, n - 1) # 再帰部(b から c に a を経由して移動)
disk_n = 8
tower_a = [i + 1 for i in range(disk_n)] # [1, 2, 3, 4, 5, 6, 7, 8]
tower_b = []
tower_c = []
hanoi(tower_a, tower_c, tower_b, disk_n)
これでハノイの塔が解けていることを確認してください.
- どうなっていたら解けているのでしたっけ?
また, disk_n
の数を変更しても問題なく解けるでしょうか?
まとめと応用のヒント¶
本章では, First Python の復習として, 二種類の問題に挑戦しました.
フィボナッチ数列とハノイの塔です. フィボナッチ数列の問題では, 再帰というテクニックを紹介しました。
このテクニックを利用すると, 小難しい数式をそのままコードに変換することができるということをみました.
一方で再帰を利用するとしばしば計算量が多くなってしまい、 いつまでたっても, 計算が終わらないという問題も確認しました. こういう場合にメモ化というテクニックを利用すると, とても簡単に計算速度を向上させることができることを確認しました.
第二の問題であるハノイの塔では, まず問題をモデル化するということを行いました. その上で実際の解法を確認し、それを再帰を使って解いてみることを試しました.
この章で練習をした再帰(そう基底を先に考え, その後, それ以外の処理を考え, そのまま実行するのです)は, 例えば, 論文に出て来る新しい方法を自分で試してみる際には必須のテクニックになります.
また、数式を怖がってはいけません。 基本的に python を使っている限り, 数式さえわかれば、あとはそのままコードに書けばよいのです.
こういう体力をつけるためには、例えば高校生の頃の教科書がいい練習材料になります. 各種公式をコード化してみてください. そしてそのコードで教科書の問題を全問正解できるか挑戦してみてください. それだけでコードを書くための基礎体力は付いてきます.
練習問題¶
円周率¶
本章一節ではフィボナッチ数列を例に数式をプログラムに変換する方法を説明しました. これに対応する練習問題として円周率の計算をしてみましょう.
円周率 \(\pi\) を計算するには多くの公式がありますが, ここでは ライプニッツの公式 を使います.
上記公式に従うと, \(\pi\) が次の無限級数の収束値になります(式はずっと続くけど解ければその解が \(\pi\) です)
この式では分子は常に 4 です. また, 分母は 1 から始まり, 2つずつ増えます. 更に各項では, 加算と減算が繰り返されます.
この式をプログラムにし, n = 1000000 の場合の値を計算してください.
注釈
ヒント
無限級数は基本的には再帰ではなく for 文で考えた方が素直かと思います.
どうしても分からない方は以下のコードを穴埋めするのが良いでしょう:
def cal_pi(n):
pi = 0
m = □
d = □
o = □
for i in range(n):
pi = pi + □
d = d + □
o = o * □
return pi
cal_pi(10000000) # 3.1415925535897915
- □ の部分が穴埋め箇所です.
- □ は一文字とは限りません.
サイン波 (発展問題)¶
音を扱う人間が知っていなければいけないものに サイン波という波があります.
これは以下の数式で決定されています
この数式を関数にしなさい. また, 以下のページ を参考に任意のサイン波を鳴らしなさい.
- この課題に取り組むためには
scipy
を導入する必要があります. - 従ってこの課題は自身でライブラリの導入が行える(
pip
コマンドが使える) 方のみの課題とします.
- 従ってこの課題は自身でライブラリの導入が行える(
- この課題に取り組むためには
- 課題に取り組む場合, 何か適当な歌(キラキラ星などでもいいですし, 某エポナの歌とかでもよいです) を再生できるプログラムを書くことを目標にするのがよいでしょう.
K 平均クラスタリング¶
省略しようかな...
WEB API 入門: 応用課題¶
- 文責: qh73xe
- 作成日: 2020-04-04
ここからは応用, つまり, python を使ったアプリ開発を説明します. アプリ開発の最初のお題は WEB-API です.
近年のアプリケーションは所謂、サバクラ(サーバー&クライアント) を前提としたものが大多数を占めています. 例えば, facebook とか line とか Instagram, slack のようなチャット, SNS サービスもそうですし, google のサービスも大抵がサバクラシステムです.
こういうシステムは大抵ブラウザ(例えば chrome とか)で動きます.
ところで、皆様はサーバーって何か、ご存知でしょうか? 普通のPC(皆様の目の前にあるMAC) とは何が違うのでしょうか?
実は、(誤解を恐れず言うのなら)そんなに違いはありません. サーバー機といっても実は単なるPCです(今、丁度研究室で動いでいるので, 実機が見たければ, 見ることができますよ). 少しだけ、違う部分を挙げるなら、サーバー機は色々な人が情報にアクセスすることができます(皆様のPCが色々な人に見られたら結構いやですよね)。 一方、皆様のPCは色々なサーバーの情報をみることができます.
つまり, 皆が見て色々作業をするPCをサーバーと読んで、 そのサーバーから情報を貰うPC(皆様のPC)のことをクライアントと呼ぶのです. で皆で見るために使用されているルールが例えば http プロトコルであったり, WEB とか言われているやつです.
最初のアプリ¶
まあ, 前置きはこの程度にしておいて, 早速アプリを作ってみましょう.
今回は, tornado というツールを使います(これは facebook が作成したツールです).
注釈
Django
python で WEB-API を作ろうとした場合, 一番の大御所は, Django というライブラリです. これは開発の初期段階では google が大きく関わっていて, 現在でも Instagram に利用されていたりするライブラリです.
このライブラリはとても素晴らしいものですが, 覚えることが多いです.
一方 tornado は覚えることは少く使用できるので, 簡単なものをチョロッと作る場合には, tornado を使います.
django に興味がある方は, 公式のチュートリアルを試してみてください. あれは読む度に発見のあるとてもよいドキュメントです.
まずは, tornado を使用できるようにしましょう. tornado を導入するには以下のコマンドを使います:
$ pip3 install tornado
今回のテーマはサバクラシステムで、クライアントはもう既に存在する(皆様のPCのブラウザがクライアントです)ので, 作っていくものはサーバーになります.
そのまま, server.py
としましょう.
内容は以下の通りにしてください:
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
かけましたでしょうか? かけたら、これを実行します:
$ python3 server.py
さあ、どうでしょう. 何も起きませんね. 何故ですか?
クライアントを使っていないからです.
ブラウザを立ち上げ, 以下の URL にアクセスしてみてください.
わー, すごい, なんと文字が出てきましたね.
なんとも面白くもない例ですが, 実は凄いことをしています.
なぜ, "Hello, world" と出て来たのかわかりますか? 別の文字に変えることはできますか?
やってみてください.
- なお,
server.py
を止めるには Ctrl + C を押します.
この例はとてもシンプルな例ですが, サーバーとしての機能を充分に果たしています(クライアントに伝えるべき情報を伝えているので).
server.py
には一つのクラスと, 一つの関数,
そして if __name__ == "__main__":
で書かれた実行部分が書かれています.
このなかで, サーバー, つまりクライアントに情報を渡している部分はどれでしょう.
そう, MainHandler
クラスですね.
より正確に言えば MainHandler
クラスの get
関数です.
さらにデータを送っている部分だけでいれば, self.write
関数になります.
ここでなんで self.write
関数が動くのか説明できる方はいますか?
だって, そんな関数どこにも書いていないじゃないですか.
- この問の意味が分からない方は First Python のクラスの説明をよく読み直してください.
その答えのヒントは MainHandler
クラスの最初の行にあります.
ここには以下のように書かれています:
class MainHandler(tornado.web.RequestHandler):
First Python でやったクラスとは ()
の中身が異なりますね.
これは, tornado.web.RequestHandler
に書かれている関数をすべて使えるようにするという意味になります.
こういう風に、別の誰かが書いたクラスの持つ関数を使える新しいクラスを作ることを継承といいます.
つまり, なんで自分では書いていない self.write
という関数が使えるのかというと,
tornado.web.RequestHandler
というクラスの中に誰かが先に書いておいてくれたからな訳です.
このようにすることで, 具体的にどうやってブラウザに情報を伝えるのかを知らなくても,
ただ, 伝えたい情報を self.write
関数の中にいれてあげればサーバーが作れる訳です.
どうです?ちょっとは凄さが分かっていただけたでしょうか?
ともかく, self.write の中にブラウザに表示をしたい値(文字列)を書けば良いだけです。 あとは, どんなことを書いてもよいです.
例えば一寸計算をしてみましょう:
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
x = 1 + 5
self.write(str(x))
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
さて, このコードを実行した結果ブラウザには何が表示されるでしょうか?
望みの値になったら、一寸ここまでのおさらいをしましょう.
計算機科学基礎 ではフィボナッチ数列を計算する関数を作ってみました. これを利用して, n=10 の場合のフィボナッチ数列を計算してくれるサーバーを作成してみてください。 それができたら、最初のアプリ作成は終了にします.
入力を受け取る¶
んーでも... と思っている方もいるかもしれません. 折角フィボナッチ数列を出せたとしても(出せて何がうれしいのだという話はおいておいて), n = 10 だけしか出せないのではどうしようもありません.
もっと色々な n で計算させることはできないのでしょうか?
言い換えれば, クライアント側で n を指定することはできないのでしょうか?
当然可能です.
次のコードを書いてみてください(名前は squared_server.py
にしましょうか):
import tornado.web
class SquaredHandler(tornado.web.RequestHandler):
def get(self):
kwargs = self.request.arguments
x = int(kwargs.get("x", [0])[0])
self.write(str(x**2))
def make_app():
return tornado.web.Application([
(r"/", SquaredHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
さて, http://localhost:8888 にアクセスしてみましょうか. この結果は 0 になります.
続いて http://localhost:8888?x=2 にアクセスしてみてください. この結果は 4 になります.
それぞれ何が起きているかわかりますでしょうか?
まず, http://localhost:8888 にアクセスをした場合, 変数 kwargs
には空の辞書が入ります.
これは辞書型ですので, get
関数が使えます.
辞書型の get
関数は第一引数の key があった場合, その value を返します.
では無かった場合にはどうなるのでしょうか?
その場合には第二引数に与えた値, つまり [0]
を返します.
http://localhost:8888 にアクセスした場合には, {}.get("x", [0])
ということですから、
x
という key は存在しません.
そのため, [0]
になります.
これは 0 ではないことに気を付けてください.
0 という値が一つだけ入ったリストです(なぜこんなことをしているのかというと, 値が入っていた場合にもその値が一つだけ入ったリストが返却されるからです).
そのため, その最初の値, つまり [0][0]
= int(0)
が変数 x
に入ります.
最後に関数 self.write(str(x**2))
をしているので 0 の二乗で 0 が表示される訳です.
では同様に http://localhost:8888?x=2 の場合にそれぞれの変数の中身はどのような値になり、最終的に 4 が出力されるのでしょうか? 少し考えてみてください.
納得できる答えができたら, 上記コードを少し変えてみてください。 例えば, http://localhost:8888?x=2&y=2 とした場合には 4, http://localhost:8888?x=2&y=3 とした場合には 6, http://localhost:8888?x=3&y=3 とした場合には 9 を返すサーバーを作ることができるでしょうか?
また、先程作成した fib(10)
の計算結果を返すサーバーを利用して, n をユーザが決めることができるサーバーを作れるでしょうか?
POST METHOD¶
さて, 上で説明をした URL を使った入力方法のことを URL クエリと呼びます. これはよく見てみると例えば Gmail とか, github とか, Slack とか, amazon とかで見かける方法です.
- 我々プログラマはしばしば URL を観察してこれらのツールを楽に使う方法を発見します.
ですが, よく見る WEB アプリでは, ユーザに態々 URL を変更させたりしません. なんか入力する画面があってそこに値を入力すると結果が帰ってきます.
こういう入力方法を行うためには POST method
を知っておく必要があります.
また MainHadler
クラスを書き換えます:
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write(''.join([
'<html>',
'<body>',
'<form action="/" method="POST">',
'<input type="text" name="x">',
'<input type="text" name="y">',
'<input type="submit" value="Submit">'
'</form>',
'</body>',
'</html>'
]))
def post(self):
self.set_header("Content-Type", "text/plain")
x = int(self.get_body_argument("x", 0))
y = int(self.get_body_argument("y", 0))
self.write("x + y = {}".format(x + y))
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
さて, http://localhost:8888/ にアクセスしてみると, 今度は form (入力する場所とボタン) が表示されます. 適当な数字を入れて, submit ボタンを押すと, 2つの入力が合計された値が出てきます.
では MainHandler
を見てみましょう.
関数の数が今までと違いますね.
新しく post()
が定義されています.
コードの中で実際に足し算をしている場所はこの post
関数なので, submit ボタンを押した後に呼び出される関数なのだと分かります.
で入力の受取りは self.get_body_argument()
を使っています.
ここで引数は "x"
や "y"
と書いているので, 多分 submit ボタンを押す前には,
x, や y が定義されているのでしょう.
じゃあ, submit ボタンを押す前の表示はどの関数なのでしょうか?
これは多分, 今まで書いてきた get()
(だって submit って書いてあります).
で, x, y はどこなのかと考えると, '<input type="text" name="x">'
と書いてあるのが分かります.
つまり, '<input type="text" name="x">'
の name="x"
の部分でどのような辞書が造られるのかが決まります.
後はこれに習えば, 自由な入力を作ることができるはずです.
更に input
と書いてある部分は, form
と書かれた文字列に囲まれていると
気がつけた人は大変よろしいです.
'<form action="/" method="POST">'
の意味は, その中身(ここでは 3 つの input)を,
action で書かれた場所に POST メゾットで送りますよという意味です.
だからこそ, URL "/" に紐付いている MainHandler
クラスの post()
が機能したのです.
さあ, これで入出力も自由にできるようになりました.
- このチュートリアルでは基本的に CSS/HTML については話しません.
- やや詳しい人に追記しておくと, put その他 HTML method は同様の記法で機能します.
では, post
メゾットが理解できたのかを確認するために再び fib
関数を使いましょう.
まず, input が一つだけの get
関数を作ってみてください.
序で, 任意の n のフィボナッチ数列を計算できる post
関数を作ってみてください.
練習課題¶
さて, ここまでの説明で皆様はユーザの入力を受け取り結果を返すサーバーを作成することができるようになったはずです. では, 単純な計算だけでなく, 少しアプリケーションっぽいものを作成してみましょう.
元になるものは First Python でやった TODO アプリです. これをサーバーにしましょう.
作りたいものは以下の通りです.
- 最初の画面では, 新規に作成する TODO のフォームが存在する
- フォームには, TODO の内容が入力される
- サブミットボタンをおすと 新規TODO が登録される
- この画面には, 最初の画面画面に戻るボタンが存在する
- 上記ボタンを押すと, 新規に作成する TODO のフォームと今迄登録した TODO の一覧が見える
まずはここまで作成してみましょう. 余力のある方は, 以下の状態にまで, アプリを作ってみてください.
- それぞれの TODO 一覧の横には終了ボタンがある
- そのボタンをクリックすると、該当のTODOがなくなる(見えなくなる)
どうでしょう?できるでしょうか?
まとめと応用のヒント¶
今回は最初のアプリケーションとして WEB サーバーを作ってみました. Tornado の tornado.web.RequestHandler にはWEBサーバーの入力、出力に必要な関数が初めから用意をされていて, あとはやりたい作業をただ記述すれば, WEB アプリケーションを作ることができます.
今回は単純な計算をさせただけですが, 例えば機械学習機と組み合わせることで, 様々なアプリケーションを作ることができます(例えば, 音声を入力として, 機械学習にかけて, 元気かそうじゃないかを判断するとか). あるいは, 文字列を入力として, そのお返事を返す関数さえ作れば BOT 的なものも作れます.
また, TODO アプリに取り組んでもらいましたが、これを少し変えていけば、 例えば, メールやチャットのようなアプリを作ることもできます (というか Facebook や google が絡んでいるので、彼らの公開しているようなサービスを作ることができるのですよ)。
ここでサーバーアプリとは結局、入力があって出力をするものだということに気づけたでしょうか? そういう入力と出力があるもののことをなんと言いましたか?
そう関数です.
今回の実習で気がついて欲しいことは何かというと、 アプリというプログラムから少し皆様の馴染みのあるものに変わっても、 実は、関数という世界の繰り返しだということです.
最初に入力と出力を決めて、あとはそのための処理さえ書けば、何でもできてしまうし、 そこにはなんの魔法(理由のないこと)も存在しないと気がついていただければ幸いです.