オブジェクトからクラスオブジェクトを得る

特殊メソッドである__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>

うまくいってますね。

特殊メソッド

入門Python3 6.12


クラスには予め定義されている特殊なメソッドがたくさんあります。「__メソッド名__」というやつです。

コンストラクタを実現する__init__も特殊メソッドのひとつです。

かなりの種類があるのでとりあえず今はドキュメントにざっくり目を通すくらいでまたいずれ個別の記事でどんな機能があるのか書いていきます。

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


とっても便利ですね。すごい。