自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

Pythonプログラミングに役立つ情報をまとめてみた

Python
Python

スッキリわかるPython入門 第2版でPythonを勉強しました。

いい機会なのでPythonについて諸々まとめておきたいと思います。

スッキリシリーズはJava入門を昔購入し保有していました。オブジェクト指向についてRPGの概念と組み合わせて分かりやすく説明してくれたので仕事の勉強をする上でかなり助かった記憶があります。

今回はそのPython入門版を読んでみました。

Pythonに関しては2系が主流のときから使っていましたが、元々CやJavaなど他のプログラミング言語を知っていたことや、コードで実現したいことで分からないことは都度調べれば何とかなっていました。

そろそろきちんと体系的に学びたいと思いPython言語について説明している本を読んでみることにしました。

結果としては読んでみて正解でした。

tupleやlistの使い分けや関数の応用テクニックなど図解と共に説明されており非常に分かりやすかったです。

ほぼ自分用になってしまいますが、Pythonに関してまとめておこうと思います。

スポンサーリンク
  1. 本記事で利用しているPythonのバージョン
  2. Python Enhancement Proposals (PEPs)
  3. PythonでPrintのやり方を色々まとめてみた
    1. 単純に羅列したい場合
    2. 単純に羅列したい場合 (コンマ区切り)
    3. 文章で表示 (formatメソッド) ※ 体重を小数点1桁表示ver
    4. 文章で表示 (Formatted String Literals known as f-strings) ※ 体重を小数点1桁表示ver
  4. Pythonリストの使い方
    1. インデックスでリストの要素を参照する
    2. リストへ要素の追加・削除・変更
    3. スライスによる要素の参照
    4. for loopによるリストの要素へのアクセス
  5. Pythonディクショナリの使い方
    1. キーを選択しディクショナリの要素(Value:値)を参照する
    2. ディクショナリのキー・バリュー・アイテムの値を出力する
    3. ディクショナリへ要素の追加・削除・変更
    4. Valueの合計値を出してみる
    5. for loopによるディクショナリ要素へのアクセス
  6. 要素の追加・変更・削除を防げるタプル(Tuple)
  7. 要素の重複を許さないセット(Set)
    1. 集合演算
  8. その他のcollections
    1. Counterを使ってみる
  9. 繰り返し処理の制御 (break・continue・pass)
  10. Pythonでの関数の使い方のまとめ
    1. 返却値をタプルにすると複数の値を返すことが出来る
    2. 関数の引数の設定方法 (デフォルト引数・キーワード指定・可変長引数)
  11. シャローコピーとディープコピーの確認
    1. シャローコピーはいつ使うのがいいのか
    2. そもそもなぜオブジェクトをコピーする必要があるのか?同じ変数を使い回せばいいのではないか
  12. Pythonでクラス作成 (オブジェクト指向)
    1. (補足) なぜselfが必要なのか
    2. (補足) なぜ変数の前にアンダーバーがついているのか
  13. Pythonのコマンドライン引数を確認してみる
  14. まとめ

本記事で利用しているPythonのバージョン

python3 -V
Out[0]
Python 3.8.13

Python3.8.13になります。

ちなみに執筆時点だと2023-03-08 Python 3.12.0 alpha 6 releasedが最新版のようです。

Pythonでゴリゴリ処理を書く場合、バージョンごとの違いを調べておいた方が良いかも知れません。(私はPython自体のバージョンの違いにライブラリのインストール以外で困ったことはなかったのですが、使っているPython標準ライブラリの挙動が変わる場合があるようです。)

例えばos.pathの挙動がValueErrorの代わりにFalseを返却するように3.8から変わったようです。

os.path functions that return a boolean result like exists(), lexists(), isdir(), isfile(), islink(), and ismount() now return False instead of raising ValueError
引用: https://docs.python.org/3/whatsnew/3.8.html#os-path

皆さん大体Python3.6以上を使う機会が多いと思うので、各バージョンでの変更点をまとめてあるwhatsnewページへのリンクを記載しておきます。

