0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Python] クラス メモ

Last updated at Posted at 2020-11-28

単語帳=毎回検索するのが面倒なので転載多め.元URLあり.
主に自作クラス関連.

クラスの定義

抽象クラス

テンプレート

from abc import ABCMeta, abstractmethod

# 抽象クラス
class AbstClass(metaclass=ABCMeta):

    @abstractmethod
    def my_abstract_method(self):
        pass

    @property
    @abstractmethod
    def my_abstract_property(self):
        pass

[Qiita@kaneshin: PythonのABC - 抽象クラスとダック・タイピング]
(https://qiita.com/kaneshin/items/269bc5f156d86f8a91c4)
[stackoverflow: How to create abstract properties in python abstract classes]
(https://stackoverflow.com/questions/5960337/how-to-create-abstract-properties-in-python-abstract-classes)

※親クラス (含抽象クラス) ではメソッドの引数の個数などを拘束できない
(オーバーライドすると,サブクラスでの定義が優先される)

from abc import ABCMeta, abstractmethod

class AbstClass(metaclass=ABCMeta):
    @abstractmethod
    def mymethod(self, arg1, arg2):
        pass

class SuperClass:
    def mymethod(self, arg1, arg2):
        pass

class MyClass(AbstClass):
# class MyClass(SuperClass): 同じく拘束できない
    def __init__(self):
        pass

    def mymethod(self, arg1):
        print(arg1)


obj = MyClass()
obj.mymethod(1)
obj.mymethod(1, 2) # TypeError: mymethod() takes 2 positional arguments but 3 were given

[stackoverflow: Abstract classes with varying amounts of parameters]
(https://stackoverflow.com/questions/42778784/abstract-classes-with-varying-amounts-of-parameters)

@abstractmethodの'__' (アンダースコア2つ) で始まるメソッドをオーバーライドするには

通常の方法ではオーバーライドできない.
(抽象クラスの継承時にこの問題が起きるとCan't instantiate abstract class … with abstract methodsのエラー.)
_親クラス名__メソッド名でオーバーライド.

@abstractmethodを使わない場合,この問題は起こらない:

class BaseClass0:

    def __init__(self): pass

    def mymethod0(self):
        print('BaseClass0.mymethod0')

    def _mymethod1(self):
        print('BaseClass0._mymethod1')

    def __mymethod2(self):
        print('BaseClass0.__mymethod2')

    def mymethod2(self):
        print('BaseClass0.mymethod2')
        self.__mymethod2()

class BaseClass1(metaclass=ABCMeta):

    def __init__(self): pass

    def mymethod0(self):
        print('BaseClass1.mymethod0')

    def _mymethod1(self):
        print('BaseClass1._mymethod1')

    def __mymethod2(self):
        print('BaseClass1.__mymethod2')

    def mymethod2(self):
        print('BaseClass1.mymethod2')
        self.__mymethod2()

class SubClass0(BaseClass0):

    def __init__(self): pass

    def mymethod0(self):
        print('SubClass0.mymethod0')

    def _mymethod1(self):
        print('SubClass0._mymethod1')

    def __mymethod2(self):
        print('SubClass0.__mymethod2')

    def mymethod2(self):
        print('SubClass0.mymethod2')
        self.__mymethod2()


class SubClass1(BaseClass1):

    def __init__(self): pass

    def mymethod0(self):
        print('SubClass1.mymethod0')

    def _mymethod1(self):
        print('SubClass1._mymethod1')

    def __mymethod2(self):
        print('SubClass1.__mymethod2')

    def mymethod2(self):
        print('SubClass1.mymethod2')
        self.__mymethod2()

for obj in [BaseClass0(), SubClass0(), BaseClass1(), SubClass1()]:
    obj.mymethod0()
    obj._mymethod1()
    obj.mymethod2()

しかし,親クラスに@abstractmethodをつけた途端,インスタンスの生成も出来なくなる:

class BaseClass2(metaclass=ABCMeta):

    @abstractmethod
    def mymethod0(self): pass

    @abstractmethod
    def _mymethod1(self): pass

    @abstractmethod
    def __mymethod2(self): pass

    @abstractmethod
    def mymethod2(self): pass

class SubClass2(BaseClass2):

    def __init__(self): pass

    def mymethod0(self):
        print('SubClass2.mymethod0')

    def _mymethod1(self):
        print('SubClass2._mymethod1')

    def __mymethod2(self):
        print('SubClass2.__mymethod2')

    def mymethod2(self):
        print('SubClass2.mymethod2')
        self.__mymethod2()

obj = SubClass2() # TypeError: Can't instantiate abstract class SubClass2 with abstract methods _BaseClass2__mymethod2

これを解決するためには,

  • __mymethod2の代わりに_BaseClass2__mymethod2をオーバーライド
  • self.__mymethod2()では呼び出しが出来ないので,これをself._BaseClass2__mymethod2()に変更

の2点を行う:

class SubClass2_ver2(BaseClass2):

    def __init__(self): pass

    def mymethod0(self):
        print('SubClass2.mymethod0')

    def _mymethod1(self):
        print('SubClass2._mymethod1')

    def _BaseClass2__mymethod2(self):
        print('SubClass2.__mymethod2')

    def mymethod2(self):
        print('SubClass2.mymethod2')
        self._BaseClass2__mymethod2()
        # self.__mymethod2() AttributeError: 'SubClass2_ver2' object has no attribute '_SubClass2_ver2__mymethod2'

obj = SubClass2_ver2()
obj.mymethod0()
obj._mymethod1()
obj.mymethod2()

[stackoverflow: Can't instantiate abstract class … with abstract methods]
(https://stackoverflow.com/a/31458576)

class MyClass(object):(object)は省略可能 (Python3のみ)

元々クラスには2種類 (Python2.1以前と以降) の2種類があり,
Python2だと(object)

  • 明記: 新スタイル
  • 省略: 旧スタイル,@propertyのsetterが動かなかったり

として区別される.Python3では新スタイルに統一されている.

[Qiita@alt-core: Python2.7で class C: と class C(object): の違いに大ハマりした話]
(https://qiita.com/alt-core/items/d20e9a1864ff3a35946e)

クラスのキャスト

[民主主義に乾杯: クラスをキャストする]
(https://python.ms/cast/)

メソッド

特殊メソッド

一覧:
[公式: 3. Data model > 3.3. Special method names]
(https://docs.python.org/3/reference/datamodel.html#special-method-names)

__contains(self, item)__: item in self
[stackoverflow: Override Python's 'in' operator?]
(https://stackoverflow.com/questions/2217001/override-pythons-in-operator)

__getattr____getattribute__

  • __getattr__: 未定義のメンバーにアクセスするとき
    • 明示的に定義していない特殊メソッドにアクセスしたときも
  • __getattribute__: 未定義・定義済み関わらず、すべてのメンバーアクセスで

[SDN開発エンジニアを目指した活動ブログ: Python勉強メモ:特殊メソッド__getattribute__とは?]
(http://ttsubo.hatenablog.com/entry/2014/05/04/215202)
[Qiita@fumitoh: ドットによるオブジェクトの属性の取得をカスタマイズするための__getattr__と__getattribute__]
(https://qiita.com/fumitoh/items/b090ae6ccd52e6a88d9e)

__call__の使い方

[Qiita@ganariya: 【Python中級者への道】__call__でクラスインスタンスを関数のように呼び出す]
(https://qiita.com/ganariya/items/27ade6b149728f6aae88)

__getattr__の副作用

1つ1つ属性やゲッターを定義しなくても良くなる半面,副作用もある:

  • pickle化できなくなる
  • copy.copycopy.deepcopyが動作しなくなる

解決策としては,

  • __getattr__内で特殊メソッドを除外する (色々な問題に効果あり)
  • 各問題に対して処方箋

pickle化できなくなる例

pickle化できるかどうかの確認方法
import pickle
def is_picklable(obj):
    try:
        pickle.dumps(obj)
    except:
        return False
    return True

[stackoverflow: How to check if an object is pickleable]
(https://stackoverflow.com/questions/17872056/how-to-check-if-an-object-is-pickleable)
ではpickle.PicklingErrorという例外をキャッチしているが,今回の例では他の例外 ('str' object is not callable) も発生する.

基本ケース
class MyClass:
    def __init__(self, val):
        self.val = val

obj = MyClass(1)
print(is_picklable(obj)) # True
__getattr__を追加で定義
class MyClassGA:
    def __init__(self, val):
        self.val = val

    def __getattr__(self, attr):
        return '{0} {1}'.format(attr, self.val)

obj = MyClassGA(1)
print(obj.foo) # foo1
print(is_picklable(obj)) # False
解決策1: __getattr__内で特殊メソッドを除外する
class MyClassPickleble:
    def __init__(self, val):
        self.val = val

    def __getattr__(self, attr):
        if attr.startswith('__') and attr.endswith('__'):
            raise AttributeError
        return '{0} {1}'.format(attr, self.val)

obj = MyClassPickleble(1)
print(obj.foo) # foo 1
print(is_picklable(obj)) # True
解決策2: __getstate____setstate__を追加で定義
class MyClassPickleble2:
    def __init__(self, val):
        self.val = val

    def __getattr__(self, attr):
        return '{0} {1}'.format(attr, self.val)

    def __getstate__(self):
        return self.__dict__
    
    def __setstate__(self, d):
        return self.__dict__.update(d)

obj = MyClassPickleble2(1)
print(obj.foo) # foo
print(is_picklable(obj)) # True

[stackoverflow: Pickle of object with __getattr__ method in Python returns TypeError, object not callable]
(https://stackoverflow.com/questions/50888391/pickle-of-object-with-getattr-method-in-python-returns-typeerror-object-no)
[銀月の符号: pickle できない属性をもつクラスへの対策例]
(https://fgshun.hatenablog.com/entry/20080812/1218559308)
[Python: Python deepcopy with custom __getattr__ and __setattr__]
(https://stackoverflow.com/questions/40583131/python-deepcopy-with-custom-getattr-and-setattr)

文字列による動的なメソッドの呼び出し

getattr(オブジェクト, 'メソッド名')(引数)でOK.

adict = {'a': 1}
getattr(adict, 'get')('a', None)

[やつらかやつらだ: Pythonで動的にメソッドを呼ぶ]
(https://themorthem.hatenadiary.org/entry/20110209/1297264984)

クラス/インスタンスに後からメソッドを追加する

def fooFighters(self):
    print "fooFighters"

A.fooFighters = fooFighters # クラス自体にfooFightersメソッドを追加
a.barFighters = types.MethodType(barFighters, a) # インスタンスだけにbarFightersメソッドを追加

a.barFighters = barFighters # これではselfが使えない (単なるプロパティとして追加されてしまう→メソッドだとは思われない)

[stackoverflow: Adding a Method to an Existing Object Instance]
(https://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object-instance)
[Ian Lewis: Pythonでメソッドをクラスまたはインスタンスに動的に追加する]
(https://www.ianlewis.org/jp/python-add-class-method)

インスタンスへの追加にsetattrを使う時

methods = [method0, method1]
for method in methods:
    setattr(obj, method.__name__, types.MethodType(method, obj))

※これらの方法は特殊メソッドには適用不可
e.g. __add__をあるobjに追加すると,obj.__add__(item)はできてもobj + itemは不可
上記はインスタンスにメソッドを追加するが,特殊メソッドを追加・上書きするにはクラスでの定義自体を変更する必要がある.
[stackoverflow: Setting special methods using setattr()]
(https://stackoverflow.com/questions/10484304/setting-special-methods-using-setattr)
[stackoverflow: Overriding special methods on an instance]
(https://stackoverflow.com/questions/10376604/overriding-special-methods-on-an-instance)

イテレータ

[Qiita@tchnkmr: [Python] イテレータを実装する]
(https://qiita.com/tchnkmr/items/e740173d7400f8672d75)
[くろのて: [Python] 部屋とYシャツとイテレータとジェネレータと私]
(https://note.crohaco.net/2016/python-iterator-generator-and-tshirt-me/)
[デジタリ: PythonのIteratorのちょっと詳しい説明]
(https://digitali.biz/python%E3%81%AEiterator%E3%81%AE%E3%81%A1%E3%82%87%E3%81%A3%E3%81%A8%E8%A9%B3%E3%81%97%E3%81%84%E8%AA%AC%E6%98%8E/)

list()__iter__の全要素を取得可能

class MyClass:

    def __init__(self): pass

    def __iter__(self):
        for i in range(3):
            yield i

obj = MyClass()
for i in obj:
    print(i)
# 0
# 1
# 2

obj = MyClass()
print(list(obj)) # [0, 1, 2]

__call__経由で引数を渡しても期待通りの動作になる.

class MyClass:

    def __init__(self, n=None):
        self.n = n

    def __iter__(self):
        for i in range(self.n):
            yield i
    
    def __call__(self, n):
        self.n = n
        return self

obj = MyClass()
for i in obj(3):
    print(i)
# 0
# 1
# 2

obj = MyClass()
print(list(obj(5))) # [0, 1, 2, 3, 4]

デコレータ

classmethod, staticmethod

DjangoBrothers: 【Python】インスタンスメソッド、staticmethod、classmethodの違いと使い方

property

Effective Pythonに

その他

getattr(obj, attr_name, default_value)

attributeが存在しないときにはデフォルト値を返す

import datetime as dt
a = dt.date(2000, 1, 1)
print(a.year)
print(getattr(a, 'years', 'not found'))

あるオブジェクトがiterableか確認

from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower
isinstance(obj, Iterable)

[stackoverflow: In Python, how do I determine if an object is iterable?]
(https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable)

時々紹介されているhasattr(obj, '__iter__')は必ずしも正しくない.
イテレータは__iter__ではなく__getitem__でも動作するからである.
(但し,__getitem__はint以外でも動作するため,__getitem__があるからiterableだ,というのは誤り.)

[民主主義に乾杯: イテラブル, iterable ってなに?]
(https://python.ms/iterable/)

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?