見出し画像

Pythonの復習

6/12, 2024
reference: Data Science from Scratch


Pythonの基本や便利な標準ライブラリについて知っていますか?
僕は基礎をすっ飛ばして、できるだけ早くレベルの高いことをできるようになりたかったので、基礎が疎かになっていました。
確かに要点だけを学習して、早く次の段階に進むことはプログラミングにおいて大事です。
しかし基礎が固まっていないことによる弊害を経験したので、今が復習するタイミングだと思いました。
よってこの記事を書いています。
早速コードを書いてみましょう。

word_counts = {}
for word in document:
    previous_count = word_counts.get(word, 0)  # getメソッドならエラーが出ない
    word_counts[word] = previous_count + 1

上記では、あるテキストに該当の単語が含まれているか調べています。
もし含まれていたら、辞書の値にその単語数を数え上げていきます。
しかしdefaultdictやCounterを使用すると、もっと簡単に実装できます。

from collections import defaultdict  # 辞書を生成するときに使う
                                     # 値を生成時に決められる

word_counts = defaultdict(int)  # 返す根を初期値に0を設定
for word in document:
    word_counts[word] += 1
    
    
# 一番簡単な方法
# 一つ続きの値を、キーとその出現回数の辞書として返す
from collections import Counter
word_counts = Counter(document)

counter = Counter('voldemotehadokoniitta')
for word, count in counter.most_common(3):
    print(word, count)   
# int以外にも設定できる。例: list, dictなど
dd_list = defaultdict(list)
dd_list["friends"].append("Sasha")  # {"friends": ["sasha"]}

dd_dict = defaultdict(dict)
dd_dict["islands"]["Kyushu"] = "Tanega-shima"  # {"islands": {"kyushu": "Tanega-shima"}}

集合(Set)はin演算子が高速

stopwords_list = ["a", "an", "at"] + hundred_of_other_words + ["yet", "you"]
"zip" in stopwords_list  # False

stopwords_set = set(stopwords_list)
"zip" in stopwords_set  # より高速

集合の性質

num_list = [1, 2, 3, 1, 2, 3]
print(len(num_list))  # 6
num_set = set(num_list)
print(len(num_set))  # 3

andやorを使って文字や数の存在を確かめる

def some_function_that_returns_a_string():
    return "string"

# 文字を返す関数を調べる
s = some_function_that_returns_a_string()
if s:
    first_char = s[0]
else:
    first_char = ""

# 1つ目の値がTrueであれば、演算子の結果はsの1文字目になる
first_char = s and s[0]

# 数値の存在を確かめる
# xがNoneの時、0に変える
safe_x = x or 0
safe_x = x if  x is not None else 0

# all(iterable) iterableの全ての要素が真ならばTrueを返す
# any(iterable) 要素のいずれかが真であればTrue
all([True, 1, {3}])  # True
all([True, 1, {}])  # False
any([True, 1, {}])  # True
any([])

sortの使い方

# sortメソッドは元のリストを変更する
# sorted関数は新しいリストを返す
x = [4, 1, 3, 2]
y = sorted(x)
print(x)
print(y)

# 絶対値で降順にsort
x = [-9, 1, -8, 7]
y = sorted(x, key=abs, reverse=True)
print(y)

# 単語(キー)とその出現回数を多い順にsort
reversed_word_count = sorted(word_counts.items(),
                             key=lambda word_and_count: word_and_count[1],
                             reverse=True)

リスト内放棄表現の使い方

# %は余りを求める
even_numbers = [x for x in range(5) if x % 2 == 0]
squares = [x * x for x in range(5)]
even_sauares = [x * x for x in even_numbers]

sd = {x: x * x for x in range(5)}
ss = {x * x for x in [-1, 1]}
print(ss)
print(sd)

zeros = [0 for _ in sd]

for の性質

pairs = [(x, y)
        for x in range(10)
        for y in range(10)]  # 10 * 10 = 100個の組み合わせ
                             # 前のforを後ろのforが参照している

increasing_pairs = [(x, y)
                    for x in range(10)
                    for y in range(x + 1, 10)]  # x=0に対してy=1~9の値をとる

assertを使って自分のコードをテストすることは良い習慣。

def smallest_items(xs):
    return min(xs)

assert smallest_items([11, 3, 5, 10]) == 5
assert smallest_items([-19, 19, 0]) == -19

オブジェクト指向プログラミングの例

# "カウンター"(数を数えるという概念)をクラスにして定義

class CountingClicker:
    """数を数えるクラス"""
    def __init__(self, count=0):  # self: クラスから派生するインスタンスを表す
        self.count = count

    def __repr__(self):  # オブジェクトを表す「公式の (official)」文字列を返す
        return f"CountingClicker(count={self.count})"

    def click(self, num_times=1):
        self.count += num_times

    def read(self):
        return self.count

    def reset(self):
        self.count = 0

# assertで正常に動くか確認していく
clicker = CountingClicker()

assert clicker.read() == 0  # 最初のカウントは0であるか

clicker.click()
assert clicker.read() == 1  # カウントが1増えているか

clicker.reset()
assert clicker.read() == 0  # カウントは0にリセットされているはず

ジェネレータの例

# ジェネレータ:リストのように繰り返し処理されるが、値は必要な分だけ生成される

def generate_range(n):
    i = 0
    while i < n:
        yield i  # 関数の処理を中止し、現在の結果を返している
        i += 1

# ジェネレータを作る1つ目の方法
# yieldで返された値を1つづつ取り出している
for i in generate_range(10):
    print(f"i: {i}")

