30分で応用まで一気に!【Python入門・応用講座】16.関数|関数はレシピを記憶させるようなもの(初心者にもわかりやすく説明)

Python入門・応用16.サムネイル
Python入門・応用講座

こんにちは。キノコードです。
この動画では、Pythonの「関数」について説明します。
Pythonの超入門コースでは、関数を料理ロボットに例えて説明していきます。
自動で料理を作ってくれるロボットがあれば便利ですよね。それが関数です。
最後に確認問題もあるので頑張って進めてみてください。

この記事の執筆・監修

キノコード
キノコード

テクノロジーアンドデザインカンパニー合同会社のCEO。
日本最大級のプログラミング教育のYouTubeチャンネル「キノコード」や、プログラミング学習サービス「キノクエスト」を運営。
著書「あなたの仕事が一瞬で片付くPythonによる自動化仕事術」や、雑誌「日経ソフトウエア」や「シェルスクリプトマガジン」への寄稿など実績多数。

関数とは

まず、関数について説明します。
関数とは、いろいろな「処理」をまとめて1つにしたものです。
なぜ関数があるのでしょうか?
料理で例えてみます。
例えば、いつも作るカレーがあるとします。
そのレシピを料理ロボットに記憶してもらいます。
またカレーが食べたくなったときに、
ボタン1つで作れる。
しかも、その料理ロボットは、自分も使えるし、家族も使える。
その料理ロボットが関数なのです。
関数の便利なところは色々あるのですが、3つあげてみます。

  1. 同じものを2回書く必要がない。
  2. 1行で使い回しができる。
  3. 関数の中のコードを理解していなくても他の人も使うことができる。
    といった便利な点があります。

関数の種類

関数には2種類あります。
自分で作る関数と、Pythonがあらかじめ用意してくれている関数です。
Pythonがあらかじめ用意してくれている関数のことを「組み込み関数」と言います。
今まで使ってきたprint関数は、組み込み関数です。
print関数はたった1行で変数の中身を表示してくれますが、
print関数の中身は何行ものコードが書かれています。
もしprint関数がなければ、変数の中身を表示させたいときに、イチからそのコードを書くことになり、大変です。
しかし、print関数があることで、コードを書く必要がありませんし、たった1行で使い回すことができます。
また、print関数の中身でどんなことが書かれているか理解していなくても、print関数を使うことができます。

関数の定義、引数、戻り値

関数を作ることを「関数を定義する」と言います。
Pythonで関数の定義の仕方を見ていきましょう。
"def 関数名():
実行する処理"
Pythonでは関数の定義にdefを使います。
defの後に関数の名前である関数名を書き、
丸括弧を書きます。
丸括弧の中に記述するものを引数と言います。
関数は、引数を受け取ることができます。
受け取った引数は、関数内で使うことができます。
例えば、関数内にある数字と引数を掛け算することができます。
このように、関数に引数の値を渡すことで関数のできる処理の幅が広がります。
引数という言葉は、「引数を関数に渡す」と言ったり、
「引数を関数が受け取る」と言ったりします。
引数は、必ず必要と言うわけではなく、省略することができます。
また、引数は、何個でも渡すことができるので、必要な分だけカンマで区切って入れることができます。
引数の丸括弧の後に
コロンを書いて、
次の行に実行する処理を書きます。
実行する処理の行は右にインデントする必要があります。
関数の記述が
終わったら、
インデントを元に戻します。
そして、関数は、引数を受け取ることができる一方、
関数は処理結果を返すことができます。
これを戻り値と言います。
return文を使うことで、戻り値として関数の外に値を返すことができます。
ここからは実際にコードを書きながら解説を進めていきたいと思います。

def say_hello():
    print('Hello World')

say_hello()

引数なしの関数

まず、引数なしの関数を見ていきましょう。
文字列の「Hello World」を表示させる関数を定義して実行してみましょう。
まずは defを書きます。
関数名は、「say_hello」としましょう。
今回、引数はないので丸括弧のみを書きます。
コロンを書いて、次の行に実行する処理「print('Hello World')」を書きます。
これで関数を定義することができました。

