Subscribed unsubscribe Subscribe Subscribe

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


f:id:domodomodomo:20161101225414p:plain

ポイント

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

例、よく使う手

1. 定義前の変数の参照

 

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

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

 

OK コード

>>> # 未定義の変数 a, b
>>> def main(a, b):
...   print(a+b) 
... 
>>> 
>>> main(1,2)
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 コード
未定義の date が呼び出されてます。
でも実行されないので、エラーとしてはじかれません。

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

 


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

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

 


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

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

まず、以下の例について考えてみます。なんと "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()
... 
>>> 

 

 

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

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

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

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

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

>>> class GirlFriend():                                                                
...   max_intimacy = 100
...   min_intimacy = 0
...   intimacy = 0
...   name = ''

実は、これ推奨されないやり方です。何故なら、名前 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                                                              
...              

 

 
 

疑問: クラス定義文の中で属性に代入した時と、__init__ 関数の中で属性に代入した時で何か違いがあるの?

答え: あります。

クラス定義文の中で属性に代入した時は、クラスオブジェクトの属性に代入されます。

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

さて、これはどういうことでしょうか。すこしずつ順を追って見ていきたいと思います。
 


疑問: クラスオブジェクトって具体的に何のこと?

答え: クラス定義を抜けると生成されるオブジェクトを指しています。

クラス定義から普通に (定義の終端に到達して) 抜けると、 クラスオブジェクト (class object) が生成されます。

9.3.1. クラス定義の構文

 

>>> # クラスオブジェクトの定義
>>> class GirlFriend():                                                                
...   max_intimacy = 100                                                               
...   min_intimacy = 0                                                                 
...                                                                                    
...   def __init__(self, new_name=''):                                              
...     self.name = new_name                                               
...     self.intimacy = 0 
>>>
>>>
>>> # クラスオブジェクト
>>> GirlFriend
<class '__main__.GirlFriend'>
>>>
>>>
>>>
>>> # クラスオブジェクトの属性
>>> GirlFriend.max_intimacy
100
>>>
>>> GirlFriend._min_intimacy
0
>>>
>>> # インスタンスオブジェクトの属性は参照できない。
>>> GirlFriend.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'GirlFriend' has no attribute 'name'
>>>
>>> GirlFriend.intimacy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'GirlFriend' has no attribute 'intimacy'

 


疑問: インスタンスオブジェクトって具体的に何のこと?

答え: クラスオブジェクトからインスタンス化されたものを指しています。

>>> class GirlFriend():
...   max_intimacy = 100                                                               
...   min_intimacy = 0                                                                 
...
...   def __init__(self, new_name=''):                                              
...     self.name =  new_name
...     self.intimacy = 0                                                              
... 
>>>
>>> # 変数 girl_friend に代入されたものが
>>> # インスタンスオブジェクト
>>> girl_friend = GirlFriend('サーバルちゃん')
>>>
>>>
>>> # インスタンスオブジェクトの属性
>>> girl_friend.name
サーバルちゃん
>>>
>>> girl_friend.intimacy
0
>>>
>>> # クラスオブジェクトの属性も参照できる
>>> girl_friend.max_intimacy
100
>>> girl_friend.min_intimacy
0

 



 

疑問: インスタンスオブジェクトの属性とクラスオブジェクトの属性って、何か違いがあるの?

答え: あります。例えば、クラスオブジェクトの属性に値を代入すると、そのクラスオブジェクトから生成された全てのインスタンスオブジェクトの属性が変更されます。
 

1. クラスオブジェクトの属性の参照

クラスオブジェクトは、クラス定義の内部で属性に代入されたインスタンスオブジェクトを、外部から参照できる仕様になっています。

