[Work/Class/Python3の基礎とデータ処理/python_basic]

Pythonの関数とクラスの定義

関数

プログラミング言語の関数とは,「ひとまとまりの処理を記述し,入力と出力を変数で整えることで,その処理を容易に再利用可能にしたもの」である.

数学でいう所の関数とほぼ同義である.

実はこれまで使用してきたprint(表示したい変数や文字列)というのも関数である.Pythonに標準で用意されている関数であることから,組み込み関数・ビルトイン関数などと呼ぶ.listの長さを返してくれるlen()も組み込み関数である.

関数の定義と呼び出し

自分独自の関数を定義したい場合,

def 関数名(引数をカンマで区切って定義):
    # 関数の処理内容...
    # 戻り値がある場合
    return 戻り値

というように定義する.

「引数」とは関数の入力,「戻り値」とは関数の出力である.例えば,

def increment(x):    # 関数名と引数名を定義する
  return x + 1    引数で取った値に+1をして返す

という関数が定義されている時,

a = 1
b = increment(a)  # 関数に引数aを与えて「呼び出す」
print(b)

というコードを実行すると,increment関数を引数整数1を与えて呼び出したことになり,既に定義されたincrement関数が呼び出され,1を足して返してくれる.それを変数bに代入し,printする.

従って,出力は整数の2になる.

この時,関数の定義の時に使った引数の名前(上記の例ではx)と,関数を呼び出している側の変数の名前(上記の例ではa)の名前は違ってもよい.関数を呼び出した時に,自動的に値が関数側の変数にコピーされるからである.

逆に言えば,関数と呼びだす側と関数内部の変数は全く別のものであり,同じものとして捉えるのは間違っている.関数呼び出し時に,変数内部に入っている値だけがコピーされるのである.これは戻り値についても同様である.

Pythonは関数型言語の側面も持っているので,クラスのインスタンスメンバ関数でない関数の場合,必ずreturnで何らかの値を返すようにしておく方が良い.(クラスのインスタンスメンバ関数の場合で返すものがない場合は,後述のselfを返すのが一番わかりやすい)

のように定義し呼び出す.ここでは,戻り値を受け取った変数aの中には6*2つまり12が,変数bの中には6/2つまり3が入ることになる.

引数が単純な「値」や文字列の場合,値は関数に入る時にコピーされる.しかしlistなど,単純な値以外のものを取る場合,実体(インスタンスと呼ぶ)がそのまま関数の中に引き継がれるという違いがある.

def append_apple(l):
  # listを取る関数.listの場合は,値がコピーされずに,listの実体そのままが飛んでいく
  l.append('apple')
  return l


my_list = ['grape', 'orange']
my_new_list = append_apple(my_list)
print(my_new_list)

また,関数の中でも,for文やif文をそのまま使うことができる.

def generate_list(x):
  return_list = list([])
  for item in range(x, x+10, 1): # 関数の中のfor文
    return_list.append(item)
  return return_list

a = 3
my_new_list = generate_list(a)
print(my_new_list)

Pythonでは複数の戻り値を指定することができる.例えば,

def multi_div_return(x, y):
    return x*y, x/y
    # 関数定義はここまで

# インデントを元に戻したので関数の外側
a, b = multi_div_return(6, 2)

グローバル変数

「クラスを定義してそれを駆動する」以前のPythonコードで,関数外部で宣言初期化された変数はグローバル変数となる.

変数は,その関数内部でしか通用しない.例えば違う関数の中で同じ名前の変数が宣言されていても,別の変数の扱いとなる.これをローカル変数と呼ぶ.(Pythonの変数のスコープについては前項を参照されたい)

それに対しグローバル変数は,どの関数からでもアクセスできる変数である.

関数内部でグローバル変数にアクセスするためには,アクセスする前にglobal グローバル変数名という式を実行しておく必要がある.

ただし,グローバル変数の多用は,名前がかぶってしまったり,実行する順番が明確になっていないと意図しない動作を引き起こしてしまう事が多い.グローバル変数はなるべく使用せずに,引数と戻り値で制御する方が良い.

# 関数の外側で変数を定義.グローバル変数になる.
var1 = 123

# 関数を定義する
def add_gv(x):
  global var1     # この関数内でvariable1というグローバル変数を使う事を宣言

  return var1 + x # グローバル変数を使って演算をした戻り値を返す
  # 関数定義ここまで

a = 789
b = add_gv(a)
print(b)          # 123 + 789で912がbの中には入っている

既にある関数やクラスの読み込み

自分で定義した関数やクラスではなくとも,関数やクラスをまとめたモジュールが既に存在し,それを読み込み,利用することができる.

例えば,数学的な関数や定数が定義されているmathモジュールを読み込むときには,

import math

という風にimportキーワードを使って,読み込みを行う.データ分析の大体の部分は既存のモジュールのみで可能である.

読み込んだ後は,モジュール名.モジュールで定義されている定数や,モジュール名.モジュールで定義されている関数などのように,「.」を付けて個別の定数や関数を呼び出すことができる.

print(math.pi) # mathモジュール内で定義済みの円周率の定数

