1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pythonのお勉強

Last updated at Posted at 2024-12-19

はじめに

pythonのおべんきょ用にアウトプットするです。
選出基準は私が面白いと思ったか否かです。順番とかないです()
ライブラリ関係は今回は飛ばします。

基本

・型

:ballot_box_with_check: pythonには以下の型がある。

説明
str 文字列型
int int型
list リスト型(配列)
dict 辞書型(jsでいうオブジェクトみたいな)
bool ブール型
float 浮動小数点型
tuple タプル型
from typing import List, Dict, Tuple

tmp_str: str = "hoge"
tmp_int: int = 1
tmp_list: List[str] = ["hoge", "huga"]
tmp_dict: Dict[str, str] = {
    "hoge": "hoge1",
    "fuga": "fuga1",
}
tmp_bool: bool = False
tmp_float: float = 1.41421356237
tmp_tuple: Tuple[int, int, str] = (0, 1, "2")

tupleとlistの違い

要素数が決まっているか否かというのもそうだが、
・tuple -> イミュータブル(変更不可)
・list -> ミュータブル(変更可)
という違いがある。

tmp_list[0] = "aaa" # -> OK
tmp_tuple[0] = 3    # -> NG: TypeError: 'tuple' object does not support item assignment

・型アノテーション

:ballot_box_with_check: 変数が何の型か明示的に示すときに型アノテーションというものを使う。

tmp: string = "hoge"
#変数: 型名

・繰り返し文

:ballot_box_with_check: forwhileがある。

・for

# for 変数 in 繰り返し条件:   みたいな書き方が通例

#1. 0からNまで繰り返し
for i in range(5):
  print(i, end=', ') # -> 0, 1, 2, 3, 4, 

#インデックスが要らない場合
for _ in range(5):
  print("hello world", end='!, ') # -> hello world!, hello world!, hello world!, hello world!, hello world!, 
#2. AからBまで繰り返し
for i in range(2, 5):
  print(i, end=', ') # -> 2, 3, 4, 
#3. AからBまでN回飛ばしで繰り返し
#range(start, stop, step)

for i in range(2, 10, 2):
  print(i, end=', ') # -> 2, 4, 6, 8,

# 以下のようにすれば逆順で繰り返しも可
for i in range(10, 5, -1):
  print(i, end=', ') # -> 10, 9, 8, 7, 6,
#4. オブジェクトを繰り返し処理
list_var = ["apple", "banana", "grape", "peach"]
for fruit in list_var:
  print(fruit, end=', ') # -> apple, banana, grape, peach

tuple_var = ("neko", True, 123, 0.222)
for i in tuple_var:
  print(i, end=', ') # -> neko, True, 123, 0.222, 
#5. オブジェクトをインデックス付きで繰り返し処理
list_var = ["apple", "banana", "grape", "peach"]
for index, fruit in enumerate(list_var):
  print(index, fruit, end=', ') # -> 0 apple, 1 banana, 2 grape, 3 peach, 
#6. dictからkeyのみ取得
dict_var = {
  "neko": "にゃー",
  "inu": "わん",
  "とり": "ぴよぴよ",
}

for key in dict_var.keys():
  print(key, end=', ') # -> neko, inu, とり, 

for index, key in enumerate(dict_var.keys()):
  print(index, key, end=', ') # -> 0 neko, 1 inu, 2 とり,
#7. dictからvalueのみ取得
dict_var = {
  "neko": "にゃー",
  "inu": "わん",
  "とり": "ぴよぴよ",
}

for value in dict_var.values():
  print(value, end=', ') # -> にゃー, わん, ぴよぴよ, 
#8. dictからkey, valueのセットを受け取る
dict_var = {
  "neko": "にゃー",
  "inu": "わん",
  "とり": "ぴよぴよ",
}

for k,v in dict_var.items():
  print(k, v, end=', ') # -> neko にゃー, inu わん, とり ぴよぴよ, 

for index, (k,v) in enumerate(dict_var.items()):
  print(index, k, v, end=', ') # -> 0 neko にゃー, 1 inu わん, 2 とり ぴよぴよ, 

#9. リストの特定のものを処理したいとき
# rangeと同じく、list[start:stop:step]と書ける。
list_var = ["python", "c", "objective-c", "java", "typescript"]

for list in list_var[1:3]:
  print(list, end=', ') # ->c, objective-c,

for list in list_var[2:len(list_var):2]:
  print(list, end=', ') # -> objective-c, typescript, 

for list in list_var[::-1]:
  print(list, end=', ') # -> typescript, java, objective-c, c, python, 

・if

