搜索
查看: 62|回复: 0

[网站] 深入理解Python虚拟机中描述器的实现原理

[复制链接]
发表于 2023-5-8 21:51:07 | 显示全部楼层 |阅读模式
这篇文章主要给大家介绍一个我们在使用类的时候经常使用但是却很少在意的黑科技——描述器的实现原理,文中的示例代码讲解详细,需要的可以参考一下
  |8 r4 _& h6 {# f" W

/ C. ~* W% u8 t: G7 h! O
4 J6 W; M$ Y8 e5 n/ D0 H
+
目录" Y  b# ]. B! a( m: i

在本篇文章当中主要给大家介绍一个我们在使用类的时候经常使用但是却很少在意的黑科技——描述器,在本篇文章当中主要分析描述器的原理,以及介绍使用描述器实现属性访问控制和 orm 映射等等功能!在后面的文章当中我们将继续去分析描述器的实现原理。

# M3 ^; q; U9 m! {  ^6 F. j% D
描述器的基本用法

描述器是一个实现了 __get__、__set__ 或 __delete__ 中至少一个方法的 Python 类。这些方法分别用于在属性被访问、设置或删除时调用。当一个描述器被定义为一个类的属性时,它可以控制该属性的访问、修改和删除。

下面是一个示例,演示了如何定义一个简单的描述器:

  1. class Descriptor:
    0 d! B. ^# V) E. a# x
  2.     def __get__(self, instance, owner):
    9 D& @4 P# n% I' y+ ?- u
  3.         print(f"Getting {self.__class__.__name__}")
    8 l; W% C* n, `2 u
  4.         return instance.__dict__.get(self.attrname)
    ( q" h0 U  J, N+ ^, X

  5. . W  V0 M$ s" j; m# P- i5 @/ t
  6.     def __set__(self, instance, value):: |% H3 [0 k! G
  7.         print(f"Setting {self.__class__.__name__}")2 l: y7 p6 q! x, [6 H1 e+ w
  8.         instance.__dict__[self.attrname] = value" k: ~7 r3 ~8 s6 T- |  Y

  9. 2 R- f- R' Q9 r# J4 K2 K" y1 j% p
  10.     def __delete__(self, instance):- g* Z+ w9 G% |* C8 F4 H
  11.         print(f"Deleting {self.__class__.__name__}")/ ?; e+ u  O" X( Y8 ]
  12.         del instance.__dict__[self.attrname]
    ! P( p, v& F7 Y. J& D
  13. ' S0 |3 f- Q' g. {* C# G$ m$ J/ C
  14.     def __set_name__(self, owner, name):8 v5 h2 y2 f2 H  H: a1 |; [# _
  15.         self.attrname = name
复制代码
3 s8 B0 k& a8 B

在这个例子中,我们定义了一个名为 Descriptor 的描述器类,它有三个方法:__get__、__set__ 和 __delete__。当我们在另一个类中使用这个描述器时,这些方法将被调用,以控制该类的属性的访问和修改。

要使用这个描述器,我们可以在另一个类中将其定义为一个类属性:

  1. class MyClass:! D7 Z" F! e) f0 `1 v4 V7 r' f* `
  2.     x = Descriptor()
复制代码

现在,我们可以创建一个 MyClass 对象并访问其属性:

>>> obj = MyClass()
+ k& ?; k; J4 `; W6 [, h+ C7 L>>> obj.x = 1
8 ~8 |5 }) I$ h9 t. B' ~- y1 uSetting Descriptor
8 ]* \2 u/ _) B* U7 P1 Y% t>>> obj.x5 Z" ?  F+ ~$ Q# ?
Getting Descriptor9 Z& h( E% k9 h3 h- H9 g2 K
18 z/ }0 m  j6 ]( @
>>> del obj.x$ Q- w  a; F- e
Deleting Descriptor
' V2 @. v  G/ e8 Y>>> obj.x
6 e1 d& g! D2 t7 S2 a6 {9 SGetting Descriptor

在这个例子中,我们首先创建了一个 MyClass 对象,并将其 x 属性设置为 1。然后,我们再次访问 x 属性时,会调用 __get__ 方法并返回 1。最后,我们删除了 x 属性,并再次访问它时,会调用 __get__ 方法并返回 None。从上面的输出结果可以看到对应的方法都被调用了,这是符合上面对描述器的定义的。如果一个类对象不是描述器,那么在使用对应的属性的时候是不会调用__get__、__set__ 和 __delete__三个方法的。比如下面的代码:

  1. class NonDescriptor(object):+ i+ A( Z* n, q0 _7 X
  2.     pass
    . U$ e  a1 D7 `5 ~' p' y

  3. % U/ U) R$ m: g

  4. 4 d: t# n% D: k0 q& J( _
  5. class MyClass():
    ! ^6 U9 C7 f! w, ~0 f/ V

  6. 5 [) x( [' ~! H" b" _8 ~4 F6 B+ X
  7.     nd = NonDescriptor()$ i% Z% l8 u; x! U
  8. + R9 a0 V: Z# ~+ ~9 K- z- S& I

  9. ! B  _; D# f  g8 A: n: x* G
  10. if __name__ == '__main__':
    . l5 D" X0 D4 z& y& j/ h  g5 v' A0 h
  11.     a = MyClass()
    * n; ]0 o- n' ~' ^
  12.     print(a.nd)
复制代码

5 S4 r3 M, g. p9 S$ z) j

