「ディスクリプタ」という概念はよく聞くかもしれませんが、ほとんどのプログラマーはほとんど使用しないため、その原理をよく理解していないかもしれません。 python ビデオ チュートリアル コラムで詳しく紹介します
推奨 (無料): Python ビデオ チュートリアル
ただし、キャリアを向上させたい場合はPython の使用にさらに習熟するには、descriptor
の概念を明確に理解しておく必要があると思います。これは、将来の開発に非常に役立ち、また、将来的には Python の設計をより深く理解できるようになります。
開発プロセス中に記述子を直接使用したことはありませんが、最下位レベルでは非常に頻繁に使用されます。たとえば、次のようになります:
-
function
、バインドされたメソッド
、アンバインドされたメソッド
- インストーラー
property
、staticmethod
、classmethod
これらはすべてご存知ですか?
実際、これらは記述子と密接に関係しています。次の記事で記述子の背後にある動作原理を探ってみましょう。
#記述子とは何ですか?
記述子とは何かを理解する前に、まず参照する例を見つけます。
class A: x = 10print(A.x) # 10
この例は非常に単純です。まずクラス A# で見てみましょう。
## クラス属性 x を定義し、その値を取得します。
クラス属性を直接定義するこの方法に加えて、次のようにクラス属性を定義することもできます:
class Ten: def __get__(self, obj, objtype=None): return 10class A: x = Ten() # 属性换成了一个类print(A.x) # 10今回はクラス属性
x が特定のものではないことがわかります。値ですが、クラス
Ten を介して、
Ten は特定の値を返す
__get__ メソッドを定義します。
クラスの属性をクラスにホストでき、そのような属性は 記述子であると結論付けることができます# #In簡単に言えば、Descriptor
は バインディング動作
Attribute で、これは何を意味するのでしょうか?
behaviour
は何と呼ばれるでしょうか? Behavior
はメソッドです。 したがって、
は次のように理解することもできます。 オブジェクトの属性は特定の値ではなく、定義するメソッドに与えられます。
メソッドを使用して属性を定義すると、どのような利点があるか想像できますか?
メソッドを使用すると、メソッド内に独自のロジックを実装できます。最も単純なのは、次のように、さまざまな条件に従ってメソッド内の属性にさまざまな値を割り当てることができます:
class Age: def __get__(self, obj, objtype=None): if obj.name == 'zhangsan': return 20 elif obj.name == 'lisi': return 25 else: return ValueError("unknow")class Person: age = Age() def __init__(self, name): self.name = name p1 = Person('zhangsan')print(p1.age) # 20p2 = Person('lisi')print(p2.age) # 25p3 = Person('wangwu')print(p3.age) # unknow
この例では、
age クラス属性は別のクラスによってホストされています。このクラスの __get__
では、Person
クラス属性に基づきます。 name
は、age
の値を決定します。 このような例を通して、記述子の使用を通じて、クラス属性の定義方法を簡単に変更できることがわかります。
記述子プロトコル記述子の定義を理解した後、管理プロパティのクラスに焦点を当てます。
実際、クラス属性をクラス上でホストしたい場合、このクラス内に実装されたメソッドを簡単に定義することはできません。「記述子プロトコル」に準拠している必要があります。つまり、次のメソッドは次のとおりです。実装:
- __get__(self, obj, type=None) -> value
さらに、記述子は「データ記述子」と「非データ記述子」に分類できます。
は、非データ記述子と呼ばれる
__get____- のみを定義します。データ
- __get__
の定義に加えて、記述子
はデータ記述子 -
# と呼ばれる
__set__または
__delete__も定義します。 ##それらの違いは何ですか。以下で詳しく説明します。
次に、
メソッドと
__set__ メソッドを含む記述子の例を見てみましょう。 <pre class="brush:php;toolbar:false"># coding: utf8class Age:
def __init__(self, value=20):
self.value = value
def __get__(self, obj, type=None):
print('call __get__: obj: %s type: %s' % (obj, type))
return self.value
def __set__(self, obj, value):
if value type: <class># 20print(Person.age)# call __get__: obj: None type: <class># 20p1.age = 25# call __set__: obj: <__main__.person> value: 25print(p1.age)# call __get__: obj: <__main__.person> type: <class># 25p1.age = -1# ValueError: age must be greater than 0</class></__main__.person></__main__.person></class></class></pre>
この例では、クラス属性 age
は、その値が
クラスに依存する記述子です。 出力から判断すると、
age
属性を取得または変更するときに、
の __get__
と __set__# が呼び出されます。 ## 方法:###<ul>
<li>当调用 <code>p1.age
时,__get__
被调用,参数 obj
是 Person
实例,type
是 type(Person)
Person.age
时,__get__
被调用,参数 obj
是 None
,type
是 type(Person)
p1.age = 25
时,__set__
被调用,参数 obj
是 Person
实例,value
是25p1.age = -1
时,__set__
没有通过校验,抛出 ValueError
其中,调用 __set__
传入的参数,我们比较容易理解,但是对于 __get__
方法,通过类或实例调用,传入的参数是不同的,这是为什么?
这就需要我们了解一下描述符的工作原理。
描述符的工作原理
要解释描述符的工作原理,首先我们需要先从属性的访问说起。
在开发时,不知道你有没有想过这样一个问题:通常我们写这样的代码 a.b
,其背后到底发生了什么?
这里的 a
和 b
可能存在以下情况:
-
a
可能是一个类,也可能是一个实例,我们这里统称为对象 -
b
可能是一个属性,也可能是一个方法,方法其实也可以看做是类的属性
其实,无论是以上哪种情况,在 Python 中,都有一个统一的调用逻辑:
- 先调用
__getattribute__
尝试获得结果 - 如果没有结果,调用
__getattr__
用代码表示就是下面这样:
def getattr_hook(obj, name): try: return obj.__getattribute__(name) except AttributeError: if not hasattr(type(obj), '__getattr__'): raise return type(obj).__getattr__(obj, name)
我们这里需要重点关注一下 __getattribute__
,因为它是所有属性查找的入口,它内部实现的属性查找顺序是这样的:
- 要查找的属性,在类中是否是一个描述符
- 如果是描述符,再检查它是否是一个数据描述符
- 如果是数据描述符,则调用数据描述符的
__get__
- 如果不是数据描述符,则从
__dict__
中查找 - 如果
__dict__
中查找不到,再看它是否是一个非数据描述符 - 如果是非数据描述符,则调用非数据描述符的
__get__
- 如果也不是一个非数据描述符,则从类属性中查找
- 如果类中也没有这个属性,抛出
AttributeError
异常
写成代码就是下面这样:
# 获取一个对象的属性 def __getattribute__(obj, name): null = object() # 对象的类型 也就是实例的类 objtype = type(obj) # 从这个类中获取指定属性 cls_var = getattr(objtype, name, null) # 如果这个类实现了描述符协议 descr_get = getattr(type(cls_var), '__get__', null) if descr_get is not null: if (hasattr(type(cls_var), '__set__') or hasattr(type(cls_var), '__delete__')): # 优先从数据描述符中获取属性 return descr_get(cls_var, obj, objtype) # 从实例中获取属性 if hasattr(obj, '__dict__') and name in vars(obj): return vars(obj)[name] # 从非数据描述符获取属性 if descr_get is not null: return descr_get(cls_var, obj, objtype) # 从类中获取属性 if cls_var is not null: return cls_var # 抛出 AttributeError 会触发调用 __getattr__ raise AttributeError(name)
如果不好理解,你最好写一个程序测试一下,观察各种情况下的属性的查找顺序。
到这里我们可以看到,在一个对象中查找一个属性,都是先从 __getattribute__
开始的。
在 __getattribute__
中,它会检查这个类属性是否是一个描述符,如果是一个描述符,那么就会调用它的 __get__
方法。但具体的调用细节和传入的参数是下面这样的:
- 如果
a
是一个实例,调用细节为:
type(a).__dict__['b'].__get__(a, type(a))复制代码
- 如果
a
是一个类,调用细节为:
a.__dict__['b'].__get__(None, a)复制代码
所以我们就能看到上面例子输出的结果。
数据描述符和非数据描述符
了解了描述符的工作原理,我们继续来看数据描述符和非数据描述符的区别。
从定义上来看,它们的区别是:
- 只定义了
__get___
,叫做非数据描述符 - 除了定义
__get__
之外,还定义了__set__
或__delete__
,叫做数据描述符
此外,我们从上面描述符调用的顺序可以看到,在对象中查找属性时,数据描述符要优先于非数据描述符调用。
在之前的例子中,我们定义了 __get__
和 __set__
,所以那些类属性都是数据描述符。
我们再来看一个非数据描述符的例子:
class A: def __init__(self): self.foo = 'abc' def foo(self): return 'xyz'print(A().foo) # 输出什么? 复制代码
这段代码,我们定义了一个相同名字的属性和方法 foo
,如果现在执行 A().foo
,你觉得会输出什么结果?
答案是 abc
。
为什么打印的是实例属性 foo
的值,而不是方法 foo
呢?
这就和非数据描述符有关系了。
我们执行 dir(A.foo)
,观察结果:
print(dir(A.foo))# [... '__get__', '__getattribute__', ...]复制代码
看到了吗?A
的 foo
方法其实实现了 __get__
,我们在上面的分析已经得知:只定义 __get__
方法的对象,它其实是一个非数据描述符,也就是说,我们在类中定义的方法,其实本身就是一个非数据描述符。
所以,在一个类中,如果存在相同名字的属性和方法,按照上面所讲的 __getattribute__
中查找属性的顺序,这个属性就会优先从实例中获取,如果实例中不存在,才会从非数据描述符中获取,所以在这里优先查找的是实例属性 foo
的值。
到这里我们可以总结一下关于描述符的相关知识点:
- 描述符必须是一个类属性
-
__getattribute__
是查找一个属性(方法)的入口 -
__getattribute__
定义了一个属性(方法)的查找顺序:数据描述符、实例属性、非数据描述符、类属性 - 如果我们重写了
__getattribute__
方法,会阻止描述符的调用 - 所有方法其实都是一个非数据描述符,因为它定义了
__get__
描述符的使用场景
了解了描述符的工作原理,那描述符一般用在哪些业务场景中呢?
在这里我用描述符实现了一个属性校验器,你可以参考这个例子,在类似的场景中去使用它。
首先我们定义一个校验基类 Validator
,在 __set__
方法中先调用 validate
方法校验属性是否符合要求,然后再对属性进行赋值。
class Validator: def __init__(self): self.data = {} def __get__(self, obj, objtype=None): return self.data[obj] def __set__(self, obj, value): # 校验通过后再赋值 self.validate(value) self.data[obj] = value def validate(self, value): pass 复制代码
接下来,我们定义两个校验类,继承 Validator
,然后实现自己的校验逻辑。
class Number(Validator): def __init__(self, minvalue=None, maxvalue=None): super(Number, self).__init__() self.minvalue = minvalue self.maxvalue = maxvalue def validate(self, value): if not isinstance(value, (int, float)): raise TypeError(f'Expected {value!r} to be an int or float') if self.minvalue is not None and value self.maxvalue: raise ValueError( f'Expected {value!r} to be no more than {self.maxvalue!r}' )class String(Validator): def __init__(self, minsize=None, maxsize=None): super(String, self).__init__() self.minsize = minsize self.maxsize = maxsize def validate(self, value): if not isinstance(value, str): raise TypeError(f'Expected {value!r} to be an str') if self.minsize is not None and len(value) self.maxsize: raise ValueError( f'Expected {value!r} to be no bigger than {self.maxsize!r}' )复制代码
最后,我们使用这个校验类:
class Person: # 定义属性的校验规则 内部用描述符实现 name = String(minsize=3, maxsize=10) age = Number(minvalue=1, maxvalue=120) def __init__(self, name, age): self.name = name self.age = age # 属性符合规则 p1 = Person('zhangsan', 20)print(p1.name, p1.age)# 属性不符合规则 p2 = person('a', 20)# ValueError: Expected 'a' to be no smaller than 3p3 = Person('zhangsan', -1)# ValueError: Expected -1 to be at least 1复制代码
现在,当我们对 Person
实例进行初始化时,就可以校验这些属性是否符合预定义的规则了。
function与method
我们再来看一下,在开发时经常看到的 function
、unbound method
、bound method
它们之间到底有什么区别?
来看下面这段代码:
class A: def foo(self): return 'xyz'print(A.__dict__['foo']) # <function>print(A.foo) # <unbound>print(A().foo) # <bound>>复制代码</bound></unbound></function>
从结果我们可以看出它们的区别:
-
function
准确来说就是一个函数,并且它实现了__get__
方法,因此每一个function
都是一个非数据描述符,而在类中会把function
放到__dict__
中存储 - 当
function
被实例调用时,它是一个bound method
- 当
function
被类调用时, 它是一个unbound method
function
是一个非数据描述符,我们之前已经讲到了。
而 bound method
和 unbound method
的区别就在于调用方的类型是什么,如果是一个实例,那么这个 function
就是一个 bound method
,否则它是一个 unbound method
。
property/staticmethod/classmethod
我们再来看 property
、staticmethod
、classmethod
。
这些装饰器的实现,默认是 C 来实现的。
其实,我们也可以直接利用 Python 描述符的特性来实现这些装饰器,
property
的 Python 版实现:
class property: def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self.fget if self.fget is None: raise AttributeError(), "unreadable attribute" return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError, "can't set attribute" return self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, "can't delete attribute" return self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)复制代码
staticmethod
的 Python 版实现:
class staticmethod: def __init__(self, func): self.func = func def __get__(self, obj, objtype=None): return self.func 复制代码
classmethod
的 Python 版实现:
class classmethod: def __init__(self, func): self.func = func def __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.func(klass, *args) return newfunc 复制代码
除此之外,你还可以实现其他功能强大的装饰器。
由此可见,通过描述符我们可以实现强大而灵活的属性管理功能,对于一些要求属性控制比较复杂的场景,我们可以选择用描述符来实现。
总结
这篇文章我们主要讲了 Python 描述符的工作原理。
首先,我们从一个简单的例子了解到,一个类属性是可以托管给另外一个类的,这个类如果实现了描述符协议方法,那么这个类属性就是一个描述符。此外,描述符又可以分为数据描述符和非数据描述符。
之后我们又分析了获取一个属性的过程,一切的入口都在 __getattribute__
中,这个方法定义了寻找属性的顺序,其中实例属性优先于数据描述符调用,数据描述符要优先于非数据描述符调用。
さらに、メソッドは実際には非データ記述子であることもわかりました。クラス内に同じ名前のインスタンス属性とメソッドを定義すると、__getattribute__
の属性検索順序に従って、インスタンス属性が優先されます。
最後に、function
と method
の違いと、Python を使用して property
と staticmethod## を実装する方法を分析しました。 #,
classmethod デコレータ。
以上がPython 記述子の意味を紹介します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

