【基本】Pythonの自作関数定義時の細かいテクニックを解説!

2023-07-17

Pocket

こんにちは、爽です。皆さん、いかがお過ごしでしょうか?

今回はPythonの自作関数定義時の細かいテクニックについて確認します。
なお、Pythonの関数定義の基本については以前の記事で執筆しましたのでこちらをご確認ください!

■この記事の対象読者
・Pythonに興味がある方
・Pythonを学び始めてみたい方

・Pythonの自作関数について細かく知りたい方

なお、私はPythonをAnacondaをインストールしてJupyterで実行しています。MacのAnacondaのインストール方法とJupyterの使い方は下記記事にまとめているので良かったらご参考ください。

それではどうぞ!

関数内関数

関数内関数とは名前の通り、関数の中でのみ使用される関数のことです。
関数の中で定義されている関数は、関数の外から呼び出すことができません。従って、どうしても関数の中で飲み関数を使用したい場合に使います。

a = 10
b = 20

def outer(a, b):
    
    def add_num(c, d):
        return c + d
    
    def minus_num(e, f):
        return e - f
    
    #r1には30が返される
    r1 = add_num(a, b)
    #r2には10が返される
    r2 = minus_num(r1,b)
    
    #30 - 10
    print(r1 - r2)

outer(a, b)

# 20
#関数の外から関数内関数を呼び出すとエラー
minus_num(a,b)

# ---------------------------------------------------------------------------
# NameError                                 Traceback (most recent call last)
# Input In [57], in <cell line: 1>()
# ----> 1 minus_num(a,b)

# NameError: name 'minus_num' is not defined

クロージャー

クロージャーは最初に引数を設定しておき、その引数を後で用途によって使い分けたい場合に使用します。

例えば下記の例では円の面積を求める関数を例に取っていますが、cal1には簡易な円周率、cal2には細かい円周率をそれぞれ設定しておきます。
そして、簡易に円の面積を求めたい場合はcal1を実行する、細かく円の計算をしたい場合はcal2を実行する、というように用途によって使い分けることができます。
ポイントは7行目の関数内関数を返すところで関数を実行せず、関数をオブジェクトとして返す、というところです。

#円の面積を求める関数
def circle_area_func(pi):
    #引数に半径を取る関数内関数
    def circle_area(radius):
        return pi * radius * radius
    #関数内関数を返す
    return circle_area

#円周率を用途によって変更する
cal1 = circle_area_func(3)
cal2 = circle_area_func(3.141592)

#関数内関数を実行
print(cal1(10))
print(cal2(10))

# 314.0
# 314.1592

デコレーター

Pythonでは関数名の前に@関数名と書くことで、関数実行時に@関数名の関数を実行することができます。この機能をデコレーターと呼びます。デコレーターは関数間で処理を共通化したい場合に用いられます。
デコレーターを使うことで、同じような処理を何度も書く必要がなくなり、アプリケーション開発の工数の省力化とソースの可読性向上につながります。
なお、デコレーターはいくつでもつけることができます。また、複数デコレーターをつけた場合は、デコレーターは上から順番に実行されます。

#共通関数
def common_func(func):
    def wrapper(*args, **kwargs):
        print('#########start#########')
        result = func(*args, **kwargs)
        print('#########end#########')
        return result
    #関数オブジェクトを返却
    return wrapper

#デコレータ使用
@common_func
def multiple_num(a, b):
    return a * b

#デコレータ使用
@common_func
def divide_num(a, b):
    return a / b

r = multiple_num(10, 5)
print(r)

r = divide_num(10, 5)
print(r)

#関数実行時にcommon_funcが実行されている
# #########start#########
# #########end#########
# 50
# #########start#########
# #########end#########
# 2.0

ラムダ

ラムダは関数定義を簡易化することができる機能です。
下記の例では10行目のラムダ式で引数numに入力された数を2倍するようにしています。このような簡単な構造の関数はわざわざ関数を定義しなくともラムダで書くことでソースの記述量を少なくすることができます。

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

def calc_func(nums, func):
    result = 0
    for num in nums:
        result += func(num) 
    return result

