見出し画像

【Python】関数を使ってまとまった処理をパーツにしてしまおう

みなさん、こんにちは。きぃです。

今回はPythonの関数の作り方を説明します。

関数の使い方を学ぶことで、

「同じような処理」を何度も書く

ことがなくなりますので、覚えておいて

「ソン♪」はナイ

です。それではまいりましょう。

👇  👇  👇

基礎編

そもそもPythonの関数ってどうやって作るの?

Pythonの関数の定義の仕方を見ていきましょう。

def [関数の名前]([引数],...): 
 <処理> 
 ︙

ですが、

「宣言」しただけでは実行

できません。

def func(str):
 print(str)

func('こんにちは。')

上のように

def [関数の名前]([引数],...): 
 <処理> 
 ︙

を宣言したうえで、

func('a')

と書くことで使えるようになります。

なお、引数は一つだけに限らず、

「何個でもつけられる」

ので、

必要に応じて数を足していく

とよいでしょう。

なお、関数の引数を

「仮引数」

といい、

「仮引数は関数内でしか使えない」

ことを覚えておきましょう。

有効範囲をまとめると、以下の通りになります。

#<strは使えません。>
def func(str):
  #<仮引数strの有効範囲>
  ︙

#<strは使えません。>  

また、関数のすぐ下に、

「1つインデント」

を加えていないと、

「関数内の処理ではない」

と判断されてしまうので、関数の下には必ず

「1つのインデントを加える」

ようにしてください。

Python の戻り値もreturn文を使う

また、Pythonの関数でも

「処理した結果」

を返すことができます。
処理した結果を返す値を

「戻り値」

といい、処理した結果を

「return文」

をつかって書きます。
なお、戻り値は

「数値・文字」などに

置きかえて使うことができます。
たとえば、

def func():
 return 0

a=func()
print(a)

であればfunc()の結果(戻り値)が0なので、

aの値は

「0」

になります。
なお、returnをつかってしまうと、

return以降の文は実行されません

ので、注意してください。

🗒POINT!!
ちなみに、return文を実行してしまうと、このあと関数は、処理の結果を値を持ちかえって、もとの呼び出し側のコードに戻ります。つまり、関数はreturnで設定された値として使うことが可能です。

docstringを使ってすでに作った関数の使い方を忘れないうちにメモしとこ〜♪

実は、Pythonでは自作の関数に

どういうふうに使ったらよいのか

を書くことができます。

どういうふうに書くかというと、docstringと呼ばれる書き方で書いていきます。

えっ!?docstringって何〜〜〜🤯!?

て混乱するかもしれません。docstringとは、

'''〜'''

で囲んで書くことで、実現できちゃうのです。

じゃあ、コメントで書いてるのと変わらないじゃん…🤔

そうかもしれません。では、どのようにすれば、先ほど説明した
docstringの書き方を

「関数のメモ書き」

としてVisualStudioCodeに認識させるにはどうすればよいのでしょうか?これから、私が使っているエディタ、

「Visual Studio Code」

をもとに一つずつ丁寧に説明していきたいと思います。

まず、以下の関数を作ったとします。

def create(filename=__file__):
   '''
   所定のフォルダにシンボリックリンクを作り、短いファイルパスで入力できるようになる関数です。
   初回はファイル元で直接実行してください。一度実行すれば、シンボリックリンクが消滅しない限り有効です。
   短いパスで実行することができます。
   Parameters
   ------------
   filename: str
   シンボリックリンクの名前を入力してください。入力しなければ、スクリプト名が入ります。
   
   '''

まずdefの関数名の下に

シングルクォート「'」(またはダブルクォート「"」)を3つ

入力します。そうすると、VisualStudioCodeなどのエディタ
は、

なるほど、開始部分なんだな…🤔

と認識します。(かしこいですね〜😳)

まず1行目に書くことが

関数の説明です。

こちらは、

関数はどういうふうに使ったらいいのか

を書きます。

つぎに仮引数の説明ですが、

Parameters
-----------

と書いた上で

仮引数の名前: [変数の型]

<インデント入れる>[説明]

と打っていくと…

画像1

このように表示されます。