Pythonは、自動化、スクリプト、およびタスク管理に優れています。 1)自動化:OSやShutilなどの標準ライブラリを介してファイルバックアップが実現されます。 2)スクリプトの書き込み:Psutilライブラリを使用してシステムリソースを監視します。 3)タスク管理:スケジュールライブラリを使用してタスクをスケジュールします。 Pythonの使いやすさと豊富なライブラリサポートにより、これらの分野で優先ツールになります。

限られた時間でPythonの学習効率を最大化するには、PythonのDateTime、時間、およびスケジュールモジュールを使用できます。 1. DateTimeモジュールは、学習時間を記録および計画するために使用されます。 2。時間モジュールは、勉強と休息の時間を設定するのに役立ちます。 3.スケジュールモジュールは、毎週の学習タスクを自動的に配置します。

PythonはゲームとGUI開発に優れています。 1)ゲーム開発は、2Dゲームの作成に適した図面、オーディオ、その他の機能を提供し、Pygameを使用します。 2)GUI開発は、TKINTERまたはPYQTを選択できます。 TKINTERはシンプルで使いやすく、PYQTは豊富な機能を備えており、専門能力開発に適しています。

Pythonは、データサイエンス、Web開発、自動化タスクに適していますが、Cはシステムプログラミング、ゲーム開発、組み込みシステムに適しています。 Pythonは、そのシンプルさと強力なエコシステムで知られていますが、Cは高性能および基礎となる制御機能で知られています。

