Python 補足①

ここでは、リスト内包表記、関数型プログラミングについて説明します

リスト内包表記

リスト内包表記とは、イテラブルオブジェクトから新しいリストを生成するために簡潔に記述する方法です
for文とオプションでif文を使い、1行で記述することができます
※イテラブルオブジェクトとは、for文で繰り返し処理の対象となる、要素を1つずつ順番に取り出せるデータ構造のことを指します
以下に記述方法を示します

forの繰り返し処理を使用し、以下のように記述します

```
[for 変数 in イテラブルオブジェクト]
```

以下は、for文を使用して1から10までの数値のリストを作成しています

numbers = []
for i in range(10):
    numbers.append(i+1)

リスト内包表記を使うと、以下のように記述できます

numbers = [i+1 for i in range(10)]
print(numbers)

また、for文の中に条件文を含めて、以下のように記述します

[for 変数 in イテラブルオブジェクト if 条件文]

以下は、0から10までの数値の中から、奇数だけをリストに取り出すサンプルプログラムです

numbers = [i for i in range(11) if i % 2 !0]
print(numbers)

Pythonはインタプリタ言語(コンピューターが逐次翻訳しながら実行する言語)であるため、ステップ数が少ないほど実行速度が速くなる傾向があります
そのため、リスト内包表記はfor文で記述するよりも処理が早くなるというメリットがあります

関数型プログラミング

関数型プログラミングとは、純粋な関数(入力から出力が一意に決まる関数)を組み合わせてプログラムを構築する考え方です
Pythonは、関数を第一級オブジェクトとして扱うなど、関数型プログラミングの機能を備えています

第一級オブジェクトとは、変数への代入や関数の引数として扱ったり、関数の戻り値にするなどの処理を制限なしに使用できる対象のことを表します
出典:https://ja.wikipedia.org/wiki/%E7%AC%AC%E4%B8%80%E7%B4%9A%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88

そのため、関数を関数の引数として渡したり、関数からの戻り値として関数を返したりすることができます
Pythonでは、ラムダ式やmap、filter、reduceなどの関数型プログラミングをサポートする組み込み関数があります
map、filter、reduce関数とラムダ式を組み合わせることで、より簡潔で効率的なコードが記述できます

ラムダ式(lambda)

ラムダ式は無名の関数を作成するための方法です
lambdaというキーワードを使い、以下のように記述します

lambda 引数1,引数2,…: 式

lambda式は簡潔に一行で記述する必要があり、try… exceptのような分岐を持つことはできません

2つの数値を足すラムダ式を以下に示します

add = lambda x, y: x + y
print(add(3, 5)) 
実行結果
8

上記プログラムをdefを使用したプログラムに書き換えると以下のように記述できます

def func(x,y):
    return x + y

add = func(3, 5)
print(add)

defは宣言文を表していますが、labmdaは式として簡潔に表現しています

また、2乗の計算をするラムダ式を関数の引数にした例を以下に示します

def loop(f):
  for i in range(10):
    print(f(i),end=' ')

loop(lambda x:x*x) 
実行結果
0 1 4 9 16 25 36 49 64 81 

lambdaは、コードを簡潔にまとめるために使用されます
小さな関数をいちいち定義しなくてもいいですし、簡潔なので名前の衝突のリスクも最小限に抑制できるメリットがあります

map

map関数はイテラブルオブジェクトの各要素に関数を適用し、新しいイテラブルオブジェクトを生成します
map関数は以下のように記述します

map(関数,イテラブル1,イテラブル2,)

以下にリストの各要素を2倍にするサンプルを示します

numbers = [1, 2, 3, 4]
doubled_numbers = list(map(lambda x: x * 2, numbers))
print(doubled_numbers)
実行結果
[2, 4, 6, 8]

辞書を引数にする場合は、以下のように記述します

my_dict = {'a': 1, 'b': 2, 'c': 3}

values_doubled = list(map(lambda x: x * 2, my_dict.values()))
print(values_doubled) 
実行結果
[2, 4, 6]

以下にイテラブルオブジェクトを2つ引数とし、それぞれのイテラブルオブジェクトを加算した値を求めるサンプルを示します

numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]

result = list(map(lambda x, y: x + y, numbers1, numbers2))
print(result) 
実行結果
[5,7,9]

filter

filter関数はイテラブルオブジェクトの要素を条件に基づいてフィルタリングします
filter関数は以下のように記述します

filter(関数,イテラブル)

以下に、偶数だけを抽出するサンプルを示します
ラムダ式の式の部分に条件式を記述しています

numbers = [1, 2, 3, 4, 5, 6]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers)
実行結果
[2,4,6]

reduce

reduce関数はイテラブルの要素に対して順番に累積的な処理をして単一の値を生成します
reduceは以下のように記述します