上面的代码输出结果如下所示:

<__main__.NonDescriptor object at 0x1012cce20>

从上面程序的输出结果可以知道,当使用一个非描述器的类属性的时候是不会调用对应的方法的,而是直接得到对应的对象。

: b, @1 ]$ X' D; D% X
描述器的实现原理

描述器的实现原理可以用以下三个步骤来概括:

  • 当一个类的属性被访问时,Python 解释器会检查该属性是否是一个描述器。如果是,它会调用描述器的 __get__ 方法,并将该类的实例作为第一个参数,该实例所属的类作为第二个参数,并将属性名称作为第三个参数传递给 __get__ 方法。
  • 当一个类的属性被设置时,Python 解释器会检查该属性是否是一个描述器。如果是,它会调用描述器的 __set__ 方法,并将该类的实例作为第一个参数,设置的值作为第二个参数,并将属性名称作为第三个参数传递给 __set__ 方法。
  • 当一个类的属性被删除时,Python 解释器会检查该属性是否是一个描述器。如果是,它会调用描述器的 __delete__ 方法,并将该类的实例作为第一个参数和属性名称作为第二个参数传递给 __delete__ 方法。, |% w# u6 A$ x/ M1 c/ w% g; X

在描述器的实现中,通常还会使用 __set_name__ 方法来在描述器被绑定到类属性时设置属性名称。这使得描述器可以在被多个属性使用时,正确地识别每个属性的名称。