関数を実行するには、関数名、丸括弧で実行できます。
関数の記述が終わったので、インデントをもとに戻します。
関数名の「say_hello」に引数なしの丸括弧。これで「Hello World」が表示されるはずです。
実行してみましょう。
「Hello World」を表示することができました。

def say_hello():
    print('Hello World')

say_hello()
say_hello()
say_hello()

定義した関数は何度でも呼び出すことができます。
3回「say_hello()」を記述します。

「Hello World」は3回表示されるはずです。
実行してみましょう。
「Hello World」を3回表示することができました。

引数ありの関数

def say_hello(greeting):
    print(greeting)

say_hello('Hello World')

次に、引数を使う場合の関数を見ていきましょう。
関数の引数を挨拶という意味のgreetingとしましょう。
受け取った引数をprint関数で表示させます。
関数の定義は終わりです。
関数にHello Worldという文字列を渡してみましょう。

では、実行してみます。
「Hello World」が表示されました。
実行してみましょう。

関数を変数へ代入

def say_hello(greeting):
    print(greeting)

hello = say_hello
hello('Good morning')

Pythonは関数を変数に代入することができます。
先ほどと同様に定義した関数名を、そのまま変数に代入するだけです。
変数名は「hello」とします。
helloにsay_helloを代入します。
helloの後に丸括弧、Good morningの文字列を渡してみましょう。

実行してみます。「Good morning」が表示されました。

複数の引数がある関数

def add(num01, num02):
    print(num01 + num02)

add(6, 2)

引数を2つ使ってみましょう。
足し算という意味の「add」という関数名を作ってみます。num01とnum02を足してprint関数で表示してみましょう。
では、add関数に6と2を渡してみます。
結果は、6足す2で8となるはずです。

実行してみます。
8が表示されました。

戻り値がある関数

def add(num01, num02):
    return (num01 + num02)

add(6, 2)

関数の結果は、return文で返すことができます。
printをreturnに変更してみましょう。
ただし、returnで結果を返しても、printで表示をさせていないので、確認することができません。
念のため、実行してみましょう。
何も表示されません。

def add(num01, num02):
    return (num01 + num02)

print(add(6, 2))

関数を呼び出すaddの部分をprintでくくってみます。
実行してみます。
8が表示されました。

def add(num01, num02):
    return (num01 + num02)

add_result = add(6, 2)
print(add_result)

結果を変数に代入して、print関数で表示させる方法もあります。「add_result」という変数に代入して表示させてみましょう。
表示させてみます。8が表示されました。

def div(a, b, c):
    return (a + b + c) / 3

div_result = div(9, 4, 2)
print(div_result)

デフォルト引数

次にデフォルト引数について説明します。
関数を定義するとき、引数にあらかじめデフォルトの値を渡しておくことができます。
引数にあらかじめ値を渡しておくことで、関数を呼び出すときにその引数が省略されてもデフォルトの値が渡され、エラーになりません。
具体例で確認してみましょう。

def show_number(a=1, b=2):
    print(a)
    print(b)

show_number()

まず、関数名をshow_numberとして関数を定義します。
引数名をa,bとし、a、=(イコール)、1、b、=(イコール)、2と書きます。
こうすることでaには1が、bには2がデフォルト値として渡されます。
そして、受け取った引数をprint関数で表示させます。
これで関数の定義ができました。
最初は引数を指定しないでこの関数を呼び出してみましょう。
実行します。
aとbにデフォルト値として渡した1と2が表示されました。

def show_number(a=1, b=2):
    print(a)
    print(b)

show_number(3, 4)

今度は引数に3と4を渡してこの関数を呼び出してみましょう。
実行します。
3と4が表示されました。
このように、関数を呼び出すときに引数を渡した場合は、デフォルト値として渡した引数は上書きされて関数が処理されます。

なお、デフォルト引数を使う場合、注意点が2つあります。
1つ目は、デフォルト値を渡す引数と渡さない引数がある場合、デフォルト値を渡さない引数を先に書かないとエラーになるということです。
確認してみましょう。

def show_number(a=1, b):
    print(a)
    print(b)

