Remrinのpython攻略日記

python3に入門しました。python3についてあれこれとサンプルコードとか。

クラス

クラスについて。
 

クラスとは

クラス…オブジェクトの設計図のようなもの。
    データとメソッドを持つ。
インスタンス…設計図から作られた実体。オブジェクト
 
・クラス名は大文字で始める。

class Color_test():
    pass

a = Color_test()
print(a)       # <__main__.Color_test object at 0x....>

a.red = 255
print(a.red)   # 255

上記のようにインスタンス作成後に属性(attribute=変数)を追加できるが、
保守性がよくない。
 
・スロット
スロットで、追加できる属性(変数)を制限できる。

class Color_test():
    __slots__ =["green", "red"]
    def __init__(self):
        self.green = 10

a = Color_test()

a.red = 255
print(a.red)   # 255
print(a.green) # 10
a.blue = 0     # AttributeError: 'Color_test' object has no attribute 'blue'

 
インスタンス作成時に1度だけ呼ばれる__init()__ブロックで使用する変数を定義するのが一般的。

class Triangle2():
    def __init__(self, a, b, c):
        self.a , self.b, self.c = a, b, c

t = Triangle2(3, 4, 5)
print(t.a)   # 3
print(t.b)   # 4
print(t.c)   # 5

 

クラス変数とインスタンス変数

クラス変数(クラスアトリビュート)はインスタンスを作成しなくても参照・代入ができる。
インスタンス変数(インスタンスアトリビュート)はインスタンスを作成してからインスタンスごとに参照・代入。
 
クラス変数とインスタンス変数のスコープは、
グローバル変数(モジュール変数)とローカル変数の関係に近い。

モジュールの関数 メソッド
モジュールの変数 クラスのアトリビュート
関数内の変数 インスタンスアトリビュート

 

class Test():
    a = 1        # クラス変数

x =Test()
x.a = 10         # インスタンス変数
print(x.a)       # 10
del x.a          # インスタンス変数を削除すると
print(x.a)       # 1   クラス変数を参照する

 

class Test():
    a = 1                 # クラス変数
    def __init__(self):
        self.b = 2        # インスタンス変数

Test.b = 20   # インスタンスを作らなくてもクラス変数を作れる
print(Test.a) # 1
print(Test.b) # 20

x = Test()
y = Test()

x.a = 5
print(x.a)    # 5   インスタンス変数
print(x.b)    # 2   インスタンス変数 
     
print(y.a)    # 1   インスタンス変数が存在しないときはクラス変数
print(y.b)    # 2   インスタンス変数

Test.a = 100  #     クラス変数を変更
print(x.a)    # 5   インスタンス変数
print(y.a)    # 100 クラス変数

 

selfとは

 クラスのメソッド定義で引数の1つ目は必ずselfにする。
 このselfとはインスタンス自身を引数の1つめにするという意味。

class MyClass():
    def __init__(self, x):
        self.x = x
    def f(self):
        print(self.x)
        
a = MyClass(10) # インスタンス作成

a.f()           # 10
MyClass.f(a)    # 10 最後の2行は同義

この例の最後の行でMyClass.f(a)としているのは、インスタンスaを引数にしている。
これをメソッド定義ではselfとして受け取っている。
下から2行目のa.f()は最後の行のMyClass.f(a)と同義で、
インスタンスaのアトリビュート(属性)fとしてメソッドを呼び出しているが、
このとき最終行と同様にインスタンスaが引数として渡されている。

習慣としてselfを使っている人がほとんどというだけで、他の変数名でもよい。
ただし、保守性が悪くなるのでselfにしよう。
 

dir()

アトリビュートは辞書形式で保存され、dir()で確認できる。

print(dir())  # グローバルのアトリビュート一覧表示
print(dir(x)) # インスタンスxのアトリビュート一覧表示

if "a" in dir(x):
    print("'a' is already assigned")
else:
    print("'a' is not assigned yet")
    
if "A" in dir(x):
    print("'A' is already assigned")
else:
    print("'A' is not assigned yet")

 

メソッド

クラス内に関数を定義するとメソッドとして使える。

import math

