Pythonのカスタム属性

pythonに終止笊を打ったずきに䜕が起こるか疑問に思ったこずはありたすか str文字“ \ u002E”は䜕を隠しおいたすか 圌はどのような秘密を保持しおいたすか 神秘䞻矩がなければ、Pythonでナヌザヌ属性の倀を怜玢しお蚭定する方法を知っおいたすか 知りたいですか その埌...ようこそ

読みやすく、楜しく、䟿利な時間を過ごすために、蚀語のいく぀かの基本的な抂念を知っおおくずいいでしょう。 特に、 型ずオブゞェクトを理解するこずは非垞に圹立ち、䞡方の゚ンティティのいく぀かの䟋を知るこずもできたす。 ここを含め、それらに぀いお読むこずができたす 。

収集したポむントに到達する前に䜿甚する甚語に぀いお少し説明したす。



そうそう、蚘事の䟋はすべおpython3で曞かれおいたす  これは間違いなく考慮されるべきです。

䞊蚘のどれもが、次に䜕が起こるかを知りたいずいうあなたの欲望を和らげるこずができなかったら、始めたしょう



__dict__



オブゞェクトの属性は、条件付きで2぀のグルヌプに分けるこずができたす。Pythonで定矩されたもの __class__ 、 __bases__など ずナヌザヌ定矩されたものです。 この分類による__dict__は、「システム」pythonで定矩属性を指したす。 そのタスクは、ナヌザヌ属性を保存するこずです。 これは蟞曞であり、キヌはそれぞれattribute_name 、value、 attribute_valueです 。

oオブゞェクトの属性を芋぀けるために、pythonは以䞋をスキャンしたす
  1. オブゞェクト自䜓 o .__ dict__およびそのシステム属性。
  2. オブゞェクトクラス o .__ class __.__ dict__ 。 システム属性ではなく、 __ dict__クラスのみ。
  3. オブゞェクトクラスの継承元のクラス o .__ class __.__ bases __.__ dict__ 。
したがっお、 __ dict__を䜿甚するず、特定のむンスタンスずクラス぀たり、特定のクラスのむンスタンスであるすべおのオブゞェクトの䞡方に察しお属性を定矩できたす。



class StuffHolder: stuff = "class stuff" a = StuffHolder() b = StuffHolder() a.stuff # "class stuff" b.stuff # "class stuff" b.b_stuff = "b stuff" b.b_stuff # "b stuff" a.b_stuff # AttributeError
      
      





この䟋では、䞡方のむンスタンスが継承する1぀の属性stuffを持぀StuffHolderクラスに぀いお説明したす。 オブゞェクトbに b_stuff属性を远加しおも、 aには圱響したせん。

すべおのアクタヌの__dict__を芋おみたしょう。



 StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...} a.__dict__ # {} b.__dict__ # {'b_stuff': 'b stuff'} a.__class__ # <class '__main__.StuffHolder'> b.__class__ # <class '__main__.StuffHolder'>
      
      



 __dict__のStuffHolderクラスは、さたざたなゞャンクを持぀dict_proxyクラスのオブゞェクトを栌玍したすが、ただ泚意を払う必芁はありたせん。



__dict__の aもbもstuff属性を持ちたせん。そこで芋぀けるこずなく、怜玢゚ンゞンは__dict__クラス StuffHolder でそれを怜玢し、クラスで割り圓おられた倀を芋぀けお返したす。 クラス参照は、オブゞェクトの__class__属性に栌玍されたす。

属性の怜玢は実行時に行われるため、むンスタンスを䜜成した埌でも、 __ dict__クラスに察するすべおの倉曎がそれらに反映されたす。



 a.new_stuff # AttributeError b.new_stuff # AttributeError StuffHolder.new_stuff = "new" StuffHolder.__dict__ # {... 'stuff': 'class stuff', 'new_stuff': 'new'...} a.new_stuff # "new" b.new_stuff # "new"
      
      





むンスタンス属性に倀を割り圓おる堎合、 __dict__むンスタンスのみが倉曎されたす 。぀たり、 __dict__クラスの倀は倉曎されたせんクラス属性倀がデヌタ蚘述子でない堎合



 StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...} c = StuffHolder() c.__dict__ # {} c.stuff = "more c stuff" c.__dict__ # {'stuff': 'more c stuff'} StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...}
      
      





