Python Programming 動的にclassを作成

今日も見にきてくださって、ありがとうございます。

 勉強会でPythonの講師をさせていただいているので、ここのところPythonについて思いを馳せるタイミングが増えてきています。最近Pythonを使っていて、おお!と思ったことをちょっと書こうと思います。なんと、クラス作成後に属性を追加することができるのです!すべてオブジェクトにすると、こんなこともできてしまうのですね。ちょっといろいろと確認してみます。

 まずは、何もないクラスを作成。そして、インスタンスを作成します。

>>> class Box:
...     pass
... 
>>> box1 = Box()

なんと、このあと動的にアトリビュートを追加することができるのです。

>>> box1.x = 100
>>> box1.y = 120         
>>> print(box1.x, box1.y)
100 120
>>> 

当然、メソッドも追加できますよね?
ということで、やってみます。まず関数を定義して、代入して、実行してみます。

>>> def getpos(self):
...     print(self.x, self.y)
...
>>> box1.getpos = getpos
>>> box1.getpos()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getpos() missing 1 required positional argument: 'self'
>>> </module></stdin>

おっと、うまくいきませんね。ちなみに、この「self」というのは、クラスのメソッドがインスタンスになった時、関数実行時に一つ目のパラメータとしてインスタンスが渡されるために必要になる引数です。ホントはどんな名前でもいいのですけど慣例として「self」とすることになっています。
あ、ここまで書いて気づきました。インスタンスに関数を定義したのがいけないのかも知れません。Boxクラスの方へセットしてみます。

>>> Box.getpos = getpos
>>> box2 = Box()
>>> box2.x = 300
>>> box2.y = 320
>>> box2.getpos()
300 320
>>>              

できました!
このように、クラス作成後に動的にクラスを変更できてしまうので、プログラムを使ってコーディングすることができますね。ちなみに、アトリビュートは文字列を使ってセットできることを確認しました。dir()関数を使って、定義したクラスの中身を調べてみました。

>>> dir(Box)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getpos']
>>> 

名前からアトリビュートっぽいのを探ります。以前いろいろと探して「__dir__」に含まれているのを知っていたので、中を見てみましょう。

>>> dir(Box.__dict__)
['__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'copy', 'get', 'items', 'keys', 'values']

ええと、ディクショナリークラスの中身のようですね。見方が違いました。きっと、こうですね。

>>> Box.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Box' objects>, '__weakref__': <attribute '__weakref__' of 'Box' objects>, '__doc__': None, 'getpos': <function getpos at 0x0000029DA5021E18>})

先程追加した関数getposはありましたが、xとyがありませんね。あ、、、そういえばインスタンスの方にセットしただけでした。では、インスタンスの方を確認しましょう。

>>> box1.__dict__
{'x': 100, 'y': 120, 'getpos': <function getpos at 0x0000029DA5021E18>}

そうそう、こんな感じです。
では、文字列の’x’と’y’を使って、クラスの方へアトリビュートと初期値をセットしてみます。

>>> setattr(Box,'x',10)
>>> setattr(Box,'y',20)
>>> dir(Box)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getpos', 'x', 'y']
>>> box3 = Box()
>>> box3.getpos()
10 20
>>>

はい、見事、xとyがクラスに追加されて、メソッドが最初から利用できるようになりましたね。オブジェクトの場合は「__dict__」を普通のdictと同じようにしてアトリビュートを追加することができたのですけど、クラスの場合の「__dict__」はmappingproxyになっていて読み込みのみでしたので、setattrを使う必要がありました。ちなみに、中を見てみるとどうなっているかというと、

>>> Box.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Box' objects>, '__weakref__': <attribute '__weakref__' of 'Box' objects>, '__doc__': None, 'getpos': <function getpos at 0x0000029DA5021E18>, 'x': 10, 'y': 20})
>>>

こんな感じです。オブジェクトの方にアトリビュートを追加するもう一つの方法は以下の通りです。

>>> box3.__dict__['z'] = -100
>>> box3.__dict__
 {'z': -100}
>>>

おっと、少し想定と違いました。xとyがいませんね。クラスで定義したアトリビュートはここには入らない、ということでしょうか。以下の通り、値は取り出せました。

>>> box3.x
10
>>> box3.y
20
>>> box3.z
-100
>>>

ちょっと、xに値を代入したらどうなるかやってみます。

>>> box3.x = -10
>>> box3.__dict__
{'z': -100, 'x': -10}
>>> 

なるほど、思った通りですね。クラスで定義した値が変更されていなければインスタンスごとにその値を持つ必要がない、ということですね。

そういえは、関数を文字列から作成して組み込むのはどうやるのだろうか、と、気になってまいりましたが、長くなってきたので今回はこれくらいで。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


The reCAPTCHA verification period has expired. Please reload the page.