show_number(3, 4)

先ほどと同様の関数を定義します。
引数aにはデフォルト値を渡していますが、引数bにはデフォルト値を渡していません。
この関数を呼び出して結果を確認してみましょう。
実行します。
このようにエラーになります。
デフォルト値を渡す引数と渡さない引数がある場合は、デフォルト値を渡さない引数を先に書きましょう。

2つ目は、デフォルト引数は関数が定義されるときに一度だけ評価されるということです。
確認してみましょう。

x = 1
def show_number(a=x):
    print(a)

x = 10
show_number()

関数を定義する前に、1を変数xに代入します。
そして、関数の引数aにはデフォルト値として変数xを渡します。
関数を定義した後、xに10を代入します。
最後に関数を呼び出してみましょう。
実行します。
1が表示されました。
関数を定義したときは、xには1が代入されています。
従って、引数aには1が渡され、その後xを変更しても引数aの値は変わりません。

また、デフォルト引数にリストや辞書などのオブジェクトを渡すときは更に注意が必要です。
確認してみましょう。

def show_number(a=[]):
    a.append(1)
    print(a, id(a))

show_number()
show_number()
show_number()

関数の引数aにはデフォルト値として空のリストを渡します。
appendメソッドを使用して、リストに1を追加します。
その後、引数aとそのidを表示させます。
最後に定義した関数を3回呼び出してみましょう。
実行します。
関数を呼び出す度にリストに要素が追加されていることがわかります。
最初に関数を呼び出したとき、aには空のリストが渡されます。
そして、aに1が追加され、aとそのidが表示されます。
この関数のデフォルト値には、定義したときに渡されたこのidを持つリストオブジェクトが渡されています。
従って、その後に関数を呼び出すと、1が追加されたリストがデフォルト値に渡され、更に1が追加されます。
よってこのような結果になります。

キーワード引数

次にキーワード引数について説明します。
通常、関数の引数が複数ある場合、関数を呼び出すときの引数は1番目が第1引数、2番目が第2引数、3番目が第3引数と、順番に渡されます。
これを位置引数と言います。
一方、順番ではなく名前で値を渡す引数を指定する方法もあります。
これをキーワード引数と言います。
具体例で確認してみましょう。

def show_number(a, b, c):
    print(a)
    print(b)
    print(c)

show_number(a=1, c=10, b=100)

先ほどと同様にshow_numberという関数を定義し、引数はa、b、cの3つとします。
それぞれの引数をprint関数で表示させる関数として定義します。
この関数を呼び出すときの引数に、a、=(イコール)、1、c、=(イコール)、10、b、=(イコール)、100と書き、順番を変えてみます。
結果を確認してみましょう。
実行します。
aには1が、bには100が、cには10が渡されているのがわかります。
このように、引数をキーワード指定することで、どの引数に値を渡すか指定できます。
なお、このa、b、cのように関数を定義するときの引数のことを仮引数、1、10、100のように関数を呼び出すときに渡す値のことを実引数と呼びます。

位置引数とキーワード引数の両方を使うときは、位置引数を先に書かないとエラーになります。
確認してみましょう。

def show_number(a, b, c):
    print(a)
    print(b)
    print(c)

show_number(a=1, 10, 100)

キーワード引数を先に書き、位置引数を後に書いて関数を呼び出してみます。
実行します。
このようにエラーになります。
位置引数とキーワード引数の両方を使う場合は、位置引数を先に書きましょう。

可変長位置引数

次に、可変長位置引数について説明します。
可変長位置引数は、複数の引数を1つのタプルとして受け取ります。
関数を定義するときの仮引数名の前に*(アスタリスク)を1つ書くことで、可変長位置引数を定義できます。
具体例で確認してみましょう。

def show_number(a, b, *args):
    print(a)
    print(b)
    print(args)

show_number(1, 2, 3, 4, 5)