if(条件):
  処理()
elif (条件):
  処理()
else:
  処理(その他)
#三項演算子 真の値 if 条件 else 偽の値
a = 1
b = 2 if a == 1 else 0 
# -> b = 2

・比較演算子

演算子 説明
== 等値
!= 否定
a < b aはbより小さい
a <= b aはb以下
a > b aはbより大きい
a >= b aはb以上
is 等値
not 否定
a and b aもbも真
a or b aかbが真
a in b aがbに含まれる

・ウォルラス(セイウチ)演算子

import random

if (num := random.randint(1,10) > 5):
    print("win")
else:
    print("lose")

# -> lose

:ballot_box_with_check: :=のことをウォルラス演算子という。代入した結果をそのまま式として使える。
便利だけど可読性が下がりそうなので節度が要りそう...

関数系

・アロー演算子(関数アノテーション)

def hogeFn() -> str:
    return "hoge";
    
print(hogeFn());

# -> hoge

関数アノテーションとは、関数の戻り値の型を明示的に示すことができる機能。
ただ、別に強制力はなく、戻り値の型が違っていても動くらしい。

参考

・デコレーター(関数)

#例
import functools
import os

def decorator(fn):
  def helper(args, kwds):
    print("helper\nargs: ", args, "\nkwds: ",kwds, end="\n\n");
  @functools.wraps(fn)
  def wrapper(*args, **kwds):
    helper(args, kwds)
    response = fn.__name__ + 'を開始しました。\n'
    result = fn(*args, **kwds)
    response += 'result: ' + str(result) + os.linesep
    response += fn.__name__ + 'を終了しました。\n'
    return response

  return wrapper

@decorator
def test_fn():
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn())
print(test_fn.__name__)


"""
↓出力結果

helper
args:  ((), {}) 
kwds:  {}

test_fnを開始しました。
result: 6
test_fnを終了しました。

test_fn
"""

デコレーターとは?
デコレーター = 関数をラップして返す関数。

ある関数を呼び出したときに、@関数名を書くと、デコレーターになり、デコレーターをつけた関数が実行される前にデコレーターが実行される。

上の例で説明すると、

① この部分でtest_fnが実行される前にdecoratorが実行される。

@decorator
def test_fn():
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn())

② デコレーター内部ではwrapper関数によって、呼び出し元の関数を実行し、その結果を加工して返している。

# デコレーター関数
def decorator(fn):
  def helper(args, kwds):
    print("helper\nargs: ", args, "\nkwds: ",kwds, end="\n\n");
  @functools.wraps(fn)
  def wrapper(*args, **kwds):
    helper(args, kwds)
    response = fn.__name__ + 'を開始しました。\n'
    result = fn(*args, **kwds)
    response += 'result: ' + str(result) + os.linesep
    response += fn.__name__ + 'を終了しました。\n'
    return response

  return wrapper
@functool.wrapsについて この記述は、呼び出し元の関数のメタデータを保持するための記述。

メタデータとは❓
  => 関数の名前だったり、関数の型定義だったり、メタ的な情報のこと。これがあることでデバッグに役立つ。

@functool.wrapsを書かない

# デコレーター関数
def decorator(fn):
  ~
# @functools.wraps(fn)
  def wrapper(*args, **kwds):
   ~
  return wrapper

print(test_fn.__name__) # => wrapper

このように、test_fnを呼んでいるのに、wrapper関数の情報で上書きされてしまう。これは、デコレータの中で実行関数がwarpperに上書きされてしまっているから。

@functools.wrapsを使いましょう。

# デコレーター関数
def decorator(fn):
  ~
  @functools.wraps(fn)
  def wrapper(*args, **kwds):
   ~
  return wrapper

print(test_fn.__name__) # => test_fn
wrapper(*args, **kwds)について

*argsとは❓
=> 呼び出し元の関数の、引数のこと。
ちなみに、pythonでは変数の前に"*"をつけると、可変長引数になる。

**kwdsとは❓
=> 呼び出し元の関数にkey-valueのdictがあったときに、これを受け取る。
pythonでは、変数の前に"**"をつけると、dictになる。

例えば

@decorator
def test_fn(name: str, age: int, kind:str = None):
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn("neko", 2, kind="cat"))

"""
こうすると、出力が以下のようになる。

helper
args:  ('neko', 2) 
kwds:  {'kind': 'cat'}
"""

③ 最後に、呼び出し元のtest_fnの中身が実行される。

def test_fn():
  a = 1
  b = 5
  c = a + b
  return(a + b)

print(test_fn())

💡活用例
・クエリ前にキャッシュを確認する関数をデコレーターにする。


