apeescape2.com
  • メイン
  • 仕事の未来
  • 収益と成長
  • 財務プロセス
  • アジャイルタレント
技術

Pythonクラス属性:過度に徹底したガイド

持っていた プログラミングインタビュー 最近、私たちが使用した電話スクリーン コラボレーティブテキストエディタ 。

私は実装するように頼まれました 特定のAPI 、およびでそうすることを選択しました Python 。問題の記述を抽象化して、インスタンスがいくつかのdataを格納するクラスが必要だったとしましょう。といくつかのother_data。

深呼吸してタイピングを始めました。数行後、私は次のようなものになりました。



class Service(object): data = [] def __init__(self, other_data): self.other_data = other_data ...

私のインタビュアーは私を止めました:

  • インタビュアー:「その行:data = []。それは有効なPythonだとは思いませんか?」
  • 私:「そうだと確信しています。インスタンス属性のデフォルト値を設定しているだけです。」
  • インタビュアー:「そのコードはいつ実行されますか?」
  • 私:「よくわかりません。混乱を避けるために修正します。」

参考までに、そして私が何をしようとしていたかを知るために、コードを修正した方法は次のとおりです。

不和2017のための音楽ボットの作り方
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ...

結局のところ、私たちは両方とも間違っていました。本当の答えは、Pythonクラス属性とPythonインスタンス属性の違いを理解することにあります。

Pythonクラス属性とPythonインスタンス属性

注:クラス属性のエキスパートハンドルがある場合は、スキップして先に進むことができます ユースケース 。

Pythonクラス属性

私のインタビュアーは、上記のコードが間違っていました です 構文的に有効です。

インスタンス属性に「デフォルト値」が設定されていないという点で私も間違っていました。代わりに、dataを定義していますとして クラス 値が[]の属性。

私の経験では、Pythonクラス属性は次のようなトピックです。 たくさんの 人々は知っています 何か についてですが、完全に理解している人はほとんどいません。

Pythonクラス変数とインスタンス変数:違いは何ですか?

Pythonクラス属性は、クラスの属性ではなく、クラスの属性(循環、私は知っています)です。 インスタンス クラスの。

Pythonクラスの例を使用して、違いを説明しましょう。ここで、class_varはクラス属性であり、i_varインスタンス属性です:

class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var

クラスのすべてのインスタンスはclass_varにアクセスでき、のプロパティとしてもアクセスできることに注意してください。 クラス自体 :

foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1

JavaまたはC ++プログラマーの場合、クラス属性は静的メンバーと似ていますが、同一ではありません。それらの違いについては後で説明します。

クラスとインスタンスの名前空間

ここで何が起こっているのかを理解するために、簡単に話しましょう Python名前空間 。

に 名前空間 は名前からオブジェクトへのマッピングであり、異なる名前空間の名前間には関係がないという特性があります。これらは通常Python辞書として実装されますが、これは抽象化されています。

コンテキストに応じて、ドット構文(object.name_from_objects_namespaceなど)またはローカル変数(object_from_namespaceなど)を使用して名前空間にアクセスする必要がある場合があります。具体的な例として:

class MyClass(object): ## No need for dot syntax class_var = 1 def __init__(self, i_var): self.i_var = i_var ## Need dot syntax as we've left scope of class namespace MyClass.class_var ## 1

Pythonクラス そして クラスのインスタンスにはそれぞれ、で表される独自の異なる名前空間があります。 事前定義された属性 MyClass.__dict__それぞれinstance_of_MyClass.__dict__。

node.jsは何に使用されますか

クラスのインスタンスから属性にアクセスしようとすると、最初にそのインスタンスが調べられます インスタンス 名前空間。属性が見つかると、関連する値を返します。そうでない場合は、 その後 に見える クラス 名前空間と属性を返します(存在する場合はエラーをスローします)。例えば:

foo = MyClass(2) ## Finds i_var in foo's instance namespace foo.i_var ## 2 ## Doesn't find class_var in instance namespace… ## So look's in class namespace (MyClass.__dict__) foo.class_var ## 1