>>> # クラスオブジェクトの定義
>>> class GirlFriend():                                                                
...   # クラス定義の内部で属性 max_intimacy に代入された
...   # インスタンスオブジェクト 整数 100
...   max_intimacy = 100                                                               
...
...
...   min_intimacy = 0                                                                 
...                                                                                    
...   def __init__(self, new_name=''):                                              
...     self.name = new_name
...     self.intimacy = 0 
...
>>>
>>> # クラスオブジェクト
>>> GirlFriend
<class '__main__.GirlFriend'>
>>>
>>>
>>> # 外部から "参照" できる仕様
>>> GirlFriend.max_intimacy
100
>>> 
>>> 
>>> # "変更"
>>> GirlFriend.max_intimacy = 10

 


◯ クラスオブジェクトの属性を変更すると、そこから生成された全てのインスタンスオブジェクトの属性も変更される。

>>> girl_friend1 = GirlFriend()
>>> girl_friend2 = GirlFriend()
>>> 
>>> girl_friend1.max_intimacy
100
>>> girl_friend2.max_intimacy
100
>>> 
>>> # クラスオブジェクトの属性を変更すると
>>> GirlFriend.max_intimacy = 50
>>>
>>> # そこから生成された全ての
>>> # インスタンスオブジェクトの属性も変更される。
>>> girl_friend1.max_intimacy
50
>>> girl_friend2.max_intimacy
50

 





2. クラスオブジェクトの属性の参照(関数)

クラスオブジェクトは、クラス定義の内部で定義された関数を、外部から参照できる仕様になっています。

関数もインスタンスオブジェクトです。関数は、クラスオブジェクトの属性に代入されます。

>>> class GirlFriend():                                                                
...   max_intimacy = 100                                                               
...   min_intimacy = 0                                                                 
...                                                                                    
...   def __init__(self, new_name=''):
...     self.name = new_name
...     self.intimacy = 0                                                              
...
...   # 新しい関数を追加しました。
...   # 彼女の名前をサーバルちゃんに変更できる
...   # 素晴らしい関数です。
...   def change_name(self):                                                 
...     self.name = 'サーバルちゃん'
...
>>> 
>>> 
>>> #
>>> # 1. 属性の参照 
>>> #
>>> GirlFriend.change_name
<class 'function'>
>>>
>>>
>>>
>>> #
>>> # 2. 属性の変更
>>> #
>>>
>>> # 別に僕はいいのですがサーバルちゃんだけでは
>>> # 色々あたりがあるので
>>> # new_change_name 関数を定義しました。
>>> def new_change_name(self, new_name):
...   self.name = new_name
... 
>>>
>>> # クラスオブジェクトの関数の属性に関数を代入
>>> GirlFriend.change_name = new_change_name

 

◯ クラスオブジェクトの関数を変更すると、そこから生成された全てのインスタンスオブジェクトのメソッドも変更される。

まず実際の動作を見て見ましょう。メソッドを変更する際は、クラスオブジェクトの属性に関数を代入してください。

>>> class GirlFriend():                                                                
...   max_intimacy = 100                                                               
...   min_intimacy = 0                                                                 
...                                                                                    
...   def __init__(self, new_name=''):
...     self.name = new_name                                           
...     self.intimacy = 0                                                              
...
...   # 新しい関数を追加しました。
...   # 彼女の名前をサーバルちゃんに変更できます。
...   def change_name(self):                                                 
...     self.name = 'サーバルちゃん'
...
>>> 
>>> girl_friend1 = GirlFriend()
>>> girl_friend2 = GirlFriend()
>>> 
>>> girl_friend1.change_name()
>>> girl_friend2.change_name()
>>> 
>>> girl_friend1.name
'サーバルちゃん'
>>> girl_friend2.name
'サーバルちゃん'
>>>
>>>
>>> # 別に僕はいいのですがサーバルちゃんだけでは
>>> # 色々あたりがあるので
>>> # new_change_name 関数を定義しました。
>>> def new_change_name(self, new_name):
...   self.name = new_name
... 
>>>
>>> # クラスオブジェクトの関数の属性に関数を代入
>>> GirlFriend.change_name = new_change_name
... 
>>> 
>>> #
>>> # 1) クラスオブジェクトの属性に代入する
>>> # 正 GirlFriend.change_name = new_change_name
>>> # 誤 girl_friend1.change_name = new_change_name
>>> #
>>>
>>> GirlFriend.change_name = new_change_name
>>> 
>>>
>>> #
>>> # 2) インスタンスオブジェクトのメソッドは全て変更される。
>>> #
>>>
>>> girl_friend1.change_name('岩倉玲音')
>>> girl_friend1.name
'岩倉玲音'
>>> 
>>> girl_friend2.change_name('バラライカ')
>>> girl_friend2.name
'バラライカ'

 

