変数に変数を代入したら?〜仕組みを知ってハマらないようになろう〜2020-07-04Python Charity Talks in Japan@mizzsugar04251
View Slide
前提● この発表の対象者は初学者です。● Pythonで変数に変数を代入した時の仕組みを知り、安全なプログラムを書けるようになることが目的です。2
お前、誰よ?● みずきと申します。● Twitter: @mizzsugar0425● PythonでWebサービスの開発をしています。(Pyramid, Django, PostgreSQL,Nuxt.js, Angular …)● コーヒーと自転車とPythonが好きです。3
クイズ① 〜枠の中に入るものは?〜>>> name_1 = 'aaa'>>> name_2 = name>>> name_1.replace('a', 'b')'bbb'>>> name_1'aaa'>>> name_2'aaa'4
クイズ① の答え>>> name_1 = 'aaa'>>> name_2 = name>>> name_1.replace('a', 'b')'bbb'>>> name_1'aaa'>>> name_2'aaa'5
クイズ② 〜枠の中に入るものは?〜>>> list_1 = [1, 2, 3]>>> list_2 = list_1>>> list_1.append(4)>>> list_1[1, 2, 3, 4]>>> list_2[1, 2, 3, 4]6
クイズ② の答え>>> list_1 = [1, 2, 3]>>> list_2 = l>>> list_1.append(4)>>> list_1[1, 2, 3, 4]>>> list_2[1, 2, 3, 4]7
何故List型の時は変わってしまったのでしょうか?8
値が変わってしまった理由はオブジェクトの仕組みにあります。9
Pythonでは全てオブジェクトPythonでは、int型 str型 List型 …全てをオブジェクトとして扱います。10https://docs.python.org/ja/3/reference/datamodel.html#objects-values-and-types
number = 1 # int型のオブジェクトname = 'Taro' # str型のオブジェクトnames = ['Taro', 'Jiro', 'Saburo'] # List型のオブジェクトclass Person:def __init__(self, name: str, age: int) -> None:self.name = nameself.age = ageperson = Person(name='Taro', age=10) # Person型のオブジェクト11
オブジェクトの識別値を返すid()● 組み込み関数id()はオブジェクトの識別値(identify)を返します。● id()の値が同じならば同じオブジェクトです。12
>>> num_1 = 143748927394325>>> id(num_1)139627296785840>>> num_2 = 143748927394325>>> id(num_2) # numと同じ値だけれども違うオブジェクト139627296786576>>> num_1 += 1 # 別のオブジェクトとして値が代入される>>> id(num_1)13962730519654413Pythonでは1や2などよく使われる数値は最適化のために同じ idになります。
副作用とは● 副作用とは、オブジェクトの値を変更することです。● 副作用を伴う操作を許容するオブジェクトを「ミュータブル」なオブジェクトといいます。● 「イミュータブル」なオブジェクトは副作用を許容しません。14例:>>> list_1 = [1, 2]>>> list_1.append(3)>>> list_1[1, 2, 3]
ミュータブル・イミュータブルなオブジェクト● ミュータブルなオブジェクトの例List型、Dict型、Set型… のオブジェクト● イミュータブルなオブジェクトの例int型、str型、Tuple型… のオブジェクト15
副作用を伴わない操作の場合①>>> name_1 = 'aaa' # str型のイミュータブルなオブジェクト>>> id(name_1)139720897350384>>> name_1.replace('a', 'b') # nameとは別オブジェクトを生成する、副作用のない操作'bbb'>>> name_1 # 上記のreplaceでname値は変更されていないのでidはそのまま'aaa'16
副作用を伴わない操作の場合②>>> name_1 = 'aaa' # str型のイミュータブルなオブジェクト>>> name_2 = name_1>>> name_1 = 'bbb' # nameは1行目とは別のオブジェクトになります。>>> name_2 # str型はイミュータブルなのでname_2には影響はありません。'aaa'17
18namename_2オブジェクト①namename_2オブジェクト①オブジェクト②name = 'bbb'
副作用を伴わない操作の場合③>>> list_1 = [1, 2, 3] # List型のミュータブルなオブジェクト>>> list_2 = list_1 + [4] # list_1の値が変わらないイミュータブルな操作>>> list_2[1, 2, 3, 4]>>> list_1[1, 2, 3]19
副作用を伴わない操作の場合④>>> id(list_1)140410856800704>>> id(list_2)14041088421523220
副作用を伴う操作の場合①>>> list_1 = [1, 2, 3] # List型のミュータブルなオブジェクト>>> id(list_1)139720897421120>>> list_1.append(4) # list_1の値を変更する副作用を伴う処理>>> list_1[1, 2, 3, 4]>>> id(list_1) # ミュータブルなので値が変わりオブジェクトは同じまま13972089742112021
副作用を伴う操作の場合②>>> list_2 = list_1>>> list_1.append(5)>>> list_1[1, 2, 3, 4, 5]>>> list_2 # list_1とlist_2は同じオブジェクトを指したままなのでlist_2も変更[1, 2, 3, 4, 5]22
list_2の値が変わらないようにするにはcopy.copyを使いましょう。23>>> list_1 = [1, 2, 3]>>> list_2 = copy.copy(list_1)>>> list_1.append(4)>>> list_1[1, 2, 3, 4]>>> list_2[1, 2, 3]
copy.copyを使うと何が起こるか代入元の変数の値がコピーされたオブジェクトが渡されて別のオブジェクトとなります。24>>> list_1 = [1, 2, 3]>>> list_2 = copy.copy(list_1)>>> id(list_1)140594427010048>>> id(list_2)140594427023056
まとめ● 副作用とは、オブジェクトの値を変更することです。● 副作用を伴う操作を許容しないのがイミュータブルなオブジェクト。● 副作用を伴う操作を許容するのがミュータブルなオブジェクト。● ミュータブルな型の変数を別の変数に代入したら変数の変更が別の変数にも反映されます。● ミュータブルな変数を別の変数に代入したいならcopy.copyを使いましょう。ご清聴ありがとうございました。25
[おまけ] list_1とlist_2の値は何になるでしょうか?>>> def add_number(numbers_list: List[int], number: int) -> List[int]:... numbers_list.append(number)... return numbers_list>>>>>> list_1 = [1, 2, 3]>>> list_2 = add_number(l, 4)26
list_2には値が変わってほしくなかったのに…>> # 正解は…>>> list_1[1, 2, 3, 4]>>> list_2[1, 2, 3, 4]27
copy.copyで意図しない値の変化を防ぎましょう>>> def add_number(numbers_list: List[int], number: int) -> List[int]:... added_list = copy.copy(numbers_list)... added_list.append(number)... return added_list28