クラスの属性名ずむンスタンスが䞀臎する堎合、むンタプリタは倀を怜玢するずきにむンスタンスの倀を怜玢したすクラス属性の倀がデヌタ蚘述子でない堎合



 StuffHolder.__dict__ # {... 'stuff': 'class stuff' ...} d = StuffHolder() d.stuff # "class stuff" d.stuff = "d stuff" d.stuff # "d stuff"
      
      





抂しお 、これが__dict__に぀いお蚀えるこずのすべおです。 これは、ナヌザヌ定矩の属性のリポゞトリです。 実行時に怜玢され、怜玢䞭にオブゞェクトの__dict__クラスず基本クラスが考慮されたす。 たた、この動䜜をオヌバヌラむドする方法がいく぀かあるこずを知っおおくこずが重芁です。 それらの1぀は、偉倧で匷力な蚘述子です



蚘述子



属性倀ずしお単玔型を䜿甚するず、これたでのずころすべおが明確になりたした。 同じ条件䞋で関数がどのように動䜜するかを芋おみたしょう。



 class FuncHolder: def func(self): pass fh = FuncHolder() FuncHolder.func # <function func at 0x8f806ac> FuncHolder.__dict__ # {...'func': <function func at 0x8f806ac>...} fh.func # <bound method FuncHolder.func of <__main__.FuncHolder object at 0x900f08c>>
      
      





WTF たぶん...倚分。 お願いしたす。 この堎合の機胜は、すでに芋たものずどのように異なりたすか 答えは簡単です。__get__メ゜ッドを䜿甚したす。



 FuncHolder.func.__class__.__get__ # <slot wrapper '__get__' of 'function' objects>
      
      





このメ゜ッドは、 fhむンスタンスのfunc属性の倀を取埗するメカニズムをオヌバヌラむドし、このメ゜ッドを実装するオブゞェクトは、 非デヌタ蚘述子ず呌ばれる翻蚳䞍可胜です。



howtoから

蚘述子は、プロトコル蚘述子のメ゜ッドによっお属性を介しお再定矩されるオブゞェクトアクセスです 。

 descr .__ get __self、obj、type = None-> value属性倀の受け取り方法をオヌバヌラむドしたす
 descr .__ set __self、obj、value-> None属性に倀が割り圓おられる方法をオヌバヌラむドしたす
 descr .__ delete __self、obj-> None属性の削陀方法をオヌバヌラむドしたす


蚘述子には2぀のタむプがありたす。

  1. デヌタ蚘述子-__get __および__set __メ゜ッドを実装するオブゞェクト
  2. 非デヌタ蚘述子デヌタ蚘述子ではない -__get __メ゜ッドを実装するオブゞェクト
これらは、 __ dict__むンスタンスの゚ントリに関する動䜜が異なりたす。 __dict__にデヌタ蚘述子ず同じ名前の゚ントリがある堎合、蚘述子には利点がありたす。 レコヌドの名前が「デヌタ蚘述子なし」の名前ず䞀臎する堎合、 __ dict__のレコヌドの優先順䜍は高くなりたす。



デヌタ蚘述子


デヌタ蚘述子を詳しく芋おみたしょう。



 class DataDesc: def __get__(self, obj, cls): print("Trying to access from {0} class {1}".format(obj, cls)) def __set__(self, obj, val): print("Trying to set {0} for {1}".format(val, obj)) def __delete__(self, obj): print("Trying to delete from {0}".format(obj)) class DataHolder: data = DataDesc() d = DataHolder() DataHolder.data # Trying to access from None class <class '__main__.DataHolder'> d.data # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'> d.data = 1 # Trying to set 1 for <__main__.DataHolder object at ...> del(d.data) # Trying to delete from <__main__.DataHolder object at ...>
      
      





DataHolder.dataの呌び出しが、クラスのむンスタンスではなく__get__ Noneメ゜ッドに枡されるこずに泚意しおください。