https://docs.python.org/3/whatsnew/3.6.html
https://docs.python.org/3/whatsnew/3.7.html
https://docs.python.org/3/whatsnew/3.8.html
https://docs.python.org/3/whatsnew/3.9.html
https://docs.python.org/3/whatsnew/3.10.html
https://docs.python.org/3/whatsnew/3.11.html

スポンサーリンク

Python Enhancement Proposals (PEPs)

Pythonに関する規約や提案がたくさんまとめられているPEP indexというリンク集が公式に存在します。

その中でもPEP8はPythonのコーディング規約について書かれていて勉強になりました。(日本語版はreadthedocsにありました。)

よく読んでみると空白入れない方がいいところにいれてしまうとかはやりがちかも知れません 汗 リーダブルコードという本を思い出しました。

スポンサーリンク

PythonでPrintのやり方を色々まとめてみた

まずは基本の出力(print)の方法をまとめてみました。

表示する変数の準備
name="ヒノマルク"
height=180.5
weight=80
age=38

値はもちろん適当です 笑

単純に羅列したい場合

print(name,height,weight,age)
Out[0]
ヒノマルク 180.5 80 38

単純に羅列したい場合 (コンマ区切り)

print(name,height,weight,age,sep=",")
Out[0]
ヒノマルク,180.5,80,38

文章で表示 (formatメソッド) ※ 体重を小数点1桁表示ver

### 文章で表示 (formatメソッド) ※ 体重を小数点1桁表示ver
print("{}の身長は{}cmで、体重は{:.1f}kgで、年齢は{}歳です。".format(name,height,weight,age))
Out[0]
ヒノマルクの身長は180.5cmで、体重は80.0kgで、年齢は38歳です。

文章で表示 (Formatted String Literals known as f-strings) ※ 体重を小数点1桁表示ver

print(f"{name}の身長は{height}cmで、体重は{weight:.1f}kgで、年齢は{age}歳です。")
Out[0]
ヒノマルクの身長は180.5cmで、体重は80.0kgで、年齢は38歳です。

先頭の「f」は大文字でも小文字でもOKですが、Python3.6以上でないと使えません。

スポンサーリンク

Pythonリストの使い方

基本のリストの使い方になります。私は一番よく使っていると思います。

リストの作成
python_list=[1,2,3,4,5,6,7,8,9,10]

インデックスでリストの要素を参照する

インデックスは0から開始します。そのためインデックスの番号は今回の例だと9までしか存在しません。10番目のインデックスを参照しようとするとIndexErrorになります。

# 0番目
print(python_list[0])
# 5番目
print(python_list[5])
# エラーになる
print(python_list[10])
Out[0]
1
6
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[28], line 3
      1 print(python_list[0])
      2 print(python_list[5])
----> 3 print(python_list[10])

IndexError: list index out of range

リストへ要素の追加・削除・変更

リストの操作
# リストに11を追加
python_list.append(11)
print(python_list)

# リストから5を削除
python_list.remove(5) #または del python_list[4]で5の要素を削除する
print(python_list)

# リストの最後の値を5に変更 (11->5に変更)
python_list[-1] = 5
print(python_list)
Out[0]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

[1, 2, 3, 4, 6, 7, 8, 9, 10, 11]

[1, 2, 3, 4, 6, 7, 8, 9, 10, 5]

スライスによる要素の参照

numpyやpandasで使ってる方も多いのではないでしょうか?リストでも出来るようです。

リスト変数へスライスを適用
print(python_list[3:]) # index >= 3
print(python_list[:3]) # index < 3
print(python_list[3:5]) # index 3 <= index < 5
Out[0]
[4, 6, 7, 8, 9, 10, 5]
[1, 2, 3]
[4, 6]

for loopによるリストの要素へのアクセス

ループでリストにアクセス
for i in python_list:
    print(i)
Out[0]
1
2
3
4
6
7
8
9
10
5
スポンサーリンク

Pythonディクショナリの使い方

まずはディクショナリ(辞書)を作成します。オーソドックスに各教科の成績のディクショナリを作成しました。