reduce(関数,イテラブル)

以下は、リストの要素を合計するサンプルを示します
reduce関数を使用するには、reduce関数をインポートする必要があります

from functools import reduce
numbers = [1, 2, 3, 4]
total = reduce(lambda x, y: x + y, numbers) 
print(total)
実行結果
10

以下はリストから最小となる値を取得するサンプルを示します

numbers = [10,15,22,4,6]
min = reduce(lambda x, y: x if x < y else y, numbers)
print(min)
実行結果
4

関数内関数

Pythonでは、関数の中に関数を定義することができます
以下に関数内関数を定義するサンプルプログラムを示します

def outer_function(name):
    def inner_function():
        return "Hello! " + name

    return inner_function()

message = outer_function("Taro")
print(message)
実行結果
Hello! Taro
def inner_function(name):

inner_function関数は、outer_function関数の内部で定義している関数内関数になります
inner_function関数は、引数nameの前に「Hello! 」を追加しています

return inner_function(name)

outer_function内からinner_function関数を呼び出しています

message = outer_function("Taro")

outer_function関数を呼び出しています
inner_function関数はouter_function関数ブロックの中に定義されているため、outer_function関数内でしか呼び出すことができません

デコレータ

デコレータは、Pythonにおいて関数を修飾するための強力なツールです
デコレータを使用することで、既存の関数をラップし、既存の関数の機能を変更したり、拡張したりすることができます
具体的には、デコレータは関数を引数として受け取り、修飾された新しい関数を返す高階関数です
(※高階関数とは、関数の引数や戻り値に関数が定義されている関数のことです)

デコレータは、以下のような場面で役立ちます
・ログ出力
・実行時間の計測
・認証処理
・キャッシュ処理

関数やクラス宣言の前に@デコレータ名と記述します

@デコレータ名
def function():
    処理

以下にデコレータを使用し、実行時間を表示するサンプルを示します

import time

def my_decorator(func):
    def wrapper():
        start_time=time.time()
        func()
        end_time=time.time()
        print(f"実行時間: {end_time - start_time}秒")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")
    time.sleep(2)
    
say_hello()
実行結果
Hello!
実行時間: 2.000200033187866

my_decoratorは、引数として計測対象の関数 (func) を受け取るデコレータ関数です
内部で wrapperという関数内関数を定義しています
wrapper関数はfuncを実行する前後に時間を記録します
time.time() を使用して、開始時刻と終了時刻を取得し、実行時間を表示します
my_decorator関数は戻り値にwrapper関数を返すように定義されています

@my_decorator
def say_hello():
    print("Hello!")
    time.sleep(2) # 2秒待機

@my_decorator は、say_hello 関数に my_decorator デコレータを適用する構文です
先に定義したmy_decoratorをデコレータとして使用しています

say_hello()

say_hello関数を実行すると、デコレータのwrapper関数が実行され、実行時間が計測されます

上のサンプルプログラムをデコレータを使用せずに書き換えたプログラムを以下に示します

import time

def my_decorator(func):
    def wrapper():
        start_time=time.time()
        func()
        end_time=time.time()
        print(f"実行時間: {end_time - start_time}秒")
    return wrapper

def say_hello():
    print("Hello!")
    time.sleep(2)

f = my_decorator(say_hello)
f()

クロージャ(Clojure)

クロージャは、関数内関数として定義され、内側の関数が外側の関数の変数(外部スコープの変数)を参照できる機能のことです
外側の関数が実行を終えても、内側の関数(クロージャ)は外側の関数の変数を記憶しアクセスすることができます

以下にクロージャのサンプルプログラムを示します

def greet():
    name = "John"
    def inner():
      return "Hi " + name

    return inner

message = greet()
print(message())  # 出力: “Hi John”
実行結果
Hi John

この例では、greet関数がクロージャのinner関数を返しています
message変数は、greet()関数を実行し、戻り値のクロージャを message 変数に代入します
greet() 関数の実行が終了した後でも、message 変数に代入されたクロージャは name 変数の値を保持しています

message() を実行すると、クロージャが実行されます
クロージャは、外側の greet() 関数で定義された name変数にアクセスすることができ、”Hi John” という文字列を生成して出力します
クロージャは、関数の実行が終了しても、その関数のスコープ内の変数を保持することができるという特徴があります

また、Johnをgreetの引数に渡すことで以下のように記述することもできます

def greet(name):
    # クロージャの定義
    def innner():
      # greetの変数nameから文字列を生成し生成した文字列を返す
      return "Hi " + name

    # クロージャ関数の定義を返す
    return innner

# greet()を利用しクロージャ関数の定義を受け取る
message = greet("John")
# クロージャ(inner)を実行
print(message())  # 出力: “Hi John”

