読者です 読者をやめる 読者になる 読者になる

Paradise Lost

駄人間にならないための日々の記録

"Think Python:コンピュータサイエンティストのように考えてみよう"を読んでみた

自分は普段C言語JavaPython等のプログラミング言語を使用するが、C言語以外はほとんど全てがインターネットからの知識で独学で覚えてきたものなので、大切な知識が抜けていたりすることがよくある。そのような理由から常日頃から基礎的な知識をまとめて学習したいと思っていた。

最近大学の友人が後輩たちに対してPythonの講義をしていて、その資料としてThink Python:コンピュータサイエンティストのように考えてみようを使用していると聞いた。気になったのでチラっと見てみたらなかなかいい感じで、他の言語でも使えそうな知識なども多く、自分の力になると思ったので軽く読んでみようと思った。

ここでは読んだ内容で興味を持った部分を纏めていく。

Pythonについて新しく知ったこと

  • ^は他の言語では階乗算に使う事が多いがPythonではXORのビット演算に使う
  • Pythonの"(ダブルクオート)と'(シングルクオート)の使い分け
    • 文字列中に'がある場合は"を使い、その他は'を使う
  • キー付き引数
    • 関数に引数を渡す時に以下のコードのようにキーを付けて読みやすくできる。意味があるのかわからないが、これを使うと引数で渡す順番も変えられる
>>> def myprint(a, b, c):
    print a
    print b
    print c

    
>>> myprint(15, 'aaa', 23.)
15
aaa
23.0
>>> myprint(b=15, c='aaa', a=23.)
23.0
15
aaa
  • 後から何か書き加えるが一時的にif文等の中で何もしないようにしたい時にはpassと書く
  • スライス演算子を使う事でリスト内の要素を一度に複数更新できる
>>> t=["a","b","c","d"]
>>> t[1:3]=["x","y"]
>>> t
['a', 'x', 'y', 'd']
  • リストのにおいてappendはリストに要素を加えるだけでリストは返さないが、リスト同士で+をすると新たなリストを生成する
>>> t=['a','b','c','d']
>>> t2=t.append('e')
>>> print t
['a', 'b', 'c', 'd', 'e']
>>> print t2
None
>>> t3=t+['f']
>>> print t3
['a', 'b', 'c', 'd', 'e', 'f']
  • 関数内などでグローバル変数を使う時にグローバル変数だと明示する時はglobal 変数名という行を入れる
  • 変数の中身を入れ替える時にtempのような一時的に入れる変数を用意しなくても変数1, 変数2 = 変数2, 変数1とすることで一行でできる
>>> a=1
>>> b=2
>>> a, b = b, a
>>> a
2
>>> b
1
  • 引数の数を可変にして、関数側では引数をまとめたタプルとして受けとる方法を可変引数タプルという。やり方はdef myprint(*arg)のように仮引数名の前に(アスタリスク)を入れる。逆に引数の数が決められた関数にタプルの要素を引数として渡したい時はmysum(*t)のようにタプル型の引数の前にを入れることでタプルの中身を分解して渡す事ができる
>>> def myprint(*arg):
    print arg   
>>> myprint(1, 'AA')
(1, 'AA')

>>> t = (7,3)
>>> def mysum(a, b):
    print a + b 
>>> mysum(t)
Traceback (most recent call last):
  File "<pyshell#73>", line 1, in <module>
    mysum(t)
TypeError: mysum() takes exactly 2 arguments (1 given)
>>> mysum(*t)
10

言語の種類

  • 高級言語 … C, C++, Perl, Java, Python
    • 長所
      • 可読性が高いため、速く読み書きができる
      • 移植性がある
    • 短所
      • 実行前に加工プロセスが必要になるため、時間がかかる
  • 低級言語 … 機械語, アセンブリ言語
    • 長所
      • コンピュータが直接実行できる
    • 短所
      • 移植性が無い

エラーの種類

  • 構文エラー:構文が正しくない時に出すエラー
  • 実行時エラー:実行時に問題が起きたときに出るエラー
  • 意味的エラー:プログラムは動くが自分の意図した動きではない時のエラー

関数に分ける理由

  • 関数を生成することで一括りの処理に名前を付けることができ、読みやすく、デバッグし易いプログラムを作ることができる
  • 関数化することで繰り返しをなくし、プログラムを短くすることができる。また、何か変更が必要な時には一個所を修正するだけでよい
  • 長いプログラムを関数に分割することで関数ごとのデバッグが可能になる
  • 一度書いてデバッグして完成させると、それを再利用することができる

カプセル化

データとそれを処理するためのメソッドを一つのオブジェクトとしてまとめること。オブジェクトの外部からデータやそのオブジェクトの振る舞いを隠したり、オブジェクトの外部からデータを直接いじることができないようにするため、オブジェクトの独立性が高まり、保守性が高まったり、デバッグが楽になったり、他のプログラムでも使用できるなどのメリットがある。

再因子分解

プログラムを書いていて、ある関数で行った処理のまとまりの一部だけを他の関数で使いたいときに、その一部の処理を関数から分解して一つの関数として作り直し、二つの関数から作った関数を呼ぶという形にすることを再因子分解という。そのようにすることで関数に分ける理由と同じようなメリットがある。

ドキュメント文字列

関数のインタフェース(使い方の指針)を説明するために関数の始めに書く文字列。Pythonだとhelp(class名)でそれを読むことができる。

基底ケース

再帰関数において、自分を呼び出さず再帰の最後となるケースのこと。再帰関数で基底ケースを書き忘れたり、基底ケースに条件文が進まないような書き方をすると無限ループに入って大変なことになる。

死コード