インスタンス名前空間はクラス名前空間よりも優先されます。両方に同じ名前の属性がある場合、インスタンス名前空間が最初にチェックされ、その値が返されます。これがコードの簡略版です( ソース )属性ルックアップの場合:

def instlookup(inst, name): ## simplified algorithm... if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name]

そして、視覚的な形で:

視覚的な形式での属性ルックアップ

クラス属性が割り当てを処理する方法

これを念頭に置いて、Pythonクラス属性が割り当てを処理する方法を理解できます。

  • クラスにアクセスしてクラス属性を設定すると、次の値が上書きされます。 すべて インスタンス。例えば:

    foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2

    名前空間レベルで… MyClass.__dict__['class_var'] = 2を設定しています。 (注:これ 正確なコードではありません (これはsetattr(MyClass, 'class_var', 2)になります)as __dict__を返します dictproxy 、直接割り当てを防ぐ不変のラッパーですが、デモンストレーションのために役立ちます)。次に、foo.class_varにアクセスすると、class_varクラス名前空間に新しい値があるため、2が返されます。

  • インスタンスにアクセスしてPaythonクラス変数を設定すると、値が上書きされます そのインスタンスのみ 。これは基本的にクラス変数をオーバーライドし、直感的に利用可能なインスタンス変数に変換します。 そのインスタンスのみ 。例えば:

    foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1

    名前空間レベルで… class_varを追加します属性はfoo.__dict__であるため、foo.class_varを検索すると、2が返されます。一方、MyClassの他のインスタンス意志 ない 持っているclass_varインスタンスの名前空間で、class_varを見つけ続けます。 MyClass.__dict__でしたがって、1を返します。

可変性

クイズの質問:クラス属性に 可変タイプ ?特定のインスタンスを介してクラス属性にアクセスすることで、クラス属性を操作(切断?)できます。 すべてのインスタンスがアクセスしている参照オブジェクトを操作する (によって指摘されたように ティモシーワイズマン )。

これは例によって最もよく示されます。 Serviceに戻りましょう以前に定義し、クラス変数の使用が将来の問題にどのようにつながる可能性があるかを確認しました。

class Service(object): data = [] def __init__(self, other_data): self.other_data = other_data ...

私の目標は、[]のデフォルト値として、およびdataのインスタンスごとに、空のリスト(Service)を設定することでした。持つため 独自のデータ これは、インスタンスごとに時間の経過とともに変更されます。ただし、この場合、次の動作が発生します(Serviceが引数other_dataを取ることを思い出してください。これは、この例では任意です)。

s1 = Service(['a', 'b']) s2 = Service(['c', 'd']) s1.data.append(1) s1.data ## [1] s2.data ## [1] s2.data.append(2) s1.data ## [1, 2] s2.data ## [1, 2]

これは良くありません。1つのインスタンスを介してクラス変数を変更すると、他のすべてのインスタンスでも変更されます。

名前空間レベルで… ServiceのすべてのインスタンスService.__dict__の同じリストにアクセスして変更しています自分で作ることなくdataインスタンス名前空間の属性。

割り当てを使用してこれを回避できます。つまり、リストの可変性を利用する代わりに、Serviceを割り当てることができます。次のように、オブジェクトに独自のリストを設定します。

s1 = Service(['a', 'b']) s2 = Service(['c', 'd']) s1.data = [1] s2.data = [2] s1.data ## [1] s2.data ## [2]

この場合、s1.__dict__['data'] = [1]を追加しているので、元のService.__dict__['data']変更されません。

残念ながら、これにはServiceが必要です。ユーザーはその変数についての深い知識を持っており、間違いを犯しがちです。ある意味では、原因ではなく症状に対処することになります。構造上正しいものが望ましいです。

私の個人的な解決策:クラス変数を使用して、Pythonインスタンス変数になる予定の変数にデフォルト値を割り当てる場合は、 可変値を使用しないでください 。この場合、ServiceのすべてのインスタンスオーバーライドしようとしていたService.data最終的には独自のインスタンス属性を持つため、デフォルトとして空のリストを使用すると、見落とされがちな小さなバグが発生しました。上記の代わりに、次のいずれかを行うことができます。

  1. はじめに示したように、インスタンス属性に完全に固執しました。
  2. 空のリスト(可変値)を「デフォルト」として使用することは避けました。

    class Service(object): data = None def __init__(self, other_data): self.other_data = other_data ...

    もちろん、Noneを処理する必要があります適切なケースですが、それは少額の支払いです。

