Python で定義前の変数、関数、クラスの参照するときは関数定義内で参照する。




未定義の変数、関数、クラスは
関数の定義内で参照する。


例、よく使う手

1. 定義前の変数の参照


NG コード
未定義なので、エラーで返ってきます。

# 未定義の変数 a, b
print(a + b)
>>> # 未定義の変数 a, b
>>> print(a + b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>>


OK コード

# 未定義の変数 a, b
def main(a, b):
    print(a + b) 

# a, b を定義する。
a = 1
b = 2
main(a, b)
>>> # 未定義の変数 a, b
>>> def main(a, b):
...   print(a + b) 
... 
>>> 
>>> # a, b を定義する。
>>> a = 1
>>> b = 2
>>>
>>> main(a, b)
3
>>>



Java, C から流れてきた人にとっては main 関数を頭にかけばプロトタイプ宣言のように使えると考えるとわかりやすいかもしれません。

ちなみに残念ながらプロトタイプ宣言は Python ではないようです。でも、プロトタイプ宣言がなくても困りません。何故なら、関数は定義時には実行されないからです。


2. 定義前の関数の参照

NG コード
date は呼び出せません。何故なら、まだ定義していないから

# 未定義の関数 date
date() 
>>> # 未定義の関数 date
>>> date() 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'date' is not defined
>>>


OK コード
未定義の party が呼び出されてます。
でも実行されないので、エラーとしてはじかれません。

def main():
    # 未定義の関数 date
    date() 

def date():
    print("Hello, world!")

main()
>>> def main():
...   # 未定義の関数 date
...   date() 
... 
>>> 
>>> def date():
...   print("Hello, world!")
... 
>>> 
>>> main()
Hello, world!
>>> 



3. 定義前のクラスの参照

あるクラスに、定義前の別のクラスを属性として登録することを考えてみましょう。例えば BoyFriend クラスに属性として GirlFriend クラスのオブジェクト girl_friend を登録してみましょう。

NG コード
弾かれます。

class BoyFriend(object):
    # 未定義のガールフレンド(仮)クラス
    # もれなく弾かれます。
    girl_friend = GirlFriend()
>>> class BoyFriend(object):
...   # 未定義のガールフレンド(仮)クラス
...   # もれなく弾かれます。
...   girl_friend = GirlFriend()
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in BoyFriend
NameError: name 'GirlFriend' is not defined
>>>


OK コード
関数の定義内であれば、未定義の変数、関数、クラスも参照できることを利用して __init__ 関数の中で定義前のクラスを参照します。

__init__ 関数の中で属性を参照、変更する際には self が必要です。

class BoyFriend():
  def __init__(self):
    # 未定義の GirlFriend クラス
    # => 未定義なので直接クラス内では参照できない。
    
    # 関数の中で属性を参照する場合は self が必要
    self.girl_friend = GirlFriend() 


class GirldFriend():
    # 定義済 の BoyFriend クラス
    # => 定義済みなので直接クラス内で参照できる。
    
    # 直接クラス定義の中で参照する場合は self は不要
    boy_friend = BoyFriend()


# 成功! 
boy_friend = BoyFriend()
>>> class BoyFriend():
...   def __init__(self):
...     # 未定義の GirlFriend クラス
...     # => 未定義なので直接クラス内では参照できない。
...
...     # 関数の中で属性を参照する場合は self が必要
...     self.girl_friend = GirlFriend() 
...
...
>>>
>>> class GirldFriend():
...   # 定義済 の BoyFriend クラス
...   # => 定義済みなので直接クラス内で参照できる。
...
...   # 直接クラス定義の中で参照する場合は self は不要
...   boy_friend = BoyFriend()
>>>
>>>
>>> # 成功! 
>>> boy_friend = BoyFriend()
>>>




補足: 「インスタンス変数」と「ローカル変数」を区別する。
ちなみに __init__ 関数内で self を記載しなかった変数は、外部から参照できません。なぜなら __init__ 関数の中だけで参照できる変数として定義されてしまっているからです。このように関数の外側から見れない変数を「ローカル変数」と呼びます。

class GirlFriend():
    def __init__(self, name, place_of_birth):
        # OK: インスタンス変数
        self.name = name
        # NG: ローカル変数
        place_of_birth = place_of_birth

girl_friend = GirlFriend('やるみ', '東京')


# OK
girl_friend.name

# NG
girl_friend.place_of_birth
>>> # OK
... girl_friend.name
'やるみ'
>>> 
>>> # NG
... girl_friend.place_of_birth
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'GirlFriend' object has no attribute 'place_of_birth'
>>> 



補足: 「クラス変数」と「インスタンス変数」を区別する。
とりあえず、いまは "上のコードは、エラーにならない" ということだけを確認しておいてください。実は GirlFriend クラスの設計は、間違っています。正しい設計、「クラス変数」と「インスタンス変数」の違いについては、あとで説明します。

# 間違った設計
class BoyFriend():
    def __init__(self):
        self.girl_friend = GirlFriend() 

class GirldFriend():
    # クラス変数
    boy_friend = BoyFriend()
# 正しい設計
class BoyFriend():
    def __init__(self):
        self.girl_friend = GirlFriend() 

class GirldFriend():
    def __init__(self):
        # インスタンス変数
        self.boy_friend = BoyFriend()










スクリプトをモジュールとして再利用しやすくする。



関数が、定義時に実行されないことを利用して、自分が作ったスクリプト sample.py をモジュールとして再利用しやすくして見たいと思います。

1. 最初に実行する処理を頭に持って来る。

コードを追いかけやすくすること、どこから読めばいいようにするかをわかりやすくすることは、コードを理解しやすくするのと同じくらい可読性において重要です。

ディベロッパがコードを読んでいる時、作業を大幅に遅らせる2つの根本的な要因があります。
1. コードが理解しづらい
2. コードが追いかけづらい
まずコードの可読性を最適化しよう | POSTD

◯ main 関数

そこで、関数は定義時には処理が実行されないことを利用して、一番最初に実行する処理を関数にまとめて頭に書くことがあります。

そのとき、処理を頭に持ってくるために書く関数名は main と記載されていることが多い気がします。別に決まりではありません。

なぜ main が多いかと言うと Java や C 言語だと main 関数は最初に実行される仕様になっていて、それに習っているような感じです。

def main():
    # 未定義の関数 date
    date()

def date():
    print("Hello, world!")

main()



2. import 時に処理を実行させない

作ったスクリプトの関数をインタラクティブシェルで、ちょっと触って動作確認したい時があります。

# 1. こんなスクリプトを
# ファイル名は sample.py
def main():
    # 未定義の関数 date
    date()

def date():
    print("Hello, world!")

main()




しかし import すると同時にスクリプト全体が実行されてしまいます。
import と同時に main() が実行され "Hello, world!" が出力されます。

$ # 2. インタラクティブシェルで
$ python3
>>> # 3. ちょっと触って動作確認したい
>>> import sample
Hello, world!
>>> 
>>> sample.date()
Hello, world!
>>>




本当はこんな感じになってほしい。

>>> # import しても何も実行されない。
>>> import sample
>>> 
>>> sample.date()
Hello, world!
>>>



◯ if __name__ == '__main__':

このようにするには main 関数に加えて if __name__ == '__main__': を合わせて書きます。

def main():
    # 未定義の関数 date
    date()

def date():
    print("Hello, world!")

if __name__ == '__main__':
    main()

このコードを追加することで、このファイルが import できるモジュールであると同時にスクリプトとしても使えるようになります。
6.1.1. モジュールをスクリプトとして実行する



1. スクリプトとして実行

スクリプトとして実行すると、__name__ には '__main__' が代入されます。if 文内が実行されます。

host:directory user$ # 1. スクリプトとして実行
host:directory user$ python3 sample.py
Hello, world!
host:directory user$ 

Python モジュールを

$ python3 fibo.py arguments

と実行すると、__name__ に __main__ が設定されている点を除いて import したときと同じようにモジュール内のコードが実行されます。
6.1.1. モジュールをスクリプトとして実行する



この書き方も決まりではありませんが、たまに見かけます。Python に標準で付いてくる timeit という実行時間を計測するモジュールでも、この書き方がなされているのを見たことがあります。
4. timeit モジュール - Python で実行時間を計測したい。
cpython/Lib/timeit.py - GitHub

2. モジュールとして実行

モジュールとして import されると、__name__ にはモジュール名が代入されます。簡単に言えば、例えばファイル名が sample.py だったら 'sample' が代入されます。if 文内は実行されません。

host:directory user$ # 2. モジュールとして実行
host:directory user$ python3
Python 3.6.3 (default, Nov  5 2017, 23:48:30) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> import sample
>>>
>>> # モジュール内の関数を利用できる。
>>> sample.date()
Hello, world!
>>>

モジュールの中では、(文字列の) モジュール名をグローバル変数 __name__ で取得できます。
6. モジュール (module) — Python 3.6.5 ドキュメント











クラス変数とインスタンス変数の違い



疑問: なんで関数では未定義の変数等を参照できたのにクラスでは未定義の変数等を参照できなかったの?

答え: クラスを定義した段階で、処理が実行されてしまうから
 

まず、以下の例について考えてみます。なんと "Hello, world!" が出力されています。クラスが定義された段階で処理が実行されていることがわかります。

class BoyFriend():
    print("Hello, world!")
    girl_friend = GirlFriend()
>>> class BoyFriend():
...   print("Hello, world!")
...   girl_friend = GirlFriend()
... 
Hello, world!
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in BoyFriend2
NameError: name 'GirlFriend2' is not defined
>>>

 

次に関数を見てみます。処理が実行されません。

def date():
    print('Hello, world!')
    girl_friend = GirlFriend()
>>> def date():
...   print('Hello, world!')
...   girl_friend = GirlFriend()
... 
>>> 


クラスは、単純にモジュールを分割したものです。簡単に言えば、クラスはモジュールの子供みたいなものです。

クラスオブジェクトは、基本的にはクラス定義で作成された名前空間の内容をくるむラッパ (wrapper) です。
9.3.1. クラス定義の構文



クラスには、モジュールよりも2つの機能が付け加えられています。1つ目は、クラスは、インスタンス化してオブジェクトを生成できること。2つ目は、クラスは、他のクラスを継承できること。

クラスが、モジュールを分割しただけでシンプルに表現できているなんて、すごい設計ですよね。ワイだったらもっと複雑怪奇なものしか思いつかない.. これを見た時は、感動しました。


疑問: じゃあ __init__ 関数の中で全部属性の代入してしまえば、いいんじゃないの?

答え: ダメです。






f:id:domodomodomo:20180611161644j:plain





インスタンスオブジェクト間で共有される変数ならば...
クラス変数に代入します。
→ それをするには、クラス定義文の中で属性に代入します。

インスタンスオブジェクト固有の変数ならば...
インスタンス変数に代入します。
→ それをするには、__init__ 関数の中で属性に代入します。
9.3.5. クラス変数とインスタンス変数
 

◯ 具体的にどういうことだってばよ...

恋愛ゲームについて考えましょう。彼女 GirlFriend には名前 name, 彼氏への親密度 intimacy があり最小値 0, 最大値 100 とします。これでクラス設計をすると

# NG
class GirlFriend():                                                                
    max_intimacy = 100
    min_intimacy = 0
    intimacy = 0
    name = None
    boy_friend = None



実は、これ間違っています。何故なら、名前 name と親密性 intimacy がすべての GirlFriend インスタンスオブジェクトで共有されるクラスオブジェクトの属性として定義されているからです。

インスタンスオブジェクトで共有しない属性、名前 name と親密性 intimacy は、__init__ 関数の中で定義してインスタンス変数に代入します。

# OK
class GirlFriend():
    # インスタンスオブジェクト間で共有される変数
    max_intimacy = 100                                                               
    min_intimacy = 0                                                                 

    # インスタンスオブジェクト間で共有しない変数
    def __init__(self, new_name=''):                                              
        self.name =  new_name
        self.intimacy = 0
        self.boy_friend = None

 

 

疑問: クラス変数とインスタンス変数って、何か違いがあるの?

答え: あります。