[Python]基礎文法の範疇だけど時々忘れちゃうやつまとめていく[ジェネレーター、高階関数、lamda etc,,,]
高階関数(high order function)
関数を引数として受け取る関数、あるいは返り値として関数を返す関数のこと。
Pythonでは関数もオブジェクトなので変数に入れたり上述のようなことが出来たりする。
- 例
def print_greeting(name):
print(f"{name}さん、こんにちは")
def print_morning():
print("Good morning !!")
def print_hello(func):
func("Kumamoto") #引数funcを関数のように利用します
return print_morning # 関数print_mornigを返す
if __name__ == "__main__":
en_hello = print_hello(print_greeting) # Kumamotoさん、こんにちは
en_hello() # Good morning
lambda式
無名関数。「lamda 引数 : 返り値」って感じで書ける。
- 前提
本筋からはズレるがif文を下記のように一行で書ける。今回はこれを利用する。
# 偶数か判断
y = 11
x = 0 if y % 2 == 0 else 1
print("x", x) # debug 1
- 例
hoge = lambda x: x * x
print(hoge(2)) # 4
fuga = lambda x, y, z=10: x * y * z
print(fuga(2, 3)) # 60
piyo = lambda x,y: y if x < y else x
print(piyo(10, 3)) # 10
再帰
関数の中でまた自分自身を呼ぶ。
このQuoraの記事が直感的な説明をしてくれている。(文字を反転させる関数を例に話を勧めてくれている。本当にわかりやすい!)
def reverse_str(target_str):
if len(target_str) == 1: # 説明だと0になっているが
return target_str
return reverse_str(target_str[1:]) + target_str[0]
- 例
def recursion_hoge(n):
if n < 10:
return
else:
print(n)
recursion_hoge(n-1)
if __name__ == "__main__":
recursion_hoge(12) # 12, 11, 10
-
前提 フィボナッチ数列
1, 1, 2, 3, 5, 8, 13, 21... -
発展:再帰を使わないフィボナッチ数列
def fib_not_recursion(n):
now_n, next_n = 0, 1
for i in range(n):
now_n, next_n = next_n, next_n + now_n
return now_n
LIMIT = 1000
sequence = [1] * LIMIT
# dp
def fib4(order):
if order <= 1:
return 1
i = 2
while order > i:
print("i", i) # debug
sequence[i] = sequence[i-1] + sequence[i-2]
i += 1
return sequence[order]
- 発展:再帰を使うフィボナッチ数列
def fibo(order):
if order <= 2: # (2)特殊パターン
return 1
return fibo(order-1) + fibo(order-2) # (1)先輩がorderより一つ前と2つ前の数値は出るように関数を書いてくれていた
def fib_with_recursion(n):
return 1 if n < 3 else fib(n-1) + fib(n-2)
# memo_recursion
memo = [1] * LIMIT
def fib3(order):
if order <= 1:
return 1
if memo[order] == 1:
memo[order] = fib3(order-1) + fib3(order-2)
return memo[order]
ジェネレーター関数
ジェネレーターはリストよりメモリ消費が圧倒的に少ない(sys.getsizeを使って実際試してみましょう)。for文で回すとジェネレーターは尽きる。
※参考:リストとジェネレータでメモリ使用量とコストを比較する
- 基本: yield、next、send、throwを使う
yieldが呼び出されるとそのたびに処理がストップし、値が返る
def gen_func(num):
print("generate gen_func")
for n in range(num):
yield n
print("yield実行")
if __name__ == "__main__":
gen = gen_func(10) # これだけでは処理は走らない
n = next(gen)
print("n", n) # debug
"""結果
generate gen_func
n 0
"""
n = next(gen)
print("n", n) # debug
"""結果
yield実行
n 1
"""
n = next(gen)
print("n", n) # debug
"""結果
yield実行
n 2
"""
もちろん毎回next()を使うのはだるいので普通は下記のようにやります。
動き方がこの方が直感的ですね。(nextを使うとyieldで止まってprintはされないことを比較してみて!)
if __name__ == "__main__":
gen = gen_func(10)
for g in gen:
print("g", g) # debug
- 因数分解的なやつ(実際やるならリストを使ったほうが良いとは思います)
ここでのポイントは「ジェネレーターをnext使わずにfor文を使って回すことが出来る」ということです。
def generate_prime_factors(n, n_max):
for i in range(1, n_max + 1):
if n % (i) == 0:
yield i
if n // (i) != i:
yield int(n // (i))
if __name__ == "__main__":
n = 72 # 72の因数分解などを出したい
max_n = math.sqrt(n) # 素因数を出すにはせいぜい√対象数だけ試行すれば良い
gen = generate_prime_factors(n, n_max)
for i in gen:
print(i)
"""
#下記のように順番はそのまま出てくるのでリストを使って普通に後でsortしてprintしたほうが良い
1
72
2
36
3
24
4
18
6
12
8
9
"""
ちなみに素因数分解なら下記
def generator_facotorize(n, n_max):
i = 2
while n >= i:
if n % i == 0:
yield i
n /= i
i = 1
i += 1
- 応用: リスト内包表記っぽく(listにするなら[]で囲えば良い)
# 1~10の中で偶数のみ入る
new_gen = (x for x in range(1, 11) if x % 2 == 0)
for i in new_gen:
print(i)
- sendを使う
yieldで停止している箇所に値を送る
まずは下準備。
def gen_func(num):
print("generate gen_func")
for n in range(1, num):
x = yield n
print(x)
print("yield実行")
if __name__ == "__main__":
gen = gen_func(10)
n = next(gen)
print("n", n) # debug
n = next(gen)
print("n", n) # debug
"""ポイントはxがNoneとなっていること
generate gen_func
n 1
x None
yield実行
n 2
"""
つづいてsendを使用してみます。(上で作った関数は共通)
# Part1
gen = gen_func(10)
n = next(gen)
print(n)
# gen.send(10)
"""
generate gen_func
1
"""
# Part2
gen = gen_func(10)
n = next(gen)
print(n)
n = gen.send(10)
print(n)
"""
generate gen_func
1
x 10
yield実行
2
"""
# Part3(若干蛇足)
gen = gen_func(10)
n = next(gen)
n = gen.send(10)
next(gen)
print(n)
"""
generate gen_func
x 10
yield実行
x None
yield実行
2
"""
上記よりsendはnextと同様に処理をすすめる+値を送る機能があるとわかる。
※公式にも「ジェネレータ関数の内部へ値を "送り"、実行を再開します」と書いていますね。
※ちなみにいきなりnextをせずsendをすると「can't send non-None value to a just-started generator
」とタイプエラーになる
- throw
指定した例外を発生させて処理を終了させる。
gen = gen_func(10)
n = next(gen)
print(n)
n = gen.send(100)
print(n)
gen.throw(ValueError("Invalid Value"))
n = next(gen) # ここはもう動かない
"""
generate gen_func
1
x 100
yield実行
2
Traceback (most recent call last):
File "hoge.py", line 16, in <module>
gen.throw(ValueError("Invalid Value"))
File "hoge.py", line 4, in gen_func
x = yield n
ValueError: Invalid Value
"""
ちなみに公式の説明を借りれば「ジェネレータが中断した位置で type 型の例外を発生させて、そのジェネレータ関数が生成する次の値を返します」ということなので処理自体は進んでいます。これはtry exceptを使えばわかることです。
def gen_func(num):
print("generate gen_func")
for n in range(1, num):
try:
x = yield n
print("x", x) # debug
print("yield実行")
except Exception as e:
print("hoge")
if __name__ == "__main__":
gen = gen_func(10)
n = next(gen)
print(n)
n = gen.send(100)
print(n)
n = gen.throw(ValueError("Invalid Value"))
print(n)
n = next(gen)
print(n)
"""
generate gen_func
1
x 100
yield実行
2
hoge
3
x None
yield実行
4
"""
- close
公式の「ジェネレータ関数が一時停止した時点で GeneratorExit を発生させます」という説明だと例外を投げるだけなのか?と一瞬なりましたが普通にgeneratorを終了させる処理です。
if __name__ == "__main__":
gen = gen_func(10)
n = next(gen)
print(n)
n = gen.close()
print(n)
next(gen)
"""
generate gen_func
1
None
Traceback (most recent call last):
File "hoge.py", line 19, in <module>
next(gen)
StopIteration
"""
サブジェネレーター(TODO)
ジェネレーターからジェネレーターを呼び出すことが出来る。呼び出される側をサブジェネレーターと言う。
「yield from 呼び出し先」と書く。「return」で呼び出し先に値を返すことが出来る。またサブサブジェネレーターが「yield」を使うと呼び出し先(サブジェネレーター)に値が返る。
-
イメージ
メイン処理 -> ジェネレータ -> サブジェネレーター -> サブサブジェネレーター
(サブジェネレーターの)returnでジェネレータに。yieldでメイン処理に値が返る。 -
基本(準備)
def sub_sub_generator():
yield "Sub subのyield"
return "Sub subのreturn"
def sub_generator():
yield "subのyield"
res = yield from sub_sub_generator()
print(f"sub_genでの{res=}")
return "subのreturn"
def origin_generator():
yield "generatorのyield"
res = yield from sub_generator()
print(f"originの{res=}")
return "originのreturn"
- 結果 Part1
if __name__ == "__main__":
gen = origin_generator()
n = next(gen)
print("n", n) # debug
n = next(gen)
print("n", n) # debug
n = next(gen)
print("n", n) # debug
# n = next(gen)
# print("ここでストップ(表示されない)")
# print("n", n) # debug
n generatorのyield
n subのyield
n Sub subのyield
- 結果 Part2
if __name__ == "__main__":
gen = origin_generator()
n = next(gen)
print("n", n) # debug
n = next(gen)
print("n", n) # debug
n = next(gen)
print("n", n) # debug
n = next(gen)
print("ここでストップ(表示されない)")
print("n", n) # debug
n generatorのyield
n subのyield
n Sub subのyield
sub_genでのres='Sub subのreturn'
originのres='subのreturn'
Traceback (most recent call last):
File "sub.py", line 27, in <module>
n = next(gen)
StopIteration: originのreturn
ジェネレーター結局何に用いるのか
莫大なデータを格納してもリストと違ってメモリ使用量が少なく済む!!(処理は遅くなる)
DBから引っ張り出す時に活きてくる。
# list, generator, memory
import sys
def num_generator(n):
i = 0
while i < n:
yield i
i += 1
if __name__ == "__main__":
list_a = [i for i in range(100000)]
print(sys.getsizeof(list_a))
ng = num_generator(100000)
print(sys.getsizeof(ng))
リスト内包表記
- 例
jump_dict = {1: "鬼滅の刃",
2: "呪術廻戦"}
hoge_list = ["a", "b", 1, "c", 2]
jump_list = [jump_dict.get(x) for x in hoge_list if type(x) == int]
print("jump_list", jump_list) # debug
- 二重配列をリスト内包表記で
grids = [[None] * 3 for _ in range(3)] # 二重配列
デコレータ
デコレータの説明はぶっちゃけ公式がわかりやすい気がする
『デコレータの文法はシンタックスシュガーです。次の2つの関数定義は意味的に同じものです:』
def f(...):
...
f = staticmethod(f)
@staticmethod
def f(...):
...
ちなみにシンタックスシュガーとは、日本語に直せば糖衣構文と言ってまぁある構文をもっと楽(簡潔)に書ける書き方だよということです。
関数間である処理を共通に利用したい場合によく用いられます。
- 基本(準備)
def deco(func):
def wrapper(*args, **kwargs):
print(args[0])
if args[0] == 1:
return 1
print("*" * 100)
func(*args, **kwargs)
print("*" * 100)
return wrapper
@deco
def func_a(*args, **kwargs):
print("func_aを実行")
print(args)
@deco
def func_b(*args, **kwargs):
print("func_aを実行")
print(args)
if __name__ == "__main__":
func_a(2, 3, 5)
func_b(1, 3, 5, 7)
- 基本(結果)
2
****************************************************************************************************
func_aを実行
(2, 3, 5)
****************************************************************************************************
1
※追記:Twitter
def hoge(func):
def wrapper(n):
print("hoge")
func(n)
return wrapper
def fuga(n):
print(n)
if __name__ == "__main__":
hoge(fuga)(5)
補足:クロージャーとエンクロージャー
可変長引数
*args:可変長のタプル
**kwargs:可変長の辞書
def func_a(*args, **kwargs):
map関数
map(関数, 引数)
イテレーターを第二引数で入力として受け取り、
実行時に第一引数の関数に代入した値を出力する。
- 基本
下記で標準入力を受け取ってそれを一つづつint型にしたリストを作成できる
if __name__ == "__main__":
num_l = list(map(int, input().split()))
print(num_l)
- 応用
lambdaと組み合わせて。
if __name__ == "__main__":
list_a = [i for i in range(1, 6)]
map_a = map(lambda x: x**2, list_a)
for a in map_a:
print(a)
- 応用2
辞書と組わせて。
if __name__ == "__main__":
man_info = {
"name": "Ichiro",
"age": "18",
"country": "Japan",
}
map_man = map(lambda x: x + "," + man_info.get(x), man_info)
for i in map_man:
print(i)
- 応用3
複数の引数を持つ関数と。
def calc_two(x, y, sign):
if sign == "plus":
return x + y
elif sign == "minus":
return x - y
if __name__ == "__main__":
result_map = map(calc_two, range(5), (3, 3, 3, 3, 3), ["plus", "minus", "minus", "plus", "plus"])
for r in result_map:
print(r)
クラス オブジェクト指向 前編
オブジェクト指向は筆者自身がまだ不慣れなためかなり基礎からスタートする。
Part1 クラスの基礎の基礎
プロパティ:属性
メソッド:機能、要は関数
ドキュメンテーション文字列:クラスの説明、helpで呼び出せる
class クラス名:
"""ドキュメンテーション文字列"""
プロパティ
def メソッド名(self,...):
インスタンス名 = クラス名() # インスタンス化
help(クラス名)
Part2 2種類のプロパティ
クラス変数とインスタンス変数
(1)クラス変数
オブジェクト同士で共有することが出来る変数。
=>あるクラス及びそのクラスからインスタンス化されたインスタンス同士で共有している。
メソッド内部ではなくクラス直下に書け!!
クラス名.クラス変数
インスタンス名.__class__.クラス変数
(2)インスタンス変数
インスタンスごとに別々に利用する変数。
メソッドの内部に記述。
インスタンス名.インスタンス変数
- 具体例
class Hoge():
class_val = "class value"
def set_val(self):
self.instance_val = "instance value"
def print_val(self):
print(f"クラス変数 = {self.class_val}")
print(f"インスタンス変数 = {self.instance_val}")
print(f"くらす変数 = {self.__class__.class_var}") # この書き方のほうがより正確(selfはインスタンスなので)
if __name__ == "__main__":
instance_hoge = Hoge() # インスタンス化
instance_hoge.set_val()
instance_hoge.print_val()
print(Hoge.class_val) # クラスから
print(instance_hoge.__class__.class_val) #インスタンスからは__class__を使って
print(instance_hoge.class_val) # これもアクセス出来てしまうんだよなぁ。。。
3つ目がなぜ出来るのかイマイチわかってないので保留。
=>selfがクラス自身を表しクラスの中でself.class_valがアクセス出来るんだしそりゃそうやろって話よ。
=>selfはインスタンスを表す。clsがクラスを表す。(あくまでPEP8で決まったルールであり本質的にはどっちも一緒。)
いずれにしてもself.__class__.クラス変数、self.インスタンス変数という書き方が正確だと思う。
なお、class変数はオブジェクト(インスタンス群+そのクラス)間でも当然共有されているので
hoge1 = Hoge()
hoge2 = Hoge()
hoge1.__class__.class_val = "fuga"
# 下記の結果はいずれもfuga
print(hoge2.__class__.class_val)
print(Hoge.class_val)
※実際クラス変数をid関数で調べれば同じidが出てくる
※やたら修正するのは駄目
ちなみにインスタンス変数は当然共有されない。
# 結果はinstance value
print(hoge.instance_val)
hoge.instance_val = "fuga"
# 結果は(hogeインスタンスの影響受けず)instance valueのまま!
print(hoge2.instance_val)
クラス オブジェクト指向 中編
Part3 コンストラクタ (_init_)
コンストラクタとはオブジェクトをインスタンス化する際に呼び出されるメソッドのこと。
※ほぼ必ず定義!
Part4 デストラクタ(_del_)
インスタンスを削除する際に呼び出される。
インスタンスの削除はdel インスタンス名
で良い。
# Part 3 ~ 4
class Hoge():
def __init__(self, name):
print("nameが初期化されます")
self.name = name # インスタンス変数
def __del__(self):
print("デストラクタが実行中")
print(f"name={self.name}")
def print_name(self):
print(f"インスタンス変数 = {self.name}")
if __name__ == "__main__":
hoge = Hoge("kumamoto")
hoge.print_name()
del hoge # delを書かなくてもスクリプトの最後にデストラクタが実行される
Part5 3種類のメソッド(特殊メソッドを除く)
インスタンスメソッド と クラスメソッド 、 スタティックメソッド
(1) インスタンスメソッド
クラスから作成したオブジェクト(インスタンス)を用いて呼び出すメソッド(クラスからは当然アクセス出来ない)
(2)クラスメソッド
クラスをインスタンス化せずに実行できるメソッド
@classmethodをメソッドの上につける。(デコレータ)
また、clsを引数に取り、cls.変数でクラス変数にアクセス出来る。(クラスからもインスタンスからもアクセスできる)
※参考:Difference between 'cls' and 'self' in Python classes? => PEP8
- Always use
self
for the first argument to instance methods. - Always use
cls
for the first argument to class methods.
(3)スタティックメソッド
(インスタンスメソッドやクラスメソッドと違って)インスタンス(self)やクラス(cls)が引数に渡されることはない。
@staticmethodをつける。荒く言えば他のメソッドと違い普通の関数と思えば良い
クラス変数へもインスタンス変数にもアクセスできない!(クラスからもインスタンスからもアクセスできる)
class Human():
class_name = "Human" # クラス変数
def __init__(self, name, age):
self.name = name # インスタンス変数
self.age = age # インスタンス変数
# インスタンスメソッド
def print_name(self):
print("インスタンスメソッド実行")
print(f"名前は{self.name}、年齢は{self.age}") # インスタンス変数
# クラスメソッド
@classmethod
def print_class_name(cls, msg):
print("クラスメソッド実行")
print(f"クラスnameは{cls.class_name}") # クラス変数
print(msg)
# スタティックメソッド こいつはクラスcls(クラス)やself(インスタンス)を引数に取らない/アクセス出来ない
@staticmethod
def print_msg(msg):
print("スタティックメソッド実行")
print(msg)
if __name__ == "__main__":
# クラスメソッド実行
Human.print_class_name("おはよう")
# インスタンスメソッド実行
man = Human("キズナアイ", 17)
man.print_name()
# スタティックメソッド実行
Human.print_msg("クラスから、こんばんは")
man.print_msg("インスタンスから、こんばんは")
スタティックメソッドを使うのは敢えてクラス変数やインスタンス変数にアクセス出来ないようにする制限を設けることで開発メンバーに「これは単なる関数だよ」と明示的に伝えるためのものです。
クラス オブジェクト指向 後編 その1
Part6 特殊メソッド
インスタンスにアクセスする際に特定の処理を実行すると呼び出されるメソッド
例)a+bとして実行する時内部的にはa.add(b)として呼び出される。
メジャーな下記についてまず理解しよう。
※参考:Python初心者でも__hoge__使いたい!〜特殊属性,特殊メソッドの使い方〜
(1)__str__: str(obj)、print(obj)の際に呼び出される。objを文字列として返す。
(2)__bool__: if文で論理値を返すことが出来る
※__bool__の中にはどういう場合にTrue/Falseを書いておく
(3)__len__: len()実行時に呼び出される
(4)__eq__: ==の際に呼び出される
(5)__hash__: 文字列をハッシュ値として返す。この関数を定義することでクラスを辞書として扱う時やsetに要素を入れる際に利用する。
=>set, frozenset, dict のようなハッシュを使ったコレクション型の要素に対する操作から呼び出される!=>setやdictに入れたいときに使おう
※参考:Python における hashable
※参考2:set オブジェクトは、固有の hashable オブジェクトの順序なしコレクション 公式
(6)__name__: クラスの名前を表す
(7)__dict__: クラス(obj)の(書き込み可能な) 属性を保存するために使われる辞書またはその他のマッピングオブジェクトです。あくまでデバック用に使ってください!
※補足:アクセスするときは何か処理をしてから返すというようになっていた場合も,それを全部無視してアクセスできてしまうため
※補足2:名前空間の実装に対する抽象化を侵すことになるって公式に書いてある。
(8)__doc__:
※参考:公式-特殊メソッド
以下下記のクラスを共通して使うことにする
class Human():
"""This is the Human class for studing class
"""
class_name = "Human"
def __init__(self, name, age, tel_num):
self.name = name
self.age = age
self.tel_num = tel_num
def __str__(self):
return f"""class_name: {self.__class__.class_name}
- name: {self.name}
- age: {str(self.age)}
- tel_num: {self.tel_num}"""
def __bool__(self):
return True if self.age >= 20 else False
def __len__(self):
print("__len__ が呼び出された")
return len(self.name)
def __eq__(self, other):
return (self.name == other.name) and (self.tel_num == other.tel_num)
def __hash__(self):
# return hash(self.name) # nameの要素しか見ない
# return hash(self.name + self.tel_num) # こういう書き方もあり(もちろん下とハッシュ値は違う)
return hash((self.name, self.tel_num))
def doc_test():
"""This function is for test
"""
pass
(1)__str__
# __str__があるとき
if __name__ == "__main__":
man = Human("TakemotoPiano", 39, "0120-37-0009")
print(man, type(man))
=> Human <class '__main__.Human'>
man_str = str(man)
print(man_str, type(man_str))
=>Human <class 'str'>
# __str__がないとき
if __name__ == "__main__":
man = Human("TakemotoPiano", 39, "0120-37-0009")
print(man, type(man))
=> <__main__.Human object at 0x10925b520> <class '__main__.Human'>
man_str = str(man)
print(man_str, type(man_str))
=> <__main__.Human object at 0x10925b520> <class 'str'>
※+アルファ
こんな感じにすればすばやくそのインスタンスの変数を確認出来て便利
def __str__(self):
return f"""- class_name: {self.__class__.class_name}
- name: {self.name}
- age: {str(self.age)}
- tel_num: {self.tel_num}"""
if __name__ == "__main__":
print(man, type(man))
- class_name: Human
- name: TakemotoPiano
- age: 39
- tel_num: 0120-37-0009 <class '__main__.Human'>
(2)__bool__
man = Human("TakemotoPiano", 39, "0120-37-0009")
if man:
print(f"{man.name}さんは20歳以上です")
else:
print(f"{man.name}さんは未成年です")
=> TakemotoPianoさんは20歳以上です
(3)__len__
man = Human("TakemotoPiano", 39, "0120-37-0009")
print(len(man))
__len__ が呼び出された
13
=> TakemotoPianoの13と出る!
(5)__hash__
- 前提 :「hash化とは」を理解する
man = Human("TakemotoPiano", 39, "0120-37-0009")
print(hash(man.name + man.tel_num))
print(hash("TakemotoPiano" + "0120-37-0009"))
=> 上記が同じ結果になる
man = Human("TakemotoPiano", 39, "0120-37-0009")
man2 = Human("TakemotoPiano", 29, "0120-37-0009")
man3 = Human("YamahaPiano", 29, "3120-37-0009")
# __hash__がないと
set_men = {man, man2, man3}
=> TypeError: unhashable type: 'Human' #クラスはhash化出来ないと出る!
# __hash__があると
set_men = {man, man2, man3}
for i, x in enumerate(set_men):
print(f"{i}番目")
print(x)
0番目
class_name: Human
- name: YamahaPiano
- age: 29
- tel_num: 3120-37-0009
1番目
class_name: Human
- name: TakemotoPiano
- age: 39
- tel_num: 0120-37-0009
=>上記より、nameとtel_numが両方一緒のインスタンスは入らないことがわかる。
=>__hash__が下記のような形なら今回は全部入る(全ての要素が同じオブジェクトは今回ないので)
def __hash__(self):
return hash((self.name, self.age, self.tel_num))
(6)__eq__
if __name__ == "__main__":
man = Human("TakemotoPiano", 39, "0120-37-0009")
man2 = Human("TakemotoPiano", 29, "0120-37-0009")
print(man == man2) # man2がotherになっている
=> True
print(man.__eq__(man2))
=> True
(7)__dict__
if __name__ == "__main__":
man = Human("TakemotoPiano", 39, "0120-37-0009")
print(man.__dict__)
=> {'name': 'TakemotoPiano', 'age': 39, 'tel_num': '0120-37-0009'}
(8)__doc__
print(man.__doc__)
=> This is the Human class for studing class
print(man.doc_test.__doc__)
=> This function is for test
番外編 クラスを使った実例
class Character:
def __init__(self, name, hp, offence, defense):
AllCharacter.character_append(name)
self.name = name
self.hp = hp
self.offence = offence
self.defense = defense
def attack(self, enemy, critical_point=1):
if self.hp <= 0:
print("キャラクターは死んでいます")
return
attack_point = self.offence - enemy.defense
attack_point = 1 if attack_point <= 0 else attack_point
enemy.hp -= attack_point * critical_point
if enemy.hp <= 0:
AllCharacter.character_delete(enemy.name)
def critical_hit(self, enemy):
self.attack(enemy, 2) # atack()の使いまわし!selfがないことに注目!
class AllCharacter:
all_characters = []
alive_characters = []
dead_characters = []
@classmethod
def character_append(cls, name):
if name in cls.all_characters:
raise CharacterAlreadyExistException("キャラクターはすでに存在します")
cls.all_characters.append(name)
cls.alive_characters.append(name)
@classmethod
def character_delete(cls, name):
cls.dead_characters.append(name)
cls.alive_characters.remove(name)
class CharacterAlreadyExistException(Exception):
pass
if __name__ == "__main__":
character_a = Character("A", 10, 5, 3)
character_b = Character("B", 8, 6, 2)
クラス オブジェクト指向 後編 その2
Part7 継承(Inheritance)
-
継承とは?
ある別のクラスからそのクラスの持っている性質を引き継ぐこと。
例)車(スーパークラス) => 軽自動車 (サブクラス)
この時軽自動車は車と同じプロパティ/メソッドを継承する -
オーバーライドとオーバーロード
「オーバーライド」とは親クラスと同じ名前、同じ引数、同じ戻り値のメソッドを上書いて再定義すること。
一方で「オーバーロード」とは引数や返り値が異なるが名称が同一のメソッドを複数定義すること。Pythonでは(Javaと違い)引数や戻り値の違うメソッド群を区別しないので下にあるものに上書きされる。そういう意味でPythonではオーバーロード出来ないと言われることがある。
親クラス Person
# クラスの継承
class Person: # 親(スーパー)クラス
def __init__(self, name, age):
self.name = name
self.age = age
def greeting(self):
print(f"hello {self.name}")
def say_age(self):
print(f"{self.age} years old")
まずは最低限の子クラス
これでも動く。継承してるから当然!
class Employee(Person): # Personの機能を継承
pass
if __name__ == "__main__":
e = Employee("Kumamoto", 26)
print("e.name", e.name) # debug
print("e.age", e.age) # debug
e.greeting()
e.say_age()
子クラスを肉付け
見るべきポイントはsuper()による親メソッドのそのままの利用。
あと、オーバーライド/オーバーロード(仮)。
# クラスの継承
class Person: # 親(スーパー)クラス
def __init__(self, name, age):
self.name = name
self.age = age
def greeting(self):
print(f"hello {self.name}")
def say_age(self):
print(f"{self.age} years old")
class Employee(Person): # Personの機能を継承
def __init__(self, name, age, number):
super().__init__(name, age) # 親クラスのメソッドをsuper()で呼び出し
self.number = number
def say_number(self): # 子クラス特有のメソッド
print(f"my number is {self.number}")
def greeting(self): # オーバーライド
super().greeting()
print(f"I'm employee phone_num = {self.number}")
def greeting(self, num): # オーバーロード
super().greeting()
print(f"急に思い立った数字を言います {num}です!")
print("""\
pythonではオーバーロード(引数違いのメソッドを定義する)はできない
下(今回だとこのメソッドに)に上書きされる/上を実行するとエラーになる\
""")
if __name__ == "__main__":
man = Employee("Kumamoto", 26, "001-000-000")
print("man.name", man.name) # debug
print("man.age", man.age) # debug
print("man.number", man.number) # debug
# オーバーロード的なことをしたければnum=Noneなどにして引数があるときとない時で分けるなどが有効か
# man.greeting() # TypeError: greeting() missing 1 required positional argument: 'num'
man.greeting(num=5)
man.say_age()
-
ポリモーフィズム(Polymorphism/多態性)とは?
サブクラスを複数作成して、サブクラスごとに同じ名前のメソッドをそれぞれ作成し処理の中身を変える。呼び出す際には中身を意識せずに実行出来るのが嬉しい。
例)人クラス => アメリカ人クラス/日本人クラス
この時アメリカ人クラスの話すのメソッドは英語で、日本人クラスは日本語で実行される。
クラス 発展編 多重継承
多重継承
# クラスの多重継承
class ClassA:
def __init__(self, name):
self.a_name = name
def print_a(self):
print("ClassAのメソッド実行")
print(f"a = {self.a_name}")
def print_hi(self):
print("A hi")
class ClassB:
def __init__(self, name):
self.b_name = name
def print_b(self):
print("ClassBのメソッド実行")
print(f"b = {self.b_name}")
def print_hi(self):
print("B hi")
class NewClass(ClassA, ClassB):
def __init__(self, a_name, b_name, name):
ClassA.__init__(self, a_name)
ClassB.__init__(self, b_name)
self.name = name
def print_new_name(self):
print(f"name = {self.name}")
def print_hi(self): # オーバーライド
ClassA.print_a(self)
ClassB.print_b(self)
print("NewClass hi")
if __name__ == "__main__":
sample = NewClass("AName", "BName", "New Class Name")
sample.print_a()
sample.print_b()
sample.print_new_name()
sample.print_hi()
補足
-
ClassA.__init__(self, a_name)
とsuper().__init__(a_name)
の違い
前者はselfを引数に取るが、後者は取らない。これはなぜだろうか。
前者は、親クラスのコンストラクタでもあるインスタンスメソッド(init)を子クラスからインスタンス化せずに呼び出しています。そのため第一引数selfにはインスタンス自身が自動的に入ります。
また前者はClassAのコンストラクタをスタティックメソッドのように呼び出し、第一引数selfをself(つまり子クラスのインスタンス)、第2引数をa_nameとしています。
他実例)
a_instance = ClassA()
a_instance.method_a()
インスタンスメソッドはスタティックメソッドのように実行することもできますがインスタンス化していないため、引数selfには自動的にインスタンスは入りません。そのため、明示的にCLASS.method_a(instance)として、instanceを引数にとならなければならなくなります。
クラス オブジェクト指向 発展編 メタクラス
ポリモーフィズムの抽象クラスをやる前にまずは「クラスを再定義するクラス」であるメタクラスについて学ぼう。
※抽象クラスの原理はメタクラスを元にしている。
メタクラスは主に「その定義で良いのかクラスを検証する際に用いる」
デフォルトのメタクラスはtypeクラスだが、自身で定義することも出来る。
class定義時に継承と同じ形で「metaclass=メタクラス名」とすると自身のメタクラスを指定出来る。
class Meta(type):
def __new__(metacls, name, bases, class_dict):
# クラスのチェックを行う
# name:クラスの名前
# bases:継承しているクラス
# class_dict:クラスの持っている値、関数等
return super().__new__(metacls, name, bases, class_dict) # typeクラスでクラスを生成
class MetaException(Exception):
pass
class Meta1(type): # type(デフォルトのメタクラス)
def __new__(metacls, name, bases, class_dict):
print(f"metacls = {metacls}")
print(f"name = {name}")
print(f"bases = {bases}")
print(f"class_dict = {class_dict}")
# if "my_var" not in class_dict.keys():
# raise MetaException("my varを定義してください")
for base in bases: # 継承したくないクラスをここで設定できる(Javaで言うfinalクラス)
if isinstance(base, Meta1):
raise MetaException("継承出来ません")
return super().__new__(metacls, name, bases, class_dict)
class ClassA(metaclass=Meta1):
a = "123"
my_var = "hoge"
pass
class SubClassA(ClassA):
pass
if __name__ == "__main__":
pass
クラス オブジェクト指向 発展編 ポリモーフィズム(多態性)
「ポリモーフィズム」とはサブクラスを複数作成し、同じメソッドを定義して呼び出す際にどのクラスか意識せずに呼び出すことだ。
抽象クラスの抽象メソッドを定義しない子クラスがあると下記のようなエラーコードが出る。
TypeError: Can't instantiate abstract class Woman with abstract methods say_something
from abc import abstractmethod, ABCMeta
class Human(metaclass=ABCMeta):
def __init__(self, name):
self.name = name
@abstractmethod
def say_something(self):
pass
def say_name(self):
print(self.name)
class Man(Human):
def say_something(self):
print("おとこ!")
class Woman(Human):
def say_something(self):
print("おんな!")
if __name__ == "__main__":
while True:
flag = int(input("0か1か入力してください"))
if flag not in (0, 1):
print("入力が間違ってるよ")
continue
elif flag == 0:
human = Man("Taro")
elif flag == 1:
human = Woman("Hanako")
human.say_something()
break
親クラスが抽象クラスをメタクラスとして指定していて、抽象メソッドが存在している場合は必ずそのメソッドを子クラスで実装する。
クラス オブジェクト指向 発展編 プライベート変数
カプセル化の前提となるプライベート変数について学ぶ
クラス変数、インスタンス変数は外部からアクセスして値を書き換えることが容易。
アクセス出来ない(しないでほしいと明確化した)変数、プライベート変数を使用しよう。
※プライベート変数は__variableとアンダースコア2つ付けて定義する
class Human:
# クラス変数のプライベート変数
__msg = "Hello"
def __init__(self, name, age):
# インスタンス変数のプライベート変数
self.__name = name
self.__age = age
# プライベート変数にアクセスするならクラス内から
def print_msg(self):
print(f"{self.__msg} name = {self.__name} {self.__age}")
if __name__ == "__main__":
taro = Human("Taro", 20)
jiro = Human("Jiro", 18)
taro.print_msg()
# print("taro.__name", taro.__name) # これはAttributeError: 'Human' object has no attribute '__name'
print("taro._Human__name", taro._Human__name) # 特殊な書き方でアクセス自体は出来る
print("Human._Human__msg", Human._Human__msg) # debug # 特殊な書き方でアクセス自体は出来る
クラス オブジェクト指向 発展編 カプセル化 前半
参考:python property
参考:pythonのプロパティのあれこれ
Private変数を使う場合にはクラスの外部から変数が見えないようにする(カプセル化)必要があります。
カプセル化をする場合にはgetter
とsetter
を定義して利用しないと変数にアクセスが出来ないようにします。
カプセル化の方法 その1
setter、getter、propery
property関数を使うことで外部的にインスタンス変数のように扱えるが内部的には必ず実行したい処理を走らせることが出来る
def get_変数名()
def set_変数名()
変数名 = property(get_変数名, set_変数名)
実際にやってみる。
# getter, setterその1
class Human:
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
print("getter nameを呼び出しました")
return self.__name
def get_age(self):
print("getter ageを呼び出しました")
return self.__age
def set_name(self, name):
print("setter nameを呼び出しました")
self.__name = name
def set_age(self, age):
print("setter ageを呼び出しました")
self.__age = age
name = property(get_name, set_name) # nameを指定するとget_name, set_nameが呼び出される
age = property(get_age, set_age) # ageを指定するとget_name, set_nameが呼び出される
if __name__ == "__main__":
human = Human("Taro", 15)
human.name = "Jiro" # 普通のインスタンス変数にアクセスしているように見えて実はsetterを呼び出し
print("human.name", human.name) # 普通のインスタンス変数にアクセスしているように見えて実はgetter呼び出し
クラス オブジェクト指向 発展編 カプセル化 後半
# getter, setterその2
class Human:
def __init__(self, name, age):
self.__name = name
self.__age = age
@property
def name(self): # getter
print("getter nameを呼び出しました")
return self.__name
@property
def age(self):
print("getter ageを呼び出しました")
return self.__age
@name.setter
def name(self, name):
print("setter nameを呼び出しました")
self.__name = name
@age.setter
def age(self, age):
if age < 0:
print("0以上の値を入力してね")
return
print("setter ageを呼び出しました")
self.__age = age
if __name__ == "__main__":
human = Human("Taro", 15)
human.name = "Jiro" # 普通のインスタンス変数にアクセスしているように見えて実はsetterを呼び出し
human.age = -13
print("human.name", human.name) # 普通のインスタンス変数にアクセスしているように見えて実はgetter呼び出し
propetyを使って変数をアクセス専用にする
class MyProperty(object):
def __init__(self, x):
self._x = x
@property
def x(self):
return self._x
mypro = MyProperty(100)
print(mypro.x) # 100
mypro.x = -200
AttributeError # 定義されていないのでエラー
例外処理
例外処理とは実行時エラーを制御して処理を行うこと。
try:
処理
except エラー名:
例外発生時の処理
else:
例外が発生しなかった場合のみの処理
finally:
例外の有無に関わらずの処理
代表的な例外
- FileNotFoundError:指定ファイルが見つからない
- IndexError:配列で指定したインデックスに値が存在しない
- TypeError:型に関するエラー
- ZeroDevisionError:0で割ろうとしたことによるエラー
- Exception:全ての例外
具体例1
- スタックトレース
In computing, a stack trace (also called stack backtrace or stack traceback) is a report of the active stack frames at a certain point in time during the execution of a program. When a program is run, memory is often dynamically allocated in two places; the stack and the heap.
実行中のコンピュータプログラムにエラーが発生した際に、直前に実行していた関数やメソッドなどの履歴を表示すること。
- スタックとヒープ
ヒープ:ヒープ領域とは、動的に確保と解放を繰り返せるメモリ領域のこと
スタック:コンパイラやOSによってソフトウェアへの割り当てを決める、、確保したのとは逆の順番で解放する
if __name__ == "__main__":
try:
b = [10, 20, 30]
c = b.method_a()
a = b[4]
a = 10 / 0
except ZeroDivisionError as e:
import traceback
traceback.print_exc()
# print(e, type(e))
except IndexError as e:
print("indexerror発生")
except Exception as e:
print("Exception発生", e, type(e))
else:
print("Else処理")
finally:
print("Finally処理") # finallyはログ出力などにも利用
print("処理が完了したよ")
具体例2 例外を返す(raise)と例外クラスの自作
class MyException(Exception):
pass
def devide(a, b):
if b == 0:
raise ZeroDivisionError("0では割り切れません")
elif b < 0:
raise MyException("今回はマイナスで割らないで")
return a / b
if __name__ == "__main__":
try:
c = devide(10, -1)
except Exception as e:
print(e, type(e))
ファイルの入出力とWith
ファイル入力
(1)ファイルを開く:f = open(file_path, mode='r')
、mode='rb'
(2)ファイルの中身読み込み:f.read()
、f.readline()
、f.readlines()
(3)ファイルを閉じる:f.close()
(1)と(3)を省略するwith
を普通使う。
セイウチ演算子を用いたファイルのオープン/クローズ
with open("file.csv", mode="r") as f:
while msg := f.readline(): # msgがNoneになるまで読み込みが続く
print(msg)
入力まとめ
if __name__ == "__main__":
file_path = "./input.csv"
f = open(file_path, mode="r", encoding="utf-8")
# 中身全体
"""
line = f.read()
print(line)
"""
# 行単位で配列
# lines = f.readlines()
# lines = [l.rstrip() for l in lines]
"""
lines = [l.rstrip() for l in f.readlines()]
print(lines)
"""
# 一行づつ取得
# line = f.readline()
# while line:
# print(line.rstrip())
# line = f.readline()
while line := f.readline():
print(line.rstrip())
f.close() # 開きっぱなしだとメモリを食う
with open(file_path, mode="r", encoding="utf-8") as f:
lines = f.readlines()
print(lines)
ファイル出力
(1)ファイルを開く:f = open(file_path, mode='w')
、mode='a'、mode='wb'
(2)ファイルへの書き込み:f.write()
、f.writelines()
、f.write("\n".join(list))
、
if __name__ == "__main__":
file_path = "./input.csv"
# with open(file_path, mode="w", encoding="utf-8", newline="\n") as f: => windowsならこれCRLF変換
with open(file_path, mode="w", encoding="utf-8") as f:
f.write("hoge\nいい")
with open(file_path, mode="a", encoding="utf-8") as f:
list_a = [
["A", "B", "C"],
["a", "b", "c"],
]
# f.writelines(list_a[0]) # リストの中身がつながって出力
for a in list_a: # これなら指定のつなぎ目で書き込み可能
f.write("\n")
f.write(",".join(a))
with詳細
withの中の処理を実行する前にwithの後に指定したクラスの__init__
と__enter__
がそれぞれ呼ばれ、処理終了後にクラスの__exit__
メソッドが呼ばれる。
-
with
の使い所
(1)ファイルの書き込み処理(ファイル開く=>書き込む=>ファイル閉じる)
(2)DBへのデータの書き込み処理(DBへコネクションを張る=>書き込み=>コネクションを閉じる) -
with
で使えるクラスの作成
class WithTest:
def __init__(self, msg):
print("init called")
self.msg = msg
def __enter__(self):
print("enter called")
def __exit__(self, exc_type, exc_val, traceback): # エラーハンドリングタイプのための引数
print("exit called")
if __name__ == "__main__":
with WithTest("Hello") as t:
print("withの中")
-----------
init called
enter called
withの中
exit called
- さらに実用的な
with
で使うクラスの作成
class WithTest:
def __init__(self, file_name):
print("init called")
self.__file_name = file_name
def __enter__(self):
print("enter called")
self.__file = open(self.__file_name, mode="w", encoding="utf-8")
return self # as tのtに入る
def write(self, msg):
self.__file.write(msg)
def __exit__(self, exc_type, exc_val, traceback): # エラーハンドリングタイプのための引数3つ必要
print("exit called")
self.__file.close()
if __name__ == "__main__":
with WithTest("./input.csv") as t:
print("withの中")
t.write("fuga")
そのほか雑多な話
標準入力あれこれ
- 複数行の標準入力を受け取り
-- 入力(最初をn、あとをa_lリストにいれたいとき)
5
3
3
1
6
1
n, *a_l = map(int, sys.stdin.buffer.read().split())
print("a_l", a_l) # debug
1
2
5
num_l = list(map(int, sys.stdin.buffer.readlines()))
上記のようにbufferオブジェクトを上手く使うこと
bit演算子/シフト演算子
何方かと言えば、競プロで使う知識
-
参考
Python de アルゴリズム(bit全探索)
【Python】AtCorderに立ち向かうためのbit全探索その1 -
2進数の作り方を直感で理解する
# 普通にbin(num)した方がもちろん早い
def convert_bin(num):
bin_r = ceil(log2(num))
bin_l = [0] * bin_r
for i, b in enumerate(range(bin_r-1, -1, -1)):
tmp = num - (2 ** b)
if tmp >= 0:
num = tmp
bin_l[i] = 1
#joinはListの各要素が全てstrでなくてはならない
bin_s = "".join(map(str, bin_l))
return bin_s
切り上げ
ans1 = 4 //3
print(ans1) # 1 これは普通に切り捨て
ans2 = -(-4 // 3)
print(ans2) # 2 なんと!これで切り上げになる
正確には切り上げではなくmath.floor(x)のx以下で最大の整数になるというルールに従っているだけ。
たとえばx=-1.3ならx以下の最大の整数は-2となる。
参考:Floor division with negative number / stackover flow
モンティ・ホール問題
import random
first_result = [0, 0]
change_result = [0, 0]
for _ in range(1000):
doors = ["新車", "ヤギ", "ヤギ"]
random.shuffle(doors)
# print(doors)
first_select = random.randint(1, 3)
# print("first_select", first_select) # debug
door_no = [1, 2, 3]
door_no.remove(first_select)
# print("door_no", door_no) # debug
master_select = random.choice(door_no)
door_no.remove(master_select)
if doors[master_select - 1] == "新車":
master_select, door_no[0] = door_no[0], master_select
change_select = door_no[0]
# print("master_select", master_select) # debug
if doors[first_select - 1] == "新車":
first_result[0] += 1
change_result[1] += 1
else:
first_result[1] += 1
change_result[0] += 1
print("first_result", first_result) # debug
print("change_result", change_result) # debug
セイウチ演算子 :=
変数の代入と変数の使用が同時にできる。whileと一緒に使うパターンが多い。
n = 0
while(n:=n+1)<10:
print(n)
f文字列で小数点を指定
division = a // b
modulo = a % b
f_division = a / b
print(f"{division} {modulo} {f_division:.6f}")
標準入力あれこれ
import sys
readlines = sys.stdin.buffer.readlines # 複数行全てリスト,各要素全てbyteになってるけどあくまでリスト!
map_readlines = lambda: map(int, readlines()) # 上を全てintにしたmap
readline = sys.stdin.buffer.readline # 一行スペース空きをbyteで取る 毎回\nがツイてくる
map_readline = lambda: map(int, readline().split()) #上の各要素をintにしたmap
sreadline = lambda: readline().decode("utf-8").rstrip() # 上の要素を文字列にして合体
# read = sys.stdin.buffer.read # 複数行全てbyte、これはreadlinesと違ってリストにならない、ほぼ使わない
# read = sys.stdin.read # 複数行を複数行のまま文字列で取ってくる これはstr、使わない
# readline = sys.stdin.readline # 一行を文字列で、これはstr \nがツイてくる
# readlines = sys.stdin.readlines # 一行を文字列で、これもstr、毎度\nがツイてくる
# 普通は
input()
文字列を出すだけなら非buffer、intにするならbufferのほうがいいかも
※非bufferは最初からstrなので。
有効桁で切り捨て
# 小数点第三位まで
def truncate(num):
return math.floor(num * 1000) / 1000
ピックル pickle
オブジェクトの直列化(シリアライズ)とその復元(デシリアライズ)を行うために使用。
直列化とはPythonのオブジェクトをバイト列に変換する処理のことで、復元とはバイト列をPythonのオブジェクトに変換する処理のこと。
参考:[解決!Python]バイナリファイルを読み書きするには:pickle編
何が嬉しいのか?
例えば、pandasと呼ばれるデータ解析ライブラリでは、巨大なサイズのCSVファイルを読み込んで、それにさまざまな形で手を加えることがよくある。そうしたときには、CSVファイルを読み込んで、パースするだけでも時間がかかることがよくある。そうした場合には、CSVファイルを読み込んで、いろいろな操作を加えた時点で、それらを一度ファイルに保存しておくと、その後、それを読み込むだけで以前の作業結果を復元できる。
参考:[Python入門]pickleモジュールによるオブジェクトの直列化
JSON
JSON文字列 => 辞書
-
load
「ファイルオブジェクト型」を引数に取る。 -
loads
「string型」を引数に取る。(loadsのsはstringの略)
辞書 => JSON文字列
-
dump
「ファイルオブジェクト型」を返す -
dumps
「string型」を返す
長い文字列を複数行に分けて書く
Pythonで時間を扱う
タイムゾーン ^Python3.9
djangoならdjango.utils.timezoneに頼れば良いが。
from datetime import datetime
from zoneinfo import ZoneInfo
tokyo = ZoneInfo("Asia/Tokyo") # タイムゾーン情報を取得
now = datetime(2020, 10, 1, 0, 0, 0, tzinfo=tokyo) # Asia/Tokyoタイムゾーンでの現在時刻を取得
print(now.isoformat())
こういう書き方もおk
now = datetime.now(ZoneInfo("Asia/Tokyo"))