ディクショナリの作成
python_dict={'名前':'ヒノマルク','国語':80,'数学':30,'英語':90,'理科':20,'社会':70}
print(python_dict)
Out[0]
{'名前': 'ヒノマルク', '国語': 80, '数学': 30, '英語': 90, '理科': 20, '社会': 70}

キーを選択しディクショナリの要素(Value:値)を参照する

国語と社会の点数を参照する
print(python_dict['国語'])
print(python_dict['社会'])
Out[0]
80
70

ディクショナリのキー・バリュー・アイテムの値を出力する

keys,values,itemsを出力する
print(python_dict.keys())
print(python_dict.values())
print(python_dict.items())
Out[0]
dict_keys(['名前', '国語', '数学', '英語', '理科', '社会'])
dict_values(['ヒノマルク', 80, 30, 90, 20, 70])
dict_items([('名前', 'ヒノマルク'), ('国語', 80), ('数学', 30), ('英語', 90), ('理科', 20), ('社会', 70)])

ディクショナリへ要素の追加・削除・変更

ディクショナリへ要素の追加・削除・変更
# 体育を追加
python_dict["体育"] = 100
print(python_dict)

# 社会を削除
del python_dict["社会"]
print(python_dict)

# 体育の値を変更
python_dict['体育'] = 50
print(python_dict)
Out[0]
{'名前': 'ヒノマルク', '国語': 80, '数学': 30, '英語': 90, '理科': 20, '社会': 70, '体育': 100}
{'名前': 'ヒノマルク', '国語': 80, '数学': 30, '英語': 90, '理科': 20, '体育': 100}
{'名前': 'ヒノマルク', '国語': 80, '数学': 30, '英語': 90, '理科': 20, '体育': 50}

Valueの合計値を出してみる

合計値を出してみる

Valueが数値のみの情報であればsum(python_dict.values())で270という合計値が出せるが、今回は名前もディクショナリに含めてしまっているのでエラーになる。

解決方法として、intのみ選択して合計していくか、strを含まない新しい辞書をDict Comprehensionsで作成し合計値を出すかの方法があるので試してみる。

※ 分かりやすいように後者の方法は新しい辞書の中身をアウトプットしている。

# エラーになる (strを合算しようとするため)
try:
    print(sum(python_dict.values()))
except Exception as err:
    print(err)

# valueがintの場合、合計していく
print(sum(v for v in python_dict.values() if isinstance(v, int)))

# valueがint以外のキーは除外する。sum({}.values())で合計が出る
print({key: python_dict[key] for key in python_dict if isinstance(python_dict[key], int)})

# valueがint以外のキーの値は0に置換する。sum({}.values())で合計が出る
print({key: python_dict[key] if isinstance(python_dict[key], int) else 0 for key in python_dict})
Out[0]
unsupported operand type(s) for +: 'int' and 'str'
270
{'国語': 80, '数学': 30, '英語': 90, '理科': 20, '体育': 50}
{'名前': 0, '国語': 80, '数学': 30, '英語': 90, '理科': 20, '体育': 50}

for loopによるディクショナリ要素へのアクセス

Keyのみ、Valueのみ、Key,Value両方の値を取り出しながらループする方法をそれぞれ紹介します。

for loopによるディクショナリ要素へのアクセス
for key,val in python_dict.items():
    print(key,val)

print("---")

for key in python_dict.keys():
    print(key)

print("---")

for val in python_dict.values():
    print(val)
Out[0]
名前 ヒノマルク
国語 80
数学 30
英語 90
理科 20
体育 50
---
名前
国語
数学
英語
理科
体育
---
ヒノマルク
80
30
90
20
50
スポンサーリンク

要素の追加・変更・削除を防げるタプル(Tuple)

変更できないリストのようなものという認識です。中身を誤って書き換えないようにしたい場合などに使えます。当然appendやremoveメソッドはありません。

タプルの作成と参照
python_tuple=(1,2,3,4,5)
print(python_tuple[0])
print(python_tuple[3])
Out[0]
1
4
書き換えとdelを試す
try:
    python_tuple[3] = 10
