Python复习--对象相关--__dict__与__get__
python复习–描述符相关–get / set / delete
你看到的这些:
__dict__
__get__
__init__
统称 魔术方法(dunder methods)
本质:Python在背后偷偷调用的普通函数
一. __dict__是什么
dict 就是对象用来存属性的“字典”
1.1 普通对象的__dict__
class A:
pass
a = A()
a.x = 10
a.y = 20
print(a.__dict__)
输出:
{'x': 10, 'y': 20}
示例:
a.x = 10
等价于:
a.__dict__['x'] = 10
1.2 类对象也有__dict__
class A:
"""示例类"""
z = 100
def foo(self):
pass
print(A.__dict__)
你会看到:
{'__module__': '__main__',
'z': 100,
'foo': <function A.foo at 0x7f8b8c0a9e50>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>,
'__doc__': '示例类'}
-
‘module’: ‘main’
表示 类 A 定义所在的模块
当前代码在主程序中运行,因此是 main
正常且必然存在 -
‘z’: 100
属于 类属性(Class Attribute)
属于类本身,所有实例共享
这是你写的代码产生的结果 -
‘foo’: <function A.foo at 0x7f8b8c0a9e50>
类中定义的 方法
在类的 dict 中,方法本质是 函数对象
当通过实例访问时,才会被“绑定”为方法A.foo # 函数 A().foo() # 绑定方法地址 0x7f8b8c0a9e50 是函数在内存中的位置,每次运行可能不同
-
‘dict’: <attribute ‘dict’ of ‘A’ objects>
用于存放 实例对象的属性
当你创建实例时:a = A() a.x = 10 print(a.__dict__) # {'x': 10}类本身会自动拥有该属性
-
‘weakref’: <attribute ‘weakref’ of ‘A’ objects>
用于支持 弱引用(weak reference)
主要用于内存管理和垃圾回收
普通开发中很少直接使用
Python 自动添加 -
doc: ‘示例类’
类的文档字符串(docstring)
来自类定义第一行的三引号字符串若没有添加注释,这里会显示’doc’: None
二. __get__是什么
__get__不是“给你用的函数”,
而是:当对象作为「类属性」被访问时,
Python 自动调用的一个“加工钩子”。
注:例,A类被实例化后 a=A(); a.xxx 语法中,xxx 永远叫“属性”,语法层面统一叫:属性访问
2.1 一个普通对象,没有__get__
class A:
z = 100 # 类属性(普通 int)
a = A() # 先实例化
print(a.z) # 再访问
访问:
a.z
Python 找到 A.dict[‘z’] == 100
100 是普通对象
没有 get 方法
直接返回 100
普通对象(int / str / list)
没有 get
不会被加工
直接返回
2.2 function ,有__get__
class A:
def f(self):
pass
a = A() # 实例化
print(A.f)
print(a.f)
-
f 到底存在哪
f 只存在一份,存放在类 A 里
A.f 和 a.f 访问的是“同一个函数对象”class A: def f(self): pass类创建完成后:
A.__dict__['f'] ---> function 对象只有这一份,存放在类 A 里,不在实例里
-
那 A.f 是什么?
class A: def f(self): pass a = A() # 实例化 print(A.f)行为是:
function.__get__(f, None, A)因为:
f 是 function
function 实现了 get
当通过类访问时:A.f ---> 原始 function(未绑定)你可以理解为:
“还没绑定 self 的函数”
你可以验证:
class A: def f(self): pass a = A() # 实例化 print(A.f) print(type(A.f)) # <class 'function'> -
那 a.f 是什么?
class A: def f(self): pass a = A() # 实例化 print(a.f)行为是:
function.__get__(f, a, A)返回的是:
<bound method A.f of a>这是一个 method 对象
它里面已经偷偷绑定了 self = a你可以验证:
class A: def f(self): pass a = A() # 实例化 print(a.f) print(type(a.f)) # <class 'method'>结论(非常重要):
方法不是新定义的东西
它是 function 被 get 加工后的结果 -
用一张表帮你彻底区分
表达式 从哪里找 f 返回的是什么 A.f A.__dict__[‘f’] function(未绑定) a.f A.__dict__[‘f’] method(已绑定 self) f 是类属性;
A.f 是“未绑定函数”;
a.f 是“绑定了实例的 method”。方法不是存在于实例里的东西,
而是类里的函数在“被访问时”动态绑定出来的。 -
为什么 Python 要这样设计
因为这样可以:A.f(a) # 手动传 self a.f() # 自动绑定 self这两句 完全等价:
A.f(a) == a.f()因为 Python 想做到:
函数写在类里
用的时候自动拿到实例
但你不需要手动传 selfget 就是完成这件事的“隐藏机关”。
否则你每天都要写:
A.f(a)
2.3. 写一个“带 get 的东西
例子
class MyDescriptor:
def __get__(self, obj, objtype=None):
print("属性被访问了")
return 123
class A:
x = MyDescriptor()
a = A()
print(a.x)
输出:
属性被访问了
123
你现在亲眼看到 get 被调用了
这段代码证明了三件事
1.x 在类里
2.x 实现了 get
3.所以 a.x 一定会触发 get
三. 访问属性时,Python 做了什么
你写:
class A:
z = 100
def f(self):
return "normal method"
@staticmethod
def s():
return "static method"
a = A()
print(a.z)
print(a.f)
print(a.s)
Python 内部做的是:
def object_getattr(obj, name):
# 1.先从类中找(注意:不是实例)
cls = type(obj)
# —— 数据描述符优先 ——
if name in cls.__dict__:
attr = cls.__dict__[name]
if has_data_descriptor(attr):
return attr.__get__(obj, cls)
# 2.再从实例 __dict__ 中找
if name in obj.__dict__:
return obj.__dict__[name]
# —— 非数据描述符 ——
if name in cls.__dict__:
attr = cls.__dict__[name]
if has_non_data_descriptor(attr):
return attr.__get__(obj, cls)
else:
return attr
# 3. 还找不到就报错
raise AttributeError(name)
这就是 Python 访问 a.z 时“真正干的事”
执行 a.z 时:
-
第一步:看类
A.__dict__['z'] == 100100 有没有 get?
不是描述符
跳过第 1 步 -
第二步:实例
a.__dict__ # {}没有 z
-
第三步:类(非描述符)
return 100
所以 a.z == 100
执行 a.f 时:
-
第一步:类
A.__dict__['f'] # <function f>function 有没有 get?
但它是非数据描述符
第 1 步(数据描述符)不处理 -
第二步:实例
a.__dict__ # {}没有 f
-
第三步:类(非数据描述符)
attr = A.__dict__['f'] return attr.__get__(a, A)等价于:
bound_method = MethodType(A.f, a)所以:
type(a.f) # method
执行 a.s 时:
-
第一步:类
A.__dict__['s'] # staticmethod 对象staticmethod 有没有 get?
但它是 非数据描述符
第 1 步跳过 -
第二步:实例
a.__dict__ # {}没有 s
-
第三步:类(非数据描述符)
attr = A.__dict__['s'] return attr.__get__(a, A)而 staticmethod.get 做的是:
return 原始函数所以:
type(a.s) # function
把“对照结果”汇成一张表
| 访问 | 命中哪一步 | 返回结果 | 类型 |
|---|---|---|---|
| a.z | 第 3 步 | 100 | int |
| a.f | 第 3 步 + get | 绑定方法 | method |
| a.f | 第 3 步 + get | 原始函数 | function |
四. 可以这样理解整个流程(非常重要)
a.f
↓
找 A.__dict__['f']
↓
看有没有 __get__
↓
有 → 调 __get__(a, A)
↓
返回结果
五. 总结
dict:对象存属性的地方
get:属性被访问时自动执行的钩子
方法 / staticmethod 的区别,本质就是 get 行为不同
更多推荐


所有评论(0)