これまでC/C++で生きてきましたが、時代の流れにあらがえず、Pythonでコーディングを始めて3,4年。あまり困ることもなくなって来て、便利な道具として使ってきましたが、少し勉強してみようと思い、「Python文法詳解」というオライリー読んでみました。
そこでこれまで気づかなかった発見があったのでそれを記事にしようかと思います。やはり本は読むものです。
通常の文法知識はやってるうちにわかってきますが、ぼーっと生きてると気づけないような、「へぇ~そーなんだぁ。」的な内容を、コードを交えてかいつまんで記事にします。全部読んだら4ページの「へぇ~」がありましが、目を通していただけると新しい発見があるかも。
要約でも解説でもないです。個人的「へぇ~」だけまとめました。詳細は買って読んでください。
Python文法詳解
程度触ってきた方や他の言語をやりこんだ方なら抵抗なくすらすら読める本だと思います。上っ面だけなめてきた自分のような「にわか」Pythonプログラマには新しい発見があって面白かったです。
第1章 イントロダクション
ここは…まぁいいか
第2章 Pythonの実行
ここでは書き方の基本が記されています。色々コーディングする中で慣れてきた記述ルールが書かれています。何点か新発見があったので紹介します。
スクリプトファイルの文字コード
「へぇ~」だったのが、文字コードのエンコーディング名の示し方です。使ってるIDE(Spyder)ではデフォルトで行頭に
# -*- coding: utf-8 -*-
という文字列が入っています。まぁこのルールで文字コード書いとけばいいんだろうなぁくらいに思っていましたが、
# coding: utf-8
だけで良かったんですね。 -*- はEmacsなどのエディタ向けの記述だったんですね。
(どうやらPython3.xでは記述が省略されているとutf-8扱いとの情報もあり。この本の情報ではないですけど。)
Pythonのスクリプトの構造
この章ではPythonコーディングスタイルに対して記述がありました。これもだいぶ慣れました。「へぇ~」な部分として、()や[]や{}などのカッコ内は改行し放題、インデントずらし放題。って事。
確かに配列の初期化とか、そんな時はそろえたかったのでやってたかもなぁ。
あとは「そーだよね」的な部分としてタブのインデントでは無くスペース4つのインデントを推奨されているよ。って箇所。これからも気を付けましょう。エディタがタブを押しても自動でスペース4文字にしてくれるのであまり意識しませんが…。
式と代入文
ここも新しい発見はないのですが、よく使ってた”//”(整数除算)や”**”(べき乗)は便利だなぁと思っています。トリッキーな演算子が他には無いんだなぁ。ということも再認識しました。
比較演算子
比較演算子の紹介です。x is y や x in y を使わずにこれまで過ごしてきました。ちゃんと意識した方がいいかもなぁ…。3章に詳細あったのでここでは省略
あと「へぇ~」だったのが
if (0 < x) and (x < 100):
を
if 0 < x < 100:
で行けるのは知らなかった。
この章の残りはさらっとリスト・タプル・辞書・クラス等のお話だったり、if/elseやforの文法のお話がされております。3章以降で詳細記述があるので割愛
第3章 式とデータ型
この章ではPythonの基本のデータ型について詳しく教えてくれています。intとかfloatとか。
Pythonのデータ型
ここで「へぇ~」だったのがPythonのデータや処理の実態はすべて「オブジェクト」として統一管理されているという事。整数型や浮動小数点型もすべてオブジェクト。関数もオブジェクト。なんか抽象的な話に聞こえますが、実際すべての型もobject型の派生らしいです。そういう作りになってるようです。
> (256).bit_length() # 整数直値に属性がある 9 > (256.0).bit_length() # 整数と浮動小数で属性が違う AttributeError: 'float' object has no attribute 'bit_length'
Noneオブジェクト
値が指定されていない事を示すオブジェクトでこれまでもなんとなく使ってました。このオブジェクトは1つしか実体のないシングルトンのオブジェクトですべてのNoneはみな1つだそうです。なので、Noneかどうかチェックするときは、
a == None
で行うのではなく、
a is None
がイケてる記述になります。( == は値が全部同じかチェックするのに対して is は同じオブジェクトかどうか比較するだけなので処理が早い)
Elipsisオブジェクト
省略。別に記述を省略したのではなく、省略って意味だそうです。こいつもシングルトン。ググって見ましたがいまいち使い方が思いつきませんでした。
関数の呼び出し
関数もオブジェクト。()は演算子の一種。だそうです。()演算子の動作を定義しているオブジェクトが呼び出し可能オブジェクト。ここは「ふ~ん」って感じでした。
それより「へぇ~」だったのは関数の引数にリストを指定できるって事でした。
func(0,1,2,3,4,5,6,7,8,9)
と
args = [0,1,2,3,4,5,6,7,8,9] func(*args)
が同じ意味だということ。引数に*をつけて関数を呼ぶと、呼び出し時に展開されて関数に渡される様です。また
func(0, 1, a = A, b = B)
と
kwargs = { "a" : A, "b" : B } func(0, 1, **kwargs)
が同じ意味。引数に**をつけた辞書を指定して呼ぶと、これまた展開してキーワード引数として使える様です。
これは便利かもしれない。この後の6章でもこのあたり細かく記述があります。
変数と参照
たまに混乱する参照。Pythonの世界では、変数に代入するとは、オブジェクトを変数名で参照できるようにすること。だそうです。なので基本参照。なので、
a = [1,2,3] b = a
ではbもaも同じオブジェクトを参照しているので、aが変わればbも変わる。
a.append(4) print(a) [1,2,3,4] print(b) [1,2,3,4]
になります。a is bです。では同じように
a = 1 b = a a = a + 1
ではどうでしょう。一見先ほどのリストと同じようにbの値も変わってしまっているかのように思いますが、a+1の計算の結果新たなオブジェクトが生成されてaはそれを参照しているので、
print(a) 2 print(b) 1
となります。結局右辺の計算の結果新しいオブジェクトが作られるのか、今のオブジェクトを変更するのかで振る舞いが変わる。って事なんだと思います。基本参照だと思っていた方がよさそうです。どっちかってーとこの動きを試してておっかなかったのは
import numpy as np a = np.array([1,2,3,4]) b = a
でbを用意しaをインクリメントした時
a = a + 1 # a+1という新しいオブジェクトを代入 print(a) [2,3,4,5] print(b) [1,2,3,4]
とこちらはintの時と同じ振る舞いだったのですが、インクリメントのやり方を変えると
a += 1 # aのオブジェクトの要素をインクリメント print(a) [2,3,4,5] print(b) [2,3,4,5]
とまぁ答えが違いました。おそらくnumpy arrayの型に対する=と+=で新しオブジェクトを返すのか、既存のオブジェクトをいじるのか違いがあるんでしょう。理解して使わないといけない怖い話です。これは冷や汗が出たので詳細色々調べてしまいました。別記事です。
要素の分解
ここにも少し「へぇ~」がありました。説明するよりコード見た方がいいです。
> a = [1,2,3,4] > c, d, e, f = a > c, d, e, f 1, 2, 3, 4
これは知ってた。
> a = [1,2,3,4] > c, d, *e = a > c, d, e 1, 2, [3, 4]
またでました。*記述。これをつけると残りをリストに束ねてくれるみたいです。この先にも*記述は登場してきます。基本値を束ねる系です。
del文
変数の削除ができるらしいです。とはいえスコープから消えれば実質削除だろうし。あまり使わないかな。
変数名
なんと変数名に日本語が使えます。関数名にも使える。でも普通使わないですよね…。全角は半角に置き換えられたり、名前の重複も気にしながらなので、色々面倒くさい。積極的には使いません。
あ=1 い=あ+1 print(あ) 1 print(い) 2
ちょっと便利かななのはせいぜい
π = 3.14
とかそんなもんですかね…。
論理演算子
演算子はまぁそれほど驚きは無かったのですが、論理演算子に関しては少し知っておいた方がいいかなぁ~な記述がありました。返り値が少しトリッキーでした。返ってくるのはTrue/Falseではないです。
まずはor。x or y はxが真ならxを返し、yが真ならyの値を返します。どちらも偽ならyを返します。先から評価し条件に合致したものを返す。って事ですかね。後の評価は省略されます。それが仮に関数だとしてもその関数は呼ばれません。
> x = 2 > y = 0 > x or y 2 > x = 0 > y = 0.0 > x or y 0.0
偽だと後のものが返ってます。こんなトリッキーな式に出くわしてもビビらないようにしないといけません。
# x が真なら x を偽なら y を z へ代入 z = x or y
andも同じ。先に評価して偽が見つかった時点でそれを返す。全部真なら最後に評価したものを返す。そんな仕様の様です。
and/orは先から評価し、確定したところでその値を返す。と覚えておくといいですね。
比較演算子
こちらは合致したらTrue/Falseを返します。まぁ他に返しようがないですしね。この中で「へぇ~」だったのは、isとinですかね。
is演算子。こいつは同じオブジェクトか否かを判定する演算子で、同じ場合にTrueそうでない場合にFalseを返します。
値が同じだとしてもisがTrueを返すとは限りません。
> a = [1,2,3] > b = [1,2,3] > a == b True > a is b False > a = [1,2,3] > b = a > a == b True > a is b True
となります。すべからずすべてのオブジェクトにあるIDってので評価しているのがisでIDが一致しているか否かで判断しています。なので値のコンペアまでしない分isの方が答えが速いです。Noneオブジェクトはシングルトンなので比較はisを使う。ってのはこのあたりが理由ですね。
次にin演算子。右辺のコレクションに左辺の値が含まれていればTrueをそうでなければFalseを返します。これもよく見かける演算子です。
条件演算子
これもトリッキーな印象です。いわゆる三項演算子ってやつですね。Cでいう a ? b : cです。順番が違うのでややこしいですが。ここでこんな例が紹介されています。
a = x if y else z a = y and x or z
この2つが同じ答えを出します。(書籍の記述が間違ってる。図書券もらえるかも)
y and xが真ならこの出力はx。or zの計算は先に真が確定したので行われません。y and xが偽ならzを出力することになるので、結果としてx if y else zと同じ。
書籍がミスするくらいわかりにくいので、こんな記述はしないようにしたいと思います。
自動型変換
異なる型を使って演算を行うと自動的に型変換されます。整数×浮動小数点は浮動小数点。実数+複素数は複素数。
これは素直に便利ですね。はっきりとこう定義されているのはとても安心できます。
整数のビット操作
Cと同じようにビット演算子(論理和・論理積・ビットシフト)は用意されています。ただ驚いたのはどうやらPythonでは負の値を2の補数を使っていないようです。ただ論理演算をする際に律儀に2の補数に直して計算して返してくれるようです。Cに慣れたユーザーにも安心仕様です。ただbin()関数は2の補数にしてくれないようなのでビット演算のデバッグは苦労しそうです。
inf と nan
無限大と非数の事です。計算結果が型の有効な範囲を超えた場合にinfを出します。このあたりが出てくると「あっバグったな」って思うわけでよく見かけます。そして最後はなくします。Noneとは違います。Not a Numberのnanです。
10進浮動小数点数
0.1とかそんな10進の世界で誤差なく表現できる小数も2進の世界では循環小数になってしまいます。
> 0.3 - 0.2 0.09999999999999998
通常問題ないのですが、お金を扱う世界ではこれは問題です。というわけでそんなモジュールが用意されているようです。標準ではなさそうですね。
import decimal
してあげると使えるようになります。
print(decimal.Decimal('0.3') - decimal.Decimal('0.2')) 0.1
今回はここまでのメモにとどめますが。
つづく
ひとまず3章までの「へぇ~」ポイントだけかいつまんでメモしました。
コメント