○ 明示的に self を第一引数に与える理由

このようにして関数の変更もできるから。

"self" を明示的に引数として記述することで,関数を追加して,クラスを動的に書き換えることができるようになる,というものである。つまり,複数のクラスで共用できるメソッドを作れるというわけである。

和訳 なぜPythonのメソッド引数に明示的にselfと書くのか

上記の引用元の記事は Pythonの生みの親 Guido によって書かれたブログを和訳したものです。この記事は self を第一引数に与えると動的にメソッドが追加できるよということを説明しているだけではなく

Bruce という人が「第一引数にわざわざ self を与えなくたって、暗黙的に呼び出し元のインスタンスオブジェクトを self に代入すればいいじゃん」と言う提案に対して、Guido が、それがなぜダメなのかの理由を述べています。
 

 

疑問: なぜクラスオブジェクトの属性を変更すると、そこから生成された全てのインスタンスオブジェクトの属性も変更されるの?

答え:
インスタンスオブジェクトの属性
→ クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの親クラスオブジェクトの属性
...
といった順番に属性にアクセスしてるから。
 




 
ちょっと復習...

>>> girl_friend1 = GirlFriend()
>>> girl_friend2 = GirlFriend()
>>> 
>>> girl_friend1.max_intimacy
100
>>> girl_friend2.max_intimacy
100
>>> 
>>> # クラスオブジェクトの属性を変更すると
>>> GirlFriend.max_intimacy = 50
>>>
>>> # そこから生成された全ての
>>> # インスタンスオブジェクトの属性も変更される。
>>> girl_friend1.max_intimacy
50
>>> girl_friend2.max_intimacy
50

 

ここから本題...

>>> # step1. girl_friend1 の属性 max_intimacy に
>>> #          インスタンスオブジェクトは代入されてるかな?
>>> # -> されてない...
>>>
>>> # step2. GirlFriend の属性 max_intimacy に
>>> #          インスタンスオブジェクトは代入されてるかな?
>>> # -> 50 があった!
>>> girl_friend1.max_intimacy
50
>>>
>>>
>>> #
>>> # インスタンスオブジェクトの属性を変えても...
>>> #
>>> girl_friend1.max_intimacy = 100
>>> girl_friend1.max_intimacy
100
>>>
>>> # クラスオブジェクトの属性に影響はない。
>>> GirlFriend.max_intimacy
50
>>> girl_friend2.max_intimacy
50
>>>
>>>
>>> # 
>>> # インスタンスオブジェクトの属性を消せば...
>>> #
>>> girl_friend1.max_intimacy
100
>>> del(girl_friend1.max_intimacy)
>>> 
>>> # またクラスオブジェクトの属性が参照される。
>>> girl_friend1.max_intimacy
50

○ 属性アクセスのだいたいの動作

f:id:domodomodomo:20170406193028j:plain


だいたいこんな順番で属性にアクセスしてます。
a.x
→ a.__class__.x
→ a.__class__.__bases__[0].x
→ a.__class__.__bases__[0].__bases__[0].x
...
 

Step1.
まず、マニュアルの文章を読んで見ましょう。

属性アクセスのデフォルトの振る舞いは、オブジェクトの辞書の属性の取得、設定、削除です。例えば a.x は、まず a.__dict__['x']、それから type(a).__dict__['x']、さらに type(a) のメタクラスを除く基底クラスへと続くというように探索が連鎖します。

デスクリプタ HowTo ガイド — Python 3.6.1 ドキュメント

 