#入力された数字を2倍する関数
add_num = lambda num: num * 2

calc_func(l, add_num)

# 110

ジェネレーター

ジェネレーターはyiledを用いることで定義できる関数です。
yieldを用いて関数を宣言して関数を実行すると、yiledの部分で処理がストップし、yieldの後に記載されているオブジェクトが呼び出し元に返されます。
その後、再度ジェネレーターを呼び出すと、yiledのところから処理がスタートし、yieldの部分で処理がストップします。
また、プログラム終了条件に達するまでジェネレーターは何度も呼び出し可能です。
、、このように文字にすると分かりづらいのですが、実際に下記のサンプルを見るとイメージ湧くと思います。

def generator_func(num):
    print('start')
    for i in range(num):
        print('{}回目のyield実行'.format(i+1))
        #yieldを見るとPythonがジェネレーターと判断する
        yield i + 1

#オブジェクト生成
g = generator_func(5)

生成した関数を呼び出すためにはにはnext関数を使用します。
出力結果を見ると分かりますが、yiledの部分で処理がストップし、yieldの後に記載されているオブジェクトが呼び出し元に返されているのが分かります。

next(g)
# start
# 1回目のyield実行
# 1

更にもう1度next関数を使用するとyiledのところから処理がスタートし、yieldの部分で処理がストップします。

next(g)
# 2回目のyield実行
# 2

なお、最初にgenerator_func生成時の引数に5を設定しましたが、それを超える数をnextで実行するとエラーになります。

next(g)
# 2回目のyield実行
# 2
next(g)
# 3回目のyield実行
# 3
next(g)
# 4回目のyield実行
# 4
next(g)
# 5回目のyield実行
# 5
next(g)
#  ---------------------------------------------------------------------------
#  StopIteration                             Traceback (most recent call last)
#  Input In [32], in <cell line: 1>()
#  ----> 1 next(g)

#  StopIteration: 

なお、ジェネレーターを都度nextで呼び出すのは手間ですし、上記でやったようにnextの数を間違えるとエラーになってしまうので、ループと組み合わせて使う方法が便利です。

def generator_func(num):
    print('start')
    for i in range(num):
        print('{}回目のyield実行'.format(i+1))
        #yieldを見るとPythonがジェネレーターと判断する
        yield i + 1

#オブジェクト生成
g = generator_func(5)

#ループでジェネレーターを実行
for i in g:
    i

# start
# 1回目のyield実行
# 2回目のyield実行
# 3回目のyield実行
# 4回目のyield実行
# 5回目のyield実行

ここまででジェネレーターを色々見てきましたが、結局どのような時に使うかというと、アプリケーションのメモリ使用量を省力化したい時に利用することが多いようです。
例えばループで大量の繰り返し処理をする場合や、データ分析で大量のデータを扱う場合はメモリを大量に消費しますが、ジェネレーターを使うことでメモリ消費量を節約することができます。
ただし、ジェネレーターはその分処理が遅くなるようなので、使うかどうかは性能要件とマシンスペックを鑑みて検討をするべきでしょう。

内包表記の色々

リスト内包表記

リスト内包表記はループを使って1行でリストを作成する方法です。
具体的には下記のように書くことができ、括弧の後の変数にforの後の変数が次々と入れられてリストが作成されます。

#普通の書き方
l = []
for i in range(10):
    l.append(i)
l

#リスト内包表記
l = [i for i in range(10)]
l

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

また、リスト内包表記では条件式もつけることができます。

#普通の書き方
l = []
for i in range(10):
    if i % 2 == 0:
        l.append(i)
l

#リスト内包表記
l = [i for i in range(10) if i % 2 == 0]
l

# [0, 2, 4, 6, 8]

辞書内包表記

辞書型も内包表記で生成することができます。

#普通の書き方
name = ['Taro', 'Goro', 'DAIGO']
age = ['5', '20', '41']

d = {}

for k, v in zip(name,age):
    d[k] = v

d

#辞書内包表記
name = ['Taro', 'Goro', 'DAIGO']
age = ['5', '20', '41']

