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



みなさまへの大切なメッセージ

ポイント



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

例、よく使う手

1. 定義前の変数の参照


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

>>> # 未定義の変数 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)
3
>>>



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

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


2. 定義前の関数の参照

NG コード
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()
Hello, world!
>>> 



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

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

NG コード
弾かれます。

>>> class BoyFriend():
...   # 未定義のガールフレンド(仮)クラス
...   # もれなく弾かれます。
...   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()
>>>


請注意

BoyFriend クラスは OK ですが GirlFriend クラスの方は、実際にはあまり OK なコードではありません。boy_friend 変数に代入された1つの BoyFriend クラスのオブジェクトを、全ての GIrlFriend クラスのオブジェクトで共有するという陰キャのワイには、大変辛い設計になっています。


何が問題かと言えば boy_friend は「クラス変数」として定義されていることです。しかし、ここでは、「クラス変数」とは何かは置いておいて、とりあえず、上のコードがエラーにならないという動作だけを確認しておいてください。詳しいことは追って説明します。




ちなみに __init__ 関数内で self.name と記載しなかった name は、外部から参照できません。なぜなら __init__ 関数内だけで参照できる関数のローカル変数として定義されてしまっているからです。

>>> class GirlFriend():
...   def __init__(self):
...     self.boy_friend = BoyFriend() 
...
...     # self を書かないと...
...     name = 'Yarumi' 
...
>>>
>>> boy_friend.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'BoyFriend' object has no attribute 'name'

モジュールを再利用しやすくする。

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

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

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

◯ main 関数

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

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

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

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

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

main()



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

モジュール内の関数をインタラクティブシェルで、ちょっと触って動作確認したい時があります。しかし import すると同時にスクリプト全体が実行されてしまいます。

>>> # import と同時に main() が実行され
>>># "Hello, world!" が出力されます。
>>> import sample
Hello, world!
>>> 
>>> sample.main()
Hello, world!

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

>>> import sample
>>> 
>>> sample.main()
Hello, world!



◯ if __name__ == '__main__':

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

Python に標準で付いてくるモジュールでも、この書き方がなされているのを見たことがあります。例えば、実行時間を計測してくれる timeit という標準でついてくるモジュールがあるのですが、そこで見かけました。
cpython/timeit.py at master · python/cpython · GitHub

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

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

if __name__ == '__main__':
    main()

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




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

$ python sample.py
Hello, world!
$

Python モジュールを

$ python fibo.py arguments

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


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

$ python
>>> import sample
>>>
>>> # モジュール内の関数を利用できる。
>>> sample.date()
Hello, world!
>>>

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

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

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

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

>>> 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()
... 
>>> 


クラスは、名前空間の内容をくるむラッパ (wrapper)で、モジュールを分割したものみたいなものなので、こんな動作をします。クラスとモジュールの違いは、クラスはインスタンス化できることみたいな感じでしょうか。

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

 

 

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

答え: ダメです。

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

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

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

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

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



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

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

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

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

 

 

疑問: クラス変数に代入した時と、インスタンス変数に代入した時は何か違いがあるの?

答え: あります。

クラス変数に代入した時は、クラスオブジェクトの属性に代入されます。インスタンス変数に代入した時は、インスタンスオブジェクトの属性に代入されます。

クラスオブジェクトインスタンスオブジェクトとは何でしょうか?すこしずつ順を追って見ていきたいと思います。