これを文章にすると、このような感じ。
a.x
→ a.__dict__['x']
→ type(a).__dict__['x']
→ type(a) のメタクラスを除く基底クラス
→ type(a) のメタクラスを除く基底クラス のメタクラスを除く基底クラス
...
 

Step2.

クラスオブジェクトの基底クラスのタプルです。

class.__bases__

「type(a) のメタクラスを除く基底クラス」は
オブジェクト a を生成したクラスの親クラスのことで

だいたいの場合 type(a).__bases__[0]) と同じ

__bases__ がタプルになっているのは
多重継承している場合があるから。


a.x
→ a.__dict__['x']
→ type(a).__dict__['x']
type(a).__bases__[0].__dict__['x']
type(a).__bases__[0].__bases__[0].__dict__['x']
...
 

Step3.

オブジェクトの (書き込み可能な) 属性を保存するために使われる辞書またはその他のマッピングオブジェクトです。

__dict__ 属性

a.__dict__['x'] は
だいたいの場合 a.x と同じ

a.x
→ a.x
→ type(a).x
→ type(a).__bases__[0].x
→ type(a).__bases__[0].__bases__[0].x
...


もう1回書き換えて
a.x
→ a.x
→ type(a).x
→ type(a).__bases__[0].x
→ type(a).__bases__[0].__bases__[0].x
...
 


Step4.

引数が1つだけの場合、object の型を返します。返り値は型オブジェクトで、一般に object.__class__ によって返されるのと同じオブジェクトです。

type 関数

クラスインスタンスが属しているクラスです。

__class__ 属性

tyep(a) は
だいたいの場合 __class__ と同じ


a.x
a.__class__.x
a.__class__.__bases__[0].x
a.__class__.__bases__[0].__bases__[0].x
...
 

○ 変数の命名規則

__bases__, __class__, __dict__ とかアンダースコア _ をたくさん使った変数が出てきました。これは何でしょうか?システムで定義された (system-defined) 名前です。

その他にもアンダースコア _ を使う命名規則として、プライベートな識別子であるかどうかを明示するために使います。プライベートな識別子であるとは、obj.attr のようにオブジェクトの属性として外部から参照されないということです。

厳密にという訳ではありませんが、だいたい、以下のようになります。例えばユーザが定義したクラスの __init__ 関数はシステムではなく、ユーザが定義しますしね。

識別子 定義者 定義箇所 参照される箇所
_val ユーザ モジュール定義文 モジュール定義文、あるいはその
配下のクラス定義文、関数定義文
__val ユーザ クラス定義文 クラス定義文、あるいはその
配下の関数定義文
__val__ システム システム -
# モジュール定義文
_val = 1

class Person():
  # クラス定義文
  __val = 10
  
  """
    # システムが定義
    __val__
  """
  
  def func():
    # 関数定義文
    # ___val = 100
    # とはならない。
    # 
    # 何故なら関数には属性という概念は無いので
    # func.val という様に外部から参照できないから。
    #
    # プライベートっぽい局所変数であっても
    # アンダースコアは _ は不要という訳です。
    val = 100

2.3.2. 予約済みの識別子種 (reserved classes of identifiers)
実践されている命名方法

○ __class__ 属性への代入

__class__ 属性に別のクラスオブジェクトを代入することでクラスキャストのような動作をします。
Python でクラスキャスト -


疑問: なぜクラスオブジェクトの属性に関数を代入したら配下のインスタンスオブジェクトのメソッドも全て変更されるの?

答え: 実は、メソッドが参照された時の動作は、呼び出されたその都度、クラスオブジェクトの関数を参照して実行しているだけだから。
 

◯ メソッドもインスタンスオブジェクト

メソッドもインスタンスオブジェクトです。メソッドも変数に代入できます。そして、変数に代入されたメソッドから、① メソッドを実行したり、② メソッドを保持しているインスタンスオブジェクト(girl_friend)を変更できます。

