我が子「おぎゃあおぎゃあ!!Python全然わかんないよう!!」
ぼく「おやおや、Python初心者の我が子ちゃんがPythonわかんなくて今日もないとるで…」
ぼく「Pythonは誰でもさくっと簡単にプログラムを組むことができんねんけど、そのせいで仰山あるスーパー便利機能が知られていない気がすんねん。しかも便利機能を使おうとすると途端に意味わからんくなるんやな。」
ぼく「だから今回は比較的新しいけどあんまり知られてなさそうな文法とか紹介するで!みんなでPython完全理解マン目指そうな!」
目次
型ヒント
ぼく「最初はバージョン3.5で追加された型ヒントや!」
ぼく「Pythonは動的型付け言語なのはみんな知っとるか?」
ぼく「動的型付けっていうのは型を指定しなくても自動で判別してくれるってことなんや!」
ぼく「例えば静的型付け言語ではこんな感じで変数を宣言すんねん。」
var name str = "nokonoko" // str型
var age int = 5 // int型
ぼく「変数にどんな値が入るか先に決めておかなあかん。指定した以外の型を入れるとエラーが起きてまう。」
ぼく「それに対して、Pythonは…」
name = "nokonoko" # str型
age = 5 # int型
ぼく「こんな感じでわざわざ型名を宣言せんでも普通に変数を宣言するだけで、勝手に型を決めてくれんねん!Pythonちゃん便利やな!」
ぼく「これだけ見ると静的型付けってめんどいだけやん…ってなるわな。やけど、なんで今でも静的型付けの言語がある(むしろ静的な方が人気…?)んかっていったら…メリットがあるからやな。多分ざっとこのくらいあるで。」
- 速度:型の判別がコード上ですでに終了しているので動作が早い
- 安全面:型がある事で、一見では原因不明のバグが発生する確率が下がる
- 読みやすさ:書くのは大変になるが、引数や戻り値の型が自明であるため、動作を推測しやすい
ぼく「小さい規模でとりあえず動けばOKで速度とかどうでもよくて、一人で開発するんなら型なんてどうでも良いんやけど、普通はそうじゃないわな。」
ぼく「そもそも、Pythonは簡単なスクリプトを書くために生まれたらしいんやけど、こんなに流行ってしまった状況から、Pythonには静的型付けを可能とするよう強い要望があったみたいや。でもそんなんは流石に難しいから…と生まれたのが型ヒントや!」
ぼく「こんな感じで使われるんや。」
def hello(name: str) -> str:
return f"Hello {name}"
ぼく「変数や関数の引数・戻り値とかの型を設定できるで!これでIDEなどを利用してコードを書くときに、型が違う変数を引数にとったりしたときに警告してくれるようになるで!!」
ぼく「ただ、本当の意味で型付きの言語になったわけではなく、実行することは出来てしまうから注意な!」
まとめ
- Pythonは動的型付けの言語だから型は自動判別
- 型ヒントは変数や関数に型を指定できる
- ただし、あくまで型ヒントであり、コンパイルエラーになったりせず実行できてしまう。
Docstring
ぼく「お次はDocstringや!!これも便利な機能やで!まずはどんなもんか見てみよか!」
def hello(name):
"""引数に入れた人に挨拶します"""
return f"Hello {name}"
ぼく「こんな感じで関数(クラス・モジュールでも可)にクォーテーション3つで囲んだ文字列を入れてあげることで、コードにドキュメントを埋め込むことができるで!」
ぼく「これだけだとただのコメントと何が違うねんってなるんやけど、ここに埋め込んだ文字列はそのオブジェクトの特殊メソッド__doc__
に格納されんねん。」
ぼく「つーことは対話モードでhelp()
を使えばドキュメントが参照できるようになったり、Sphinxってのを使えば自動でドキュメント作成をしてくれたりとお得盛り沢山や!ただのコメントとは一味違うんやな。」
>>> help(hello)
Help on function hello in module __main__:
hello(name)
引数に入れた人に挨拶します
ぼく「さらに!!!!!!複数行のDocstringを埋め込むことで、さっき説明した型ヒントもDocstringに書けるようになるで!」
ぼく「こんな感じや!」
def hello(name):
"""引数に入れた人に挨拶します
Args:
name(str): 挨拶されたい人の名前
Returns:
str: 挨拶文
"""
return f"Hello {name}"
ぼく「これで型ヒントを入れたのと同じように、IDEなどで型チェックができるで!」
ぼく「上で書いたのはGoogle スタイル
って呼ばれるものやけど、引数・戻り値意外にも色々書けたり、そもそも他にも書き方があったりするから気になったら調べてみるとええで!」
ぼく「大人数で開発するときとかにとっても便利だからみんな使ってみてな!」
まとめ
- クォーテーション3つで囲んだ文字列を関数・クラス・モジュールに埋め込むことで特殊メソッド
__doc__
に文字列を格納 - help()やIDEでの参照など、開発が捗る便利機能が使えるようになる
- 複数行書くことで型ヒントなども付けられる
参考:PEP 257 -- Docstring Conventions
f-string
ぼく「次はバージョン3.6で追加されたf-string(f文字列や!)」
ぼく「文字列に変数を埋め込む時って2種類くらい方法があるやろ?こんなんやな。」
>>> name = "nokonoko"
>>> age = 5
>>> print("{}は{}歳です".format(name, age))
nokonokoは5歳です
ぼく「とか」
>>> print("%sは%s歳です" % (name, age))
nokonokoは5歳です
ぼく「とかやな。」
ぼく「そのやり方…古いで。」
ぼく「今はこんなふうにできんねん!」
>>> print(f"{name}は{age}歳です")
nokonokoは5歳です
ぼく「どや。訳わからんメソッドや記号を全部消して簡潔になったやろ!」
ぼく「さらにや。Python3.8からはこんなんもできる。」
>>> print(f"name={name}, age={age}") # 3.7まで
name=nokonoko, age=5
>>> print(f"{name=}, {age=}") # 3.8から
name='nokonoko', age=5
ぼく「{}の中に変数
と=
を入れることで自動で変数名も表示してくれるようになったんや!デバッグ時に重宝するで!」
ぼく「便利やから新しいプロジェクトを作成するときにはこれを積極的に使っていこうな!」
まとめ
- 文字列の埋め込みには種類があるが、一番便利なのは最新のf-string
- Python3.8からは変数名の埋め込みもできるようになってさらに便利
セイウチ演算子
ぼく「次はバージョン3.8で追加されたセイウチ演算子や!」
ぼく「セイウチ演算子は代入演算子:=
の事で、横からみるとセイウチ の目と牙に見えることから呼ばれるようになったみたいやで!」
ぼく「……まぁ、見えへんけどな!!!!!」
ぼく「それはひとまず置いておいて、if文やfor文などの中で変数へ代入が行える機能や!こんな感じやな!」
# 3.7まで
>>> name = "nokonoko"
>>> if name == "nokonoko":
... print("イケメンですね")
... else:
... print("イケメンじゃないですね")
イケメンですね
# 3.8から
if name := "nokonoko" == "nokonoko":
... print("イケメンですね")
... else:
... print("イケメンじゃないですね")
イケメンですね
ぼく「まぁ、当然の結果やな。」
ぼく「こんな感じで、条件式内で変数への代入を行える機能で、冗長な表現を短くすることのできる優れものや。」
ぼく「ただ、これは本当に新しい機能やからあんまり知られとらんし、使い方によっては可読性を落とすことにもなりかねないから注意して使ってな!」
ぼく「特に、Pythonにおいてif文やfor文はローカルスコープを作らないので、要注意やで!」
まとめ
- セイウチ演算子は条件式中などでも代入を行える演算子
- 可読性に注意して利用すること
参考:PEP 572 -- Assignment Expressions
内包表記
ぼく「次は内包表記や!」
ぼく「これはコンテナオブジェクトっていう変数などの値が複数格納された(他のオブジェクトへの参照を持つ)オブジェクトをシンプルに作成する機能のことや。」
ぼく「よく使われるのはリスト内包表記やな。」
リスト内包表記
# 新しいリストを作成
>>> numbers = []
>>> for i in range(10):
... numbers.append(i)
>>> numbers
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 内包表記を使うとこうなる
>>> numbers = [i for i in range(10)]
>>> numbers
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ぼく「for文を使って配列を作っていたところを、1行で定義できるようになる優れものやな!」
ぼく「しかも、if文を埋め込むこともできるで!」
# 普通のfor文
>>> numbers = []
>>> for i in range(10):
... if i % 2 == 0:
... numbers.append(i)
...
>>> numbers
[0, 2, 4, 6, 8]
# 内包表記
>>> numbers = [i for i in range(10) if i % 2 == 0]
>>> numbers
[0, 2, 4, 6, 8]
ぼく「さらに、for文と違ってスコープを閉じてくれるんや!」
# 普通のfor文
>>> numbers = []
>>> for i in range(10):
... numbers.append(i)
# for文で定義された変数iが参照できてしまう
>>> i
9
# 内包表記
>>> numbers = [i for i in range(10)]
# 変数iが未定義として例外が発生する
>>> i
Traceback (most recent call last):
File "<input>", line 1, in <module>
NameError: name 'i' is not defined
ぼく「いらんバグを防いでくれるから、新しいリストを作る時はなるべく内包表記を使おうな!」
ぼく「ちなみに、内包表記はリスト以外でも使えんねん!それも紹介するで!」
辞書内包表記
>>> dict_obj = {str(i): i for i in range(10)}
>>> dict_obj
{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
集合内包表記
>>> set_obj = {i for i in range(10)}
>>> set_obj
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
ジェネレータ式
ぼく「今までの流れから、括る記号を()
にするとタプル内包表記でも出来上がるんかな?と思うけど、これだけはちょっと例外で、ジェネレータが出来るんや。ややこしいから注意な!」
gen_obj = (i for i in range(10))
gen_obj
<generator object <genexpr> at 0x10df04580>
ぼく「いろんな内包表記があって、全部便利だから使ってみてな!」
まとめ
- 内包表記は、シンプルにコンテナオブジェクトを作成する機能
- スコープが閉じるのでバグを生みづらい
- リスト以外のコンテナオブジェクトも作成できる
- 深いネストは可読性を落とすので注意
参考:5. データ構造
ジェネレータ
ぼく「次はジェネレータや!これは昔からある機能やけど、初心者にとっては使い道がちょっとよくわからん機能やな。わざわざジェネレータ作らなくてもリストを作ればええんやし…」
ぼく「と思っていたんやけど、以下の2点で大活躍する気がすんねん。」
- 処理を止めずに無限ループさせたい時
- 大容量のテキストデータを扱いたい時
ぼく「例えば…」
while True:
print("値を返します")
ぼく「なんてコードを書いてしまったときには、即無限ループ突入。メモリを消費し続けてシステムダウン…なんてことも考えられるから普通はやらんように注意するわな。」
ぼく「無限ループに限らんでも、重たい数学的な計算処理とかでどでかいリストを作ったりしていてもおんなじことが起きるかもしらん。」
ぼく「でも下のようにジェネレータを利用すると、次の要素が求められる(特殊メソッド__next__
の呼び出し)まで新しい要素を作り出さんからメモリを圧迫しなくてすむんや!」
ぼく「ジェネレータを作成する方法は2つあって、具体的はこうやって作るで!」
# 関数を作成する方法
>>> def my_gen():
... x = 0
... while True:
... yield x
... x += 1
# 戻り値がgeneratorになっている
>>> my_gen()
<generator object my_gen at 0x10df04510>
>>> for i in my_gen():
... print(i)
0
1
2
3
...
# ジェネレータ式を使う方法
>>> my_gen = (i for i in range(10000) if i % 2 == 0)
>>> for i in my_gen:
... print(i)
0
2
4
6
8
ぼく「ジェネレータは戻り値がイテレータやから、for文で使うこともできるんやな!」
ぼく「100万レコードのあるcsvとかを読み込んで処理させたいときとかにはめっちゃ便利そうやな!」
ぼく「ちなみに、ジェネレータは他のコンテナオブジェクトに変換することもできるで!でも生成時にメモリを大量に食う可能性があるから注意してな!」
>>> my_gen = (i for i in range(10000) if i % 2 == 0)
>>> my_list = list(my_gen)
>>> type(my_list)
<class 'list'>
>>> my_list
[0, 2, 4, 6, 8, 10, 12, 14, 16,...9992, 9994, 9996, 9998]
まとめ
- ジェネレータは呼び出される度に値を生成してくれるイテレータ
- 値を全て展開するわけではないので、メモリ消費を抑えることができる
- 大容量データを扱う場合などに便利
総評
ぼく「どうやったろか!Python完全理解マンに近づいたんちゃうか!」
我が子「おぎゃあおぎゃあ!!Python超簡単だよう!!」
ぼく「よっしゃ。我が子ちゃんもPython完全に理解できたみたいやな。画面の前の君もPython完全理解マン目指して頑張るんやで!」
お し ま い