先ほどと同様にshow_numberという関数を定義し、仮引数はa、b、そして(アスタリスク)argsとします。
この
(アスタリスク)argsが可変長位置引数になります。
argsは引数という意味のargumentsの略です。
引数名はargsでなくても良いですが、可変長位置引数はargsと書くことが多いです。
この関数の実引数に1、2、3、4、5を渡し、関数を呼び出してみましょう。
実行します。
結果が表示されました。
aとbにはそれぞれ1と2が渡されています。
可変長位置引数は、複数の引数を1つのタプルとして受け取るので、残りの3、4、5が1つのタプルとしてargsに渡されます。

また、関数の引数にタプルを渡す場合、タプルの前に*(アスタリスク)を書くとタプルが展開されて渡されます。
確認してみましょう。

def show_number(a, b, *args):
    print(a)
    print(b)
    print(*args)

show_number(1, 2, 3, 4, 5)

今度はprint関数の引数のargsの前にも*(アスタリスク)を書きます。
そうすると、タプルが展開されて渡されます。
実行します。
先ほどは3、4、5が1つのタプルとして表示されましたが、今度はタプルが展開されて表示されているのがわかります。

def show_number(a, b, *args):
    print(a)
    print(b)
    for i in args:
        print(i)

show_number(1, 2, 3, 4, 5)

また、可変長位置引数はタプルとして受け取るので、1つ1つの要素をfor文で取り出せます。
for文でタプルの要素を取り出し、1つ1つの引数をprint関数で表示してみましょう。
実行します。
引数を1つずつ表示できました。

print("a")
print("a", "b", "c")
print(max(1, 2, 3))
print(max(1, 2, 3, 4, 5))

可変長位置引数が使われている代表例がprint関数やmax関数です。
Pythonの組み込み関数として定義されているので、あまり意識することはありませんが、仮引数には可変長位置引数が定義されています。
そのため、これらの関数を使うときは何個引数を渡してもエラーになりません。
確認してみましょう。
実行します。
エラーにならずに結果が表示されました。

可変長キーワード引数

次に可変長キーワード引数について説明します。
可変長キーワード引数は、複数のキーワード指定された引数を1つの辞書として受け取ります。
関数を定義するときの仮引数名の前に**(アスタリスク2つ)を書くことで、可変長キーワード引数を定義できます。
具体例で確認してみましょう。

def show_number(**kwargs):
    print(kwargs)

show_number(a=1, b=2, c=3)

先ほどと同様にshow_numberという関数を定義し、仮引数は(アスタリスク2つ)kwargsとします。
この(アスタリスク2つ)kwargsが可変長キーワード引数になります。
kwargsはキーワード引数という意味のkeyword argumentsの略です。
可変長位置引数と同様に引数名は何でも良いですが、可変長キーワード引数はkwargsと書くことが多いです。
この関数の引数にa、=(イコール)、1、b、=(イコール)、2、c、=(イコール)、3と書き、関数を呼び出してみましょう。
実行します。
仮引数をキー、実引数を値に持つ辞書が表示されました。

def show_number(**kwargs):
    print(kwargs)

d = {"a": 1, "b": 2, "c": 3}

show_number(**d)

また、関数の引数に渡すとき、タプルが*(アスタリスク1つ)で展開できるように、辞書は(アスタリスク2つ)で展開できます。
辞書を展開すると、キーが仮引数、値が実引数として渡されます。
確認してみましょう。
今度はあらかじめ辞書を作成しておき、変数dに代入します。
関数show_numberの引数にこのままdを渡すと、dが位置引数として渡されるので、エラーになります。
そこで、dの前に(アスタリスク2つ)を書き、辞書を展開して渡してみましょう。
実行します。
引数に辞書を展開して渡すことができ、先ほどと同じ結果が表示されました。

print(dict(a=1, b=2))
print(dict(a=1, b=2, c=3, d=4))

可変長キーワード引数が使われている代表例がdict関数です。
dict関数を使うときは何個引数を渡してもエラーになりません。
確認してみましょう。
実行します。
エラーにならずに結果が表示されました。

関数内関数

次に関数内関数について説明します。
関数の中で定義された関数のことを関数内関数といいます。
for文の中にfor文を書くように、defの中にdefを書きます。
具体例で確認してみましょう。