except Exception as err:
    print(err)

try:
    del python_tuple[3]
except Exception as err:
    print(err)
Out[0]
'tuple' object does not support item assignment
'tuple' object doesn't support item deletion

無事?エラーになりました。

要素数が1のタプルを作成する
print(type((1)))
print(type((1,)))
Out[0]
class 'int'>
class 'tuple'>

「,」がないとタプルにならないことに注意が必要です。

スポンサーリンク

要素の重複を許さないセット(Set)

私はよく分析やデータ加工するときに重複を除外したい場合に使うことがあります。

セットの作成
python_set={1,2,3,4,5}
print(type(python_set))
Out[0]
class 'set'>
6をたくさん追加してみる
python_set.add(6)
python_set.add(6)
python_set.add(6)
python_set.add(6)
python_set.add(6)
python_set.add(6)
print(python_set)
Out[0]
{1, 2, 3, 4, 5, 6}

1つしか追加されていません。

集合演算

セットだと集合演算が出来るようです。具体的には2つのセットから和集合・積集合・差集合・対象差を求めることが出来ます。

2つのセットを作成し集合演算を試す
python_set1={1,2,3,4,5}
python_set2={4,5,6,7,8}

print("和集合:", python_set1 | python_set2)
print("積集合:", python_set1 & python_set2)
print("差集合:", python_set1 - python_set2)
print("対象差:", python_set1 ^ python_set2)
Out[0]
和集合: {1, 2, 3, 4, 5, 6, 7, 8}
積集合: {4, 5}
差集合: {1, 2, 3}
対象差: {1, 2, 3, 6, 7, 8}

・和集合はpython_set1とpython_set2のどちらかに存在している値を演算
・積集合はpython_set1とpython_set2の両方に存在している値を演算
・差集合はpython_set1にしかない値を演算
・対象差はpython_set1とpython_set2両方に存在している値は除外したものを演算 (つまり和集合 - 積集合)

スポンサーリンク

その他のcollections

何かしらの実装や処理を書いているとList・Dictionary・Tuple・Setでは少し痒い所に手が届かない状況が出てくる場合があります。

その場合はPythonビルトインのコンテナデータ型が使えるかも知れません。

This module implements specialized container datatypes providing alternatives to Python’s general purpose built-in containers, dict, list, set, and tuple. 引用: https://docs.python.org/3/library/collections.html

・namedtuple()
・deque
・ChainMap
・Counter
・OrderedDict
・defaultdict
・UserDict
・UserList
・UserString

Counterを使ってみる

Counterを使ってリストの中身の件数を数えてみる
from collections import Counter

fruits_list=['イチゴ', 'バナナ', 'イチゴ', 'オレンジ', 'オレンジ', 'オレンジ']
cnt = Counter()
for fruit in fruits_list:
    cnt[fruit] += 1

print(cnt)
Out[0]
Counter({'オレンジ': 3, 'イチゴ': 2, 'バナナ': 1})

簡単ですね。

次にCounterを使わない場合どういう書き方になるのか見てみます。

ディクショナリを使ってリストの中身の件数を数えてみる
fruit_dict={}
for key in fruits_list:
    try:
        fruit_dict[key] = fruit_dict[key] + 1
    except KeyError as err:
        print(f"Unexpected {err=}, {type(err)=}")
        fruit_dict[key] = 1
print(fruit_dict)
Out[0]
Unexpected err=KeyError('イチゴ'), type(err)=class 'KeyError'>
Unexpected err=KeyError('バナナ'), type(err)=class 'KeyError'>
Unexpected err=KeyError('オレンジ'), type(err)=class 'KeyError'>
{'イチゴ': 2, 'バナナ': 1, 'オレンジ': 3}

結果は同じですが、記述量が多くちょっと複雑ですね。

ちなみに色々な書き方があると思います。もっと簡単に書けないかChatGPTにも聞いてみました。

