Pythonの詳細な__slots__、プロパティとコードの仕様

この記事は個人のパブリックアカウントから作成されました:TechFlow、オリジナルは簡単ではありません、注意を求めてください


今日は、Pythonトピックに関する11番目の記事です。オブジェクト指向の高度な使用法について話しましょう。

__slots__

githubでいくつかの大牛のコードを読むと、多くの大牛がクラスの先頭に__slots__キーワードを追加することがよくあります。気になる場合は、このキーワードを削除してもう一度実行してみてください。削除しても何も起こらず、すべてが正常に機能しています。

では、この__slots__キーワードは正確に何をするのでしょうか?

主な機能は2つありますが、最初の機能、つまりユーザーの使用制限する機能について説明します

Pythonは非常に柔軟で動的な言語であることは誰もが知っています。Pythonでは他の言語では完全に不寛容に思える多くのことが実現可能です。これはPythonの設計概念でもあります。柔軟性とコードの利便性のために効率が犠牲になります。たとえば、Pythonは動的言語なので、非常に単純な例を見てみましょう。クラスのメンバーは、クラスの作成後に動的に作成することもできます。これは静的言語では絶対に不可能です。クラスの既存のプロパティのみを呼び出すことができ、新しいプロパティ追加することは不可能または困難です。

たとえば、次のコード:

class Exp:
    def __init__(self):
        self.a = None
        self.b = None


if __name__ == "__main__":
    exp = Exp()
    exp.c = 3
    print(exp.c)

Expというクラスを定義し、そのために2つのメンバーaとbを作成しました。しかし、それを使用するときは、メンバーcに値割り当てますExpクラスにメンバーcがないことを確認するために、プログラムはエラーを報告しません。このように実行すると、このインスタンスにcが追加されます。

これは確かに非常に柔軟ですが、一方で、隠れた危険も残します。ユーザーが自由に属性を追加すると、特に複雑なシステムでは、未知の問題が発生する可能性があります。したがって、厳密にするために、ユーザーにそのような動的な変更を行わせたくない場合があります。__slots__はこれを行うために使用されます。

このキーワードを追加すると、結果は異なります。

class Exp:

    __slots__ = ['a', 'b']
    def __init__(self):
        self.a = None
        self.b = None


if __name__ == "__main__":
    exp = Exp()
    exp.c = 3
    print(exp.c)

このコードを実行すると、エラーが発生し、Expオブジェクトにメンバーcがないことを示すメッセージが表示されます。つまり、キーワード__slots__で定義されたメンバーしか使用できません。自由に作成してください。ユーザーの使用が制限されます。

現在、ほとんどの人がこの目的でこのキーワードを使用していますが、Pythonの作成者の本来の意図がこれではないのは残念です。これは、メモリ節約するという__slots__キーワードの2番目の役割について説明します

Pythonの基本的な実装原理を理解すると、有名な__dict__辞書であるPythonのインスタンスごとに辞書作成されることがわかりますもともと存在していなかったメンバーを作成し、そのようなダイナミックな効果をサポートできるのは、背後に辞書があるからこそです。このディクショナリを手動で呼び出してコンテンツを出力できます__slots__キーワードを追加する前の出力は次のようになります。

{'a': None, 'b': None}

しかし、このキーワードを追加すると、エラーが発生し、Expオブジェクトに__dict__メンバーがないことが通知されます。dictを使用してインスタンスを維持すると大量のメモリが消費され、大量の追加データが保存されるため、理由は非常に単純です。__slots__を使用すると、Pythonは維持するインスタンスの辞書を作成しなくなりますが、固定サイズを使用します配列。多くのスペースを節約します。この節約は少しではなく、一般的に半分以上節約できますつまり、パフォーマンスを確保するために、ある程度の柔軟性が犠牲になります。これも__slots__のキーワードデザインの本来の目的ですが、今では多くの人が間違った場所を使用しています。

財産

このキーワードは記事で言及されていますが、記事を書くときに理解が非常に限られているため、説明にいくつかの誤りがあるので、このキーワードの使用についてここで触れますメイクアップとして。

プロパティは、クラス内のいくつかのプロパティの割り当てと取得をバインドするのに役立ちます。つまり、取得と設定です。例を見てみましょう:

class Exp:
    def __init__(self, param):
        self.param = param

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        self._param = value

ここでのプロパティアノテーションは.paramを呼び出すときに実行され、param.setterはparamプロパティに値を割り当てるときに実行されます。そのため、__ init__メソッドで初期化するときに、self._param = paramではなくself.param = paramを使用した理由を不思議に思うかもしれません。これは、前者を実行すると、Python も@paramを呼び出すためです。アノテーションセッターなので、後者のフォームを記述する必要はありません。もちろん、これを書くこともできますが、この2つは完全に同等です。

