Python 黒魔術記述子の使用の概要
はじめに
ディスクリプター (記述子) は、Python 言語における奥深くも重要な黒魔術です。これらは Python 言語のカーネルで広く使用されており、Python プログラマーにとって有益です。追加のトリック。この記事では、記述子の定義といくつかの一般的なシナリオについて説明し、記事の最後に同じ 3 つの getattr
、getattribute
、 を追加します。 >getitem
属性アクセスを伴うマジック メソッド getattr
,getattribute
, getitem
这三个同样涉及到属性访问的魔术方法。
描述符的定义
descrget(self, obj, objtype=None) --> value descr.set(self, obj, value) --> None descr.delete(self, obj) --> None
只要一个<a href="http://www.php.cn/wiki/60.html" target="_blank">object</a> attribute
(对象属性)定义了上面三个方法中的任意一个,那么这个类就可以被称为描述符类。
描述符基础
下面这个例子中我们创建了一个RevealAcess
类,并且实现了get
方法,现在这个类可以被称为一个描述符类。
class RevealAccess(object): def get(self, obj, objtype): print('self in RevealAccess: {}'.format(self)) print('self: {}\nobj: {}\nobjtype: {}'.format(self, obj, objtype)) class MyClass(object): x = RevealAccess() def test(self): print('self in MyClass: {}'.format(self))
EX1实例属性
接下来我们来看一下get
方法的各个参数的含义,在下面这个例子中,self
即RevealAccess类的实例x,obj
即MyClass类的实例m,objtype
顾名思义就是MyClass类自身。从输出语句可以看出,m.x
访问描述符x
会调用get
方法。
>>> m = MyClass() >>> m.test() self in MyClass: <main.MyClass object at 0x7f19d4e42160> >>> m.x self in RevealAccess: <main.RevealAccess object at 0x7f19d4e420f0> self: <main.RevealAccess object at 0x7f19d4e420f0> obj: <main.MyClass object at 0x7f19d4e42160> objtype: <class 'main.MyClass'>
EX2类属性
如果通过类直接访问属性x
,那么obj
接直接为None,这还是比较好理解,因为不存在MyClass的实例。
>>> MyClass.x self in RevealAccess: <main.RevealAccess object at 0x7f53651070f0> self: <main.RevealAccess object at 0x7f53651070f0> obj: None objtype: <class 'main.MyClass'>
描述符的原理
描述符触发
上面这个例子中,我们分别从实例属性和类属性的角度列举了描述符的用法,下面我们来仔细分析一下内部的原理:
如果是对
实例属性
进行访问,实际上调用了基类object的getattribute方法,在这个方法中将obj.d转译成了type(obj).dict['d'].get(obj, type(obj))
。如果是对
类属性
进行访问,相当于调用了元类type的getattribute方法,它将cls.d转译成cls.dict['d'].get(None, cls)
,这里get()的obj为的None,因为不存在实例。
简单讲一下getattribute
魔术方法,这个方法在我们访问一个对象的属性的时候会被无条件调用,详细的细节比如和getattr
, getitem
的区别我会在文章的末尾做一个额外的补充,我们暂时并不深究。
描述符优先级
首先,描述符分为两种:
如果一个对象同时定义了get()和set()方法,则这个描述符被称为
data descriptor
。如果一个对象只定义了get()方法,则这个描述符被称为
non-data descriptor
。
我们对属性进行访问的时候存在下面四种情况:
data descriptor
instance dict
non-data descriptor
getattr()
它们的优先级大小是:
data descriptor > instance dict > non-data descriptor > getattr()
这是什么意思呢?就是说如果实例对象obj中出现了同名的data descriptor->d
和 instance attribute->d
,obj.d
对属性d
进行访问的时候,由于data descriptor具有更高的优先级,Python便会调用type(obj).dict['d'].get(obj, type(obj))
而不是调用obj.dict[‘d’]。但是如果描述符是个non-data descriptor,Python则会调用obj.dict['d']
。
Property
每次使用描述符的时候都定义一个描述符类,这样看起来非常繁琐。Python提供了一种简洁的方式用来向属性添加数据描述符。
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
fget、fset和fdel分别是类的getter、setter和deleter方法。我们通过下面的一个示例来说明如何使用Property:
class Account(object): def init(self): self._acct_num = None def get_acct_num(self): return self._acct_num def set_acct_num(self, value): self._acct_num = value def del_acct_num(self): del self._acct_num acct_num = property(get_acct_num, set_acct_num, del_acct_num, '_acct_num property.')
如果acct是Account的一个实例,acct.acct_num将会调用getter,acct.acct_num = value将调用setter,del acct_num.acct_num将调用deleter。
>>> acct = Account() >>> acct.acct_num = 1000 >>> acct.acct_num 1000
Python也提供了@property
。
class Account(object): def init(self): self._acct_num = None @property # the _acct_num property. the decorator creates a read-only property def acct_num(self): return self._acct_num @acct_num.setter # the _acct_num property setter makes the property writeable def set_acct_num(self, value): self._acct_num = value @acct_num.deleter def del_acct_num(self): del self._acct_num
<a href="http://www.php.cn/wiki/60.html" target="_blank">object🎜 属性
( < a href="http://www.php.cn/wiki/60.html" target="_blank">オブジェクト 🎜 属性) が上記の 3 つのメソッドのいずれかを定義している場合、このクラスを記述シンボル クラスと呼ぶことができます。 🎜🎜記述子の基本🎜🎜 次の例では、RevealAcess
クラスを作成し、get
メソッドを実装します。これで、このクラスを記述子クラスと呼ぶことができます。 🎜class Person(object): def addProperty(self, attribute): # create local setter and getter with a particular attribute name getter = lambda self: self._getProperty(attribute) setter = lambda self, value: self._setProperty(attribute, value) # construct property attribute and add it to the class setattr(self.class, attribute, property(fget=getter, \ fset=setter, \ doc="Auto-generated method")) def _setProperty(self, attribute, value): print("Setting: {} = {}".format(attribute, value)) setattr(self, '_' + attribute, value.title()) def _getProperty(self, attribute): print("Getting: {}".format(attribute)) return getattr(self, '_' + attribute)
get
メソッドの各パラメータの意味を見てみましょう。次の例では、self
です。 > つまり、RevealAccess クラスのインスタンス x、obj
は MyClass クラスのインスタンス m、そして objtype
は、名前が示すように MyClass クラス自体です。出力ステートメントからわかるように、記述子 x
にアクセスする m.x
は get
メソッドを呼び出します。 🎜>>> user = Person() >>> user.addProperty('name') >>> user.addProperty('phone') >>> user.name = 'john smith' Setting: name = john smith >>> user.phone = '12345' Setting: phone = 12345 >>> user.name Getting: name 'John Smith' >>> user.dict {'_phone': '12345', '_name': 'John Smith'}
x
がクラスを通じて直接アクセスされる場合、 obj
接続は直接 None になります。 MyClass のインスタンスがないため、理解しやすくなります。 🎜class StaticMethod(object): def init(self, f): self.f = f def get(self, obj, objtype=None): return self.f
記述子のトリガー
🎜 上の例では、インスタンス属性とクラス属性の観点から記述子の使用法をリストしました。内部原理を注意深く分析してみましょう。 class=" list-paddingleft-2">インスタンス属性
にアクセスすると、基本クラスオブジェクトのgetattributeメソッドが実際に呼び出され、obj .dがに変換されます。 type(obj).dict['d'].get(obj, type(obj))
。 🎜classattribute
にアクセスすると、メタクラス型の getattribute メソッドを呼び出すことと同じになり、cls.d が cls.dict[ 'd' に変換されます。 ].get(None, cls)
の場合、インスタンスがないため、get() の obj は None になります。 🎜getattribute
マジック メソッドについて簡単に説明します。このメソッドは、オブジェクトの属性にアクセスするときに無条件で呼び出されます。詳細については、 などを参照してください。 getattr< /code> と <code>getitem
の違いについては記事の最後に補足しますが、今は詳しく説明しません。 🎜ディスクリプタ 優先度 🎜
🎜 まず、ディスクリプタは 2 つのタイプに分類されます。 🎜- 🎜 オブジェクトが get() メソッドと set() メソッドの両方を定義している場合、この記述子は
データ記述子
と呼ばれます。 🎜 - 🎜 オブジェクトが get() メソッドのみを定義している場合、この記述子は
非データ記述子
と呼ばれます。 🎜
- 🎜データ記述子🎜
- 🎜インスタンス辞書🎜
- 🎜非データ記述子🎜
- 🎜getattr()🎜
class MyClass(object): @StaticMethod def get_x(x): return x print(MyClass.get_x(100)) # output: 100
data descriptor->d
と instanceattribute->d
が存在する場合、obj.d
属性 d
にアクセスすると、データ記述子の優先順位が高いため、Python は type(obj).dict['d'].get(obj, obj.dict['d'] を呼び出す代わりに type(obj ))
を呼び出します。ただし、記述子が非データ記述子の場合、Python は obj.dict['d']
を呼び出します。 🎜🎜プロパティ🎜🎜記述子を使用するたびに記述子クラスを定義するのは、非常に面倒に思えます。 Python は、データ記述子をプロパティに追加する簡潔な方法を提供します。 🎜class ClassMethod(object): def init(self, f): self.f = f def get(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.f(klass, *args) return newfunc
In [1]: class Test(object): ...: def getattribute(self, item): ...: print('call getattribute') ...: return super(Test, self).getattribute(item) ...: def getattr(self, item): ...: return 'call getattr' ...: In [2]: Test().a call getattribute Out[2]: 'call getattr'
class Storage(dict): """ A Storage object is like a dictionary except `obj.foo` can be used in addition to `obj['foo']`. """ def getattr(self, key): try: return self[key] except KeyError as k: raise AttributeError(k) def setattr(self, key, value): self[key] = value def delattr(self, key): try: del self[key] except KeyError as k: raise AttributeError(k) def repr(self): return '<Storage ' + dict.repr(self) + '>'
@property
デコレータも提供します。プロパティ オブジェクトにはゲッター、セッター、削除デコレーター メソッドがあり、対応する装飾された 🎜関数🎜 のアクセサー関数を通じてプロパティのコピーを作成するために使用できます。 🎜>>> s = Storage(a=1) >>> s['a'] 1 >>> s.a 1 >>> s.a = 2 >>> s['a'] 2 >>> del s.a >>> s.a ... AttributeError: 'a'
在运行时创建描述符
我们可以在运行时添加property属性:
class Person(object): def addProperty(self, attribute): # create local setter and getter with a particular attribute name getter = lambda self: self._getProperty(attribute) setter = lambda self, value: self._setProperty(attribute, value) # construct property attribute and add it to the class setattr(self.class, attribute, property(fget=getter, \ fset=setter, \ doc="Auto-generated method")) def _setProperty(self, attribute, value): print("Setting: {} = {}".format(attribute, value)) setattr(self, '_' + attribute, value.title()) def _getProperty(self, attribute): print("Getting: {}".format(attribute)) return getattr(self, '_' + attribute)
>>> user = Person() >>> user.addProperty('name') >>> user.addProperty('phone') >>> user.name = 'john smith' Setting: name = john smith >>> user.phone = '12345' Setting: phone = 12345 >>> user.name Getting: name 'John Smith' >>> user.dict {'_phone': '12345', '_name': 'John Smith'}
静态方法和类方法
我们可以使用描述符来模拟Python中的@<a href="http://www.php.cn/wiki/188.html" target="_blank">static</a>method
和@classmethod
的实现。我们首先来浏览一下下面这张表:
Transformation | Called from an Object | Called from a Class |
---|---|---|
function | f(obj, *args) | f(*args) |
staticmethod | f(*args) | f(*args) |
classmethod | f(type(obj), *args) | f(klass, *args) |
静态方法
对于静态方法f
。c.f
和C.f
是等价的,都是直接查询object.getattribute(c, ‘f’)
或者object.getattribute(C, ’f‘)
。静态方法一个明显的特征就是没有self
变量。
静态方法有什么用呢?假设有一个处理专门数据的容器类,它提供了一些方法来求平均数,中位数等统计数据方式,这些方法都是要依赖于相应的数据的。但是类中可能还有一些方法,并不依赖这些数据,这个时候我们可以将这些方法声明为静态方法,同时这也可以提高代码的可读性。
使用非数据描述符来模拟一下静态方法的实现:
class StaticMethod(object): def init(self, f): self.f = f def get(self, obj, objtype=None): return self.f
我们来应用一下:
class MyClass(object): @StaticMethod def get_x(x): return x print(MyClass.get_x(100)) # output: 100
类方法
Python的@classmethod
和@staticmethod
的用法有些类似,但是还是有些不同,当某些方法只需要得到类的<a href="http://www.php.cn/wiki/231.html" target="_blank">引用</a>
而不关心类中的相应的数据的时候就需要使用classmethod了。
使用非数据描述符来模拟一下类方法的实现:
class ClassMethod(object): def init(self, f): self.f = f def get(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.f(klass, *args) return newfunc
其他的魔术方法
首次接触Python魔术方法的时候,我也被get
, getattribute
, getattr
, getitem
之间的区别困扰到了,它们都是和属性访问相关的魔术方法,其中重写getattr
,getitem
来构造一个自己的集合类非常的常用,下面我们就通过一些例子来看一下它们的应用。
getattr
Python默认访问类/实例的某个属性都是通过getattribute
来调用的,getattribute
会被无条件调用,没有找到的话就会调用getattr
。如果我们要定制某个类,通常情况下我们不应该重写getattribute
,而是应该重写getattr
,很少看见重写getattribute
的情况。
从下面的输出可以看出,当一个属性通过getattribute
无法找到的时候会调用getattr
。
In [1]: class Test(object): ...: def getattribute(self, item): ...: print('call getattribute') ...: return super(Test, self).getattribute(item) ...: def getattr(self, item): ...: return 'call getattr' ...: In [2]: Test().a call getattribute Out[2]: 'call getattr'
应用
对于默认的字典,Python只支持以obj['foo']
形式来访问,不支持obj.foo
的形式,我们可以通过重写getattr
让字典也支持obj['foo']
的访问形式,这是一个非常经典常用的用法:
class Storage(dict): """ A Storage object is like a dictionary except `obj.foo` can be used in addition to `obj['foo']`. """ def getattr(self, key): try: return self[key] except KeyError as k: raise AttributeError(k) def setattr(self, key, value): self[key] = value def delattr(self, key): try: del self[key] except KeyError as k: raise AttributeError(k) def repr(self): return '<Storage ' + dict.repr(self) + '>'
我们来使用一下我们自定义的加强版字典:
>>> s = Storage(a=1) >>> s['a'] 1 >>> s.a 1 >>> s.a = 2 >>> s['a'] 2 >>> del s.a >>> s.a ... AttributeError: 'a'
getitem
getitem用于通过下标[]
的形式来获取对象中的元素,下面我们通过重写getitem
来实现一个自己的list。
class MyList(object): def init(self, *args): self.numbers = args def getitem(self, item): return self.numbers[item] my_list = MyList(1, 2, 3, 4, 6, 5, 3) print my_list[2]
这个实现非常的简陋,不支持slice和step等功能,请读者自行改进,这里我就不重复了。
应用
下面是参考requests库中对于getitem
的一个使用,我们定制了一个忽略属性大小写的字典类。
程序有些复杂,我稍微解释一下:由于这里比较简单,没有使用描述符的需求,所以使用了@property
装饰器来代替,lower_keys
的功能是将实例字典
中的键全部转换成小写并且存储在字典self._lower_keys
中。重写了getitem
方法,以后我们访问某个属性首先会将键转换为小写的方式,然后并不会直接访问实例字典,而是会访问字典self._lower_keys
去查找。赋值/删除操作的时候由于实例字典会进行变更,为了保持self._lower_keys
和实例字典同步,首先清除self._lower_keys
的内容,以后我们重新查找键的时候再调用getitem
的时候会重新新建一个self._lower_keys
。
class CaseInsensitiveDict(dict): @property def lower_keys(self): if not hasattr(self, '_lower_keys') or not self._lower_keys: self._lower_keys = dict((k.lower(), k) for k in self.keys()) return self._lower_keys def _clear_lower_keys(self): if hasattr(self, '_lower_keys'): self._lower_keys.clear() def contains(self, key): return key.lower() in self.lower_keys def getitem(self, key): if key in self: return dict.getitem(self, self.lower_keys[key.lower()]) def setitem(self, key, value): dict.setitem(self, key, value) self._clear_lower_keys() def delitem(self, key): dict.delitem(self, key) self._lower_keys.clear() def get(self, key, default=None): if key in self: return self[key] else: return default
我们来调用一下这个类:
>>> d = CaseInsensitiveDict() >>> d['ziwenxie'] = 'ziwenxie' >>> d['ZiWenXie'] = 'ZiWenXie' >>> print(d) {'ZiWenXie': 'ziwenxie', 'ziwenxie': 'ziwenxie'} >>> print(d['ziwenxie']) ziwenxie # d['ZiWenXie'] => d['ziwenxie'] >>> print(d['ZiWenXie']) ziwenxi
以上がPython 黒魔術記述子の使用の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











PHPは主に手順プログラミングですが、オブジェクト指向プログラミング(OOP)もサポートしています。 Pythonは、OOP、機能、手続き上のプログラミングなど、さまざまなパラダイムをサポートしています。 PHPはWeb開発に適しており、Pythonはデータ分析や機械学習などのさまざまなアプリケーションに適しています。

PHPはWeb開発と迅速なプロトタイピングに適しており、Pythonはデータサイエンスと機械学習に適しています。 1.PHPは、単純な構文と迅速な開発に適した動的なWeb開発に使用されます。 2。Pythonには簡潔な構文があり、複数のフィールドに適しており、強力なライブラリエコシステムがあります。

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

PHPは1994年に発信され、Rasmuslerdorfによって開発されました。もともとはウェブサイトの訪問者を追跡するために使用され、サーバー側のスクリプト言語に徐々に進化し、Web開発で広く使用されていました。 Pythonは、1980年代後半にGuidovan Rossumによって開発され、1991年に最初にリリースされました。コードの読みやすさとシンプルさを強調し、科学的コンピューティング、データ分析、その他の分野に適しています。

VSコードはWindows 8で実行できますが、エクスペリエンスは大きくない場合があります。まず、システムが最新のパッチに更新されていることを確認してから、システムアーキテクチャに一致するVSコードインストールパッケージをダウンロードして、プロンプトとしてインストールします。インストール後、一部の拡張機能はWindows 8と互換性があり、代替拡張機能を探すか、仮想マシンで新しいWindowsシステムを使用する必要があることに注意してください。必要な拡張機能をインストールして、適切に動作するかどうかを確認します。 Windows 8ではVSコードは実行可能ですが、開発エクスペリエンスとセキュリティを向上させるために、新しいWindowsシステムにアップグレードすることをお勧めします。

VSコードはPythonの書き込みに使用でき、Pythonアプリケーションを開発するための理想的なツールになる多くの機能を提供できます。ユーザーは以下を可能にします。Python拡張機能をインストールして、コードの完了、構文の強調表示、デバッグなどの関数を取得できます。デバッガーを使用して、コードを段階的に追跡し、エラーを見つけて修正します。バージョンコントロールのためにGitを統合します。コードフォーマットツールを使用して、コードの一貫性を維持します。糸くずツールを使用して、事前に潜在的な問題を発見します。

VSコードでは、次の手順を通じて端末でプログラムを実行できます。コードを準備し、統合端子を開き、コードディレクトリが端末作業ディレクトリと一致していることを確認します。プログラミング言語(pythonのpython your_file_name.pyなど)に従って実行コマンドを選択して、それが正常に実行されるかどうかを確認し、エラーを解決します。デバッガーを使用して、デバッグ効率を向上させます。

VSコード拡張機能は、悪意のあるコードの隠れ、脆弱性の活用、合法的な拡張機能としての自慰行為など、悪意のあるリスクを引き起こします。悪意のある拡張機能を識別する方法には、パブリッシャーのチェック、コメントの読み取り、コードのチェック、およびインストールに注意してください。セキュリティ対策には、セキュリティ認識、良好な習慣、定期的な更新、ウイルス対策ソフトウェアも含まれます。