# greet()を利用しクロージャ関数の定義を別の変数で受け取る
message1 = greet("Bob")
# クロージャ(inner)を実行
print(message1())  # 出力: “Hi Bob”

以下は、カウンタを保持するプログラムをグローバル変数を使わずに作成したサンプルプログラムです

def counter():
  # カウンタ用の変数countを宣言し初期化する
  count = 0

  # クロージャの定義
  def get_counter():
    nonlocal count
    count = count + 1 # カウンタの加算
    print(count)

  # クロージャ関数の定義を返す
  return get_counter


# counter()を実行して、get_counter関数の『定義』を受け取る
cnt1 = counter()

# クロージャ(get_counter)を実行
cnt1()
cnt1()
cnt1()

# counter()を実行して、get_counter関数の『定義』を新たな変数で受け取る
cnt2 = counter()

# クロージャ(get_counter)を実行
cnt2()
実行結果
1
2
3
1

itertools

itertoolsは、Pythonのイテラブルオブジェクト(リスト、タプルなど)に対して一般的な操作を行うためのツールを提供します
参考URL:効率的はループ用のイテレータ生成関数群

itertoolsの一部の関数とその用途を以下に示します

chain()

itertools.chainは複数のイテレータを受け取り連結します
サンプルプログラムを以下に示します

from itertools import chain

numlist = list(chain([1, 2], [3, 4], [5, 6]))
print(numlist)

numlist1 = tuple(chain([1, 2, 3, 4], (5, 6, 7, 8)))
print(numlist1)

numlist2 = list(chain('ABC', [1, 2, 3]))
print(numlist2)

for element in chain([1, 2, 3], ['a', 'b', 'c']):
  print(element,end=' ')
実行結果
[1, 2, 3, 4, 5, 6]
(1, 2, 3, 4, 5, 6, 7, 8)
['A', 'B', 'C', 1, 2, 3]
1 2 3 a b c 
numlist = list(chain([1, 2], [3, 4], [5, 6]))

複数のリストを連結して新たなリストを作成しています

numlist1 = tuple(chain([1, 2, 3, 4], (5, 6, 7, 8)))

リストとタプルを連結し新たなタプルを生成しています

numlist2 = list(chain('ABC', [1, 2, 3]))

文字列とリストを連結し新たなリストを生成しています

for element in chain([1, 2, 3], ['a', 'b', 'c']):
  print(element,end=' ')

リスト同士を連結し、for文で要素を一つずつ取り出しています

islice()

入力されたイテレータからインデックスで指定された特定の要素を取得します
サンプルプログラムを以下に示します

from itertools import *

print("--0~6まで")
for i in islice(range(10), 6):
    print(i,end = " ")

print("\n--2~6まで")
for i in islice(range(10), 2,6):
    print(i,end = " ")

print("\n--1~7まで2ステップづつ")
for i in islice(range(10), 1, 7, 2):
    print(i,end = " ")

print("\n--文字列")
for c in islice("ABCDEFG",1,5):
  print(c,end=" ")
実行結果
--06まで
0 1 2 3 4 5 
--26まで
2 3 4 5 
--17まで2ステップづつ
1 3 5 
--文字列
B C D E 
for i in islice(range(10), 6):

range(10)で作成したイテレータの5番目までの要素を抽出します

for i in islice(range(10), 2,6):

range(10)で作成したイテレータの2番目から6番目までの要素を抽出します

for i in islice(range(10), 1, 7, 2):

range(10)で作成したイテレータの1番目から6番目までの要素を2番おきに抽出します

for c in islice("ABCDEFG",1,5):

文字列ABCDEFGの1番目から4番目までの要素を抽出します

zip_longest()

長さの異なる複数のイテレータを同時に処理します
サンプルプログラムを以下に示します

from itertools import zip_longest
 
print("在庫リスト")
for (item_name, stock_count) in zip_longest(["desktop", "laptop", "tablet", "smartphone"], [12, 83, 55],fillvalue='O'):
    print(item_name + " / " + str(stock_count))

print("\n名簿")
for (name,age,city) in zip_longest(["Yamada","Suzuki","Sato"],(25,22,18,26),["Tokyo","Kanagawa","Chiba","Tokyo"],fillvalue='None'):
  print(name + " / " + str(age) + " / " + city)
実行結果
在庫リスト
desktop / 12
laptop / 83
tablet / 55
smartphone / O

名簿
Yamada / 25 / Tokyo
Suzuki / 22 / Kanagawa
Sato / 18 / Chiba
None / 26 / Tokyo

値のないインデックスにはfillvalue='O'とデフォルトで表示する値を指定します

コメント

この記事へのコメントはありません。

関連記事

Python 基本編 1日目

Python 応用編 2日目

Python 基本編 6日目

PAGE TOP