def outer_func(a, b):
    def inner_func(c, d):
        return c - d

    x = inner_func(2, 1)
    y = inner_func(a, b)
    print(x)
    print(y)

outer_func(3, 1)

まず、外側の関数という意味で、関数名をouter_funcとして関数を定義します。
仮引数をa、bとします。
次に、内側の関数という意味で、関数名をinner_funcとして関数を定義します。
仮引数をc、dとします。
return文でc-dの計算結果を戻り値として返します。
これで内側の関数の定義は終了です。
すなわち、inner_funcを呼び出すと、第一引数から第二引数を引き算した値が返ってきます。
そして、outer_funcの中でinner_funcを呼び出し、変数xに代入します。
引数には2と1を渡します。
もう一度inner_funcを呼び出し、変数yに代入します。
今度はouter_funcの引数であるaとbを渡します。
最後にxとyを表示させましょう。
これで外側の関数の定義も終了です。
引数に3と1を渡してouter_funcを呼び出し、結果がどのようになるか確認してみましょう。
実行します。
1と2が表示されました。
このプログラムは次のように処理されます。
まず、outer_funcが呼び出され、引数aに3、引数bに1が渡されます。
そしてouter_funcの中の処理を行います。
次に、inner_funcが呼び出され、引数cに2、引数dに1が渡されます。
そして2-1の計算結果の1が戻り値として返り、xに代入されます。
次に、2つ目のinner_funcが呼び出され、引数cにa、引数dにbが渡されます。
outer_funcを呼び出すときにaには3、bには1を渡しているので、cには3、dには1が渡されます。
そして3-1の計算結果の2が戻り値として返り、yに代入されます。
最後にxとyが表示されます。
以上が関数内関数です。

関数内関数は他では使わないけれども、関数の中で似たような処理を繰り返したいときに使われます。
今回の例では、関数outer_funcの中で関数inner_funcを2回使いました。
また、関数内関数はその外側の関数の中でしか使えず、その他の場所から呼び出すことはできません。
確認してみましょう。

def outer_func(a, b):   
    def inner_func(c, d):
        return c - d

    x = inner_func(2, 1)
    y = inner_func(a, b)
    print(x)
    print(y)

inner_func(3, 1)

先ほど定義したinner_funcを外から呼び出してみます。
実行します。
エラーになりました。
このように内側にある関数を外から呼び出すことはできないので注意しましょう。

クロージャー

次にクロージャーについて説明します。
クロージャーとは、関数の外で宣言された変数や引数などを記憶した関数のことです。
具体例を交えながら一つずつ説明します。
まず、Pythonでは全てがオブジェクトとして扱われるので、関数もオブジェクトです。
確認してみましょう。

def circle_area(pi, radius):
    return pi * radius * radius

area = circle_area
print(area)

例として、円の面積を計算する関数を作成します。
まず、circle_areaという関数を定義し、仮引数は円周率のpiと半径のradiusとします。
そしてreturn文で円の面積の計算結果を返します。
この定義したcircle_areaを、丸括弧を書かずに変数areaに代入します。
areaを表示してみましょう。
実行します。
functionと表示されました。
すなわち、areaには関数のオブジェクトが代入されていることがわかります。
このように、関数に丸括弧を付けるとその関数が呼び出されて定義されたプログラムが処理されますが、丸括弧を付けないと関数オブジェクトとして扱えます。

def circle_area(pi, radius):
    return pi * radius * radius

area = circle_area
print(area(3.14, 2))

この関数オブジェクトに丸括弧を付けると、関数を呼び出せます。
変数areaに丸括弧を付けて関数を呼び出し、引数に3.14と2を渡して表示してみましょう。
実行します。
関数circle_areaが呼び出されて12.56という計算結果が表示されました。

これを踏まえて、クロージャーについて説明します。
具体例で確認してみましょう。

def circle_area(pi):
    def calculation(radius):
        return pi * radius * radius
    return calculation

area1 = circle_area(3.14)
area2 = circle_area(3)

print(area1(2))
print(area2(2))

