Pythonで関数内の変数スコープに関する理解が曖昧だったので、同じような点でつまずいた人のために共有します。
述べたいことは、__関数の引数として用意された変数に対して、その変数に丸ごとの代入を行う場合は新しくローカル変数が定義されたものとして扱われ、変数の要素に代入を行う場合はそのままグローバル変数として扱われる__という違いがあるので注意ということです。
以下のコードでは関数内で2つのリストlist_aとlist_bを操作していますが、グローバル変数として扱われ、関数の外にも影響を及ぼしているのはlist_bのほうだけです。
def func(list_a, list_b):
list_a = list_a + ['hoge']
list_b[1] = list_b[1] + 10
a = [1, 2, 3]
b = [5, 6, 7]
func(a, b)
print(f"a={a}, b={b}")
func(a, b)
print(f"a={a}, b={b}")
出力は以下になります:
a=[1, 2, 3], b=[5, 16, 7]
a=[1, 2, 3], b=[5, 26, 7]
Pythonでは関数内の"a=..."の形の式は、__関数の中にのみスコープを持つローカル変数aが新たに定義されたもの__として扱われます。したがって関数func内のlist_a = list_a + ['hoge']では、左辺のlist_aは新たにローカル変数list_aを定義したものとして扱われています(右辺のlist_aは関数の引数として与えられ、関数の外にもスコープを持つ変数です)。よって、関数の外のaには何の影響もありません。
一方でlist_b[1] = list_b[1] + 10では、両辺のlist_b[1]は、__すでに定義されている__変数list_bの1番目の要素として扱われなければいけません。今回の例ではlist_bは関数funcの引数であり、関数外部ですでに定義された変数です。よって、funcによる処理が関数外部の変数bに反映されています。
ndarrayやpandas.DataFrameなど他のtypeの変数に関しても同様です。