日付蚘述子が__dict__むンスタンスの゚ントリよりも有利であるずいうステヌトメントを怜蚌しおみたしょう。



 d.__dict__["data"] = "override!" d.__dict__ # {'data': 'override!'} d.data # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'>
      
      





぀たり、 __ dict__むンスタンスクラスたたはその基本クラスに同じ名前ず倀デヌタ蚘述子の゚ントリがある堎合、 __ dict__むンスタンスの゚ントリは無芖されたす。



もう䞀぀の重芁なポむント。 クラスを通じお蚘述子を䜿甚しお属性の倀を倉曎するず、蚘述子メ゜ッドは呌び出されず、クラスの__dict__の倀は通垞の属性であるかのように倉曎されたす。



 DataHolder.__dict__ # {...'data': <__main__.DataDesc object at ...>...} DataHolder.data = "kick descriptor out" DataHolder.__dict__ # {...'data': 'kick descriptor out'...} DataHolder.data # "kick descriptor out"
      
      







デヌタ蚘述子なし


非デヌタ蚘述子の䟋



 class NonDataDesc: def __get__(self, obj, cls): print("Trying to access from {0} class {1}".format(obj, cls)) class NonDataHolder: non_data = NonDataDesc() n = NonDataHolder() NonDataHolder.non_data # Trying to access from None class <class '__main__.NonDataHolder'> n.non_data # Trying to access from <__main__.NonDataHolder object at ...> class <class '__main__.NonDataHolder'> n.non_data = 1 n.non_data # 1 n.__dict__ # {'non_data': 1}
      
      





圌の振る舞いは、デヌタ蚘述子がしたこずずは少し異なりたす。 non_data属性に倀を割り圓おようずするず、 __ dict__むンスタンスに曞き蟌たれたため、 __ dict__クラスに栌玍されおいるハンドルが非衚瀺になりたした。



䜿甚䟋


蚘述子は、クラスのむンスタンスの属性ぞのアクセスを制埡できる匷力なツヌルです。 それらの䜿甚の䞀䟋は関数です;むンスタンスを通しお呌び出されるず、それらはメ゜ッドになりたす䞊蚘の䟋を参照。 蚘述子を䜿甚するもう1぀の䞀般的な方法は、 プロパティを䜜成するこずです。 プロパティずは、オブゞェクトの状態を特城付ける特定の倀を意味し、そのアクセスは特別なメ゜ッドゲッタヌ、セッタヌを䜿甚しお制埡されたす。 蚘述子を䜿甚しおプロパティを䜜成するのは簡単です。



 class Descriptor: def __get__(self, obj, type): print("getter used") def __set__(self, obj, val): print("setter used") def __delete__(self, obj): print("deleter used") class MyClass: prop = Descriptor()
      
      





たたは、組み蟌みクラスプロパティを䜿甚できたす。これはデヌタ蚘述子です。 䞊蚘のコヌドは次のように曞き換えるこずができたす。



 class MyClass: def _getter(self): print("getter used") def _setter(self, val): print("setter used") def _deleter(self): print("deleter used") prop = property(_getter, _setter, _deleter, "doc string")
      
      





どちらの堎合でも、同じ動䜜が埗られたす。



 m = MyClass() m.prop # getter used m.prop = 1 # setter used del(m.prop) # deleter used
      
      





プロパティは垞にデヌタ蚘述子であるこずを知っおおくこずが重芁です。 コンストラクタヌgetter、setter、たたはdeliterに関数を枡さない堎合、属性に察しお察応するアクションを実行しようずするず、 AttributeErrorがスロヌされたす。



 class MySecondClass: prop = property() m2 = MySecondClass() m2.prop # AttributeError: unreadable attribute m2.prop = 1 # AttributeError: can't set attribute del(m2) # AttributeError: can't delete attribute
      
      





組み蟌み蚘述子には次のものも含たれたす。

 class StaticAndClassMethodHolder: def _method(*args): print("_method called with ", args) static = staticmethod(_method) cls = classmethod(_method) s = StaticAndClassMethodHolder() s._method() # _method called with (<__main__.StaticAndClassMethodHolder object at ...>,) s.static() # _method called with () s.cls() # _method called with (<class '__main__.StaticAndClassMethodHolder'>,)
      
      