#キャッシュがあるかを確認し、あればキャッシュを、なければ実行関数を返す。
def check_cache(fn):
  #cache確認関数
  def check_cache_fn()
  
  def wrapper(*args, **kwds):
    cache = check_cache_fn()
    if (isinstance(cache, CacheClass)):
      return cache
    else:
      return fn(args, kwds)
  return wrapper

@check_cahce
def getBook(id: str):
  # ~
  # クエリ処理
  # ~

getBook("1")

クラス系

・基本

class TestClass():
  def __init__(self, hoge):
    self.hoge = hoge

  def show_hoge(self):
    print(self.hoge)

test_class_instance = TestClass("hoge")
test_class_instance.show_hoge() # => hoge

:ballot_box_with_check: classを書くときは、__init__メソッドとselfを書く。

__init__とは❓
インスタンス化するとき、必ず最初に呼ばれるメソッドのこと。

selfとは
インスタンス化したとき、生成されるオブジェクトそのもの。
だからself.methodで参照できるんすね😙

・withメソッド

import datetime

class AuthenticationError(Exception):
    pass
    
class TestClass():
  def __init__(self, token):
    self.token = token

  def __enter__(self):
    print("処理開始")
    expired = self.token.accessToken.expired
    today = datetime.datetime.today()
    if (expired < today):
      raise AuthenticationError("token has expired")
    else: 
      # ~
      # connection処理など
      # ~
      
  def __exit__(self, exc_type, exc_value, traceback):
    # ~
    # コネクション切断処理など
    # ~

  def getBook(self):
    # ~
    # クエリ処理など
    # ~

def getBooks():
  try:
    with TestClass(token) as test:
      test.getBook()
  except:
    #例外処理

getBooks()

:ballot_box_with_check: withメソッドは、コードの実行前後に特定の処理を挟みたいときに便利なメソッド。

① class側の定義

class TestClass():
  ~
  
  def __enter__(self):
    print("処理開始")
    expired = self.token.accessToken.expired
    today = datetime.datetime.today()
    if (expired < today):
      raise AuthenticationError("token has expired")
    else: 
      # ~
      # connection処理など
      # ~
      
  def __exit__(self, exc_type, exc_value, traceback):
    # ~
    # コネクション切断処理など
    # ~

  ~

withメソッドを使うときには、classに__enter__メソッドと、__exit__メソッドが必要。

__enter__ => withでラップしたコードが実行される前に走るメソッド。

__exit__ => withでラップしたコードが実行された後に走るメソッド。

② withメソッド

with TestClass(token) as test:
      test.getBook()

・withメソッドを使って、instance化すると、__enter__メソッドと__exit__メソッドを挟んで処理を実行できる。

=> DBとの接続準備だったり、切断処理だったりを簡潔に書くことができ、大変便利💮

・デコレーター(クラス)

二度目のデコレーターの登場。

from typing import Union

book_list = {
    "1": {"タイトル": "ノルウェイの森", "著者": "村上春樹", "ジャンル": "小説", "出版年": 1987},
    "2": {"タイトル": "コンビニ人間", "著者": "村田沙耶香", "ジャンル": "小説", "出版年": 2016},
    "3": {"タイトル": "アンドロイドは電気羊の夢を見るか?", "著者": "フィリップ・K・ディック", "ジャンル": "SF", "出版年": 1968},
    "4": {"タイトル": "嫌われる勇気", "著者": "岸見一郎", "ジャンル": "哲学", "出版年": 2013},
    "5": {"タイトル": "アリスのままで", "著者": "リリ・アール", "ジャンル": "小説", "出版年": 2007},
    "6": {"タイトル": "エンド・オブ・エタニティ", "著者": "アイザック・アシモフ", "ジャンル": "SF", "出版年": 1955},
    "7": {"タイトル": "ハリー・ポッターと賢者の石", "著者": "J.K.ローリング", "ジャンル": "ファンタジー", "出版年": 1997},
    "8": {"タイトル": "地獄の季節", "著者": "芥川龍之介", "ジャンル": "小説", "出版年": 1927},
    "9": {"タイトル": "コンビニ人間のその後", "著者": "村田沙耶香", "ジャンル": "小説", "出版年": 2021},
    "10": {"タイトル": "1984年", "著者": "ジョージ・オーウェル", "ジャンル": "ディストピア", "出版年": 1949},
    "11": {"タイトル": "月曜日のたわわ", "著者": "柴田よしき", "ジャンル": "エッセイ", "出版年": 2015},
    "12": {"タイトル": "風と共に去りぬ", "著者": "マージェリ・シャーウッド", "ジャンル": "小説", "出版年": 1936},
    "13": {"タイトル": "人間失格", "著者": "太宰治", "ジャンル": "小説", "出版年": 1948},
    "14": {"タイトル": "未来のぼくら", "著者": "村田沙耶香", "ジャンル": "小説", "出版年": 2019},
    "15": {"タイトル": "アントワネット", "著者": "ステファニー・デュモン", "ジャンル": "歴史小説", "出版年": 2010},
    "16": {"タイトル": "鴨川ホルモー", "著者": "万城目学", "ジャンル": "小説", "出版年": 2007},
    "17": {"タイトル": "永遠の0", "著者": "百田尚樹", "ジャンル": "小説", "出版年": 2006},
    "18": {"タイトル": "イノセンス", "著者": "乙一", "ジャンル": "小説", "出版年": 2001},
    "19": {"タイトル": "ノルウェーの森 2", "著者": "村上春樹", "ジャンル": "小説", "出版年": 1990},
    "20": {"タイトル": "カラフル", "著者": "森絵都", "ジャンル": "小説", "出版年": 2008}
}