まず、外側の関数としてcircle_areaを定義し、仮引数は円周率のpiとします。
次に、内側の関数としてcalculationを定義し、仮引数は半径のradiusとします。
そしてreturn文で円の面積の計算結果を返します。
これで内側の関数の定義は終了です。
ここではこのcalculationという関数がクロージャーになります。
最後に外側の関数のreturn文を書き、calculationの関数オブジェクトを返します。
これで外側の関数の定義も終了です。
この外側の関数circle_areaは引数に値を渡して呼び出すと、piに引数の値が代入されたcalculationの関数オブジェクトを返します。
次に、circle_areaを呼び出し、引数に3.14を渡して変数area1に代入します。
もう一度circle_areaを呼び出し、引数に3を渡して変数area2に代入します。
そしてarea1を呼び出し、引数に2を渡して結果を表示してみます。
同様にarea2も呼び出し、引数に2を渡して結果を表示してみましょう。
実行します。
12.56と12が表示されました。
このプログラムは次のように処理されます。
まず、area1には引数に3.14が渡された関数circle_areaの呼び出し結果が代入されます。
circle_areaはcalculationの関数オブジェクトを返します。
このとき、変数piには3.14が代入されています。
同様に、area2には引数に3が渡された関数circle_areaの呼び出し結果が代入されます。
そして、circle_areaはcalculationの関数オブジェクトを返します。
このとき、変数piには3が代入されています。
そしてarea1を呼び出し、引数に2を渡すと、仮引数radiusに2が渡されたcalculationが呼び出されます。
このときのpiには3.14が代入されているので、3.14×2×2の計算結果の12.56が返ってきて表示されています。
同様にarea2を呼び出し、引数に2を渡すと、仮引数radiusに2が渡されたcalculationが呼び出されます。
このときのpiには3が代入されているので、3×2×2の計算結果の12が返ってきて表示されています。
つまり、今回の場合は円周率を3.14で計算したい場合はarea1を使い、円周率を3で計算したい場合はarea2を使う、ということになります。
以上がクロージャーです。

クロージャーのポイントは、クロージャーの外側の関数が渡された引数などを覚えておけることです。
今回の場合では、piは外側の関数circle_areaに渡された引数ですが、クロージャーのcalculation内でも変数として使われます。
area1ではpiに3.14が渡されているので、クロージャーのcalculation内でもpiに3.14を代入して計算しています。
area2ではpiに3が渡されているので、クロージャーのcalculation内でもpiに3を代入して計算しています。
クロージャーを使うと、呼び出すときの引数を変えることで、変数を定義しなくても一部の値を変えて同じ処理をすることができます。

デコレーター

最後にデコレーターについて説明します。
デコレーターは関数を修飾する関数のことです。
デコレーターを使うことで、関数を書き換えることなくその関数に処理を追加、変更することができます。
こちらも具体例を交えながら一つずつ説明します。

def add(a, b):
    print(a + b)

print("Start!!")
add(1, 2)
print("Finish!!")

print("Start!!")
add(3, 4)
print("Finish!!")

まず、デコレーターを使わない場合を考えます。
関数addを定義します。
仮引数をa、bとして、a+bの計算結果を表示させる関数とします。
そして、関数を呼び出したということがわかりやすいように、この関数を呼び出す前後に「Start!!」と「Finish!!」と表示させたいとします。
関数addを2回使いたい場合はこのように書くことになります。
結果を確認してみましょう。
実行します。
想定通りの結果が表示されました。
ただし、「Start!!」と「Finish!!」を2回ずつ書く必要がありますし、もっと複雑になった場合コードが読みづらくなります。
そこで、関数の前後に「Start!!」と「Finish!!」を表示させる部分を別の関数にしてみましょう。

def info(func):
    def wrapper(a, b):
        print("Start!!")
        func(a, b)
        print("Finish!!")
    return wrapper

def add(a, b):
    print(a + b)

add_result = info(add)
add_result(1, 2)
add_result(3, 4)