では、いつPythonクラス属性を使用する必要がありますか?

クラス属性には注意が必要ですが、それらが役立つ場合をいくつか見てみましょう。

  1. 定数の保存 。クラス属性はクラス自体の属性としてアクセスできるため、クラス全体のクラス固有の定数を格納するために使用すると便利なことがよくあります。例えば:

    class Circle(object): pi = 3.14159 def __init__(self, radius): self.radius = radius def area(self): return Circle.pi * self.radius * self.radius Circle.pi ## 3.14159 c = Circle(10) c.pi ## 3.14159 c.area() ## 314.159
  2. デフォルト値の定義 。簡単な例として、制限付きリスト(つまり、特定の数以下の要素しか保持できないリスト)を作成し、デフォルトの上限を10アイテムにすることを選択できます。

    class MyClass(object): limit = 10 def __init__(self): self.data = [] def item(self, i): return self.data[i] def add(self, e): if len(self.data) >= self.limit: raise Exception('Too many elements') self.data.append(e) MyClass.limit ## 10

    次に、インスタンスのlimitに割り当てることで、独自の特定の制限を持つインスタンスを作成することもできます。属性。

    foo = MyClass() foo.limit = 50 ## foo can now hold 50 elements—other instances can hold 10

    これは、MyClassの典型的なインスタンスが必要な場合にのみ意味があります。 10個以下の要素を保持するために、すべてのインスタンスに異なる制限を与える場合は、limitインスタンス変数である必要があります。 (ただし、デフォルトとして可変値を使用する場合は注意してください。)

  3. 特定のクラスのすべてのインスタンスにわたるすべてのデータの追跡 。これは一種の特定のものですが、特定のクラスの既存のすべてのインスタンスに関連するデータにアクセスしたいというシナリオを見ることができました。

    シナリオをより具体的にするために、Personがあるとします。クラス、そしてすべての人がnameを持っています。使用されたすべての名前を追跡したいと思います。 1つのアプローチは ガベージコレクタのオブジェクトのリストを繰り返し処理します 、ただし、クラス変数を使用する方が簡単です。

    この場合、namesであることに注意してください。クラス変数としてのみアクセスされるため、可変のデフォルトが受け入れられます。

    class Person(object): all_names = [] def __init__(self, name): self.name = name Person.all_names.append(name) joe = Person('Joe') bob = Person('Bob') print Person.all_names ## ['Joe', 'Bob']

    このデザインパターンを使用して、関連するデータだけでなく、特定のクラスの既存のすべてのインスタンスを追跡することもできます。

    class Person(object): all_people = [] def __init__(self, name): self.name = name Person.all_people.append(self) joe = Person('Joe') bob = Person('Bob') print Person.all_people ## [, ]
  4. パフォーマンス (一種の…以下を参照)。

関連: ApeeScape開発者によるPythonのベストプラクティスとヒント

フードの下

注意: このレベルでのパフォーマンスが心配な場合は、最初からPythonを使用したくない場合があります。違いは、10分の1ミリ秒のオーダーになるためですが、それでも少し調べてみるのは楽しいです。説明のために。

クラスの名前空間は、クラスの定義時に作成および入力されることを思い出してください。つまり、割り当てを1つだけ行うということです。 これまで -特定のクラス変数に対して、新しいインスタンスが作成されるたびにインスタンス変数を割り当てる必要があります。例を見てみましょう。

def called_class(): print 'Class assignment' return 2 class Bar(object): y = called_class() def __init__(self, x): self.x = x ## 'Class assignment' def called_instance(): print 'Instance assignment' return 2 class Foo(object): def __init__(self, x): self.y = called_instance() self.x = x Bar(1) Bar(2) Foo(1) ## 'Instance assignment' Foo(2) ## 'Instance assignment'

Bar.yに割り当てます一度だけですがinstance_of_Foo.y __init__へのすべての呼び出しで。

考えられるすべての組み合わせを視覚化するために使用されるツール