__getattr __、__ setattr __、__ delattr __および__getattribute __



オブゞェクトの動䜜を属性ずしお決定する堎合は、蚘述子たずえば、 property を䜿甚する必芁がありたす。 オブゞェクトのファミリ 関数など に぀いおも同様です。 属性ぞのアクセスに圱響を䞎える別の方法は、 __ getattr __ 、 __setattr __、__delattr __、および__getattribute __メ゜ッドを䜿甚するこずです。 蚘述子ずは異なり、 属性を含むオブゞェクトに察しお定矩する必芁があり、このオブゞェクトの属性にアクセスするずきに呌び出されたす。



芁求された属性が通垞のメカニズム __dict__むンスタンス、クラスなどで芋぀からない堎合、 __ getattr __self、nameが呌び出されたす。



 class SmartyPants: def __getattr__(self, attr): print("Yep, I know", attr) tellme = "It's a secret" smarty = SmartyPants() smarty.name = "Smartinius Smart" smarty.quicksort # Yep, I know quicksort smarty.python # Yep, I know python smarty.tellme # "It's a secret" smarty.name # "Smartinius Smart"
      
      





属性倀を取埗しようずするず、__ getattribute __self、nameが呌び出されたす。 このメ゜ッドがオヌバヌラむドされた堎合、暙準の属性倀怜玢メカニズムは有効になりたせん。 組み蟌み関数を介した特別なメ゜ッド __len __ 、 __str __などの呌び出し、たたは蚀語構文を介した暗黙的な呌び出しは、 __getattribute __をバむパスするこずに泚意しおください。



 class Optimist: attr = "class attribute" def __getattribute__(self, name): print("{0} is great!".format(name)) def __len__(self): print("__len__ is special") return 0 o = Optimist() o.instance_attr = "instance" o.attr # attr is great! o.dark_beer # dark_beer is great! o.instance_attr # instance_attr is great! o.__len__ # __len__ is great! len(o) # __len__ is special\n 0
      
      





むンスタンス属性の倀を蚭定しようずするず、__ setattr __self、name、valueが呌び出されたす。 __getattribute __ず同様に、このメ゜ッドがオヌバヌラむドされた堎合、暙準倀蚭定メカニズムは䜿甚されたせん。



 class NoSetters: attr = "class attribute" def __setattr__(self, name, val): print("not setting {0}={1}".format(name,val)) no_setters = NoSetters() no_setters.a = 1 # not setting a=1 no_setters.attr = 1 # not setting attr=1 no_setters.__dict__ # {} no_setters.attr # "class attribute" no_setters.a # AttributeError
      
      





__delattr __self、name -__setattr __に䌌おいたすが、属性を削陀するずきに䜿甚されたす。



__getattribute __ 、 __setattr __、および__delattr __をオヌバヌラむドする堎合、属性にアクセスする暙準的な方法はオブゞェクトを介しお呌び出すこずができるこずに泚意しおください。



 class GentleGuy: def __getattribute__(self, name): if name.endswith("_please"): return object.__getattribute__(self, name.replace("_please", "")) raise AttributeError("And the magic word!?") gentle = GentleGuy() gentle.coffee = "some coffee" gentle.coffee # AttributeError gentle.coffee_please # "some coffee"
      
      







å¡©



したがっお、Pythonのaのむンスタンスのattrname属性倀を取埗するには

  1. .__ class __.__ getattribute __メ゜ッドが定矩されおいる堎合、このメ゜ッドが呌び出され、受信した倀が返されたす。
  2. attrnameが__class__や__doc__などの特別なpythonで定矩された属性である堎合、その倀が返されたす。
  3. .__ class __.__ dict__でattrnameを持぀゚ントリをチェックしたす 。 存圚し、倀がデヌタ蚘述子である堎合、 蚘述子の__get __メ゜ッドを呌び出した結果が返されたす。 すべおの基本クラスもチェックされたす。
  4. attrnameずいう名前の゚ントリが.__ dict__に存圚する堎合、その゚ントリの倀が返されたす。 aがクラスの堎合、属性はその基本クラス間でも怜玢され、デヌタ蚘述子がある堎合、たたは__dict__に ある堎合、蚘述子の__get __の結果が返されたす。
  5. .__ class __.__ dict__をチェックし、 attrnameを含む゚ントリがあり、これが「デヌタ蚘述子」である堎合、結果は蚘述子の__get __になりたす。レコヌドが存圚し、蚘述子がない堎合、レコヌドの倀が返されたす。 基本クラスも怜玢されたす。
  6. .__ class __.__ getattr __メ゜ッドが存圚する堎合、それが呌び出され、その結果が返されたす。 そのようなメ゜ッドがない堎合、 AttributeErrorがスロヌされたす。


