オブジェクトからクラスオブジェクトを得る
特殊メソッドである__class__からクラスオブジェクトを得ることができます。
class Foo(): pass foo = Foo() print(Foo) print(foo.__class__)
$ py main.py <class '__main__.Foo'> <class '__main__.Foo'>
取れてますね。
使い道としては例えば以下のような自分自身を生成するようなメソッドを提供する場合
class Foo(): def clone(self): return Foo() class Bar(Foo): pass foo = Foo() bar = Bar() print(foo.clone()) print(bar.clone())
$ py main.py <__main__.Foo object at 0x000000000220A908> <__main__.Foo object at 0x000000000220A908>
このように「return Foo()」でクラス名を固定して返すと継承したときにBar.cloneがFooのオブジェクトを返してしまいます。
ここでBarを返したい場合、__class__を使うと実現できます。
class Foo(): def clone(self): return self.__class__() class Bar(Foo): pass foo = Foo() bar = Bar() print(foo.clone()) print(bar.clone())
$ py main.py <__main__.Foo object at 0x000000000225A908> <__main__.Bar object at 0x000000000225A908>
うまくいってますね。
classmethodによるクラスメソッド
入門Python3 6.10
classmethodデコレータを使うと、クラスメソッドを作ることができます。
class Foo(): def hello(): return "hello foo" @classmethod def hello_print (cls): print(cls.hello()) Foo.hello_print()
$ py main.py hello foo
clsにはFooクラスが入ります。つまりこの処理は以下の処理と等価です。
class Foo(): def hello(): return "hello foo" # classmethodデコレータを使わない def hello_print (cls): print(cls.hello()) Foo.hello_print(Foo) # 自分でFooクラスを渡す
$ py main.py hello foo
ぶっちゃけて言えばFooを二回書くのが面倒なんで@classmethodでやろうよってことなのかな。
とういか一体なんの役に立つのか、そもそもclsなんて引数を受け取る必要あるの?クラス名書けばいいじゃん、以下のコードじゃダメなの?
class Foo(): def hello(): return "hello foo" def hello_print (): # clsなんて受け取らない print(Foo.hello()) # Foo.hello()で取ればいいじゃん Foo.hello_print()
$ py main.py hello foo
clsなんて受け取らずにFoo.hello()でアクセスして操作すればいいじゃない、結果も同じだしさ。
・・・とまぁ一見確かにclassmethod使おうがどっちでも同じに見えますが、継承した時にまったく挙動が変わります。
このclassmethodを使ってないFooクラスを継承した場合どうなるか見てみましょう
class Foo(): def hello(): return "hello foo" def hello_print (): print(Foo.hello()) class Bar(Foo): def hello(): return "hello bar" Foo.hello_print() Bar.hello_print()
$ py main.py hello foo hello foo
Barでhelloをオーバーライドし別の文字列を返すようにしたのに"hello foo"になってしまっています。
それも当然でFoo.hello_print内でFoo.helloを呼び出してるのだから当たり前の結果です。
でもこれは(このコードを書いた人にとっては)意図した動きではないはずです。
これをクラスメソッドに変えた場合、意図通りに動くことになります。
class Foo(): def hello(): return "hello foo" @classmethod def hello_print (cls): print(cls.hello()) class Bar(Foo): def hello(): return "hello bar" Foo.hello_print() Bar.hello_print()
$ py main.py hello foo hello bar
こうすることでclsには呼ばれたクラスのクラスオブジェクトが入ってくるので、clsの中身はFooで呼べばFooクラスだしBarで呼べばBarクラスになるとういことです。
基本的にはこの継承可能なクラスメソッドを定義できるという点で便利なので使っていきたいのですが、逆に言えば継承されたときに動作が変わって欲しくない場合はむしろclassmethodを使わないという判断も有りかとは思います(いやその用途ならアンダーバー2個から始まるメソッドにすればいいんじゃないの・・・)
ちなみにclsという引数名はselfと同様にPythonの慣習的に自身のクラスオブジェクトが来る場合にそう付けるということみたいです、従いましょう。
staticmethodによる静的メソッド
入門Python3 6.10
staticmethodデコレータを使うと、クラスに静的メソッドを作ることができます。
class Foo(): @staticmethod def hello(): return "hello foo" print(Foo.hello())
$ py main.py hello foo
と、ここまで書いてみて、あれこれそういやstaticmethod付けなくても動作したような、と思いやってみると
class Foo(): # staticmethodをつけない def hello(): return "hello foo" print(Foo.hello())
$ py main.py hello foo
うん、ちゃんと動く。
ではstaticmethodを付けるのと付けないのとで何が違うのか?
色々調べてみたんですが、どうやら恐らくPython2の時はstaticmethodをつけない場合例外になってたみたいです。
class Foo(): # staticmethodをつけない def hello(): return "hello foo" print(Foo.hello()) # Python2だとここで例外がでるっぽい
つまりPython3(正しくはPython3.3以降?)では付けても付けなくても同じってことになるんですかね。
Python3.6をベースに勉強しているのでググって調べると2の時の話とかが混ざってて調べるのが難しいですね・・・。まずは2から勉強した方が良かったのかしら。
追記
もうひとつ違いがありました。それはオブジェクトからメソッドを呼び出す場合の挙動の違いです。
staticmethodを使わない場合例外になります。
class Foo(): def hello(): return "hello foo" foo = Foo() print(foo.hello())
$ py main.py Traceback (most recent call last): File "main.py", line 8, in <module> print(foo.hello()) TypeError: hello() takes 0 positional arguments but 1 was given
オブジェクトからの呼び出しの場合、helloはオブジェクトメソッドとして呼び出されるため、引数にselfが指定されていないので例外となるわけです。
staticmethodを使えばオブジェクトからの呼び出しの場合でもオブジェクトメソッドとしてではなく静的メソッドとして呼ばれることになります。
class Foo(): @staticmethod def hello(): return "hello foo" foo = Foo() print(foo.hello())
$ py main.py hello foo
このことから基本的には静的メソッドとして提供する場合はstaticmethodにしておいた方が無難と言えるでしょう。
デコレータと対象関数が同名のケース
def bar (func): pass @bar def foo (): pass
(中身がpassなのはとりあえず横に置いといて)fooの前に@barを付けるのがデコレータなわけですが、これはつまり以下のコードと等価であると
def bar (func): pass def foo (): pass foo = bar(foo) # fooの前に@barつけるのと同じ意味
デコレータの説明でそういうのをよく見かけるんですが、どうやら完全に等価ってわけではないんですね。
というのもデコレータ関数名とデコレータ対象の関数名が同名の場合、結果が変わります。
まずはデコレータを使った場合
def foo (func): pass @foo def foo (): pass
$ py main.py
これは問題なく動作します。
では等価コードであるはずの以下のコードではどうでしょう
def foo (func): pass def foo (): pass foo = foo(foo)
$ py main.py Traceback (most recent call last): File "main.py", line 9, in <module> foo = foo(foo) TypeError: foo() takes 0 positional arguments but 1 was given
例外になります。
そりゃ当たり前の話でデコレータ用のfoo関数を再度foo関数を定義して上書きで消してしまってるのでfuncを受け取るfoo関数なんて無いよって例外になるわけです。
てかそもそもデコレータと同名の関数定義する時点でおかしいんじゃないの君って言われそうですが、そもそも何でこんなことが気になったのかというと先日の記事でプロパティというのを勉強したんですが、そのコードが
class Foo(): def __init__(self,name): self.__name = name @property def name(self): return self.__name @name.setter def name(self,name): self.__name = name
こうなってるわけなんですよ。@name.setterはnameメソッドと同名なので同じ問題が発生するわけです。
だから以下のように書くと
class Foo(): def __init__(self,name): self.__name = name @property def name(self): return self.__name def name(self,name): self.__name = name name = name.setter(name) # ここで例外
当然例外になるわけです。
ということはPython的にはデコレータと同名の関数を定義することは特に誤った使い方ってわけではないわけですよね。
単純な等価じゃないとしたら内部的にはどう解釈されてるんでしょうか。
よくわからないけど以下のような解釈になってるとか
def foo (func): pass deco_foo = foo def foo (): pass foo = deco_foo(foo) del deco_foo
既存にdeco_fooがないことが前提ですが、これなら等価と言えるのかも。うーんよくわからぬ。
読み取り専用なプロパティを生成する
入門Python3 6.8
プロパティの続きです。
プロパティを定義する時にセッターを用意しなければ読み出し専用のプロパティを生成することができます。
class Foo(): @property def bar(self): return "bar" foo = Foo() print(foo.bar) foo.bar = "new bar" # ここで例外
$ py main.py bar Traceback (most recent call last): File "main.py", line 11, in <module> foo.bar = "new bar" AttributeError: can't set attribute
Pythonのプロパティについて
入門Python3 6.8
インスタンス変数に直接アクセスするのではなくゲッターやセッターを用意してそっちを使うっていうのは他の言語でもよくやりますね。
愚直に実装するなら以下のようになります。
class Foo(): def __init__(self,name): self.__name = name def get_name(self): return self.__name def set_name(self,name): self.__name = name foo = Foo("yamada") print(foo.get_name()) foo.set_name("satou") print(foo.get_name())
$ py main.py yamada satou
get_nameやset_nameを経由することで後から事前処理を入れたりできるのでインスタンス変数を直接アクセスするより仕様変更に強くなります。
でもとはいえやっぱり「foo.get_name()」や「foo.set_name("satou")」より「foo.name」や「foo.name="satou"」と書けた方が楽なんですよねぇ。
それを実現するのがPythonのプロパティです。
先ほどのクラスに一行追加します。
class Foo(): def __init__(self,name): self.__name = name def get_name(self): return self.__name def set_name(self,name): self.__name = name name = property(get_name,set_name) # コレを追加 foo = Foo("yamada") print(foo.name) foo.name = "satou" print(foo.name)
$ py main.py yamada satou
そしてpropertyはデコレータで書くこともできます。
ゲッターには@propertyを、セッターには@変数名.setterを定義します。
class Foo(): def __init__(self,name): self.__name = name @property def name(self): return self.__name @name.setter def name(self,name): self.__name = name foo = Foo("yamada") print(foo.name) foo.name = "satou" print(foo.name)
$ py main.py yamada satou
とっても便利ですね。すごい。