ChatGPT先生による添削
fruit_dict = {}
for key in fruits_list:
    fruit_dict[key] = fruit_dict.get(key, 0) + 1
print(fruit_dict)
Out[0]
{'イチゴ': 2, 'バナナ': 1, 'オレンジ': 3}

dict.get() メソッドを使用することで、try-except ブロックを使用する必要がなくなり、より簡潔なコードになりました。get() メソッドは、辞書に指定されたキーが存在する場合にはその値を返し、存在しない場合にはデフォルト値を返します。この場合、get() メソッドは、キーが存在しない場合には 0 を返し、既に存在する場合には現在の値を返します。その後、各果物の出現回数を数え上げて、fruit_dict に追加します。最終的に fruit_dict を出力します。
引用: ChatGPT

ふむふむそうなのね。Counterいらないかもな。。これからはChatGPT先生から学ぼうと思います 笑

スポンサーリンク

繰り返し処理の制御 (break・continue・pass)

・breakは以降の繰り返し処理を全て中断
・continueは今の処理を中断し次の繰り返し処理へ進む
・passは何もしない

という意味になります。

実際に処理を書いてみて挙動を確かめてみましょう。

繰り返し処理の制御の確認
# 1から10まで繰り返す処理を考える
for i in range(1,10+1,1):
    print(i)

print("\n---break---")

# 5回目のループでbreak
for i in range(1,10+1,1):
    if i == 5:
        break
    print(i)

print("\n---continue---")

# 5回目のループでcontinue
for i in range(1,10+1,1):
    if i == 5:
        continue
    print(i)

print("\n---pass---")

# 5回目のループでpass
for i in range(1,10+1,1):
    if i == 5:
        pass
        print("pass")
    print(i)
Out[0]
1
2
3
4
5
6
7
8
9
10

---break---
1
2
3
4

---continue---
1
2
3
4
6
7
8
9
10

---pass---
1
2
3
4
pass
5
6
7
8
9
10

私はデータ加工している時に1行ずつ処理しているときはcontinueを使うことがなぜか多いです 笑

スポンサーリンク

Pythonでの関数の使い方のまとめ

一番まとめておきたかった内容が関数になります。def 関数名(引数1,引数2):と書きますが引数の設定方法などやり方が複数あります。

オーソドックスな関数を作成
# 足し算関数
def addition(a,b):
    return a+b

addition(1,2)
Out[0]
3

1足す2は3なので、3が返ってきます。とても簡単です 笑

通常戻り値は1つしか選択できません。

返却値をタプルにすると複数の値を返すことが出来る

戻り値は1つしか返却できませんが、タプルにまとめることによって複数の値を返却することが可能になります。

また戻り値であるタプルをアンパックすることによって、2つの変数を作成することも出来ます。

便利なので覚えておいて損はないです。

返却値をタプルにする方法
# 足し算と掛け算の結果を返す関数
def addition_and_multiplication(a,b):
    return (a+b,a*b) #タプルにすることによって戻り値は1つ

# タプルが返ってくる
print(addition_and_multiplication(1,2),type(addition_and_multiplication(1,2)))

# タプルをアンパック
a,b=addition_and_multiplication(1,2)
print(f"タプルのアンパック: 足し算={a} 掛け算={b}")
Out[0]
(3, 2) class 'tuple'>
タプルのアンパック: 足し算=3 掛け算=2

関数の引数の設定方法 (デフォルト引数・キーワード指定・可変長引数)

各引数の値が分かりやすいように関数を変更

def addition(a,b,c):
    return f"(a={a})+(b={b})+(c={c})={a+b+c}"

print(addition(1,2,3))
Out[0]
(a=1)+(b=2)+(c=3)=6

まずは、値を省略してもOKなデフォルト引数です。

デフォルト引数
# デフォルト引数
def addition(a,b,c=10):
    return f"(a={a})+(b={b})+(c={c})={a+b+c}"

print(addition(1,2))
Out[0]
(a=1)+(b=2)+(c=10)=13

第三引数にc=10をデフォルト値として入力しておきます。