以前のJavaプログラマーとして、クラスのすべての変数にgetメソッドとsetメソッドを追加することはほぼ政治的に正しいため、特にクラスのすべてのプロパティにプロパティを追加したいと思っています。しかし、これは間違っており、プロパティの追加には非常に時間がかかるため、これを行う必要がない場合は、それを呼び出して割り当てます。必要に応じて、getメソッドとsetメソッドを手動で作成できます。それから問題が来ます、それは標準化のためではないので、なぜプロパティを使うのですか?

答えは簡単です。変数の型確認するためです

Pythonは動的言語であり、暗黙的に型指定されているため、変数を取得したとき、それがどの型であるか、またはユーザーがどの型に割り当てているかはわかりません。したがって、場合によっては、制限を設けて、この変数をこの型にのみ割り当てるようにユーザーに指示する必要があります。そうしないと、エラーが報告されます。プロパティを使用すると、これを簡単に行うことができます。

class Exp:

    def __init__(self, param):
        self.param = param

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        if not isinstance(value, str):
            raise TypeError('Want a string')
        self._param = value

さらに、このプロパティには、関数置き換えるという別の使い方がありますたとえば、次のとおりです。

class Exp:

    def __init__(self, param):
        self.param = param

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        if not isinstance(value, str):
            raise TypeError('Want a string')
        self._param = value

    @property
    def hello(self):
        return 'hello ' + self.param

このようにして、実際には動的計算である関数を呼び出す代わりに.helloを使用できますhelloの結果は保存されておらず、後で呼び出すときに実行されるため、状況によっては非常に便利です。

命名規則

最後に、Pythonオブジェクトの命名規則を見てみましょう。前の記事では、Pythonのパブリックフィールドとプライベートフィールドに違いはないと述べました。すべてのフィールドはパブリックです。つまり、ユーザーはクラス内のすべてのフィールドとメソッド。標準化するために、プログラマはそれを共通にすることに同意し、下線が引かれたすべてのメソッドと変数はプライベートと見なされることを決定しました。それらを呼び出すことができても、通常はそうしません。

したがって、通常は2つのメソッドを記述します。1つはパブリックインターフェイスで、もう1つは内部実装です。これを呼び出すときは、パブリックインターフェイスのみを呼び出し、パブリックインターフェイスは内部実装を呼び出します。これはPythonの規則になっています。これは、内部メソッドを呼び出すときに、いくつかの内部パラメーターを渡すことが多いためです。

簡単な例を見てみましょう:

class ExpA:

    def __init__(self):
        pass

    def public_func(self):
       self._private_func()

    def _private_func(self):
        print('private ExpA')


if __name__ == "__main__":
    exp = ExpA()
    exp.public_func()

_に加えて、2つの下線付きの変数とメソッドがよく見られるので、それらの違いは何ですか?

この質問に答えるために、次の例を見てみましょう。

class ExpA:

    def __init__(self):
        pass

    def public_func(self):
       self.__private_func()

    def __private_func(self):
        print('private ExpA')

class ExpB(ExpA):

    def __init__(self):
        pass

    def public_func(self):
       self.__private_func()

    def __private_func(self):
        print('private ExpB')

if __name__ == "__main__":
    exp = ExpB()
    exp.public_func()
    exp._ExpB__private_func()
    exp._ExpA__private_func()

最終的な出力はどうなりますか?

最初の行の出力がプライベートExpBであることを確認しようとしますが、これは問題ありません。しかし、後者の2つは何ですか?

最後の2つは__private_funcですが、システムが自動的に名前を変更します。Python が2つの下線付きのメソッドをサブクラスで上書きすることを禁止しているため、名前を変更する理由も非常に単純です。これらはどちらもプライベートメソッドとプロパティと見なされますが、1つのアンダースコアではサブクラスがそれらをオーバーライドできますが、2つのアンダースコアではオーバーライドできません。したがって、開発中にメソッドの1つがサブクラスでカバーされないことを希望する場合は、2つのアンダースコアを追加する必要があります。

最後に、小さな問題を見てみましょう。C ++では、変数名がシステムキーワードと競合する場合、区別として変数の前に_を追加することがよくあります。しかし、Pythonではアンダースコアに意味が与えられているため、これを行うことはできません。変数が競合する場合はどうすればよいですか?答えも非常に簡単です。lambda_のように、アンダースコアを後ろに追加できます。

まとめ

今日のコンテンツ、主にクラスでの__slots__、プロパティ、およびアンダースコアの使用を思い出してください。これら3つはすべてPythonのオブジェクト指向一般的に使用されている知識です。これらを理解することで、より標準化されたコードを記述できるだけでなく、他の大きな牛のソースコードを理解するのにも役立つため、非常に必要です。

今日の記事はそれだけです。もし何かやりがいを感じた場合は、フォローまたは再投稿しください。あなたの努力は私にとって非常に重要です。

ここに画像の説明を挿入

117件の元の記事を公開 61のような 10,000以上の訪問

おすすめ

転載: blog.csdn.net/TechFlow/article/details/105666572