a = 4
b = math.sqrt(4) # math.sqrtは,平方根を返してくれる関数.mathモジュール内で定義済み.
print(b)

Google Colaboratoryのような環境(逐次実行できる環境 - REPL環境と呼ぶ)では,一度importすると,他のセルでも読み込んだモジュールは有効である.ただし接続制限時間というものがあり,ウィンドウを閉じてしばらく経ったり,何も操作せずにしばらく経ったり,または長時間計算をすると,Googleが提供しているサーバとの接続が切れて再起動される.その時は再度importする必要がある.

Notebookの最初のセルにたとえば「このセルを最初に必ず実行!」という風に書いて,importをしておくと良い.

クラス

クラスとは,関数や定数,保持するべき内部パラメータなど必要な機能をひとまとめにしたものである.

関数との大きな違いは,クラスはオブジェクトインスタンス化(単にインスタンス化とも)することで,保持すべき内部パラメータ……データすなわち「状態」をまとめて保持できることにある.インスタンス関数がクラスにまとめられるのはむしろ,この「状態」にアクセスするためにである.この「保持されている状態」はインスタンス変数と呼ばれる.

またクラスは,「保持されている状態」には関係しない,似たような機能を持つ定数や関数をまとめる機能を持ち,それぞれクラス変数,静的関数・クラス関数と呼ばれる.

Pythonをデータ分析に用いる場合,殆どのデータ分析用のクラスは既にあるので新たに自分で定義する必要は殆どない.

クラスの定義

class クラス名(親クラス):
    # クラス定義の内容...

と定義する.親クラスがない時(正確にはobjectクラスが親の時)は,小括弧の中はobjectにする,空にする,小括弧をつけない,のどれかを選択する.

selfキーワード

Javaでthisに相当する「このクラスの」もしくは「このインスタンスの」を表すキーワードはselfである.

コンストラクタの定義

def __init__(self, コンストラクタが取る変数...):
    # コンストラクタ関数内の処理...

と書くことで,クラスのコンストラクタを定義する.

コンストラクタは「何も返さない」ことが決められているので,return Noneを最後に実行する.Noneキーワードに注意.

内部で親クラスのコンストラクタを実行したい時には,

super().__init__(親クラスのコンストラクタの引数からselfを除いた引数)

という形で親クラスのコンストラクタにアクセスする.(Python2では書法が異なる)

インスタンスメンバ変数の定義

コンストラクタや他の関数内部でself.変数名と宣言すると,インスタンスメンバ変数となる.

クラスのインスタンスメンバ変数は,基本的にprivateであるべきである.privateな変数の定義は,変数名がアンダースコア2つ連続で始まる規則になっており,__variable_nameと書かれるので,インスタンスメンバ変数はself.__variable_nameと定義する.

インスタンスメンバ関数の定義

インスタンスメンバ関数の定義では,第一引数が必ずselfになる.ただし呼び出しの時は第一引数を書かずに第二引数から書き始める.

クラス変数と静的関数・クラス関数

クラス変数

静的変数・定数つまりインスタンスを生成しなくても使えるクラス内の定数(Javaでいう所のpublic static定数)は,selfをつけずに,コンストラクタや関数外で宣言すればよい.

class MyClass:
  クラス変数の名前 = 値

という形で宣言する.クラスの外からは

クラス名.クラス変数の名前

でアクセスする.

クラス「変数」という名前ではあるが,実際は定数として運用する.そのクラスの中の静的関数を利用する際に有用となる定数が定義されている事が多い.前述の数学的な関数や定数が定義されているmathモジュールでは,円周率が定義されており,事前にimportしておけば,

print(math.pi)

という形で呼び出すことができる.

静的関数

静的関数は,データを実行対象として直接紐づけずに,実行時にデータを与える関数であり,与えたデータ例えばlistを破壊的に操作することはなく,たいていは新しいオブジェクトインスタンスを作って,そこに元のオブジェクトインスタンスからコピーを行い,操作を加えて,戻り値として返してくれる.

Pythonでの静的関数は,一般的にC言語のみで書かれた高速ライブラリで採用されている.通常のオブジェクト指向プログラミング言語ではオブジェクトインスタンスを作り操作を行うような場合でも,Pythonのライブラリでは静的関数として実装されていることも多い.numpyが代表的である.C言語の機能を直接実走したものに,このパターンが多い.

前述の数学の関数や定数が定義されているmathモジュールでは,三角関数が用意されており,事前にimportしておけば,

b = math.sin(a)
d = math.cos(c)

の要に呼び出すことができる.

自分でクラスを定義する場合の静的関数は以下のように宣言する.

@staticmethod
def my_function(引数):
    # 処理内容....

静的関数はselfを引数に取らないので,書かない.つまり静的関数は(当たり前だが)インスタンスメンバ変数にはアクセスできない.

クラス関数

上記の静的関数他に,Pythonには「クラス関数」(クラスメソッド)というものもある.これは第一引数にselfを取る静的関数のことで,静的変数にも内部的にアクセスできる.が,ややこしくなるので書かない方がよい.クラスの静的定数には「クラス名.定数名」でアクセスする方が明確である.

