```python
class Person: pass p = Person()
print('__getattribute__' in p.__dict__) # False
print('__getattribute__' in dir(p)) # True
print(p.__getattribute__ is object.__getattribute__) # True (或者 Person.__getattribute__)
```
因为在 Python 中,点号 . 本身就是一个运算符(称为“属性访问运算符”),重写这些魔术方法,本质上就是重新定义了 . 运算符在自定义对象上的行为。在大多数编程语言中,我们通常认为 +、-、*、== 才是运算符。但在 Python 的底层设计中,点号 .(属性访问)和中括号 [](索引/键值访问)同样被视为运算符。
a + b 时,Python 底层调用的是 a.__add__(b)。p["name"] 时,Python 底层调用的是 p.__getitem__("name")。p.name 时,Python 底层调用的是 p.__getattribute__("name")(如果找不到,再调用 p.__getattr__("name"))。. 是运算符,那么通过实现 __getattr__ 等方法来改变 . 的默认行为,自然也就属于“运算符重载”的范畴你执行 dir(p) 时,dir() 函数并不是只盯着 p 自己看,它会顺藤摸瓜地把 p 的整个“家族族谱”都翻一遍:
p 自己身上有什么(实例属性)。p 的父亲 person 类身上有什么(类属性)。p 的老祖宗 object 类身上有什么。因为老祖宗 object 类里面定义了 __getattribute__,所以 dir() 在查阅族谱时看到了它,并把它列在了最终的清单里。
class person(): pass
p = person()
p.name = 'abc'
Python 所有的类都默认继承自 object,object 里面已经写好了一个默认的 __setattr__ 方法。这个方法的作用就是简单粗暴地把 'name': 'abc' 塞进对象的 __dict__(属性字典)里。因为你没有去“改变”或“重新定义”这个默认行为,只是顺其自然地使用了它,所以这绝对不叫重载。重载的前提是:你必须自己写代码,去覆盖(Override)或改变原有的默认行为。如果你想把 p.name = 'abc' 变成重载,你必须在类里面显式地定义 __setattr__ 方法:
class person():
# 自己定义了 __setattr__,这才是重载!
def __setattr__(self, key, value):
print(f"【拦截成功】有人试图给 {key} 赋值为 {value}!")
# 增加一点自定义逻辑,比如把名字全变成大写
if key == 'name':
value = value.upper()
# 最后还是要调用父类的默认方法把值存起来,否则属性存不进去
super().__setattr__(key, value)
p = person()
p.name = 'abc'
# 输出: 【拦截成功】有人试图给 name 赋值为 abc!
print(p.name)
# 输出: ABC (因为我们在重载方法里把它变成大写了)
__setattr__:Python 用默认方式默默把值存了。(不是重载)__setattr__:你接管了 = 和 . 组合而成的赋值操作,加入了你自己的逻辑(比如打印日志、转大写、类型检查等),这就叫运算符重载。(是重载)