>>> # メソッドも変数に代入できる。
>>> # -> メソッドもインスタンスオブジェクト
>>> change_name_method = girl_friend.change_name
>>>
>>> change_name_method('serval')
>>>
>>> # メソッド単体で girl_friend が変更できた
>>> # -> メソッドの属性の中に
>>> #    関数(GirlFriend.change_name)や
>>> #    インスタンスオブジェクト(girl_friend) が代入されてるはず。
>>> girl_friend.name
'serval'

 

このことから単体でメソッド(change_name_method)から

 ① 関数(GirlFriend.change_name)(* 下記引用参照)
 ② インスタンスオブジェクト(girl_friend)

を参照していることがわかります。
 

関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、インスタンスから属性が取り出されるたびに行われます。

3.2. 標準型の階層 > 呼び出し可能型 (callable type) > インスタンスメソッド

 

ということは...
 

メソッドの属性のどこかで

 ① クラスオブジェクトの関数(GirlFriend.change_name)
 ② 呼び出し元のインスタンスオブジェクト(girl_friend)

が代入されているのかなと思いました。
 

そこで dir 関数を使って属性を全て表示させてみました。

>>> for e in dir(change_name_method): e
... 
... 前略
'__func__'
... 中略
'__self__'
... 後略

 

それらしき ① __func__ と ② __self__ を確認。マニュアルを読むと ① __func__ には クラスオブジェクトの関数が ② __self__ には呼び出し元のインスタンスオブジェクトが格納されています(3.2. 標準型の階層 > 呼び出し可能型 (callable type) > インスタンスメソッド)。

>>> change_name_method.__func__ is GirlFriend.change_name
True
>>>
>>> change_name_method.__self__ is girl_friend
True

 
 

◯ メソッド呼び出しのだいたいの動作

メソッドを呼び出した時の動作についての記述を発見。

インスタンスメソッドオブジェクトが呼び出される際、根底にある関数 (__func__) が呼び出されます。このとき、クラスインスタンス (__self__) が引数リストの先頭に挿入されます。例えば、 C を関数 f() の定義を含むクラス、 x を C のインスタンスとすると、 x.f(1) の呼び出しは C.f(x, 1) の呼び出しと同じです。

3.2. 標準型の階層 > 呼び出し可能型 (callable type) > インスタンスメソッド

 

このことをを踏まえると、だいたいではありますが次のような流れで girl_friend.change_name メソッドが評価されていることがわかります。

>>>
>>> #
>>> # メソッド呼び出し
>>> #
>>>
>>> # STEP 1 
>>> #   "インスタンスメソッドオブジェクトが呼び出される際"
>>> girl_friend.change_name('serval')
>>>
>>>
>>> # STEP 2
>>> change_name_method = girl_friend.change_name
>>> change_name_method('serval')
>>>
>>> 
>>> #
>>> # 関数呼び出し
>>> #
>>>
>>> # STEP 3
>>> #   "根底にある関数 (__func__) が呼び出されます。
>>> #    このとき、クラスインスタンス (__self__) が
>>> #    引数リストの先頭に挿入されます。"
>>> change_name_method.__func__(
  change_name_method.__self__, 'serval')
>>>
>>> # STEP 4
>>> GirlFriend.change_name(
  girl_friend, 'serval')


このようにしてメソッドが参照されると、その都度、クラスオブジェクトの関数が参照されています。したがって、クラスオブジェクトの関数を変更すると同時にメソッドも変更されるというわけです。



疑問: クラス内の関数定義とか言ってるけど、メソッド定義じゃないの?

答え: 関数定義です。

理由は Python 言語リファレンスを覗いてみて funcdef 関数定義 と言うのはあったのですが methoddef のようなメソッド定義に該当しそうなものがなかったからです(8. 複合文)。

compound_stmt ::=  if_stmt
                   | while_stmt
                   | for_stmt
                   | try_stmt
                   | with_stmt
                   | funcdef <- 関数定義
                   | classdef
                   | async_with_stmt
                   | async_for_stmt
                   | async_funcdef

疑問: そもそも関数とメソッドって違うの?

答え: 関数とメソッドは違います。メソッドは第一引数に、呼び出し元のオブジェクトが与えられます。関数にはそのような動作はありません。