そうすると関数を呼ぶ時に指定しなくても自動でc=10を入力してくれます。

次はキーワード指定です。これは例えばbとcをデフォルト引数にしていた場合にcの値だけを変更したい場合に使えます。

言葉だけでは伝わらないと思うので実例を見てみましょう。

キーワード指定
# キーワード指定
def addition(a,b=5,c=10):
    return f"(a={a})+(b={b})+(c={c})={a+b+c}"

print(addition(1,c=3))
Out[0]
(a=1)+(b=5)+(c=3)=9

bはデフォルト値でいいのだけど、cだけ変更したい場合に使えます。

可変長引数(第四引数にタプル)
def addition(a,b,c,num_tuple=()):
    return f"(a={a})+(b={b})+(c={c})+(num_tuple={num_tuple})={a+b+c+sum(num_tuple)}"

print(addition(1,2,3,(5,5)))
Out[0]
(a=1)+(b=2)+(c=3)+(num_tuple=(5, 5))=16

想定通りです。

可変長引数(第四引数以降をタプルとして読み込む)
# 変数の前に*を追加するとタプルの可変長引数になる
def addition(a,b,c,*num_tuple):
    return f"(a={a})+(b={b})+(c={c})+(num_tuple={num_tuple})={a+b+c+sum(num_tuple)}"

print(addition(1,2,3,5,5,3,3))
Out[0]
(a=1)+(b=2)+(c=3)+(num_tuple=(5, 5, 3, 3))=22

特に引数でタプルを投入しなくても勝手に第四引数以降をタプルとして認識してくれています。

他にもタプルではなく、ディクショナリとして読み込むことも出来ます。

可変長引数(第四引数以降をディクショナリとして読み込む)
# 変数の前に**を追加するとディクショナリの可変長引数になる
def addition(a,b,c,**num_dict):
    return f"(a={a})+(b={b})+(c={c})+(num_tuple={num_dict})={a+b+c+sum(num_dict.values())}"

print(addition(1,2,3,d=5,e=5,f=3))
Out[0]
(a=1)+(b=2)+(c=3)+(num_dict={'d': 5, 'e': 5, 'f': 3})=19

色々な方法があるんですね。

スポンサーリンク

シャローコピーとディープコピーの確認

長年この2つの言葉には苦しめられてきました 笑 今日で決着をつけたいと思います。

シャローコピーは、元のオブジェクトの参照をコピーする方法です。新しいオブジェクトを作成しますが、元のオブジェクトと同じメモリ領域を共有します。そのため片方の値を変更するともう片方にも影響が出ます。

一方でディープコピーは、元のオブジェクトの全ての値をコピーする方法です。新しいオブジェクトを作成し、元のオブジェクトとは異なるメモリ領域を持ち独立しています。したがって片方の値を変更しても、もう片方には影響がありません。

シャローコピーとディープコピー確認用データの作成
list_moto = [1, 2, 3]
list_shallow_copy = list_moto
list_deep_copy = list(list_moto)

print("\n中身とidを確認")
print("moto",list_moto,id(list_moto))
print("shallow",list_shallow_copy,id(list_shallow_copy))
print("deep",list_deep_copy,id(list_deep_copy))
Out[0]
中身とidを確認
moto [1, 2, 3] 4560848256
shallow [1, 2, 3] 4560848256
deep [1, 2, 3] 4561224384

この時点で元データとシャローコピーはid単位で同じものということが分かりますね 笑

要素を変更した時の挙動の確認
# 内容が同じかどうかを確認 (等価判定)
print("\n等価判定")
print('shallow = moto' if list_shallow_copy == list_moto else 'shallow != moto')
print('deep = moto' if list_deep_copy == list_moto else 'deep != moto')

# identity値が同じかどうかを確認 (等値判定)
print("\n等値判定")
print('id(shallow) = id(moto)' if id(list_shallow_copy) == id(list_moto) else 'id(shallow) != id(moto)')
print('id(deep) = id(moto)' if id(list_deep_copy) == id(list_moto) else 'id(deep) != id(moto)')

