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

20 KiB
Raw Permalink Blame History

反射与双下方法

反射

  • python面向对象中的反射
    • 指程序在运行时动态地获取对象(包括类、模块、实例等)的结构信息(如属性、方法、类型),并能动态地操作这些结构(如调用方法、修改属性、创建对象)的能力。简单来说,反射让程序能在运行时“自省”——了解自身的组成,并根据需要灵活调整行为。
  • 反射的常见应用场景
    • 动态配置加载根据配置文件如JSON、YAML动态设置对象属性。例如通过setattr将配置中的debug=True映射到对象的debug属性。
    • 插件化架构:通过importlib.import_module动态加载插件模块,再用getattr获取插件类的实例并调用其方法。例如,电商系统可动态加载“限时抢购”“团购”等插件订单类。
    • 通用工具函数:编写适用于不同对象的通用工具。例如,print_object_info函数通过dir获取对象所有属性,再用getattr打印属性值。
    • ORM框架通过反射将数据库表的列映射到Python类的属性。例如Django ORM通过反射获取模型的字段信息动态生成SQL语句。

四个可以实现自省的函数

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

对对象的反射

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__)

对类的反射

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'))

当前模块的反射

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
import module_test as obj

obj.test()

print(hasattr(obj,'test'))

getattr(obj,'test')()

举例:

使用反射前

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()

用了反射之后

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. 反射

程序的自省能力

反射是程序在运行时检查、修改自身结构的能力,通过内置函数(getattrhasattrsetattrdelattr)和inspect模块,可动态获取对象的属性、方法信息,或修改对象行为。

  • 获取信息hasattr(obj, 'name')检查对象是否有name属性,getattr(obj, 'name')获取name属性的值;
  • 修改属性setattr(obj, 'age', 30)设置对象的age属性为30delattr(obj, 'age')删除age属性;
  • 动态调用方法getattr(obj, 'method')()获取并调用对象的method方法。

5. 动态代码生成与执行

运行时代码的灵活构建

通过execcompileeval等函数,可在运行时生成并执行代码字符串,实现动态代码生成。

  • 动态创建函数:例如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代码的语法结构实现更高级的代码生成与修改。

  • 解析代码将代码字符串解析为ASTast.parse("a = 1 + 2")
  • 修改AST遍历AST节点修改代码结构如将a = 1 + 2修改为a = 1 * 2
  • 生成代码将修改后的AST编译为可执行代码compile函数)。

元编程的灵活性使其成为Python框架如Django、Flask、SQLAlchemy的核心技术但也需注意代码可读性(过度使用元编程会增加代码复杂度)、调试难度(动态生成的代码难以跟踪)、安全性(动态执行代码可能引发安全漏洞)等问题。合理使用元编程,能让代码更简洁、灵活,提升开发效率。

函数 vs 方法

通过打印函数(方法)名确定

def func():
    pass

print(func)

class A:
    def func(self):
        pass

print(A.func)
obj = A()
print(obj.func)

通过types模块验证

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))

静态方法是函数

from types import FunctionType
from types import MethodType


class A:

    def func(self):
        pass

    @classmethod
    def func1(self):
        pass

    @staticmethod
    def func2():
        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__

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__

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__方法那么在打印 对象 时,默认输出该方法的返回值。

class A:
    def __init__(self):
        pass
    def __str__(self):
        return '张三'
a = A()
print(a)
print('%s' % a)

__repr__

如果一个类中定义了__repr__方法那么在repr(对象) 时,默认输出该方法的返回值。

class A:
    def __init__(self):
        pass
    def __repr__(self):
        return '张三'
a = A()
print(repr(a))
print('%r'%a)

__call__

对象后面加括号,触发执行。

构造方法__new__的执行是由创建对象触发的对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:

    def __init__(self):
        print('__init__')

    def __call__(self, *args, **kwargs):
        print('__call__')


obj = Foo()  # 执行 __init__
obj()  # 执行 __call__

__eq__

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__()方法中接收的位置参数和命名参数。
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)

单例模式

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__系列

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('张三')
f1['age'] = 18
f1['age1'] = 19
del f1.age1
del f1['age']
f1['name'] = '李四'
print(f1.__dict__)

上下文管理器相关

__enter__ __exit__

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)

自定义文件管理器

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', 'a+') as f:
    f.seek(0)
    for i in f.readlines():
        print(i.strip())

案例

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()
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_db = {}

    def register(self, k, v):
        self.registry_db[k] = v


site = AdminSite()
site.register(UserInfo, StarkConfig)
# 1
obj = site.registry_db[UserInfo](100)

# 2
# obj = site._registry[UserInfo](100)
obj.run()
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)

    @property
    def registry_db(self):
        return self._registry

site = AdminSite()
site.register(UserInfo,StarkConfig)
site.register(Department,RoleConfig)

for k,row in site.registry_db.items():
    row.run()
class A:
    list_display = []

    def get_list(self):
        self.list_display.insert(0, 33)
        return self.list_display


s1 = A()
print(s1.get_list())
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())
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())