持っていた プログラミングインタビュー 最近、私たちが使用した電話スクリーン コラボレーティブテキストエディタ 。
私は実装するように頼まれました 特定の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インスタンス属性の違いを理解することにあります。
注:クラス属性のエキスパートハンドルがある場合は、スキップして先に進むことができます ユースケース 。
私のインタビュアーは、上記のコードが間違っていました です 構文的に有効です。
インスタンス属性に「デフォルト値」が設定されていないという点で私も間違っていました。代わりに、data
を定義していますとして クラス 値が[]
の属性。
私の経験では、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
最終的には独自のインスタンス属性を持つため、デフォルトとして空のリストを使用すると、見落とされがちな小さなバグが発生しました。上記の代わりに、次のいずれかを行うことができます。
空のリスト(可変値)を「デフォルト」として使用することは避けました。
class Service(object): data = None def __init__(self, other_data): self.other_data = other_data ...
もちろん、None
を処理する必要があります適切なケースですが、それは少額の支払いです。
クラス属性には注意が必要ですが、それらが役立つ場合をいくつか見てみましょう。
定数の保存 。クラス属性はクラス自体の属性としてアクセスできるため、クラス全体のクラス固有の定数を格納するために使用すると便利なことがよくあります。例えば:
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
デフォルト値の定義 。簡単な例として、制限付きリスト(つまり、特定の数以下の要素しか保持できないリスト)を作成し、デフォルトの上限を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
インスタンス変数である必要があります。 (ただし、デフォルトとして可変値を使用する場合は注意してください。)
特定のクラスのすべてのインスタンスにわたるすべてのデータの追跡 。これは一種の特定のものですが、特定のクラスの既存のすべてのインスタンスに関連するデータにアクセスしたいというシナリオを見ることができました。
シナリオをより具体的にするために、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 ## [, ]
パフォーマンス (一種の…以下を参照)。
注意: このレベルでのパフォーマンスが心配な場合は、最初から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では、クラスメソッドは、クラスをコンテキストとして使用して呼び出されるメソッドです。これは、他のプログラミング言語では静的メソッドとしてよく知られています。一方、インスタンスメソッドは、インスタンスをコンテキストとして使用して呼び出されます。
その場合、インスタンスの名前空間がクラスの名前空間よりも優先されます。両方に同じ名前の属性がある場合、インスタンスの名前空間が最初にチェックされ、その値が返されます。