今度はクロージャーを使って「Start!!」と「Finish!!」を表示させる部分を別の関数にします。
まず、外側の関数としてinfoを定義し、仮引数をfuncとします。
この仮引数funcには関数オブジェクトを渡します。
次に内側の関数としてwrapperを定義し、仮引数はa、bとします。
そして「Start!!」と表示させます。
次に外側の関数infoの引数に渡された関数を呼び出します。
そして「Finish!!」と表示させます。
これで内側の関数の定義は終了です。
最後に外側の関数のreturn文を書き、wrapperの関数オブジェクトを返します。
これで外側の関数の定義も終了です。
この外側の関数infoは、引数に関数を渡して呼び出すと、funcに引数の関数が代入されたwrapperの関数オブジェクトを返します。
すなわち、関数を呼び出す前後に「Start!!」と「Finish!!」を表示させる処理が追加された関数オブジェクトを返します。
次に、infoを呼び出し、引数にaddを渡して変数add_resultに代入します。
そしてadd_resultを呼び出し、引数に1と2を渡します。
もう一度add_resultを呼び出し、今度は引数に3と4を渡して結果を確認してみましょう。
実行します。
先ほどと同じ結果が表示されました。
このプログラムは次のように処理されます。
まず、add_resultには引数にaddが渡されたinfoの呼び出し結果が代入されます。
infoはwrapperの関数オブジェクトを返します。
このとき、関数funcにはaddが代入されています。
そして、add_resultの関数の引数に1と2を渡して呼び出すと、仮引数aとbに1と2が渡されたwrapperが呼び出されます。
従って、「Start!!」と表示され、次に引数に1と2が渡されたaddが呼び出され、「Finish!!」と表示されます。
同様に、add_resultの関数の引数に3と4を渡して呼び出すと、「Start!!」と表示され、次に引数に3と4が渡されたaddが呼び出され、「Finish!!」と表示されます。

このままでも良いですが、関数を呼び出すところのコードが少しわかりづらいです。
そこでデコレーターを使います。

def info(func):
    def wrapper(a, b):
        print("Start!!")
        func(a, b)
        print("Finish!!")
    return wrapper

@info
def add(a, b):
    print(a + b)

add(1, 2)
add(3, 4)

処理を追加したい関数の上に@(アットマーク)を書き、その後に追加する関数名を書きます。
今回は関数addに関数infoの処理を追加したいので、addの上に@(アットマーク)、infoと書きます。
今回はこの関数infoがデコレーターとなります。
関数addを書き換えることなく、関数addに処理を追加できます。
あとはそのままaddを呼び出せばデコレーターで修飾された結果が表示されます。
確認してみましょう。
実行します。
先ほどと同じ結果が表示されました。

また、今回は関数addの引数が2つだったので、関数wrapperの引数も2つにしましたが、このままでは引数が3つある関数などには使えません。
そこで、どんな関数に対しても使えるように、wrapperの引数は可変長位置引数と可変長キーワード引数にしておくと良いです。
確認してみましょう。

def info(func):
    def wrapper(*args, **kwargs):
        print("Start!!")
        func(*args, **kwargs)
        print("Finish!!")
    return wrapper

@info
def add(a, b):
    print(a + b)

@info
def add2(c, d, e):
    print(c + d + e)

add(1, 2)
add2(3, 4, 5)

先ほどのwrapperの仮引数を可変長位置引数と可変長キーワード引数に変えます。
すると、wrapperに渡される引数の数に制限はなくなりますが、引数がタプルや辞書で渡されることになります。
このままfuncの引数に渡すとエラーになるので、引数を展開して渡す必要があります。
そこで、タプルで渡されるargsの前に*(アスタリスク)を書き、辞書で渡されるkwargsの前に**(アスタリスク2つ)を書きます。
こうすることで、タプルや辞書が展開されて引数に渡されます。
これでデコレーターの完成です。
確認のために、関数addの他にもう一つ別の関数add2を定義します。
仮引数はc、d、eの3つとし、3つの引数を足した結果を表示させます。
また、関数infoをデコレーターとして使うので、@(アットマーク)infoを関数add2の定義の上に書きます。
そして、addとadd2に引数を渡して呼び出し、結果を確認してみましょう。
実行します。
結果が表示されました。
このように、デコレーターに可変長引数を渡すことで、どのような関数にも使えるようになりました。