d = {k:v for k, v in zip(name,age)}
d

# {'Taro': '5', 'Goro': '20', 'DAIGO': '41'}

集合内包表記

集合型も内包表記で生成できます。

#普通の書き方
s = set()
for i in range(5):
    s.add(i)
s

#集合内包表記
s = {i for i in range(5)}
s

# {0, 1, 2, 3, 4}

ジェネレーター内包表記

ジェネレーターも内包表記で生成できます。

#普通の書き方
def generator():
    for i in range(10):
        yield i
g = generator()  
next(g)

#ジェネレーター内包表記
g = (i for i in range(10))
next(g)

# 0

type(g)
# generator

タプルを生成するには括弧の前にtupleと記述します。

#タプル内包表記
t = tuple(i for i in range(10))
t

# (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

内包表記はプログラムを簡易にかけるので便利ですが、あまりにも複雑な条件も内包表記で表現するとかえってプログラムが読みづらくなり、開発中にバグにつながることになるので、多用しすぎも注意しましょう。

map関数

Map関数はPythonの組み込み関数ですが、自作関数を引数にとれる関数で使用頻度もそれなりに高いと思われるので、この記事で一緒に紹介します。
使用方法はリスト、辞書などのループ可能なオブジェクトを第2引数に取り、実行時に第1引数に関数を指定します。そうすると関数内にループを書かずとも第2引数のオブジェクトを第1引数の関数でループして結果を返してくれます。

l = [1, 2, 3, 4, 5]

def add_one(x):
    return x + 1

#map関数
m = map(add_one, l)

#map関数の中身を表示
for i in m:
    print(i)
# 2
# 3
# 4
# 5
# 6

また、データフレームでもmap関数は使えるので覚えておきましょう!

import pandas as pd

#データフレーム作成
df = pd.DataFrame({'col01':[1, 2, 3], 'col02':[4, 5, 6], 'col03':[7, 8, 9]},
index=['idx01', 'idx02', 'idx03'])

#map関数実行
df['col01'].map(lambda x: x + 1)

# idx01    2
# idx02    3
# idx03    4
# Name: col01, dtype: int64

参考資料

この記事はUdemyのPython+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~という講座と現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイルという2つの講座を参考にさせていただき、作成しました。

Python+FlaskでのWebアプリケーション開発講座!!~0からFlaskをマスターしてSNSを作成する~

Udemyより抜粋

当講座は最低限のWeb開発の知識を知っていることが前提とはなりますが、とにかくPythonの説明とFlask開発の為の説明が充実しているのでおすすめです。
28時間に及ぶ長丁場の講座にはなりますが、絶対に聞く価値がある講座です。
これをマスターすればPythonでどんなアプリケーションでも作ることができると思います。

当講座のおすすめポイントを以下にまとめておきます。

当講座のおすすめポイント

とにかく説明が充実している

Webアプリ開発におけるフレームワークがなぜ有益なのか知ることができる

セキュリティ対策、Ajaxなどの技術も知れる

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

画像に alt 属性が指定されていません。ファイル名: a1ee3d048d6aa860662b6f58c4aa167f-1024x328.png
Udemyより抜粋

当講座はPythonの基礎から応用まで幅広く学べる講座なのでおすすめです。
この講座の講師はとにかくPythonについての知識が豊富ですし、話も適度な速さで聞き取りやすいです。さすがのシリコンバレーです。
また、最後の方に機械学習で使うライブラリについても解説があるので、データサイエンス・AIについても多少知ることができます。

当講座のおすすめポイントを以下にまとめておきます。

当講座のおすすめポイント

シリコンバレーで働いているということもあり、講師のPythonの知識が豊富

話も適度な速さで聞き取りやすい

コードの意味だけでなく、それをどう応用するかまで解説してくれる

なお、Udemyについては以下の記事でまとめていますのでご参考ください。

まとめ

ということで、今回はPythonの自作関数定義時の細かいテクニックについて解説しました。
どれも知っておくことでより効率的にアプリケーション開発に取り組むことができるので是非押さえておきたいですね。
では、今回はここまでとさせていただきます。