Python Stack Overflowの回答)を読んでいると、何人かの人々 ユーザーに伝える に データモデルの使用 特別な メソッド または 属性 直接。
次に、矛盾するアドバイスを(時には自分自身から)しないで、代わりに組み込み関数と演算子を直接使用するように言っています。
何故ですか?特別な「dunder」メソッドとPython data model and builtin functions )の属性の関係は何ですか?
特別な名前を使用するのはいつですか?
したがって、データモデルの特別なメソッドと属性よりも、可能な場合は組み込み関数と演算子を使用することをお勧めします。
意味的に内部のAPIは、パブリックインターフェイスよりも変更される可能性が高くなります。 Pythonは実際には何も「プライベート」とは考えず、内部を公開しますが、それはそのアクセスを悪用するのが良い考えであることを意味しません。これには次のリスクがあります。
組み込み関数と演算子は特別なメソッドを呼び出し、Pythonデータモデルの特別な属性を使用します。それらは、オブジェクトの内部を隠す、読み取り可能で保守可能なベニアです。一般に、ユーザーは、特別なメソッドを呼び出したり、特別な属性を直接使用したりするのではなく、言語で指定された組み込み関数と演算子を使用する必要があります。
組み込み関数と演算子は、より原始的なデータモデルの特別なメソッドよりもフォールバックまたはよりエレガントな動作を持つこともできます。例えば:
next(obj, default)
では、イテレータが不足したときにStopIteration
を発生させる代わりにデフォルトを提供できますが、obj.__next__()
では発生しません。str(obj)
が利用できない場合、obj.__repr__()
はobj.__str__()
にフォールバックします-obj.__str__()
を直接呼び出すと属性エラーが発生します。obj != other
_がない場合、Python 3の_not obj == other
_フォールバック___ne__
_-obj.__ne__(other)
を呼び出しても、これを利用できません。(ビルトイン関数は、必要に応じて、または必要に応じて、モジュールのグローバルスコープまたはbuiltins
モジュールに簡単に影を付けて、動作をさらにカスタマイズすることもできます。)
以下は、組み込み関数と演算子の、使用または返されるそれぞれの特別なメソッドと属性へのメモ付きのマッピングです。通常のルールは、組み込み関数は通常、同じ名前の特別なメソッドにマッピングされるということですが、これは以下にこのマップを与えることを保証するのに十分な一貫性がありません:
_builtins/ special methods/
operators -> datamodel NOTES (fb == fallback)
repr(obj) obj.__repr__() provides fb behavior for str
str(obj) obj.__str__() fb to __repr__ if no __str__
bytes(obj) obj.__bytes__() Python 3 only
unicode(obj) obj.__unicode__() Python 2 only
format(obj) obj.__format__() format spec optional.
hash(obj) obj.__hash__()
bool(obj) obj.__bool__() Python 3, fb to __len__
bool(obj) obj.__nonzero__() Python 2, fb to __len__
dir(obj) obj.__dir__()
vars(obj) obj.__dict__ does not include __slots__
type(obj) obj.__class__ type actually bypasses __class__ -
overriding __class__ will not affect type
help(obj) obj.__doc__ help uses more than just __doc__
len(obj) obj.__len__() provides fb behavior for bool
iter(obj) obj.__iter__() fb to __getitem__ w/ indexes from 0 on
next(obj) obj.__next__() Python 3
next(obj) obj.next() Python 2
reversed(obj) obj.__reversed__() fb to __len__ and __getitem__
other in obj obj.__contains__(other) fb to __iter__ then __getitem__
obj == other obj.__eq__(other)
obj != other obj.__ne__(other) fb to not obj.__eq__(other) in Python 3
obj < other obj.__lt__(other) get >, >=, <= with @functools.total_ordering
complex(obj) obj.__complex__()
int(obj) obj.__int__()
float(obj) obj.__float__()
round(obj) obj.__round__()
abs(obj) obj.__abs__()
_
operator
モジュールには_length_hint
_があり、___len__
_が実装されていない場合、それぞれの特別なメソッドによってフォールバックが実装されます。
_length_hint(obj) obj.__length_hint__()
_
ドットルックアップは状況に応じて異なります。特別なメソッド実装がない場合は、最初にクラス階層でデータ記述子(プロパティやスロットなど)を探し、次にインスタンス___dict__
_(インスタンス変数など)を調べ、次にクラス階層で非データ記述子(メソッドなど)を探します。特別なメソッドは、次の動作を実装します。
_obj.attr obj.__getattr__('attr') provides fb if dotted lookup fails
obj.attr obj.__getattribute__('attr') preempts dotted lookup
obj.attr = _ obj.__setattr__('attr', _) preempts dotted lookup
del obj.attr obj.__delattr__('attr') preempts dotted lookup
_
記述子は少し進んでいます-これらのエントリをスキップして後で戻って来てください-記述子インスタンスがクラス階層(メソッド、スロット、プロパティなど)にあることを思い出してください。データ記述子は___set__
_または___delete__
_を実装します。
_obj.attr descriptor.__get__(obj, type(obj))
obj.attr = val descriptor.__set__(obj, val)
del obj.attr descriptor.__delete__(obj)
_
クラスがインスタンス化(定義)されると、記述子に属性名を通知する記述子がある場合、次の記述子メソッド___set_name__
_が呼び出されます。 (これはPython 3.6の新機能です。)cls
は上記のtype(obj)
と同じで、_'attr'
_は属性名を表します:
_class cls:
@descriptor_type
def attr(self): pass # -> descriptor.__set_name__(cls, 'attr')
_
下付き表記も文脈に応じて異なります。
_obj[name] -> obj.__getitem__(name)
obj[name] = item -> obj.__setitem__(name, item)
del obj[name] -> obj.__delitem__(name)
_
___missing__
_がキーを見つけられない場合、dict
、___getitem__
_のサブクラスの特別なケースが呼び出されます。
_obj[name] -> obj.__missing__(name)
_
+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |
演算子には、次のような特別なメソッドもあります。
_obj + other -> obj.__add__(other), fallback to other.__radd__(obj)
obj | other -> obj.__or__(other), fallback to other.__ror__(obj)
_
および拡張代入のインプレース演算子_+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=
_、たとえば:
_obj += other -> obj.__iadd__(other)
obj |= other -> obj.__ior__(other)
_
(これらのインプレース演算子が定義されていない場合、Pythonは、たとえば、_obj += other
_から_obj = obj + other
_にフォールバックします)
と単項演算:
_+obj -> obj.__pos__()
-obj -> obj.__neg__()
~obj -> obj.__invert__()
_
コンテキストマネージャーは、コードブロックの開始時に呼び出される___enter__
_を定義します(その戻り値、通常はselfはas
でエイリアスされます)、および___exit__
_は呼び出されることが保証されています例外情報を含む、コードブロックの終了時。
_with obj as enters_return_value: #-> enters_return_value = obj.__enter__()
raise Exception('message')
#-> obj.__exit__(Exception,
#-> Exception('message'),
#-> traceback_object)
_
___exit__
_が例外を受け取ってからfalse値を返す場合、メソッドを終了すると例外が再発生します。
例外がない場合、___exit__
_はこれらの3つの引数の代わりにNone
を取得し、戻り値は無意味です。
_with obj: #-> obj.__enter__()
pass
#-> obj.__exit__(None, None, None)
_
同様に、クラスは抽象基本クラスをサポートする(メタクラスからの)特別なメソッドを持つことができます。
_isinstance(obj, cls) -> cls.__instancecheck__(obj)
issubclass(sub, cls) -> cls.__subclasscheck__(sub)
_
重要なポイントは、next
やbool
のような組み込み関数はPython 2と3の間で変わらないが、基礎となる実装名は変化します。
したがって、ビルトインを使用すると、上位互換性も提供されます。
Pythonでは、アンダースコアで始まる名前は、意味的には非公開のユーザー名です。下線は、クリエイターの言い回しであり、「手を使わないで、触れないでください」です。
これは文化的なものだけでなく、PythonによるAPIの扱いにもあります。パッケージの___init__.py
_が_import *
_を使用してサブパッケージからAPIを提供する場合、サブパッケージが___all__
_を提供しない場合、アンダースコアで始まる名前は除外されます。サブパッケージの___name__
_も除外されます。
IDEのオートコンプリートツールは、アンダースコアで始まる名前が非公開であることを考慮して混合されています。ただし、___init__
_、___new__
_、___repr__
_、___str__
_、___eq__
_など(ユーザーが作成した非パブリックインターフェイスも表示されない) )オブジェクトの名前とピリオドを入力すると、.
したがって、私は断言します:
特別な「dunder」メソッドは、パブリックインターフェイスの一部ではありません。直接使用しないでください。
それで、それらをいつ使うのですか?
主な使用例は、独自のカスタムオブジェクトまたは組み込みオブジェクトのサブクラスを実装する場合です。
どうしても必要な場合にのみ使用してください。ここではいくつかの例を示します。
__name__
_特殊属性を使用する関数を装飾すると、通常、関数に関する有用な情報を隠すラッパー関数が返されます。 @wraps(fn)
デコレーターを使用して情報を失わないようにしますが、関数の名前が必要な場合は、___name__
_属性を直接使用する必要があります。
_from functools import wraps
def decorate(fn):
@wraps(fn)
def decorated(*args, **kwargs):
print('calling fn,', fn.__name__) # exception to the rule
return fn(*args, **kwargs)
return decorated
_
同様に、メソッドでオブジェクトのクラスの名前が必要な場合(たとえば、___repr__
_で使用)、次のようにします。
_def get_class_name(self):
return type(self).__name__
# ^ # ^- must use __name__, no builtin e.g. name()
# use type, not .__class__
_
カスタム動作を定義する場合は、データモデル名を使用する必要があります。
これは理にかなっています。私たちが実装者であるため、これらの属性は私たちにはプライベートではありません。
_class Foo(object):
# required to here to implement == for instances:
def __eq__(self, other):
# but we still use == for the values:
return self.value == other.value
# required to here to implement != for instances:
def __ne__(self, other): # docs recommend for Python 2.
# use the higher level of abstraction here:
return not self == other
_
ただし、この場合でも、self.value.__eq__(other.value)
またはnot self.__eq__(other)
は使用しません(後者が予期しない動作を引き起こす可能性があることの証明については、my answer here を参照してください)。より高いレベルの抽象化を使用する必要があります。
特別なメソッド名を使用する必要があるもう1つのポイントは、子の実装で親に委任する場合です。例えば:
_class NoisyFoo(Foo):
def __eq__(self, other):
print('checking for equality')
# required here to call the parent's method
return super(NoisyFoo, self).__eq__(other)
_
特別なメソッドにより、ユーザーはオブジェクト内部のインターフェースを実装できます。
組み込み関数と演算子をできる限り使用します。ドキュメント化されたパブリックAPIがない特別なメソッドのみを使用します。
あなたがどうやら考えていなかったいくつかの使用法を示し、示した例にコメントし、あなた自身の答えからのプライバシー主張に反対します。
たとえば、len(a)
ではなくa.__len__()
を使用する必要があるというあなた自身の回答に同意します。私はそれを次のように記述します:len
が存在するために使用でき、__len__
が存在するためlen
が使用できる。または、実際にはlen(a)
は実際には多くのfasterになる可能性があるため、内部的には機能します。たとえば、リストや文字列の場合は次のようになります。
>>> timeit('len(a)', 'a = [1,2,3]', number=10**8)
4.22549770486512
>>> timeit('a.__len__()', 'a = [1,2,3]', number=10**8)
7.957335462257106
>>> timeit('len(s)', 's = "abc"', number=10**8)
4.1480574509332655
>>> timeit('s.__len__()', 's = "abc"', number=10**8)
8.01780160432645
しかし、これらのメソッドを組み込み関数や演算子で使用するために自分のクラスで定義するほかに、次のように使用することもあります。
ある関数にフィルター関数を与える必要があり、フィルターとしてセットs
を使用したいとします。追加の関数lambda x: x in s
またはdef f(x): return x in s
を作成するつもりはありません。いいえ、私はすでに使用できる完全に優れた関数を持っています。それは、セットの__contains__
メソッドです。よりシンプルで直接的です。そして、ここに示されているように、さらに高速です(ここではf
として保存していることを無視してください。これは、このタイミングデモのためだけです)。
>>> timeit('f(2); f(4)', 's = {1, 2, 3}; f = s.__contains__', number=10**8)
6.473739433621368
>>> timeit('f(2); f(4)', 's = {1, 2, 3}; f = lambda x: x in s', number=10**8)
19.940786514456924
>>> timeit('f(2); f(4)', 's = {1, 2, 3}\ndef f(x): return x in s', number=10**8)
20.445680107760325
したがって、私は直接呼び出しs.__contains__(x)
のような魔法のメソッドを使用しませんが、時々passsome_function_needing_a_filter(s.__contains__)
のような魔法のメソッドを使用します。そして、それは完全に問題なく、lambda/defの代替よりも優れていると思います。
あなたが示した例についての私の考え:
items.__len__()
と回答しました。どんな理由もなく。私の評決:それは間違っています。 len(items)
である必要があります。d[key] = value
について言及していますか?次に、d.__setitem__(key, value)
を理由付きで追加します "キーボードに角かっこキーがない場合" 、これはほとんど適用されず、私が深刻であったとは思えません。それが最後のポイントのドアの足元にすぎなかったと私は思います、それが私たち自身のクラスで角括弧構文をサポートする方法であると言及しました。角括弧を使用するという提案に戻ります。obj.__dict__
を提案します。悪い、__len__
の例のように。しかし、私は彼がvars(obj)
を知らなかったのではないかと思います。vars
はあまり一般的ではないため、名前は__dict__
の「dict」とは異なります。__class__
を提案します。 type(obj)
である必要があります。 type
の方がよく知られていると思いますが、それは__dict__
の話に似ていると思います。プライバシーについて:あなた自身の答えでは、これらの方法は「意味的にプライベート」であると言います。私は強く反対します。シングルおよびダブルleadingアンダースコアはそのためのものですが、データモデルの特別な「dunder/magic」メソッドではなく、ダブルリーディング+トレーリングアンダースコアを使用します。
_foo
および__bar__
でクラス/オブジェクトを作成しましたが、オートコンプリートは失敗しましたtは_foo
を提供しますが、-didは__bar__
を提供します。両方の方法を使用した場合、PyCharmは_foo
(「保護されたメンバー」と呼ぶ)についてのみ警告しましたnot__bar__
について。Andrewの記事以外にも、これらの「マジック」/「ダンダー」メソッドについてさらにいくつかチェックしましたが、プライバシーについて話しているものはまったく見つかりませんでした。それだけではありません。
繰り返しますが、len(a)
ではなくa.__len__()
を使用する必要があります。プライバシーのためではありません。