Closed
23

[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)

補足:クロージャーとエンクロージャー

【Python】クロージャ(関数閉方)とは

可変長引数

*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=メタクラス名」とすると自身のメタクラスを指定出来る。

※参考:__new__と__init__の違い

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変数を使う場合にはクラスの外部から変数が見えないようにする(カプセル化)必要があります。
カプセル化をする場合にはgettersetterを定義して利用しないと変数にアクセスが出来ないようにします。

カプセル化の方法 その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演算子/シフト演算子

何方かと言えば、競プロで使う知識

# 普通に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
このスクラップは4ヶ月前にクローズされました
作成者以外のコメントは許可されていません