さらなる証拠として、 Python逆アセンブラ :

import dis class Bar(object): y = 2 def __init__(self, x): self.x = x class Foo(object): def __init__(self, x): self.y = 2 self.x = x dis.dis(Bar) ## Disassembly of __init__: ## 7 0 LOAD_FAST 1 (x) ## 3 LOAD_FAST 0 (self) ## 6 STORE_ATTR 0 (x) ## 9 LOAD_CONST 0 (None) ## 12 RETURN_VALUE dis.dis(Foo) ## Disassembly of __init__: ## 11 0 LOAD_CONST 1 (2) ## 3 LOAD_FAST 0 (self) ## 6 STORE_ATTR 0 (y) ## 12 9 LOAD_FAST 1 (x) ## 12 LOAD_FAST 0 (self) ## 15 STORE_ATTR 1 (x) ## 18 LOAD_CONST 0 (None) ## 21 RETURN_VALUE

バイトコードを見ると、Foo.__init__であることが再び明らかです。 Bar.__init__が、2つの割り当てを行う必要があります1つだけ行います。

実際には、このゲインは実際にはどのように見えますか?私は、タイミングテストがしばしば制御できない要因に大きく依存しており、それらの間の違いを正確に説明するのが難しいことを最初に認めます。

ただし、これらの小さなスニペット(Pythonで実行) timeit モジュール)クラス変数とインスタンス変数の違いを説明するのに役立つので、とにかくそれらを含めました。

c法人とsの違い

注:私はOS X10.8.5とPython2.7.2を搭載したMacBookProを使用しています。

初期化

10000000 calls to `Bar(2)`: 4.940s 10000000 calls to `Foo(2)`: 6.043s

Barの初期化1秒以上速いので、ここでの違いは統計的に有意であるように見えます。

では、なぜこれが当てはまるのでしょうか。 1 投機的 説明:Foo.__init__で2つの割り当てを行いますが、Bar.__init__では1つだけ割り当てます。

割り当て

10000000 calls to `Bar(2).y = 15`: 6.232s 10000000 calls to `Foo(2).y = 15`: 6.855s 10000000 `Bar` assignments: 6.232s - 4.940s = 1.292s 10000000 `Foo` assignments: 6.855s - 6.043s = 0.812s

注:各トライアルでセットアップコードを再実行する方法はありません。 timeit 、したがって、試行時に変数を再初期化する必要があります。時間の2行目は、以前に計算された初期化時間を差し引いた上記の時間を表します。

上記から、Fooのようになります。 Barの約60%しかかかりません割り当てを処理します。

なぜそうなのですか? 1 投機的 説明:Bar(2).yに割り当てるとき、最初にインスタンスの名前空間(Bar(2).__dict__[y])を調べ、yを見つけられず、次にクラスの名前空間(Bar.__dict__[y])を調べます。 、次に適切な割り当てを行います。 Foo(2).yに割り当てると、インスタンスの名前空間(Foo(2).__dict__[y])にすぐに割り当てるので、半分の数のルックアップを実行します。

要約すると、これらのパフォーマンスの向上は実際には重要ではありませんが、これらのテストは概念レベルで興味深いものです。どちらかといえば、これらの違いがクラス変数とインスタンス変数の機械的な違いを説明するのに役立つことを願っています。

結論として

クラス属性はPythonでは十分に活用されていないようです。多くのプログラマーは、彼らがどのように機能し、なぜ彼らが役立つのかについて異なる印象を持っています。

私の見解:Pythonクラス変数は、優れたコードの学校内でその位置を占めています。注意して使用すると、物事を簡素化し、読みやすさを向上させることができます。しかし、不注意に特定のクラスに投げ込まれると、彼らは必ずあなたをつまずかせます。

付録 :プライベートインスタンス変数

含めたかったのですが、自然な入り口がありませんでした…

Pythonにはありません 民間 いわば変数ですが、クラスとインスタンスの命名の間のもう1つの興味深い関係には、名前のマングリングがあります。

Pythonスタイルガイドでは、疑似プライベート変数の前に二重アンダースコア「__」を付ける必要があると言われています。これは、変数が非公開で扱われることを意図していることを他の人に示すだけでなく、ある種の変数へのアクセスを防ぐ方法でもあります。これが私が意味することです:

class Bar(object): def __init__(self): self.__zap = 1 a = Bar() a.__zap ## Traceback (most recent call last): ## File '', line 1, in ## AttributeError: 'Bar' object has no attribute '__baz' ## Hmm. So what's in the namespace? a.__dict__ {'_Bar__zap': 1} a._Bar__zap ## 1

それを見てください:インスタンス属性__zap _Bar__zapを生成するために、クラス名のプレフィックスが自動的に付けられます。

a._Bar__zapを使用して設定および取得可能ですが、この名前マングリングは、「プライベート」変数を作成する手段です。 そして 他の人が偶然または無知によってそれにアクセスすることから。

編集:Pedro Werneckが親切に指摘したように、この動作は主にサブクラス化を支援することを目的としています。の中に PEP8スタイルガイド 、彼らはそれを2つの目的に役立つと考えています:(1)サブクラスが特定の属性にアクセスするのを防ぐこと、および(2)これらのサブクラスで名前空間の衝突を防ぐこと。変数のマングリングは便利ですが、Javaに存在するような、官民の区別を前提としたコードを書くための招待と見なされるべきではありません。

関連: より高度になる:Pythonプログラマーが犯す最も一般的な10の間違いを回避する

基本を理解する

Python名前空間とは何ですか?

名前が示すように、Python名前空間は名前からオブジェクトへのマッピングであり、異なる名前空間の名前間には関係がないという特性があります。名前空間は通常、Python辞書として実装されますが、これは抽象化されています。

Pythonクラスメソッドとインスタンスメソッド:違いは何ですか?

Pythonでは、クラスメソッドは、クラスをコンテキストとして使用して呼び出されるメソッドです。これは、他のプログラミング言語では静的メソッドとしてよく知られています。一方、インスタンスメソッドは、インスタンスをコンテキストとして使用して呼び出されます。

インスタンス属性とクラス属性の両方が定義されている場合はどうなりますか?

その場合、インスタンスの名前空間がクラスの名前空間よりも優先されます。両方に同じ名前の属性がある場合、インスタンスの名前空間が最初にチェックされ、その値が返されます。

きれいなアプリのデザインに対する暴言

Uxデザイン

きれいなアプリのデザインに対する暴言
リモートワークを簡単に移行する方法

リモートワークを簡単に移行する方法

デザイナーライフ

人気の投稿
シニアコーポレートカウンセル
シニアコーポレートカウンセル
エンタープライズクライアントサービスディレクター、産業用製品およびサービス
エンタープライズクライアントサービスディレクター、産業用製品およびサービス
フリーランスのファイナンスコンサルタントが大企業をどのように打ち負かしているか
フリーランスのファイナンスコンサルタントが大企業をどのように打ち負かしているか
あなたのスキルを披露する–ポートフォリオを作成する方法
あなたのスキルを披露する–ポートフォリオを作成する方法
Reactフックをテストするための完全ガイド
Reactフックをテストするための完全ガイド
 
かんばんとTrelloを使用してソフトウェア開発を管理するための初心者向けガイド
かんばんとTrelloを使用してソフトウェア開発を管理するための初心者向けガイド
生存のための再編成:シナリオの構築
生存のための再編成:シナリオの構築
Android開発のためのReactNativeに飛び込む
Android開発のためのReactNativeに飛び込む
安全で健全–パスワードUXへのアプローチ方法
安全で健全–パスワードUXへのアプローチ方法
.NETプロジェクトをブートストラップして作成する方法
.NETプロジェクトをブートストラップして作成する方法
人気の投稿
  • C ++のコーディング方法
  • データ視覚化ソフトウェアとは
  • 法人LLCとは
  • 最高財務責任者の役割と責任
  • VisualStudioユニットテストフレームワーク
  • .netオープンソースです
カテゴリー
ライフスタイル プロセスとツール Uiデザイン リモートの台頭 収益性と効率性 収益と成長 プロジェクト管理 モバイルデザイン 技術 データサイエンスとデータベース

© 2021 | 全著作権所有

apeescape2.com