Pythonの基礎文法

コンストラクタ(イニシャライザ)とは

Pythonに限らず、プログラムの世界では必ず”初期化”と呼ばれる作業が行われます。今回の「コンストラクタ」(※イニシャライザとも言います)も初期化作業の1つです。Pythonのコンストラクタもオブジェクトのインスタンス化が行われたときに最初に行われる処理を記述します。最初に行われる処理とは主にインスタンス変数の初期化や設定を行います。

class Adam:
    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)         
        
        for key in params.keys():
            self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)

上記のソースコードは、アダム(Adam)と言われる機械学習で使う関数です。このコードで__init__という関数がありますが、これがPythonのコンストラクタ(イニシャライザ)です。引数に”self, lr=0.001, beta1=0.9, beta2=0.999”とあります。「self」はクラスAdamが持つメンバ変数への参照に使われるものです。関数の一番最初に記載されるルールです。「lr=0.001, beta1=0.9」など他の引数がなくてもクラス内の関数であれば「self」は必ず記入するようです。

余談ですが「lr=0.001」と引数にある”=0.001”の部分は引数の初期値です。呼び出す際に「Adam()」と呼び出しても「Adam(0.001, 0.9, 0.999)」と記載しているのと同じとなります。私は、様々なプログラミング言語で引数にデフォルト値を指定するコードを見てきましたが、Pythonのこの記述の仕方がシンプルで解りやすく好きです。どうして今までのプログラム言語でこの記述が出来なかったのか?と思うほど初めて見たときは衝撃を受けました。

インデントの意味

一般的なプログラミング言語でインデント(4文字分の空白やTab)といえば、コードを読みやすくするために用いてましたよね?判定処理(if文)の開始から終わりまで。繰り返し処理(for文など)の開始から終わりまでなど。JavaScriptなどのライブラリで少しでも通信量(データ容量)を減らすために改行やスペースをなくしたコードが使われることはありますが、一般的なコードではインデントを設けるのはごくごく当たり前のことと思います。Pythonにおいては見やすさ以外にも重要な意味を持ちます。前述したような処理の開始と終了をインデントで表現するために用います。

def update(self, params, grads):
    if self.m is None:
        self.m, self.v = {}, {}
        for key, val in params.items():
            self.m[key] = np.zeros_like(val)
            self.v[key] = np.zeros_like(val)
    
    self.iter += 1

上のソースは再びAdamの一文ですが、if文やfor文の終わりが記載されていないです。JavaやC言語などでは”{}”で囲われた範囲を処理のスコープとして定義されていましたが、Pythonはインデントでそれを表現します。最終行の「self.iter += 1」のインデントがif文と同じラインにあるのは、このコードの前がif文の終わりを意味します。また、for文がif文よりインデントが深いのは、for文がif文の中の処理であることを意味します。

スコープについて

Pythonのスコープは単純です。ローカルスコープか、一つ外側のスコープか、どこからで参照可能なグローバルスコープの3種類です。

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

上記のソースはPython3のマニュアル「9.2.1. スコープと名前空間の例」のサンプルプログラムです。異なるスコープと名前空間がどのように参照されるか、また global および nonlocal が変数の束縛にどう影響するかの例です。実行結果は以下の通りです。「global」で宣言された変数は関数呼び出し後も変更後の情報が保持されます。

ネームスペースについて

他のプログラミング言語(JavaやC#)で馴染みがある人は”ネームスペース”と聞けば「あ~アレのことね!」と。思う方もいらっしゃるかと思います。多分Javaでいう名前空間とかパッケージを思い浮かぶと思いますが、Pythonでもそのような概念が存在します。Pythonで他のファイルに定義されているモジュールを参照できるようにするためには「import」文を使用します。いくつか定義の仕方があります。まずは、モジュールをパッケージから import する一番シンプルな書き方をまずご紹介します。

import sound.effects.echo

上記のような指定でインポートした場合は、完全修飾型で呼び出すことになります。

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

”sound.effects.echo”と毎回書くのは面倒なので、以下のようにインポートすることができます。

from sound.effects import echo

このように書くのは、サブモジュール echo をロードし、 echo をパッケージ名を表す接頭辞なしで利用できるようにした書き方です。呼び出しは以下のような形で”sound.effects”を省略して書けるようになります。私個人的には、このインポート文が一番わかりやすいと思っています。どこのモジュールを使っているのか判るのもありますし、他の人がコードを読むときも可読性が上がります。また、書籍等で紹介されているソースも、この形式のインポート方法を用いて書かれてあることが多い印象です。

echo.echofilter(input, output, delay=0.7, atten=4)

もう一つのバリエーションとして、必要な関数や変数を直接 import する方法があります。それが下記のようなインポート方法です。

from sound.effects.echo import echofilter

このインポート方法だと”sound.effects.echo”すべてを省略して書けるようになります。コード量は少なくなるメリットはありますが、どのモジュールを使っているのかパッと見て解りにくいので私は好みません。

echofilter(input, output, delay=0.7, atten=4)

さらに、「from sound.effects import *」というインポートの仕方もあります。このような定義をした場合、パッケージ sound.effects の全てのサブモジュールを現在の名前空間の中へ importしないそうです。この文は単に(場合によっては初期化コード __init__.py を実行して) パッケージ sound.effects が import されたということを確認し、そのパッケージで定義されている名前を全て import するだけらしいです。「__init__.py」の定義を書く場合、以下のように書きます。このように定義するとsound.effectsパッケージの中から指定された3つのモジュールが読み込まれるようになるようです。

__all__ = ["echo", "surround", "reverse"]

これまでパッケージのインポートの仕方をご紹介しましたが、ソースはPython3マニュアルの「6.4. パッケージ」の内容をかみ砕いて説明しました。詳細はマニュアルを参照ください。上記のことがより詳しく記載されています。

まとめ

今回は「Pythonの基礎文法」ということで、コンストラクタとインデントの意味、スコープの考え方とモジュールのインポート方法について説明しました。文法というと、他にも予約語の説明や判定処理や繰り返し処理の書き方などもあります。その説明は別の機会で行います。

ではまた。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

This website stores cookies on your computer. These cookies are used to provide a more personalized experience and to track your whereabouts around our website in compliance with the European General Data Protection Regulation. If you decide to to opt-out of any future tracking, a cookie will be setup in your browser to remember this choice for one year.

Accept or Deny