# 三角形の面積を求めるクラス
class Triangle2():
    def __init__(self, a, b, c):
        self.a , self.b, self.c = a, b, c
        
    def is_triangle(self):
        return abs(self.b-self.c) < self.a and (self.b+self.c) > self.a

    def area(self):
        if self.is_triangle() != True:
            return 0
        else:
            s = (self.a + self.b + self.c) / 2
            return round((s * (s - self.a) * (s - self.b) * (s - self.c))**0.5, 2)

t = Triangle2(6, 8, 10)
print(t.is_triangle())  # True 
print(t.area())         # 24.0

 
カプセル化
属性やメソッドは変数のように扱えるので書き換えが可能。
整数型を念頭に置いて作った変数に文字列型を代入できたりもする。
意図せぬ動作が起きないように、クラス外からは属性・メソッドを変更しないようにするのが「カプセル化
 
アンダースコア1つを始める → 参照しても、外部から書き換えはしないようにというルール。
アンダースコア2つで始める → クラスの外部からは直接アクセスできなくなる。
 

クラスの継承

親クラス = スーパークラス
子クラス = サブクラス
として、親クラスをひな形にした子クラスを作成できる。
親クラスのメソッドはすべて子クラスで使えるが、変更したいものだけ再定義(オーバーライド)する。

# 正三角形の面積を求めるクラス
class Regular_triangle(Triangle2):
    def __init__(self, length):
        self.a = self.b = self.c = length
        
r = Regular_triangle(5)
print(r.area())          # 10.83

この例では初期化メソッドだけ再定義して、その他のメソッドはすべて親クラスのメソッドを使う。
 
・super()を使って、親クラスのメソッドを呼び出せる。
親クラスの仕様変更に対応しやすくなる。

class Regular_triangle(Triangle2):
    def __init__(self, length):
        # 親クラスの初期化メソッドを呼び出す
        super().__init__(length, length, length) 
        
r = Regular_triangle(5)
print(r.area())         # 10.83

・多重継承
 複数の親クラスから子クラスを作れる。

class MyClass(Super1, Super2, Super3):

継承した属性の探索順は深さ優先で左から右へ。
  

組み込みクラスの継承

組み込みのクラスを継承してカスタマイズできる。
 
インタラクティブシェルでhelp(dict)とするとdictクラスのメソッドを確認できる。
dictクラスを継承し、キーとして文字列だけを指定できるようにカスタマイズ。

# dictクラスを継承
class StrDict(dict):                    
    # 書き換えたいメソッドのみを上書きする。
    def __setitem__(self, key, value):
        if not isinstance(key, str):
            raise ValueError("Key must be str.")
        super().__setitem__(key, value)
        
d1 = StrDict()
d1["red"] = "0xFF0000"
print(d1["red"])

#d1[0] = 0  # ValueError: Key must be str.

親クラスの__setitem__()メソッドを呼び出すときは、
super().__setitem__(key, value)
super(StrDict, self).__setitem__(key, value)
dict.__setitem__(self, key, value)
とか。
 

インスタンスの型チェック

左側のオブジェクトは右側のクラスまたはその派生クラスのインスタンスか。

print(isinstance("1.23", float))  # False
print(isinstance(1.23, float))    # True
print(isinstance("1.23", object)) # True

クラスの継承チェック

左側のクラスは右側のクラスのサブクラスか。

print(issubclass(int, object))    # True 

すべての型はobject型からの派生(object型を継承)
 

アンダーバーによる変数の隠蔽

変数名の左側にアンダーバー2つで、右側にアンダーバーなしまたは1つのとき、
その変数は外部からのアクセスにAttributeErrorを返す。

class MyClass():
    def __init__(self):
        self._a = -1
        self.__b = 0
        self.__c_ = 1
        self.__d__ = 2
        self.__e___ = 3

x = MyClass()
print(x._a)      # -1
#print(x.__b)    # AttributeError   
#print(x.__c_)   # AttributeError
print(x.__d__)   # 2
print(x.__e___)  # 3    

 
上記の方法でのアクセスではAttributeErrorになるが、
インスタンス名._クラス名__属性名でアクセスできる。

print(x._MyClass__b)  # 0

 

特殊メソッド

組み込みクラスでよく使われている特殊メソッド。
組み込みクラスを継承するときは必要に応じて書き換える。