# 元を変更
print("\n元を変更 list_moto[1] = 10")
list_moto[1] = 10
print("moto",list_moto)
print("shallow",list_shallow_copy)
print("deep",list_deep_copy)

# シャローコピーオブジェクトを変更
print("\nシャローコピーオブジェクトを変更 list_shallow_copy[2] = 20")
list_shallow_copy[2] = 20
print("moto",list_moto)
print("shallow",list_shallow_copy)
print("deep",list_deep_copy)

# ディープコピーオブジェクトを変更
print("\nディープコピーオブジェクトを変更 list_deep_copy[0] = 100")
list_deep_copy[0] = 100
print("moto",list_moto)
print("shallow",list_shallow_copy)
print("deep",list_deep_copy)
Out[0]
等価判定
shallow = moto
deep = moto

等値判定
id(shallow) = id(moto)
id(deep) != id(moto)

元を変更 list_moto[1] = 10
moto [1, 10, 3]
shallow [1, 10, 3]
deep [1, 2, 3]

シャローコピーオブジェクトを変更 list_shallow_copy[2] = 20
moto [1, 10, 20]
shallow [1, 10, 20]
deep [1, 2, 3]

ディープコピーオブジェクトを変更 list_deep_copy[0] = 100
moto [1, 10, 20]
shallow [1, 10, 20]
deep [100, 2, 3]

ポイントはシャローコピーも元データも要素を変更すると両方変わってしまうという点とディープコピーは完全に独立していそう(自分自身の変更しか影響しない)という点かなと思います。

作業しているうちにちょっと疑問に思ったことがあるので、下記はChatGPT先生に聞いてみた回答を自分なりに解釈して修正した内容です。

シャローコピーはいつ使うのがいいのか

シャローコピーは、オブジェクトが大きい場合にメモリ使用量を削減するために使用することができます。オブジェクトが大きい場合、ディープコピーを行うとコピーに時間がかかり、また大量のメモリを消費する可能性があります。そのため、オブジェクトの内容を変更することがない場合は、シャローコピーを使用することでメモリ使用量を削減することができます。

また、複数の変数が同じオブジェクトを参照する必要がある場合にもシャローコピーが便利です。例えば、複数の関数で同じオブジェクトを使用する場合、シャローコピーを使用することでオブジェクトを一度だけ作成して複数の変数からアクセスすることができます。

そもそもなぜオブジェクトをコピーする必要があるのか?同じ変数を使い回せばいいのではないか

プログラミングにおいて、同じ変数を使い回すことは必ずしも問題ではありませんがオブジェクトによってはコピーを作成する必要があります。

1つの理由として、オブジェクトの参照が変わる可能性があるためという理由が挙げられます。

同じ変数に異なるオブジェクトを代入する場合、その変数が参照しているオブジェクトが変わることになります。これは、参照型のオブジェクトの場合によくある問題になります。

例えばある関数で受け取ったリストを変更すると、そのリストが参照しているオブジェクトが変更される可能性があります。その後、同じ変数に別のリストを代入すると、以前のリストが参照していたオブジェクトが解放されることになり、予期しないエラーが発生する可能性があります。

他にもオブジェクトがイミュータブル(不変)である場合はコピーが必要になる場合があります。イミュータブルなオブジェクトは、変更不可能であるため、変更するためには新しいオブジェクトを作成する必要があります。Pythonの文字列やタプルはイミュータブルのため、新しいオブジェクトをコピーして作成する必要があります。

要するに、同じ変数に対して色々な情報を出し入れしているうちに参照元が変になってしまいバグになる危険性を防ぐためや、そもそもコピーしないと元の情報を保持した別のオブジェクト作れないよねということのようです。

スポンサーリンク

Pythonでクラス作成 (オブジェクト指向)

今回はHumanクラスというのを作ってみます。Javaなど勉強したことがある人であればすでに見覚えがある方が多いのではないでしょうか?

ちなみに面接の準備で、objectとclassの違いは何かという問題があったことを今でも覚えています。答えはobject is an instance of class