例にもれず、また次のような GirlFriend クラスを考えてみましょう。

>>> class GirlFriend():                                                                
...   max_intimacy = 100                                                               
...   min_intimacy = 0                                                                 
...                                                                                    
...   def __init__(self, new_name=''):                                              
...     self.name = new_name                                            
...     self.intimacy = 0                                                              
...
...   # 好きな名前に変えられる change_name 関数
...   def change_name(self, new_name):                                                 
...     self.name = new_name
... 

 

○ 関数

クラスオブジェクトの属性に代入されるのは "関数" です。"メソッド" ではありません。

このクラスオブジェクトの属性に代入された "関数" を使って、"関数" の動作を確認してみます。

>>> #  function -> 関数 だよ
>>> GirlFriend.change_name
<function GirlFriend.change_name ...
>>> 
>>>
>>> # 関数 change_name は2つの引数 self, new_name が必要です
>>> # 引数を1つだけ与えるとエラーが返されます。
>>>
>>> GirlFriend.change_name('岩倉玲音')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: change_name() missing 1 required
positional argument: 'new_name'
>>>
>>> 
>>> girl_friend = GirlFriend() 
>>>
>>> GirlFriend.change_name(girl_friend, '岩倉玲音')
>>> 
>>> girl_friend.name
'岩倉玲音'

 

○ メソッド

クラスオブジェクトからインスタンスオブジェクトが生成されると、"関数" は "メソッド" として代入されます。"メソッド" は第一引数に、呼び出し元のオブジェクトが与えられます。

>>> girl_friend = GirlFriend()
>>> 
>>>
>>> # bound method -> メソッド だよ
>>> girl_friend.change_name
<bound method GirlFriend.change_name ...
>>> 
>>> # 第一引数は省略されている。
>>> girl_friend.change_name('岩倉玲音')
>>> 
>>> girl_friend.name
'岩倉玲音'

 


次の2つは "厳密に等価なもの" です(9.3.4. メソッドオブジェクト)。

>>> GirlFriend.change_name(girl_friend, '岩倉玲音') 
>>>
>>> girl_friend.change_name('岩倉玲音') 
>>>

 


クラスオブジェクトもインスタンスオブジェクトです。

インスタンスオブジェクトって具体的に何を指しているの?もう1つの説明の仕方。

答え:「変数に代入」できるものを指しています。
 

例えば、整数 1, 文字列 'Hello, world!', リスト [1, 2, 3] は変数に代入できます。そしてこれらはインスタンスオブジェクトになります。

>>> # 変数に代入できるものはインスタンスオブジェクト
>>> a = 1
>>> b = 'Hello, world!'
>>> c = [1, 2, 3]

 


また、インスタンスオブジェクトを使えば、「属性の参照」ができます。

ところで、インスタンスオブジェクトを使うと何ができるのでしょうか?インスタンスオブジェクトが理解できる唯一の操作は、属性の参照です。

9.3.3. インスタンスオブジェクト

 

例えば、整数 1 はインスタンスオブジェクトです。だから、属性の参照ができます。

>>> # 整数 1 はインスタンスオブジェクト
>>> a = 1
>>>
>>> # だから、属性の参照ができる。
>>> # a の実部
>>> a.real
1
>>> 
>>> # a の虚部
>>> a.imag
0

 


○ そう言えば、クラスオブジェクトも...

まず、クラスオブジェクトも変数に代入でき、インスタンスオブジェクトとして扱うことができます。

実はクラスの定義がなされた段階で既にクラスオブジェクトを変数に代入すると言う操作が行われています。

GirlFriend という変数にクラスオブジェクトが代入されていました。

Python ではイコール '=' を使って代入するときだけではなく、次の5つの場合に "変数への代入"、正確に言えば "変数への束縛" が行われています。

以下の構造で、名前が束縛されます:
 関数の仮引数 (formal parameter) 指定、
 import 文、クラスや関数の定義
 代入が行われるときの代入対象の識別子
 for ループのヘッダ、
 with 文や except 節の as の後ろ。

