何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『Effective Python』Item 27: プライベート属性よりもパブリック属性を使おう

『Effective Python』の続き。Pythonにプライベートなどない。 www.effectivepython.com

Pythonには、パブリックとプライベートという2つのクラス属性があります。(超訳)」からこのItemは始まるが、基本的にはパブリックを使いましょうという話。 プライベートなメソッドや属性には名前の接頭辞にアンダースコア__を2つ以上つける。 プライベートメンバはクラス内で__name名から_class名__name名に置換されるだけなのでこのカラクリを知っていると外から簡単にアクセスできてしまう。 また、置換方法にクラス名が含まれているので、このカラクリを利用したままリファクタリングなどでクラスの階層構造を変更してしまうとAttributeErrorが起きてしまう。

例えば、

class MyClass(object):
    def __init__(self, value):
        self.__value = value

    def get_value(self):
        return str(self.__value)

class MyIntegerSubclass(MyClass):
    def get_value(self):
        return int(self._MyClass__value)

foo = MyIntegerSubclass(5)
assert foo.get_value() == 5

という構造だったのに、

class MyBaseClass(object):
    def __init__(self, value):
        self.__value = value

    def get_value(self):
        return self.__value

class MyClass(MyBaseClass):
    def get_value(self):
        return str(super().get_value())

class MyIntegerSubclass(MyClass):
    def get_value(self):
        return int(self._MyClass__value)

foo = MyIntegerSubclass(5)
foo.get_value()

のようにすると破綻してしまう。

そこで、アンダースコアを1つだけつけて(筆者はプロテクト属性と呼んでいる)、内部で利用していることを示して、さらにその旨をドキュメントとして残すこと、と提案している。

本当にプライベート属性を使うタイミングとは、親クラスと子クラスで属性の名前が衝突する可能性があるとき、としている。

class ApiClass(object):
    def __init__(self):
        self.__value = 5

    def get(self):
        return self.__value

class Child(ApiClass):
    def __init__(self):
        super().__init__()
        self._value = 'hello'  # OK!

a = Child()
print(a.get(), 'and', a._value, 'are different')

このItemは、PEP 8の「継承の設計」 はじめに — pep8-ja 1.0 ドキュメント に書いてある内容を詳しく迫ったItemと見なしてよいだろうか。