あれ!?この関数って作った覚えがあるけど、どういう役割を持った関数だっけ…🤔

どうでしょうか?

覚え書きにピッタリですよね!?

それでは基本的な関数の書き方について説明したので、次は応用編に移っていきたいと思います。

応用編

その①:Pythonの関数の戻り値は複数持たすことができる。

実はPythonは何を隠そう🤔関数の戻り値を

「複数」

持たせることができます。それでは、Pythonのコードの文を見ていきましょう。

def test():
	return 3,5
print(test())
print(type(test())#タプル型(Tuple型かどうか)の検証。
#[実行結果]
#(3,5)<-tuple型で返ってくる。
#<class 'tuple'> <- やはりtuple型のようだ🤔

上のコードを実行してみると、ちゃんとタプル(tuple型)で返ってきているのがわかります。Tuple型というのは、

「変更できないlist型」

のことです。Pythonにはそもそも、tuple型ほかにもいろいろな型がありますが、このNoteの記事では割愛します。

一見、講座を見回してみると、Pythonの関数の戻り値は

「1つだけの場合の説明」

が多いですが、実は、

「コンマ」

でつなげる裏技があるのに驚きです😳
ですが、

return [返す値(文字列など],…

ではいろんな組み合わせが考えられます。これからいろいろと応用したい方のためにいろいろと例を載せてみました。でも、

「ガンガン試して進めていく」

のが一番です💪

でもやっぱり、不親切かな〜〜〜🤔💦

と思ってしまったので、例をあげまっす〜〜〜💪🔥

数字と文字列

def test():
	return 3,’OK’
print(test())
#実行結果。タプル型で返ってくる。
#カンマの順番はタプルの配列の順番に対応している。
(3,’OK’)

リスト型と数字

def test():
	return [3,5],3
print(test())
#実行結果。タプル型で返ってくるのは変わらず。リスト型と文字列で構成されたtuple型の配列で返ってくる。
([3,5],3)

リスト型と文字列

def test():
	return [3,5],’OK’
print(test())
#実行結果。タプル型で返ってくるのは変わらず。
#リスト型と文字列で構成されたtuple型で返ってきました。
([3,5],’OK’)

上のように、戻り値が複数ある場合のケースをいくつかあげてみましたが、

  • return文で戻り値を指定した順番

  • 返ってきたTuple型変数の順番

との順番を比べてみると、

「左からの順番に対応している」

ことがわかります。

もし、

「文字列の操作」

を行いたい場合は、

「list関数」

を使って変更する必要があります。なぜなら、複数の戻り値はタプル(Tuple型)で返ってくるので、

「要素の変更は行えないから」

です。

配列(リスト型)の作り方がわからないよ〜〜〜🥺

という人は下のNoteの記事をみてみてくださいね↓

その②:再帰関数でforループ分を書きかえる。

再帰関数は使いにくい…😕

そんなことは思ったことはないでしょうか?再帰関数をはじめて勉強したときに、混乱したのも僕だけではないかもしれません。数学で習う階乗やフィボナッチ数列のような漸化式に使われることも多いかもしれませんが、ここでは、

「ループ文を再帰関数に書きかえる」

だけの場合に絞って解説しています。

えっ、再帰関数ってループ文書き換えられるんだ…😲

って思った方も不思議ではないのかもしれません。
ですが、東京大学の数理・情報教育研究センターさんのgithubによると、

「ループ文は再帰関数に書き換えられる」

らしいです。

一般に、再帰処理は、繰り返し処理としても書くことができます。

東京大学 数理・情報教育研究センター (CC BY-NC-ND 4.0)[再帰の再帰関数の例:べき乗の計算]

ええっ、それやったら、forループ使ったほうがいいじゃ〜ん🤯

※for文についてわからない方は以下のNoteの記事を見てみてくださいね〜😉↓

確かに、そうかもしれません。ですが、ツリー構造になっているときはどうでしょうか?以下のコードで説明していきます。

nums=[2,3,5]
#以下を再帰関数の対象部分とします。
for num in nums:
	print(num)
#【実行結果】
 2
 3
 5

では、このコードを再帰関数を使って書きかえてみましょう。

def num_print(nums,index=0):
	if len(nums)==index:
  		return None
	print(nums[index])
	return num_print(nums+1,index)
	
nums=[2,3,5]
num_print(nums)

実際に実行してみたら、ループ文よりも早いように感じられました。

def [関数の名前](配列,要素番号):
	if len(配列)==要素番号:・・・①
		return None #何か要素を返したい場合は、
	...コード
	(return) [関数の名前](配列,要素番号+1)

返って前の繰り返しのコードよりも、コードが長くなってしまいましたね🤔
このことから、再帰関数は関数の中に関数を戻り値にすることで、

関数の中に同じ関数をさらに読み出す処理

を行うことから、無限ループにする性質を持っていると考えられます。ですので、①の条件文を書かないと

「無限ループ」

になってしまいます。

確かに、再帰関数を用いるよりも、

「for文やwhile文で完結できる」

ことが多いので、こういった用途で書き換えるというのはあまり気が進まない気持ちがわかるようになってきました。ですが、ループが2重、3重になるような複雑な処理になればなるほど、再帰関数の方が効率的に計算できるようです。

再帰関数の過信は厳禁…!?関数の呼び出し過ぎには注意が必要!!

かと言って、

「関数の中の関数を呼び出す」

関係で、呼び出しすぎると、メモリ(コンピューターが一時的な記録しておく場所だと思ってください。詳細は割愛します。)を使い切ってしまうという欠点があります…🤔

⚠メモ⚠
プログラミングを理解する上で、コンピュータのハードウェア(コンピュータの体)に対する理解が必要です。そのうち、パソコンのハードウェアについての解説をしていこうかなと考えています。もうすでに「C言語を理解しているよ」という方はこのメモを読み飛ばしてもかまいません。

なので、Pythonでは、

「1000回を超えて」

関数を呼び出せないことに注意が必要です。なぜかというと、

「Python自体がメモリが消費されすぎない」

ようにガードしているからです。

例えば、以下のコードを見てみてください🤔

def nestloop(range_x,range_y,x=0,y=0):
    if y==0 and x==0:
        print(y)
    if x<range_x:
        print('\t',x)
        nestloop(range_x,range_y,x+1,y)
    else:
        if y<range_y:
            print(y)
            nestloop(range_x,range_y,0,y+1)
        else:
            return 0
nestloop(range_x=100,range_y=70)

上のコードでは、2重ループを再帰関数で実現させたコードとなります。なのでここでの関数の呼び出す回数の計算式は、

$${yのループ回数*xのループ回数=関数を呼び出した関数の回数}$$

となるので、

$${100*70=7000回}$$

となります。なので、

7000回、関数を呼び出す

ことになってしまいます…🤯なので、この場合、設定を変更しないと、実行できなくなるので、注意が必要です。

なので、この場合、for文、while文に書き換えて、

def nestloop2(range_x,range_y):
    for i in range(range_y):
        print(i)
        for j in range(range_x):
            print('\t',j)
nestloop2(range_x=100,range_y=70)

書いたほうが良いでしょう。

総合して考えると、再帰関数は

「ループの回数が少なく済ませられる場合もしくはループの回数を少なく工夫することができた場合」

の使用にとどめておいたほうがよいのかもしれません🤔

その③:ジェネレータ関数とは?

関数の中でも、for文で繰り返せるものがあります。その関数をジェネレータ関数といいます。ジェネレータ関数の中身はこんな形になっています。

def func(a):
	yield a #yieldは一時停止⏸

ここでふと疑問が湧いてきます。

えっ!?なんでyieldなの!!?returnじゃないの〜🤔💦

と思われるのかもしれませんが、ジェネレータ関数において、

yield

であることが重要なのです。そもそも、returnyield

どちらも

「値や文字列を返す」

命令文なのですが、

yield→一時停止⏸。(呼び出したら再び再開。)
return→停止⏹。(呼び出したら最初からやり直し)

なので、yieldと呼ばれる命令文は、

「一時停止する」

ことができ、続けて途中からスタートできるというふうに持っていくことができます。このことから、まるでリスト型の配列と同じように、for in文でループを回すことが可能になります。なので、

for in文でループを回せる=イテラブルである

というわけです。

そもそも、for in文において、イテラブルであって初めてループを回せます。

えっ、for in文って何!?繰り返しの文って何ぞや!?

と思われた方は、下のNoteを見てみてください!!↓

実は、ジェネレータ関数もリスト型の変数と同じように、「イテラブル」に入ります。つまり、繰り返しの構文の内、for in文でループを回すことができます。

えっ、リスト型でいいんじゃね!?

と思われるのかもしれませんが、まずは、下の表をご覧ください。

使い回したいのであれば、リスト型の変数。使わないのであれば、ジェネレータ関数にしておく。そうすることで、動作速度が速くなる。
ジェネレータ関数はイテレータの一種。リスト型は変数(配列)の型の違いだとおさえておく!!

上の表のようにリスト型だと

使いまわせる分、メモリがそのまま残ってしまってしまう

し、動作速度は遅めです🤔

ジェネレータ関数は使った分はゴミ箱(シュレッダー行き)リスト型の変数は使い回せる!!
ジェネレータ関数は使いまわせない分、高速化かつメモリの節約に使える!!

一方、使いまわせるリスト型に対し、あえてジェネレータ関数に使うことによって、一度使った分は消えてしまうデメリットがありますが、

メモリを節約かつ、時間短縮する目的

で使えるというわけです!!

えっ、ほんまにリスト型の変数をループで回すのって時間がかかるん?ジェネレータ関数のほうが速い!?えっ、ほんまに!!?

気になりますよね!?それでは、実際に動作速度を検証してみましょう。

ジェネレータ関数とリスト型のループの実行速度の違いを検証してみよう

では、ジェネレータ関数とリスト型の実行速度を比べてみましょう。

以下のコードで検証します🤔

import datetime
def arrGen(arr):#ジェネレータ関数
	for num in arr:
		yield num
arr=[2*i-1 for i in range(1,6)]
beginTime=datetime.datetime.now()
for num in arrGen(arr):
	print(num)
endTime=datetime.datetime.now()
totalTime=(endTime-beginTime).total_seconds()
print('合計時間(Gen):',totalTime)

beginTime=datetime.datetime.now()
for num in arr:
	print(num)
endTime=datetime.datetime.now()
totalTime=(endTime-beginTime).total_seconds()
print('合計時間(list):',totalTime)

iPadのPythonistaで実行した結果は、こちらでした↓

1〜9の奇数配列ループで、ジェネレータ関数は0.001776。リスト型変数は0.002482。
えっ、ジェネレータ関数のほうが早いじゃん😳

上の実行結果↓
合計時間(Gen):0.001776秒
合計時間(list):0.002482秒

※Pythonista3(iPad)で実行

なりました。以上のことから考えると

「ジェネレータ」関数

のほうが速い結果となりました。どうしても、リスト型の変数を再び使うときがあるときは避けるべきですが、

リスト型の変数を再び使うことはない

ときは

ジェネレータ関数

を使って処理の効率化を図りましょう☺️

まとめ

いかがでしたでしょうか。Pythonでも

「returnが使えることを知っておく」

だけで、覚える負担が減るように感じます。複数戻り値が設定できるのも魅力の一つですよね☺️👍

とはいえ、Pythonではインデントで階層を決めるので、注意が必要です。

関数の使い方は忘れがちになるものですが、覚書みたいなことができると、助かりますよね。かと言って、

「再帰関数の呼び出しすぎは禁物」

と覚えておくと、良いでしょう。

関数の覚え書きができるのはPythonの魅力の一つですね。

後書き

プログラミングはパソコンの内部構造だけではなく、数学も覚えておかないと、高度なプログラミングは理解が難しいように感じました🤔
私の学生のころ、数学とコンピュータの関係にピンとこなかったので、今思えば、数学の勉強をしておけばよかったなと感じています。
いつか、数学を学びなおして高度なプログラミングを組むこと、それが私の将来の夢です。…あっ、そうだ。M2MacMiniの発売日はいつだ?M2で生配信(Youtube)するのも将来の夢の一つだった笑

<参考サイト>


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