class Human:
    # 初期化 (インスタンスが生成されるたびに自動的に呼び出される)
    def __init__(self, name="ヒノマルク", weight=70.0):
        self._name = name
        self._weight = weight
    # eatメソッド
    def eat(self, grams):
        print(f"{grams/1000}kg食べました。") # 食べた分だけ増えます 笑
        self._weight += grams/1000

    # getterメソッド
    def get_name(self):
        return self._name

    def get_weight(self):
        return self._weight

    # setterメソッド
    def set_name(self, name):
        self._name = name

    def set_weight(self, weight):
        self._weight = weight

# クラスのインスタンス化とメソッドの呼び出し
h = Human()
print(f"{h.get_weight()}kgです。")
h.eat(100)
print(f"{h.get_weight()}kgです。")
Out[0]
70.0kgです。
0.1kg食べました。
70.1kgです。

(補足) なぜselfが必要なのか

eatメソッドなどの引数にselfが必要な理由は、インスタンスメソッドであるためです。インスタンスメソッドとは、クラスに定義されたメソッドのうち、インスタンスに対して呼び出されるメソッドのことです。

selfは、インスタンスメソッド内でインスタンス自身を参照するためのキーワードです。selfを使うことで、インスタンス変数や他のインスタンスメソッドを呼び出すことができます。例えば、self._weightは、インスタンス変数 _weight にアクセスするために使われます。

通常、クラス内のすべてのインスタンスメソッドには、selfパラメータが必要です。selfは、インスタンス自身を参照するためのキーワードであり、メソッドがどのインスタンスに属するかを識別するために必要です。

(補足) なぜ変数の前にアンダーバーがついているのか

Pythonでは、変数名の先頭にアンダースコアを付けることで、その変数がプライベートであることを示すことができます。ただし、Pythonでは厳密なアクセス制御ができないため、アンダースコアを付けても完全に隠すことはできません。Humanクラスの場合、インスタンス変数_nameと_weightにアンダースコアを付けることで、その変数がプライベートであることを示すことができます。クラス外からもアクセス可能であることに注意する必要がありますが、プライベート変数であることを示すためにアンダースコアを使うことが、Pythonの一般的な慣習となっています。

スポンサーリンク

Pythonのコマンドライン引数を確認してみる

よくpython3 aaa.py -f input.txtなどと引数を指定するのを見ますが、どうやるのか確認してみました。

argparseを使えば可能のようで、サンプルプログラムを作りました。

arg_test.pyファイルの作成
import argparse

parser = argparse.ArgumentParser(description='ファイルを読み込みます')
parser.add_argument('-f', '--file', type=str, help='インプットファイル名', required=True)
parser.add_argument('-n', '--number', type=int, help='整数を指定してください', required=False)
parser.add_argument('-o', '--output', type=str, help='出力先ファイル名', default='result.txt')

args = parser.parse_args()
print('インプットファイル名:', args.file)
print('入力された整数:', args.number)
print('出力先ファイル名:', args.output)
コマンドラインやターミナルで動作確認
python3.8 arg_test.py -f input.txt -n 10 -o output.txt
Out[0]
インプットファイル名: input.txt
入力された整数: 10
出力先ファイル名: output.txt
ヘルプの確認
python3.8 arg_test.py --help
Out[0]
usage: arg_test.py [-h] -f FILE [-n NUMBER] [-o OUTPUT]

ファイルを読み込みます

optional arguments:
  -h, --help            show this help message and exit
  -f FILE, --file FILE  インプットファイル名
  -n NUMBER, --number NUMBER
                        整数を指定してください
  -o OUTPUT, --output OUTPUT
                        出力先ファイル名

よく見たことがあるアウトプットですね。

スポンサーリンク

まとめ

長くなってしまいましたが、Pythonを扱う上で必要な知識を色々まとめることが出来たと思います。

自分としては色々なところを参照せずに本記事を見るだけで業務を円滑に進めることが出来たらなと思っています。

タイトルとURLをコピーしました