4. 実行モデル — Python 3.6.1 ドキュメント

 


また、属性の参照について言えば、いままで見てきた通りクラスオブジェクトは、属性を参照することができました。更に言えば属性に代入をすることもできました。
 


以上のことから、クラスオブジェクトがインスタンスオブジェクトだと、どうやら言えそうです。実際に本当にそうなのか確認していきたいと思います。
 


 

疑問: クラスオブジェクトは、どのクラスオブジェクトのインスタンスなの?

答え: type クラスオブジェクト

一体どのクラスオブジェクトをインスタンス化したものが GirlFriend クラスオブジェクト(GirlFriendインスタンスオブジェクト)になたったのでしょうか?

>>> # GirlFriend クラスは type クラスの
>>> # インスタンスオブジェクトであることがわかります。
>>> type(GirlFriend)
<class 'type'>
>>>
>>> # GirlFriend クラスのインスタンスですか?
>>> isinstance(GirlFriend, type)
True
>>>

 

GirlFriend クラスオブジェクト は type クラスオブジェクトのインスタンスオブジェクトということがわかりました。ということは、「① クラスオブジェクトを定義する」には、「② type クラスオブジェクトをインスタンス化する」ことでもできるのではないでしょうか? 次の ① と ② は、同じことを意味しています。

>>> # ① GirlFriend クラスオブジェクトの定義
>>> class GirlFriend():
...   max_intimacy = 100                                                               
...   min_intimacy = 0 
>>>
>>> # ② type クラスオブジェクトからインスタンス化された
>>> # GirlFriend インスタンスオブジェクト(クラスオブジェクト)
>>> GirlFriend = type('GirlFriend', (object,),
  dict(max_intimacy=100, min_intimacy=0))

 


実は type は...

「引数が3つの場合、新しい型オブジェクトを返します。本質的には class 文の動的な形式です。」

2. 組み込み関数 — Python 3.6.1 ドキュメント

 

さらに type は type 自身のインスタンスです。

>>> type(type)
<class 'type'>
>>> 
>>> isinstance(type, type)
True

 

疑問: もしかして type はあらゆるクラスオブジェクトの親玉みたいなもの?

答え: その通りです。
例えば int, bool, double, function も type のインスタンスでありクラスであります。
 

>>>  # int
>>> type(0)
<class 'int'>
>>> type(int)
<class 'type'>
>>> # bool
>>> type(True)
<class 'bool'>
>>> type(bool)
<class 'type'>
>>> # None
>>> type(None)
<class 'NoneType'>
>>> type(NoneType)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'NoneType' is not defined
>>> # NondeType は直接参照できないので
>>> type(None.__class__)
<class 'type'>
>>> # function
>>> def func():
...   pass
... 
>>> type(func)
<class 'function'>
>>> type(function)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'function' is not defined
>>> # function は直接参照できないので
>>> type(func.__class__)
<class 'type'>

 

そして、なんと type クラスオブジェクトは、type クラスオブジェクト自身からインスタンス化されたクラスオブジェクトです(下図参照)。

>>> # type
>>> type(type)
<class 'type'>

 


Python では、取り扱うすべてのオブジェクトは何らかのクラスオブジェクトをインスタンス化したものであるということがわかりました。

さらにそのクラスオブジェクト自体も type というクラスオブジェクトをインスタンス化したものであり、さらに type も自身 type をインスタンス化したものであります。

しかし、こうしてみるとユーザが定義したクラス class も int, boolean とかと同じクラスオブジェクト、型に属するんですね。

すべてのオブジェクトは、同一性 (identity)、型、値をもっています。

3. データモデル — Python 3.6.1 ドキュメント

 



f:id:domodomodomo:20160517214811j:plain
 


おわりに

最後までお付き合いいただき、ありがとうございました。でも、そんな人いるのでしょうか笑 定義前の変数、関数、クラスの参照ってどうやればええねんってところからはじまって、読みわるとクラスオブジェクトの概要がわかるように書いてみました。
 







Remove all ads