Files
python-book/02.面向对象/06.反射与双下方法.md
2025-09-16 10:14:02 +08:00

711 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 反射与双下方法
## 反射
- python面向对象中的反射
- 指程序在**运行时**动态地获取对象(包括类、模块、实例等)的结构信息(如属性、方法、类型),并能动态地操作这些结构(如调用方法、修改属性、创建对象)的能力。简单来说,反射让程序能在运行时“自省”——了解自身的组成,并根据需要灵活调整行为。
- 反射的常见应用场景
- **动态配置加载**根据配置文件如JSON、YAML动态设置对象属性。例如通过`setattr`将配置中的`debug=True`映射到对象的`debug`属性。
- **插件化架构**:通过`importlib.import_module`动态加载插件模块,再用`getattr`获取插件类的实例并调用其方法。例如,电商系统可动态加载“限时抢购”“团购”等插件订单类。
- **通用工具函数**:编写适用于不同对象的通用工具。例如,`print_object_info`函数通过`dir`获取对象所有属性,再用`getattr`打印属性值。
- **ORM框架**通过反射将数据库表的列映射到Python类的属性。例如Django ORM通过反射获取模型的字段信息动态生成SQL语句。
四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
**对对象的反射**
```python
class Foo:
f = '类的静态变量'
def __init__(self,name,age):
self.name=name
self.age=age
def say_hi(self):
print('hi,%s'%self.name)
obj=Foo('张三',73)
# 检测是否含有某属性
print(hasattr(obj,'name'))
print(hasattr(obj,'say_hi'))
# 获取属性
n=getattr(obj,'name')
print(n)
func=getattr(obj,'say_hi')
func()
print(getattr(obj,'aaaaaaaa','不存在啊')) # 报错
# 设置属性
setattr(obj,'food','面条')
print(getattr(obj, 'food'))
setattr(obj, 'eat', lambda self: self.name + "在吃" + self.food)
print(obj.__dict__)
print(obj.eat(obj))
# 删除属性
delattr(obj,'age')
delattr(obj,'eat')
# delattr(obj,'eat111') # 不存在,则报错
print(obj.__dict__)
```
**对类的反射**
```python
class Foo(object):
staticField = "test"
def __init__(self):
self.name = '张三'
def func(self):
return 'func'
@staticmethod
def bar():
return 'bar'
print(getattr(Foo, 'staticField'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))
```
当前模块的反射
```python
import sys
def s1():
print('s1')
def s2():
print('s2')
this_module = sys.modules[__name__]
print(hasattr(this_module, 's1'))
print(getattr(this_module, 's2'))
```
其他模块的反射
程序目录:
module_test.py
test.py
当前文件:
test.py
```python
import module_test as obj
obj.test()
print(hasattr(obj,'test'))
getattr(obj,'test')()
```
举例:
使用反射前
```python
class User:
def login(self):
print('欢迎来到登录页面')
def register(self):
print('欢迎来到注册页面')
def save(self):
print('欢迎来到存储页面')
while 1:
choose = input('>>>').strip()
if choose == 'login':
obj = User()
obj.login()
elif choose == 'register':
obj = User()
obj.register()
elif choose == 'save':
obj = User()
obj.save()
```
用了反射之后
```python
class User:
def login(self):
print('欢迎来到登录页面')
def register(self):
print('欢迎来到注册页面')
def save(self):
print('欢迎来到存储页面')
user = User()
while 1:
choose = input('>>>').strip()
if hasattr(user, choose):
func = getattr(user, choose)
func()
else:
print('输入错误。。。。')
```
## 元编程特性
**元编程Metaprogramming**是Python中一种高级编程范式其核心思想是**编写能够操作代码本身的程序**——通过在运行时动态生成、修改或执行代码实现对程序行为的灵活控制。这种特性让Python具备极强的动态性与抽象能力成为框架开发、代码生成等场景的基石。
### **1. 装饰器**
**函数/类的行为扩展工具**
装饰器是Python最常用的元编程技术之一本质是**接收函数或类作为输入、返回修改后函数/类的高阶函数**。通过`@decorator`语法糖,可在不修改原代码的情况下,为函数/类添加日志、性能监控、权限验证等功能。
- **基础装饰器**:例如日志装饰器`@log_decorator`,可在函数调用前后打印日志;
- **带参数的装饰器**:通过外层函数接收参数,实现更灵活的功能控制(如`@repeat(times=3)`让函数执行3次
- **类装饰器**用于修改类的行为例如实现单例模式确保类只有一个实例、自动注册类到全局字典如Django模型注册
### **2. 元类**
**类的创建控制器**
元类是**创建类的类**(默认元类为`type`),通过继承`type`并重写`__new__``__init__`方法,可控制类的创建过程,实现动态修改类属性、添加方法、强制类遵循接口规范等功能。
- **动态添加属性/方法**:例如在类创建时自动添加`author`属性或`hello`方法;
- **强制接口检查**:通过元类确保类实现了特定方法(如`run`方法),避免类定义不完整;
- **动态注册类**例如ORM框架中通过元类自动将模型类注册到全局注册表方便后续生成SQL语句。
### **3. 动态属性与描述符**
**属性访问的控制者**
动态属性允许在运行时修改对象的属性,而描述符则通过定义`__get__``__set__`等方法,精确控制属性的访问行为。
- **动态属性**:通过`__getattr__`(获取不存在的属性)、`__setattr__`(设置属性)等方法,实现动态属性访问(如代理类通过`__getattr__`转发属性访问);
- **描述符**:用于实现数据验证、只读属性等功能。例如`ReadOnlyDescriptor`可防止属性被修改,`ValidatedDescriptor`可验证属性值的合法性(如确保年龄为正数)。
### **4. 反射**
**程序的自省能力**
反射是程序**在运行时检查、修改自身结构**的能力,通过内置函数(`getattr``hasattr``setattr``delattr`)和`inspect`模块,可动态获取对象的属性、方法信息,或修改对象行为。
- **获取信息**`hasattr(obj, 'name')`检查对象是否有`name`属性,`getattr(obj, 'name')`获取`name`属性的值;
- **修改属性**`setattr(obj, 'age', 30)`设置对象的`age`属性为30`delattr(obj, 'age')`删除`age`属性;
- **动态调用方法**`getattr(obj, 'method')()`获取并调用对象的`method`方法。
### **5. 动态代码生成与执行**
**运行时代码的灵活构建**
通过`exec``compile``eval`等函数,可在运行时生成并执行代码字符串,实现动态代码生成。
- **动态创建函数**:例如`exec`生成`add`函数(`def add(a, b): return a + b`),并通过`eval`获取函数对象;
- **动态修改方法**猴子补丁Monkey Patching通过动态修改现有类的方法扩展其功能如为`Logger`类动态添加`log_warning`方法);
- **动态生成类**:通过`type`函数动态创建类(如`type('DynamicClass', (), {'attr': 42})`创建包含`attr`属性的类)。
### **6. AST操作**
**代码结构的深度修改**
`ast`抽象语法树模块允许解析、修改Python代码的语法结构实现更高级的代码生成与修改。
- **解析代码**将代码字符串解析为AST`ast.parse("a = 1 + 2")`
- **修改AST**遍历AST节点修改代码结构如将`a = 1 + 2`修改为`a = 1 * 2`
- **生成代码**将修改后的AST编译为可执行代码`compile`函数)。
元编程的灵活性使其成为Python框架如Django、Flask、SQLAlchemy的核心技术但也需注意**代码可读性**(过度使用元编程会增加代码复杂度)、**调试难度**(动态生成的代码难以跟踪)、**安全性**(动态执行代码可能引发安全漏洞)等问题。合理使用元编程,能让代码更简洁、灵活,提升开发效率。
## 函数 vs 方法
### 通过打印函数(方法)名确定
```python
def func():
pass
print(func)
class A:
def func(self):
pass
print(A.func)
obj = A()
print(obj.func)
```
### 通过types模块验证
```python
from types import FunctionType
from types import MethodType
def func():
pass
class A:
def func(self):
pass
obj = A()
print(isinstance(func,FunctionType))
print(isinstance(A.func,FunctionType))
print(isinstance(obj.func,FunctionType))
print(isinstance(obj.func,MethodType))
```
### 静态方法是函数
```python
from types import FunctionType
from types import MethodType
class A:
def func(self):
pass
@classmethod
def func1(self):
pass
@staticmethod
def func2(self):
pass
obj = A()
# 静态方法其实是函数
print(isinstance(A.func2,FunctionType))
print(isinstance(obj.func2,FunctionType))
```
### 函数与方法的区别
那么,函数和方法除了上述的不同之处,我们还总结了一下几点区别。
1. 函数的是显式传递数据的。如我们要指明为len()函数传递一些要处理数据。
2. 函数则跟对象无关。
3. 方法中的数据则是隐式传递的。
4. 方法可以操作类内部的数据。
5. 方法跟对象是关联的。如我们在用strip()方法是是不是都是要通过str对象调用比如我们有字符串s,然后s.strip()这样调用。是的strip()方法属于str对象。
我们或许在日常中会口语化称呼函数和方法时不严谨,但是我们心中要知道二者之间的区别。
在其他语言中如Java中只有方法C中只有函数C++么,则取决于是否在类中
## 双下方法
### `__len__`
```python
class B:
def __len__(self):
return 666
b = B()
print(len(b)) # len 一个对象就会触发 __len__方法。
class A:
def __init__(self):
self.a = 1
self.b = 2
def __len__(self):
return len(self.__dict__)
a = A()
print(len(a))
```
### `__hash__`
```python
class A:
def __init__(self):
self.a = 1
self.b = 2
def __hash__(self):
return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))
```
### `__str__`
如果一个类中定义了__str__方法那么在打印 对象 时,默认输出该方法的返回值。
```python
class A:
def __init__(self):
pass
def __str__(self):
return '张三'
a = A()
print(a)
print('%s' % a)
```
### `__repr__`
如果一个类中定义了__repr__方法那么在repr(对象) 时,默认输出该方法的返回值。
```python
class A:
def __init__(self):
pass
def __repr__(self):
return '张三'
a = A()
print(repr(a))
print('%r'%a)
```
### `__call__`
对象后面加括号,触发执行。
构造方法__new__的执行是由创建对象触发的对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
```python
class Foo:
def __init__(self):
print('__init__')
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
```
### `__eq__`
```python
class A:
def __init__(self):
self.a = 1
self.b = 2
def __eq__(self,obj):
if self.a == obj.a and self.b == obj.b:
return True
a = A()
b = A()
print(a == b)
```
### `__del__`
析构方法,当对象在内存中被释放时,自动触发执行。
此方法一般无须定义因为Python是一门高级语言程序员在使用时无需关心内存的分配和释放因为此工作都是交给Python解释器来执行所以析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
### `__new__`
- `__new__()` 方法是在类准备将自身实例化时调用。
- `__new__()` 方法始终都是类的静态方法,即使没有被加上静态方法装饰器。
- 通常来说,新式类开始实例化时,`__new__()`方法会返回clscls指代当前类的实例然后该类的`__init__()`方法作为构造方法会接收这个实例即self作为自己的第一个参数然后依次传入`__new__()`方法中接收的位置参数和命名参数。
```python
class A:
def __init__(self):
self.x = 1
print('in init function')
def __new__(cls, *args, **kwargs):
print('in new function')
return object.__new__(A, *args, **kwargs)
a = A()
print(a.x)
```
单例模式
```python
class A:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
obj = object.__new__(cls)
cls.__instance = obj
return cls.__instance
```
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
**【采用单例模式动机、原因】**
对于系统中的某些类来说只有一个实例很重要例如一个系统中可以存在多个打印任务但是只能有一个正在工作的任务一个系统只能有一个窗口管理器或文件系统一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化将弹出多个窗口如果这些窗口显示的内容完全一致则是重复对象浪费内存资源如果这些窗口显示的内容不一致则意味着在某一瞬间系统有多个状态与实际不符也会给用户带来误解不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
**【单例模式优缺点】**
**【优点】**
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
**【缺点】**
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象尤其在类库中定义的对象开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
三、对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言只有单例类能够导致实例被取消分配因为它包含对该实例的私有引用。在某些语言中如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用
### `__item__`系列
```python
class Foo:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print(self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key]=value
print('赋值成功')
def __delitem__(self, key):
print('del obj[key]时,我执行')
self.__dict__.pop(key)
def __delattr__(self, item):
print('del obj.key时,我执行')
self.__dict__.pop(item)
f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='mingzi'
print(f1.__dict__)
```
### 上下文管理器相关
`__enter__` `__exit__`
```python
class A:
def __init__(self, text):
self.text = text
def __enter__(self): # 开启上下文管理器对象时触发此方法
self.text = self.text + '您来啦'
return self # 将实例化的对象返回f1
def __exit__(self, exc_type, exc_val, exc_tb): # 执行完上下文管理器对象f1时触发此方法
self.text = self.text + '这就走啦'
with A('大爷') as f1:
print(f1.text)
print(f1.text)
```
自定义文件管理器
```python
class Diycontextor:
def __init__(self, name, mode):
self.name = name
self.mode = mode
def __enter__(self):
print("Hi enter here!!")
self.filehander = open(self.name, self.mode)
return self.filehander
def __exit__(self,*args):
print("Hi exit here")
self.filehander.close()
with Diycontextor('config', 'r') as f:
for i in f:
print(i.strip())
```
案例
```python
class StarkConfig:
def __init__(self, num):
self.num = num
def run(self):
self()
def __call__(self, *args, **kwargs):
print(self.num)
class RoleConfig(StarkConfig):
def __call__(self, *args, **kwargs):
print(345)
def __getitem__(self, item):
return self.num[item]
v1 = RoleConfig('abcedf')
v2 = StarkConfig('2333')
print(v1[3])
## print(v2[2])
v1.run()
```
```python
class UserInfo:
pass
class Department:
pass
class StarkConfig:
def __init__(self, num):
self.num = num
def changelist(self, request):
print(self.num, request)
def run(self):
self.changelist(999)
class RoleConfig(StarkConfig):
def changelist(self, request):
print(666, self.num)
class AdminSite:
def __init__(self):
self._registry = {}
def register(self, k, v):
self._registry[k] = v
site = AdminSite()
site.register(UserInfo, StarkConfig)
# 1
obj = site._registry[UserInfo]()
# 2
# obj = site._registry[UserInfo](100)
obj.run()
```
```python
class UserInfo:
pass
class Department:
pass
class StarkConfig:
def __init__(self,num):
self.num = num
def changelist(self,request):
print(self.num,request)
def run(self):
self.changelist(999)
class RoleConfig(StarkConfig):
def changelist(self,request):
print(666,self.num)
class AdminSite:
def __init__(self):
self._registry = {}
def register(self,k,v):
self._registry[k] = v(k)
site = AdminSite()
site.register(UserInfo,StarkConfig)
site.register(Department,RoleConfig)
for k,row in site._registry.items():
row.run()
```
```python
class A:
list_display = []
def get_list(self):
self.list_display.insert(0, 33)
return self.list_display
s1 = A()
print(s1.get_list())
```
```python
class A:
list_display = [1, 2, 3]
def __init__(self):
self.list_display = []
def get_list(self):
self.list_display.insert(0, 33)
return self.list_display
s1 = A()
print(s1.get_list())
```
```python
class A:
list_display = []
def get_list(self):
self.list_display.insert(0,33)
return self.list_display
class B(A):
list_display = [11,22]
s1 = A()
s2 = B()
print(s1.get_list())
print(s2.get_list())
```