ソースコードが関数定義やクラス定義から始まる時のmain関数

Pythonのソースコードは,上から順次実行されるが,関数定義やクラス定義がある場合,まず初めに関数定義やクラス定義を書いて,実行開始main関数を次に書く.main関数の定義は以下のように行う.

# ...
# ここまでクラス定義

if __name__ == '__main__':
   # ソースコードの実行開始時に読まれる関数
   # この中でクラスのインスタンスを作り,処理する.
   # ...

自分でクラスを定義する時のコード例

以下は,PyQtというGUIライブラリを使ったグラフィック表示の例である.データ分析ではクラスを自分で定義することはほとんどないので,参考程度に眺めておけばよい.

コード中では,

  1. 必要なクラスライブラリの読み込み
  2. 自分で定義するクラスの定義
  3. main関数

の順で進む.

# -*- coding: utf-8 -*-
# BounceAndColoredBall.py

# 必要なクラスライブラリの読み込み
import sys # 今回はアプリケーション起動時の引数を取ってくるために使う
import math # 数学関係
import random # ランダム用ライブラリ

# GUIに使うQt5のライブラリ
# 本筋とは関係ないので,「そんなもんか」と捉えておけば良い
from PyQt5.QtCore import * 
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


# 自分のクラスの定義
class BounceBall():
    # コンストラクタ
    # 引数の頭が必ずselfになることに注意
    def __init__(self, windowWidth, windowHeight):
        self.__windowWidth = windowWidth #privateメンバ変数は「self.__変数名」
        self.__windowHeight = windowHeight
        self.__x_pos = math.floor(random.random() * self.__windowWidth)
        self.__y_pos = math.floor(random.random() * self.__windowHeight)
        if random.random() > 0.5 :
            self.__x_direction = 1
        else:
            self.__x_direction = -1
        if random.random() > 0.5 :
            self.__y_direction = 1
        else:
            self.__y_direction = -1

        return None # コンストラクタはNoneを返すと決められている

    def updateParameters(self):
        self.__x_pos += 10 * self.__x_direction
        if self.__x_pos > self.__windowWidth :
            self.__x_direction = -1
        elif self.__x_pos < 0 :
            self.__x_direction = 1

        self.__y_pos += 10 * self.__y_direction
        if self.__y_pos > self.__windowHeight :
            self.__y_direction = -1
        elif self.__y_pos < 0 :
            self.__y_direction = 1

        return self

    def get_x(self):
        return self.__x_pos

    def get_y(self):
        return self.__y_pos


class ColoredBall(BounceBall): # BounceBallを継承して,色情報を持たせたクラス
    # コンストラクタのオーバーライド
    def __init__(self, windowWidth, windowHeight):
        # 最初に親クラス(BounceBall)のコンストラクタを呼び出す
        super().__init__(windowWidth, windowHeight)

        # 子クラス独自のコンストラクタの内容を書いていく
        self.__r_color = math.floor(random.random() * 155) + 100
        self.__g_color = math.floor(random.random() * 155) + 100
        self.__b_color = math.floor(random.random() * 155) + 100
	
	return None

    # 子クラス独自の関数を定義
    def get_r(self):
        return self.__r_color
    def get_g(self):
        return self.__g_color
    def get_b(self):
        return self.__b_color

# 実際に描画するウィンドウを呼び出して描画するクラス(JavaではJFrameに相当する)
# 本筋には関係ないので「こんなもんか」と眺めるだけで良い
class MyWidget(QWidget):
    # コンストラクタのオーバーライド
    def __init__(self, parent=None):
        super().__init__(parent)

        self.__ball_list = list([])

        for i in range(10):
            a_ball = BounceBall(640, 480)
            self.__ball_list.append(a_ball)

        self.__colored_ball_list = list([])
        for i in range(10):
            a_ball = ColoredBall(640, 480)
            self.__colored_ball_list.append(a_ball)

        self.resize(640, 480)
        self.show()

	return None


    # 描画イベント関数(Processingでいうdraw関数)のオーバーライド
    def paintEvent(self, event):
        painter = QPainter(self) # QPainterのインスタンスを作成
        painter.setPen(Qt.black) # Stroke(枠線)に相当
        # 画面のRefresh
        painter.setBrush(Qt.white) # 中の色に相当
        painter.drawRect(0, 0, 640, 480);

        for a_ball in self.__ball_list:
            a_ball.updateParameters()
            painter.drawEllipse(a_ball.get_x(), a_ball.get_y(), 20, 20)

        for a_ball in self.__colored_ball_list:
            a_ball.updateParameters()
            painter.setBrush(QColor(a_ball.get_r(), a_ball.get_g(), a_ball.get_b()))
            painter.drawEllipse(a_ball.get_x(), a_ball.get_y(), 20, 

	# 最後にupdate関数を呼び出すと描画される
        self.update()

# main関数        
if __name__ == '__main__':
        app = QApplication(sys.argv)
        w = MyWidget() # 自分で作ったクラスのインスタンスを作る
        w.raise_()
        app.exec_() #paintEventが回るようにする