__add__(self, obj) +演算子使用時に呼ばれる
__iadd__(self, obj) *=
__sub__(self, obj) -
__mul__(self, obj) *
__truediv__(self, obj) /
__floordiv__(self, obj //

 

__and__(self, obj) &演算子使用時
__or__(self, obj)
__eq__(self, obj)  ==
__ne__(self, obj)  !=
__lt__(self, obj)  < 
__le__(self, obj)  <=
__gt__(self, obj)  >
__ge__(self, obj)  >=

__cmp()__メソッドはpython3では使わない。
 

__int__(self) int()関数使用時
__float__(self) float()
__str__(self) str()、print()関数
__repr__(self) 文字列表記へ変換、インタラクティブシェルへ表示。文字列なら' 'で囲うとか。
__bytes(self) bytes()
__format(self) format()

 

__len__(self) len()
__getitem(self, key) [ ]で要素指定するとき
__setitem(self, key) 代入
__delitem(self, key) 削除
__iter(self) iter()関数の呼び出し時
__contains__(self, item) in演算子

 

__getattr(self, attr) 未定義属性の参照時
__getattribute__() すべての属性参照
__setattr(self, attr, item) 属性への代入時

 

__call__(self[, args]) オブジェクトを関数として呼び出し
__del__(self) デストラクタ(オブジェクトの破棄)
__hash(self) set、dict型の要素定義など、オブジェクトのhash値が必要になるとき。

 

decimal

decimalモジュールのDecimalクラスは数値演算での誤差を回避するクラス。
 
小数の計算では誤差が生じる。

for i in range(10):
    print(i * 0.1)

# 0.0
# 0.1
# 0.2
# 0.30000000000000004
# 0.4
# 0.5
# 0.6000000000000001
# 0.7000000000000001
# 0.8
# 0.9

 
Decimalクラスを使うと

from decimal import Decimal
for i in range(10):
    print(i * Decimal("0.1"))

# 0.0
# 0.1
# 0.2
# 0.3
# 0.4
# 0.5
# 0.6
# 0.7
# 0.8
# 0.9

 
このとき、Decimalクラスに渡す引数は引用符で囲って文字列とする。
小数型のまま引数として渡すと

for i in range(10):
    print(i * Decimal(0.1))

# 0E-55
# 0.1000000000000000055511151231
# 0.2000000000000000111022302463
# 0.3000000000000000166533453694
# 0.4000000000000000222044604925
# 0.5000000000000000277555756156
# 0.6000000000000000333066907388
# 0.7000000000000000388578058619
# 0.8000000000000000444089209850
# 0.9000000000000000499600361081

なんかすごいことに。
 
Decimalクラスにはメソッドも多数ある。

d = Decimal(100)
print(d.sqrt())      # 10
print(d.is_finite()) # True
print(d.log10())     # 2

 
メソッド

adjusted() 指数部分の取り出し
as_integer_ratio() 既約分数に直す。ver3.6から
compare() 2つのDecimalインスタンスを比較し、Decimalの値で返す
copy_abs() 絶対値
copy_nagate() 符号反転
exp() 指数関数
from_fload() float型の引数を正確に表示
is_finite() 有限か
is_infinite() 無限か
is_nan() NaNか
is_zero() 0か
ln() 自然対数
log10 常用対数。底が10
quantize() 2つめと同様の丸めを行う
sqrt() 最大精度の平方根

メソッドを覚えなくても、通常のpythonの演算などが使える。
 
Decimalクラスを使った平方根いろいろ

import numpy as np
import math

d = Decimal(2)       

print(d.sqrt())               # 1.414213562373095048801688724
print(d ** Decimal("0.5"))    # 1.414213562373095048801688724
print(math.sqrt(d))           # 1.4142135623730951
print(np.sqrt(d))             # 1.414213562373095048801688724
print(pow(d, Decimal("0.5"))) # 1.414213562373095048801688724

 
9.4. decimal — 十進固定及び浮動小数点数の算術演算 — Python 3.6.1 ドキュメント

高階関数、デコレータ

高階関数...関数を関数に渡したり、戻り値として関数を返したり。
 
関数に機能を追加できたりする。

print(int(3.14))     # 3
print(int("100", 3)) # 9  :3進法の100は10進法の9

def arg_check(func, arg):
    print(arg)
    return func(arg)

print(arg_check(int, 3.14))
# 3.14
# 3

def args_check(func):
    def check(*args):
        print("args:", args)
        return func(*args)
    return check

def adder(a, b):
    return a + b
f = args_check(adder)
print(f(3, 4))
# args: (3, 4)
# 7

print(args_check(int)("100", 3))
# args: ('100', 3)
# 9

関数内の関数は変数と同様に扱える。
 
 
@マークをつけたデコレータを使って同様の動作ができる

def args_check(func):
    def check(*args):
        print("args:", args)
        return func(*args)
    return check

@args_check
def multiple(a, b):
    return a * b
print(multiple(2, 10))
# args: (2, 10)
# 20

 

lru_cacheデコレータ

(Least-recently-used cache decorator)
 
関数の引数と戻り値を記憶し、同じ引数で呼ばれたら関数を実行せずに値を返す。
同じ引数で何度も呼び出す関数だと劇的に速くなる。

counter = 0
def fib1(n):
    global counter
    counter += 1
    if n in [0, 1]:
        return 1
    return fib1(n - 1) + fib1(n - 2)

print(fib1(24))
print(counter, "回の関数呼び出し")
# 75025
# 150049 回の関数呼び出し
    
    
from functools import lru_cache
counter = 0
@lru_cache(maxsize=1024)
def fib2(n):
    global counter
    counter += 1
    if n in [0, 1]:
        return 1
    return fib2(n - 1) + fib2(n - 2)

print(fib2(24))
print(counter, "回の関数呼び出し")
# 75025
# 25 回の関数呼び出し

 
lru_cacheなしだと関数を15万回呼び出していたが、lru_cacheありだと25回で済む。

参考:
10.2. functools — 高階関数と呼び出し可能オブジェクトの操作 — Python 3.6.1 ドキュメント

イテレータ

イテレータとは:
 1. シーケンスなどの要素を次々取り出す。→ __next__
 2. 終わったら知らせる。 → StopIteration

 forループや内包表記では内部でiter()関数が呼ばれ、
 順に「__next__」をしていき、最後にStopIterationで停止するので、
 __next__やStopIterationを気にせず使える。
 
「遅延評価」:
 イテレータオブジェクト生成時には要素は生成せず、
 必要になったときに要素を生成していく。
→省メモリかつ高速化
 
ただしmax()やsum()などと一緒に使うと全要素を生成することになるので、イテレータの特長はあまり活かされない。
 

i = iter([1, 2, 3])

print(next(i))  # 1
print(next(i))  # 2
print(next(i))  # 3
print(next(i))  # StopIteration

 

 
イテレート可能なオブジェクト
・リスト
・タプル
・辞書dict
・dict.valuesオブジェクト
・dict.itemsオブジェクト
・集合set
・zipオブジェクト
・enummerateオブジェクト
・mapオブジェクト
・filterオブジェクト
・rangeオブジェクト
・文字列型
・bytes型
・reversedオブジェクト
・ファイルオブジェクト(要素は各行)
・generatorオブジェクト
・numpy.ndarrayオブジェクト
 
文字列型の例

for i in "あおによし":
    print(i)
# あ
# お
# に
# よ
# し    

 
ファイルオブジェクトの例

# テキストファイルの最初の5行を表示
print("".join([line for n, line in enumerate(
        open("data/test1.txt", encoding="utf-8")) if n < 5]))

 
集合型の例

for i in {1, 3, 5, 7, 9}:
    print(i)
# 1
# 3
# 5
# 9
# 7 

集合型では要素を取り出す順番が保障されていない。
 
mapオブジェクトの例

m = map(lambda x:x*0.1,[1, 3, 5, 7, 9])
print(m)      # <map object at 0x...>
for i in m:
    print(i)
# 0.1
# 0.30000000000000004
# 0.5
# 0.7000000000000001
# 0.9

 
dict.items()オブジェクトの例

dic1 = {"睦月":"1月", "如月":"2月", "弥生":"3月"}
dic2 = {v:k for k, v in dic1.items()}

print(dic1) # {'如月': '2月', '睦月': '1月', '弥生': '3月'}
print(dic2) # {'1月': '睦月', '2月': '如月', '3月': '弥生'}

 
numpy.ndarrayの例

import numpy as np

a1 = np.arange(15).reshape(3, 5)
order = {0:"1st", 1:"2nd", 2:"3rd"}
for i, row in enumerate(a1):
    print(order[i], "row", row)
# 1st row [0 1 2 3 4]
# 2nd row [5 6 7 8 9]
# 3rd row [10 11 12 13 14]

 

テストデータ(8) 麻雀の役

麻雀の役について
 

麻雀の役名

英語名は複数の言い方があるようなので、一例

役名 英名 鳴き 形で決まる
リーチ Ready 1 0 ×
一発 One_shot 1 0 ×
面前ツモ Self_pick 1 0 ×
役牌 Honor_tiles 1 1
タンヤオ All_simples 1 0
平和 No-points_hand 1 0 ×
一盃口 Double_run 1 0
海底撈月 Last_tile_from_the_wall 1 1 ×
河底撈魚 Last_discard 1 1 ×
嶺上開花 Dead_wall_draw 1 1 ×
槍槓 Robbing_a_quad 1 1 ×
ダブル立直 Double_ready 2 0 ×
七対子 Seven_pairs 2 0
一気通貫 Full_straight 2 1
三色同順 Three_color_runs 2 1
三色同刻 Three_color_triplets 2 2
三暗刻 Three_concealed_triples 2 2
対対和 All_triples 2 2
チャンタ Terminal_or_honor_in_each_set 2 1
三槓子 Three_kans 2 2
二盃口 Two_sets_of_double_run 3 0
ジュンチャン Terminal_in_each_set 3 2
混一色 Half_flush 3 2
小三元 Little_three_dragons 2 2
混老頭 Terminals_and_honors 2 2
清一色 Flush 6 5
四暗刻 Four_concealed_triples 役満 0
大三元 Big_three_dragons 役満 役満
国士無双 Thirteen_orphans 役満 0
小四喜 Little_four_winds 役満 役満
大四喜 Big_four_winds 役満 役満
緑一色 All_green 役満 役満
字一色 All_honors 役満 役満
清老頭 All_terminals 役満 役満
四槓子 Foug_kans 役満 役満
九蓮宝燈 Heavenly_Gates 役満 0
天和 Heavenly_win 役満 0 ×
地和 Earthly_win 役満 0 ×

 

import pandas as pd

url = 'http://python-remrin.hatenadiary.jp/entry/2017/05/24/142437'
## DataFrameのリストを得る。header=0のオプション指定で、最初の行をheader扱い。
fetched = pd.io.html.read_html(url)
df = fetched[0]

# 2次元リスト化
data = []
for i in range(len(df)):
    x = []
    for j in range(len(df.iloc[0])):
        x.append(df.iloc[i, j])
    data.append(x)
print(data)

# hatena表組み化
for i in range(len(df)):
    s = "|"
    for j in range(len(df.iloc[0])):
        s += str(df.iloc[i, j]) + "|"
    print(s)

 

yaku_list = [['役名', '英名', '翻', '鳴き', '形で決まる'], ['リーチ', 'Ready', '1', '0', '×'], ['一発', 'One_shot', '1', '0', '×'], ['面前ツモ', 'Self_pick', '1', '0', '×'], ['役牌', 'Honor_tiles', '1', '1', '△'], ['タンヤオ', 'All_simples', '1', '0', '○'], ['平和', 'No-points_hand', '1', '0', '×'], ['一盃口', 'Double_run', '1', '0', '○'], ['海底撈月', 'Last_tile_from_the_wall', '1', '1', '×'], ['河底撈魚', 'Last_discard', '1', '1', '×'], ['嶺上開花', 'Dead_wall_draw', '1', '1', '×'], ['槍槓', 'Robbing_a_quad', '1', '1', '×'], ['ダブル立直', 'Double_ready', '2', '0', '×'], ['七対子', 'Seven_pairs', '2', '0', '○'], ['一気通貫', 'Full_straight', '2', '1', '○'], ['三色同順', 'Three_color_runs', '2', '1', '○'], ['三色同刻', 'Three_color_triplets', '2', '2', '○'], ['三暗刻', 'Three_concealed_triples', '2', '2', '○'], ['対対和', 'All_triples', '2', '2', '○'], ['チャンタ', 'Terminal_or_honor_in_each_set', '2', '1', '○'], ['三槓子', 'Three_kans', '2', '2', '○'], ['二盃口', 'Two_sets_of_double_run', '3', '0', '○'], ['ジュンチャン', 'Terminal_in_each_set', '3', '2', '○'], ['混一色', 'Half_flush', '3', '2', '○'], ['小三元', 'Little_three_dragons', '2', '2', '○'], ['混老頭', 'Terminals_and_honors', '2', '2', '○'], ['清一色', 'Flush', '6', '5', '○'], ['四暗刻', 'Four_concealed_triples', '役満', '0', '○'], ['大三元', 'Big_three_dragons', '役満', '役満', '○'], ['国士無双', 'Thirteen_orphans', '役満', '0', '○'], ['小四喜', 'Little_four_winds', '役満', '役満', '○'], ['大四喜', 'Big_four_winds', '役満', '役満', '○'], ['緑一色', 'All_green', '役満', '役満', '○'], ['字一色', 'All_honors', '役満', '役満', '○'], ['清老頭', 'All_terminals', '役満', '役満', '○'], ['四槓子', 'Foug_kans', '役満', '役満', '○'], ['九蓮宝燈', 'Heavenly_Gates', '役満', '0', '○'], ['天和', 'Heavenly_win', '役満', '0', '×'], ['地和', 'Earthly_win', '役満', '0', '×']]

bytes, bytearray

ググってみると、文字列に関する型というよりは、バイナリデータを扱う型でした。
まとめてバイナリシーケンスとも呼ばれます。
 
0~255の整数のみが要素になります。
 

bytes イミュータブル(値の変更不可)
bytearray ミュータブル(値の変更可能)

 

bytes型

・bytes型の生成

リテラル b"..."
bytes()関数
文字列.encode()メソッド

 

b1 = b"abc"

print(b1)        # b'abc'
print(type(b1))  # <class 'bytes'>

b2= bytes("def", encoding="shift_jis")

print(b2)        # b'abc'
print(type(b2))  # <class 'bytes'>

b3 = "ghi"
b3 = b3.encode("utf-8")
print(b3)        # b'ghi'
print(b3[1])     # 104     
     
     
b3 = b3.decode("utf-8")
print(b3)        # ghi
print(b3[1])     # h

 
日本語エンコード

エンコード pythonでの名
シフトJIS shift-jis, shift_jis, sjis
ISO-2022-JP(JIS) iso-2022-jp
EUC-JP euc-jp
UTF-8 utf-8

 
変換エラーに対するオプション

文字列 説明
strict エラーを発生して変換停止。デフォルト
replace 変換できない文字を「?」などで表示し、変換は続ける。
ignore 変換できない文字はスルーして、変換を続ける。

 

bytearray型

ba1 = bytearray(b"abc")
ba2 = bytearray("abc".encode())

print(ba1, ba2) # bytearray(b'abc') bytearray(b'abc')
print(ba1[0])   # 97

# ba3 = bytearray(b"いろは")
# SyntaxError: bytes can only contain ASCII literal characters.
ba3 = bytearray("いろは".encode())

print(ba3)           # bytearray(b'\xe3\x81\x84\xe3\x82\x8d\xe3\x81\xaf')
print(ba3[6])        # 227
print(ba3.decode())  # いろは

 
バイナリデータを扱う予定はまだないので、ゆっくり調べます。

関数

関数についての注意事項など。
 

docstring

関数ブロックの最初に文字列リテラルを書くと、doctrrintとして扱われる。
ここに関数の説明などと記入できる。
 

引数

・引数は参照渡しになるので、リスト型などmutableな変数を渡すと、
 もとの変数も関数内の操作で変更されてしまう。

def appendzero(list2):
    list2.append(0)
    print("list2:", list2)  # list2: [1, 3, 5, 0]

list1 = [1, 3, 5]
appendzero(list1)

print("list1:", list1)      # list1: [1, 3, 5, 0]

 
・デフォルト引数
値が渡されなかったときに使う値(デフォルト値)をあらかじめ設定。
引数の個数が多いときなどに便利。

def test(a=0, b=0, c=10, d="test"):
    print(a, b, c, d)
    
test(1, 2, 3)        # 1 2 3 test
test(1)              # 1 0 10 test
test()               # 0 0 10 test
test(d="green", c=5) # 0 0 5 green  

・キーワード指定した引数の後ろに、キーワード指定しない引数を渡せない。
 
・デフォルト引数にリスト型などのmutableなものを指定するときは
 デフォルト値の評価が1度しか行われず、デフォルト値が共有されるので注意。

def f(a, li=[]):
    li.append(a)
    return li

print(f(1))       
print(f(2))      
print(f(3))      
print(f(4, [0])) 
print(f(5))      
print(f(6))      

# [1]
# [1, 2]
# [1, 2, 3]
# [0, 4]
# [1, 2, 3, 5]
# [1, 2, 3, 5, 6]

 
デフォルト値を共有したくないときは

def f(a, li=None):
    if li == None:
        li = []
    li.append(a)
    return li

print(f(1))       
print(f(2))      
print(f(3))      
print(f(4, [0])) 
print(f(5))      
print(f(6))

# [1]
# [2]
# [3]
# [0, 4]
# [5]
# [6]

 
・キーワード引数
 値を渡すときに、keyword=valueの形で指定することができる。
 デフォルト値を設定していない引数にも使用できる。

def f(a, b):
    print(a, b)
    
f(1, 2)      # 1 2
f(3, b=4)    # 3 4
f(a=5, b=6)  # 5 6
#f(7, c=8)   # TypeError: f() got an unexpected keyword argument 'c'
f(b=9, a=10) #10 9

 
・可変長引数
*argsとして受け取ると、個数に関係なく残りの引数をタプルとして受け取れる。

def test(a, b, *args):
    print(a, b, args)
    
test(1, 2)           # 1 2 ()
test(1, 2, 3)        # 1 2 (3,)
test(1, 2, 3, 4, 5)  # 1 2 (3, 4, 5)
# test(1) # TypeError: test() missing 1 required positional argument: 'b'

 **kwargsとすると、キーワード引数をすべて受け取れる 
 

返り値(戻り値)

 return文で値を返すことができ、返り値という。
 返り値の記述がない場合は、Noneを返す。

def test1(a):
    a = 1
    return

def test2(a):
    a = 1
    return a

a = 0
print(test1(a)) # None
print(test2(a)) # 1

 

変数

・変数名の探索順位
 同名の変数があるときなどの探索順。
 
 1 関数内の変数
 2 外側の関数の変数
 3 グローバル変数
 4 組み込み変数

a, b, c = 1, 1, 1
def f1():
    a, b = 2, 2
    print(a, b, c)
    
    def f2():
        a = 3
        print(a, b, c)
    f2()
    
f1() 
# 2 2 1
# 3 2 1

 
・関数内からは関数外の変数の読み取りはできるが、書き換えはできない。
 global宣言...グローバル変数を書き換えるとき。
 nonlocal宣言...一つ外側の関数の変数を書き換えるとき。
 
・関数内変数の名前割り当ては関数呼び出し時の最初に行われる。

x = 3

def f1():
    print(x)

def f2():
    print(x)
    x = 5

f1() # 3
f2() # UnboundLocalError: local variable 'x' referenced before assignmen

f2()では変数xを使うが、関数呼び出し時にxという名前を確保、ただし代入はx=5のとき。
  

代入

関数もオブジェクトの1つなので、変数に代入したりできる。

def total(n=0):
    return (sum(range(n)))

print(total(5)) # 10

t1 = total      # 関数をt1に代入。t1が関数になる。
t2 = total()    # 関数を実行し、戻り値をt2に代入。

print(t1)       # <function total at 0x...>
print(t2)       # 0
print(t1(5))    # 10

r5 = range(5)
print(max(r5))  # 4

 

関数アノテーション

引数のあとの:に続けて引数の説明を、->のあとに続けて戻り値の説明を付けることができ、関数アノテーションを呼ぶ。

def test(a:int, b:"引数bの説明") ->"戻り値の説明":
    "for test"
    return a*b

 
アノテーションの説明に書けるのは
 ・文字列
 ・式
 ・関数呼び出し
 ・関数オブジェクト(関数名)
 
関数アノテーションの内容は関数オブジェクトが持つ__annotations__という辞書に格納。
文字列の代わりに引数チェック用の関数オブジェクトを使用し、デコレータと組み合わせることで引数や戻り値のチェックが手軽にできるとのことですが、詳しい方法は理解できませんでした。