现在来仔细了解一下上面的几个函数的参数,我们以下面的代码为例子进行说明:

  1. class Descriptor(object):. x; ~/ w/ I3 W! t$ _' y6 {
  2.     def __set_name__(self, obj_type, attr_name):8 w8 H+ [. o, S4 B5 I
  3.         print(f"__set_name__ : {obj_type } {attr_name = }")
    - |- I$ W+ p, t: K1 a6 C0 x
  4.         return "__set_name__"# I( {6 F# f, k
  5.     def __get__(self, obj, obj_type):1 r5 x$ h) v: W( f# {& H  Q
  6.         print(f"__get__ : {obj = } { obj_type = }")6 w+ O$ C9 Z# P- t5 R: S
  7.         return "__get__"8 X' O3 [6 g+ Q( v; r9 l
  8.     def __set__(self, instance, value):
    9 a( |$ C; w& y* A
  9.         print(f"__set__ : {instance = } {value = }")
    ; R7 C! t% X3 @$ j& d
  10.         return "__set__"& @& k4 {3 L8 g  Y
  11.     def __delete__(self, obj):1 B9 A- N& g7 s0 g0 M
  12.         print(f"__delete__ : {obj = }")2 S6 U. U+ ~+ ]4 M2 ?/ v" X
  13.         return "__delete__". I/ M, `9 }- @2 \& j
  14. class MyClass(object):) @3 H" g( \0 M
  15.     des = Descriptor()
    ) ~+ r6 Z, {4 h0 n
  16. if __name__ == '__main__':
      ?' r. Q+ E7 v9 D$ @. i, v
  17.     a = MyClass(); w- \: e- p- ?' b' S$ \
  18.     _ = MyClass.des
    $ U6 Y$ Y  D" G
  19.     _ = a.des* r: L0 ?& ?! I% ]5 J9 l; E
  20.     a.des = "hello"
    1 y5 K; l$ B; D8 V+ Q
  21.     del a.des
复制代码
6 q0 [1 @& g1 h! j

上面的代码输入结果如下所示:

__set_name__ : <class '__main__.MyClass'> attr_name = 'des'% z) m9 ?9 C7 X
__get__ : obj = None  obj_type = <class '__main__.MyClass'>: U4 O( R: A8 f2 U; u. W" f
__get__ : obj = <__main__.MyClass object at 0x1054abeb0>  obj_type = <class '__main__.MyClass'>
# P( U7 u/ g3 A( ?__set__ : instance = <__main__.MyClass object at 0x1054abeb0> value = 'hello'
( j/ y" J! }. w" {__delete__ : obj = <__main__.MyClass object at 0x1054abeb0>

  • __set_name__ 这个函数一共有两个参数传入的参数第一个参数是使用描述器的类,第二个参数是使用这个描述器的类当中使用的属性名字,在上面的例子当中就是 "des" 。
  • __get__,这个函数主要有两个参数,一个是使用属性的对象,另外一个是对象的类型,如果是直接使用类名使用属性的话,obj 就是 None,比如上面的 MyClass.des 。
  • __set__,这个函数主要有两个参数一个是对象,另外一个是需要设置的值。
  • __delete__,这函数有一个参数,就是传入的对象,比如 del a.des 传入的就是对象 a 。. V3 f9 f' w' s' T  b) \( [/ C) W$ i
5 S1 X7 ~$ a0 X1 `( h+ B/ W) y, l: m
描述器的应用场景

描述器在 Python 中有很多应用场景。以下是其中的一些示例:

% d! U$ {. P  t
                               
登录/注册后可看大图


1 N4 G/ t3 Y) j6 w% ^: j/ ^4 E实现属性访问控制

通过使用描述器,可以实现对类属性的访问控制,例如只读属性、只写属性、只读/只写属性等。通过在 __get__ 和 __set__ 方法中添加相应的访问控制逻辑,可以限制对类属性的访问和修改。

  1. class ReadOnly:
    ' x1 x4 F" K: {1 w; L3 k6 M+ l/ T
  2.     def __init__(self, value):8 D. R" w1 U- b/ A( R2 L2 K3 ^2 ?
  3.         self._value = value
    4 ?' j. D6 u: [
  4.     def __get__(self, instance, owner):
      P) W, I8 D8 R& \0 r, f: k# J
  5.         return self._value# U6 R9 P% E8 H1 [5 C+ K5 Z) s
  6.     def __set__(self, instance, value):$ O# [7 r4 B  u
  7.         raise AttributeError("Read only attribute")
    / S/ E6 l# u4 v
  8. class MyClass:8 h6 y: u& R% {8 {, d
  9.     read_only_prop = ReadOnly(42)
    # P3 z1 ?4 u, V8 f2 R
  10.     writeable_prop = None
    8 {" w8 {0 I! M" D
  11. my_obj = MyClass()" S6 x$ e9 Z5 e# h
  12. print(my_obj.read_only_prop)  # 42
    ( \; z+ C4 i1 p5 o: i1 p7 g
  13. my_obj.writeable_prop = "hello") @( x6 t& L8 o. g  g# m3 l# d
  14. print(my_obj.writeable_prop)  # hello/ o+ M4 j5 Y5 X: m- w( O/ r
  15. my_obj.read_only_prop = 100  # raises AttributeError
复制代码

: J! R- Z5 k1 c5 f) k

6 ?2 J) j$ d% `! W8 U  A9 }1 c" ?
/ X+ t  o. ]0 ]9 a/ i) z6 I
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

热议作品
精华帖子排行
精彩推荐

虾皮社区,成立十年了!

站长自己也搞不懂想做个什么,反正就是一直在努力的做!

Copyright © 2007-2019 xp6.org Powered by Discuz

QQ|小黑屋|手机版|Archiver|虾皮社区 ( 鲁ICP备13006813号-1 ) 鲁公网安备 37021102000261号 |网站地图
返回顶部 返回列表