むンスタンスaの attrname属性の倀を蚭定するには 

  1. .__ class __.__ setattr __メ゜ッドが存圚する堎合、呌び出されたす。
  2. .__ class __.__ dict__をチェックしたす。attrnameを持぀゚ントリがあり、これがデヌタ蚘述子である堎合、蚘述子の__set __メ゜ッドが呌び出されたす。 基本クラスもチェックされたす。
  3. attrnameキヌを持぀倀゚ントリが.__ dict__に远加されたす




__slots__



Guidoが新しいスタむルのクラスがどのように発明されたかに぀いおのpythonストヌリヌで曞いおいるように

...クラスシステムの倉曎がパフォヌマンスに悪圱響を䞎えるのではないかず心配したした。 特に、デヌタ蚘述子が正しく機胜するために、オブゞェクトの属性を䜿甚したすべおの操䜜は、この属性がデヌタ蚘述子であるずいう事実に぀いお__dict__クラスをチェックするこずから始たりたした...



ナヌザヌがパフォヌマンスの䜎䞋に倱望した堎合、思いやりのあるPython開発者は__slots__を思い぀きたした 。

__slots__の存圚は、オブゞェクトの可胜な属性名をそこに瀺されおいるものに制限したす。 たた、すべおの属性名が事前にわかっおいるため、 __ dict__むンスタンスを䜜成する必芁がなくなりたす。



 class Slotter: __slots__ = ["a", "b"] s = Slotter() s.__dict__ # AttributeError sc = 1 # AttributeError sa = 1 sa # 1 sb = 1 sb # 1 dir(s) # [ ... 'a', 'b' ... ]
      
      





ギドの恐怖は実珟しなかったこずが刀明したしたが、明らかになるたでにはすでに手遅れでした。 さらに、 __ slots__を䜿甚するず、特に倚くの小さなオブゞェクトを䜜成するずきに䜿甚されるメモリの量を枛らすこずにより、実際にパフォヌマンスを向䞊させるこずができたす。



おわりに



Pythonの属性ぞのアクセスは、非垞に倚くの方法で制埡できたす。 それらはそれぞれ独自の問題を解決し、䞀緒にオブゞェクトを䜿甚するほずんどすべおの考えられるシナリオに適しおいたす。 これらのメカニズムは、耇数の継承、メタクラス、およびその他の利点ずずもに、蚀語の柔軟性の基瀎です。 属性を凊理するためのこの倚くのオプションを理解し、理解し、そしお最も重芁なこずには受け入れるのに時間がかかりたした。 䞀芋、少し冗長で、特に論理的ではないように芋えたすが、これが日垞のプログラミングではほずんど圹に立たないこずを考えるず、このような匷力なツヌルを歊噚に持぀こずは玠晎らしいこずです。

この蚘事で、あなたの手が通り抜けられなかったいく぀かのポむントを明確にしおほしいず思いたす。 そしお今、あなたの目に火を぀け、Pointに自信を持っおいるので、コヌド芁件の倉曎に察しお、最もクリヌンで読みやすく、耐性のある膚倧な量を曞きたす たあ、たたはコメント。



お時間をいただきありがずうございたす。



参照資料

  1. シャラブチャトゥルベディ。 Pythonの属性ずメ゜ッド
  2. グむドノァンロッサム。 新しいスタむルのクラスの内郚ストヌリヌ
  3. Pythonドキュメント
UPDナヌザヌleronからの䟿利なリンク Python Data Model



All Articles