returnの後に書いたコードなどの決して実行の流れが到達しない部分にあるコードの事。名前はなんかかっこいいが、理由が無い限りは作るべきではない。

メソッド

メソッドは関数と似ているが違うらしい。この資料では以下のように定義されていた。

  • メソッドはクラスとの関連を明白にするため、クラスの内部で定義される
  • メソッド起動に対する構文は関数を呼ぶ場合の構文とは異なる

少し調べてみたら厳密な定義がないようで、さまざまな議論がされており、そこには闇が広がっていた。自分もまだ詳しくはわからないが、オブジェクトに対する操作をまとめたものをメソッドと呼び、それ以外を関数と呼べばいいのかな?と思った。
メソッドと関数の違いに関しては関数にしか見えないものが「メソッド」と新たな名前で呼ばれる理由(1) - Programmer’s Logで面白い考え方がしてあった。

扱うデータ量が増加した場合のデバッグ方法

  • 入力のスケールダウン:デバッグの時はデータ全てを読み込むのではなく、データを少しにして検証をしてみる
  • 要約的把握や型の確認:データ全てを確認するのではなく、要素数や要素の総和などのデータの要約となるデータのみを確認する
  • 自己点検の書き込み:コード内で自分で点検できるような機能を入れる。例えば、平均値を出すコードを書いている時に出力がリストの最小値より大きく、最大値より小さくなっているかどうかを出力する等
  • 出力を綺麗に表示:デバッグで出力するデータを読みやすくする

特に見つけにくいバグに遭遇した時の対処法

  • 読め:自分の書いたプログラムを吟味する
  • 実行せよ:プログラムを変更し、しかるべきところで表示をしてみることで何かがわかるかもしれない
  • 熟考せよ:それがどんなエラーなのか、どこが原因のエラーなのか、いつ起きるエラーなのか時間をかけて考える
  • 後退せよ:プログラムがそれなりに動き、自分が理解できているバージョンまで戻る

オブジェクト, クラス, インスタンスの関係

これもメソッドと関数の違いのようにはっきりと決められているわけではないようで、自分も今までなんとなくで使っていた。調べるとクラスもインスタンスもオブジェクトであり、クラスは設計書のようなものでインスタンスはクラスという設計書から作られた個体と考えればいいのかな?と思った。インスタンスの事をオブジェクトという人も結構いるみたいだからややこしい。ITプロフェッショナル・コラム - 第1回 「オブジェクト,クラス,インスタンスの関係」:ITproでもそんなようなことが書いてあったが自信を持ってそうだと人に言えるかと聞かれたらNoである。余談だがこれを読んだら哲学者って変態だな~ってよくわかった。

浅いコピーと深いコピー

aという変数があり、aをbという変数にコピーしたとする。その時にbを更新した時にaの値も変わってしまうようなコピーを浅いコピーという。逆にbを更新してもaが変わらないコピーを深いコピーという。この違いは浅いコピーではbという変数にaの参照先のアドレスをコピーしているだけであり、aの内容が二つになっているわけではないのに対して、深いコピーではaの参照先と全く同じものを新たに作ってそこへのアドレスをbに入れていることから起こる。

純関数と修正関数

引数の値を更新したり、ユーザの入力を求めたり、何も表示したりしないような関数を純関数と呼び、引数として受け取ったオブジェクトを内部で修正するような関数を修正関数と呼ぶ。基本的には純関数を使い、必要な時のみ修正関数を使うようなアプローチを関数プログラミング作法と呼ぶ。

不変性がある場合のデバッグ

秒数は0から59で表されることや、時間を表す時に負の値が使われることがない等のどのような時でも満たされることを不変性があるという。逆に言えば、それらが満たされていないことがある場合はプログラムのどこかがおかしいということなので、それらを調べる処理をプログラムに含めることでデバッグが楽になる。

オブジェクト指向プログラミングの特徴の一部

  • プログラムはオブジェクトの定義、関数の定義から構成され、多くの操作がオブジェクトに対する演算として表現される
  • 各オブジェクトは現実世界の実体や概念に対応していて、オブジェクトに対し適用される関数は現実世界の実体が相互作用している様に対応している

演算子の多重定義

クラス内で特殊なメソッドを定義することでオブジェクトに対する演算子の振る舞いを変更することができる。これを演算子の多重定義という。

多態的

いくつかの型の引数を正常に処理できる関数を多態的であるという。例えば、足し算をしてその結果を返すmysum(a, b)という関数を定義したとする。ここでaとbに数値が入っても文字列が入っても正常な結果を返すのでmysumは多態的な関数であると言える。

継承

既存のクラスに変更を加えたクラスを作る事を継承という。この時もとになったクラスを親クラスといい、新しく作ったクラスを子クラスという。子クラスは親クラスと同じメソッドを持っており、必要に応じて書き加えたり、上書きしたりすることもできる。継承なしては繰り返しになってしまうプログラムも継承によって綺麗にかけることもあるが、あるメソッドが呼び出された時、その定義がどこにあるのかが明白でないため読みにくくなるということもある。

全体を読んで

最初はオブジェクト指向の方法などの他の言語でも使えるような知識を得るつもりで読み始めたが、途中でいつの間にかPythonの勉強になっていた。今までもPythonは使う事はあったが、自分が知らない使い方が多くあったので役にたったと思う。これを読めば読むほどPythonスゲーってなったので、初心者にPythonを勧める時はPythonの良さをゴチャゴチャ話すよりも、これを差し出して「読め」でいい気がした。しかし、自分は可変引数など、ところどころなんか気持ち悪いと思った部分もあった。今回はメソッドと関数の違いや、オブジェクト, クラス, インスタンスの関係などの自分の中であいまいだった部分をなんとなく整理できたから良かったと思う。