# 自然数も生成できる
def natural_numbers():
    # 1, 2, 3, ...を一つずつ返す
    n = 1
    while True:
        yield n
        n += 1

"""繰り返して処理を行うならリストの方が適切"""

# ジェネレータを作る2つ目の方法
evens_below_20 = {i for i in generate_range(20) if i % 2 == 0}

ジェネレータの活用

# 下の計算は実際に繰り返し処理が行われるまで計算されない
data = natural_numbers()
evens = {x for x in data if x % 2 == 0}
even_squares = {x * x for x in evens}
even_sauares_ending_in_eight = {x for x in even_sauares if x % 10 == 8}

enumerate関数で数え上げを楽に実現できる。

# enumerate = 数え上げる
# リストなどのシーケンスを0から数え上げる
seasons = ['January', 'February', 'Merch', 'May']
enumerated_seasons = list(enumerate(seasons))
print(enumerated_seasons)

# 値を(インデックス、値)のペアに変換する方法
names = ["Sasha", "Tanaka", "Shota", "Yuki"]
for i, name in enumerate(names):
    print(f"名前 {i}{name}")

正規表現の1例

import re

re_examples = [  # 以下は全てTrue
    not re.match('a', 'cat'),  # 文字の先頭でマッチしているか => この場合してない
    re.search('a', 'cat'),  # 文字全体を捜査する
    3 == len(re.split('[ab]', 'carbs')),  # 結果: ['c', 'r', 's']
    'R-D-' == re.sub('[0-9]', '-', 'R1D9')  #  数字を'-'に置き換える
]

zip関数: 複数のリストをまとめてタプルのリストに変換

list1 = ['a', 'b', 's']
list2 = [1, 2, 3]

[pair for pair in zip(list1, list2)]  # [('a', 1), ('b', 2), ('s', 3)]

pairs = [('a', 1), ('b', 2), ('s', 3)]
# リストやタプルでは引数の前に*をつけると、それぞれの要素が個別の引数として関数に渡される
letters, numbers = zip(*pairs)
print(letters)   # ('a', 'b', 's')
print(numbers)  # (1, 2, 3)

高階関数に任意の引数を持つ関数を渡す方法

def doubler(f):
    def g(x):
        return f(x) * 2
    return g

def f1(x):
    return x + 1

g = doubler(f1)
# 正しく動作するか確認する
assert 8 == g(3)
assert 2 == g(0)

# 複数の引数の関数では上手くいかない
def f2(x, y):
    return x + y

g = doubler(f2)
try:
    g(1, 2)
except TypeError:
    print("gの引数は1つだけです")

# 任意の引数を受け取れる関数
def magic(*args, **kwargs):
    print('名前のない引数:', args)
    print('キーワード引数:', kwargs)

magic(1, 2, key1='potato', key2='onion')

# 名前のない引数: (1, 2)
# キーワード引数: {'key1': 'botato', 'key2': 'onion'}

# 任意個の引数を必要とする高階関数に使える
def correct_doubler(f):
    """どのようなfが来ても問題ない高階関数"""
    def g(*args, **kwargs):
        return f(*args, **kwargs) * 2
    return g

g = correct_doubler(f2)  # f2の引数はxとy
assert g(1, 2) == 6

# リストや辞書を関数の引数に指定する場合にも使える
def other_way_magic(x, y, z):
    return x + y + z

x_y_list = [1, 2]
z_dict = {'z': 3}  # dictの場合は**引数として使うと、キーがキーワード、値が引数になる
assert other_way_magic(*x_y_list, **z_dict)  == 6

オブジェクトの型の指定(型アノテーション)

def add(a: int, b: int) -> int:
    return a + b

add(10 + 10)  # ok
add('My name is' + 'Ken')  # エラー出る

# 型を指定することで、読む人に関数の情報を示すことができる

型の指定でより関数の構成をわかりやすくできる。

# 修正前
def total(xs: list) -> float:
    return sum(xs)
# 修正後
from typing import List

def total(xs: List[int]) -> float:  # xsはfloatのリストなので
    return sum(xs)

型アノテーションで変数をわかりやすく示す。

from typing import List, Optional

values: List[int] = []
best_so_far: Optional[float] = None  # floatまたはNoneを受け入れる

その他の様々な型アノテーション

from typing import Dict, Iterable, Tuple

counts: Dict[str, int] = {'data': 1, 'science': 2}
# リスト、ジェネレータはどちらもiterable
if lazy:
    evens: Iterable[int] = [x for x in range(10) if x % 2 == 0]
else:
    evens = [0, 2, 4, 6, 8]

# タプル: 要素ごとにデータの型を指定する
triple: Tuple[int, float, int] = (1, 2.5, 3)
from typing import Callable
# callableとは: callable(argument1, argument2, argumentN)

# repeater: str, intを引数として受け取り、strを返すことを示している
def twice(repeater: Callable[[str, int], str], word: str) -> str:
    return repeater(word, 2)

def comma_repeater(s: str, n: int) -> str:
    n_copies = [s for _ in range(n)]
    return ', '.join(n_copies)  # joinの例: '.'.join(['ab', 'cd', 'ef']) -> 'ab.cd.ef'
                                # iterableを引数として受け取り、結合した文字列を返す

assert twice(comma_repeater, 'love poodle') == 'love poodle, love poodle'  # True

まとめ

自分で思ってたよりも基礎が身についてないことがわかりました。

この記事が気に入ったらサポートをしてみませんか?