2時間以内にPythonの基本的なプログラミングの概念とスキルを学ぶことができます。 1.変数とデータ型、2。マスターコントロールフロー(条件付きステートメントとループ)、3。機能の定義と使用を理解する4。

Pythonは、Web開発、データサイエンス、機械学習、自動化、スクリプトの分野で広く使用されています。 1)Web開発では、DjangoおよびFlask Frameworksが開発プロセスを簡素化します。 2)データサイエンスと機械学習の分野では、Numpy、Pandas、Scikit-Learn、Tensorflowライブラリが強力なサポートを提供します。 3)自動化とスクリプトの観点から、Pythonは自動テストやシステム管理などのタスクに適しています。

2時間以内にPythonの基本を学ぶことができます。 1。変数とデータ型を学習します。2。ステートメントやループの場合などのマスター制御構造、3。関数の定義と使用を理解します。これらは、簡単なPythonプログラムの作成を開始するのに役立ちます。

10時間以内にコンピューター初心者プログラミングの基本を教える方法は?コンピューター初心者にプログラミングの知識を教えるのに10時間しかない場合、何を教えることを選びますか...


ホットAIツール

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

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

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

ZendStudio 13.5.1 Mac
強力な PHP 統合開発環境

PhpStorm Mac バージョン
最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

SecLists
SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

DVWA
Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、

VSCode Windows 64 ビットのダウンロード
Microsoft によって発売された無料で強力な IDE エディター