class CacheDecorator:
    def __init__(self, func):
        self.func = func
        self.cache = {}
    
    def __call__(self, *args):
        if args in self.cache:
            print("Returning cached result. id:", args[0])
            return self.cache[args] 
        result = self.func(*args)
        self.cache[args] = result
        return result

@CacheDecorator
def getBook(id:str) -> Union[dict, None]:
  for book_id, value in book_list.items():
    if book_id == id:
      return value
  return None

book1 = getBook("1")
book2 = getBook("2")
book3 = getBook("1")

print(f"{book1}\n{book2}\n{book3}")

"""
出力結果

Returning cached result. id: 1
{'タイトル': 'ノルウェイの森', '著者': '村上春樹', 'ジャンル': '小説', '出版年': 1987}
{'タイトル': 'コンビニ人間', '著者': '村田沙耶香', 'ジャンル': '小説', '出版年': 2016}
{'タイトル': 'ノルウェイの森', '著者': '村上春樹', 'ジャンル': '小説', '出版年': 1987}
"""

:ballot_box_with_check: クラスのデコレーターでは__call__メソッドが関数の前に呼ばれる。
クラスでデコレーターを作成すると、__call__というメソッドを呼ぶことができる。

・12/23追記
これは間違い。 __call__が呼ばれるタイミングは、クラスをインスタンス化して、関数として使用した場合。
すなわち以下のような場合になる。

class TestDec:
  def __init__(self):
    ~~~

  def __call__(self):
    print('called')

@TestDec
def my_function():
    print("Hello!")

my_function() # -> called が呼ばれる。

逆に、最初から別の関数を返すように明示すれば、__call__は呼ばれない。


class MyDecorator:
    def __init__(self, func):
        self.func = func

    def helper(self, *args, **kwargs):
        print("Helper called!")
        return self.func(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("This won't be called")

@MyDecorator
def my_function():
    print("Hello!")

# helperを明示的に指定
my_function = my_function.helper  # helper関数を直接使う

my_function()  # __call__ は呼ばれず helper が呼ばれる

今回の例では、
__init__(CacheDecoratorクラス) -> __call__(CacheDecoratorクラス) -> getBook
という順で実行される。

*argsなどの挙動は変わらないので、今回みたいにクエリ前にcacheを保存しておいて、後で取り出すみたいな書き方が出来る。

その他

・f_string

フォーマット済み文字列リテラル。文字列中に変数や関数を埋め込むことができる。

name = "タマ"
print(f"吾輩は猫である。名前は{name}") # -> 吾輩は猫である。名前はタマ

:ballot_box_with_check: f_stringを使うときは文字列前にfを書く。変数は{}の中に書く。

・try, expect, raise, assert

・try
=> try-expect句の開始の文言。
・expect
=> try-expect句のエラー時の文言。jsとかでいうcatch。
・raise
=> エラーを発生させるための文言。jsとかでいうthrow。
・assert
=> aseert 条件式, エラーの時の文言という形式で書き、Trueの時は特に何も起こらないが、Falseの時はraiseと同様、エラーを投げる。

try:
  ~
  処理
  ~
  raise NameError('変数名が異常です。') #エラーを投げる
  assert 1 == 2, "間違い" #Falsyな時だけエラーを投げる。
expect Exception as e:  #エラーが起こったらここに入る。
  print("an Error occured. Error:", e)

終わり

デコレーターにとても可能性を感じています。
まだまだ慣れませんが、今後も勉強:innocent:

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?