Python中将方法作为变量赋值给对象的实现对比及优化方案咨询
方案优劣对比
第一种:字典映射方案
- 优点:
- 实例初始化阶段就校验入参合法性,传入不支持的方法标识直接抛出
KeyError,不会延迟到routine调用时才报错 - 可选调用方法清单明确,可读性和可维护性更高,调用方不需要感知类内部的方法命名,只需要传约定的字符串标识即可,封装性更好
- 实例初始化阶段就校验入参合法性,传入不支持的方法标识直接抛出
- 缺点:新增可选方法时需要同步修改字典映射,多一步维护成本,漏改就会导致新方法无法被选中
第二种:实例创建后赋值方案
- 优点:灵活性极高,除了类自带的方法外,还可以给实例绑定任意外部自定义方法,新增可选方法不需要修改类本身的代码
- 缺点:
- 没有校验逻辑,如果忘记给实例赋值
func属性,调用routine时才会抛出AttributeError,问题暴露晚 - 调用方需要感知类内部的方法命名,耦合度更高,也可能出现给
func赋值非可调用对象的问题,运行时才会报错
- 没有校验逻辑,如果忘记给实例赋值
方案选择建议
如果可选调用方法是固定集合,优先选第一种字典映射方案,安全性和可维护性更好;如果需要支持动态扩展自定义方法,可以选第二种,建议补充初始化校验逻辑避免运行时报错。
更简洁的实现方式
1. 用getattr替代硬编码字典映射
如果可选方法都是类内部定义的方法,可以用内置函数getattr直接根据方法名获取对应方法,不需要手动维护字典,新增方法时不需要修改初始化逻辑:
class Foo: def __init__(self, name, func_name) -> None: self.name = name # 也可以传入第三个参数作为默认值,避免传入非法方法名直接报错 self.func = getattr(self, func_name) def routine(self): print('fluff') self.func() def bear(self): print(f'I, {self.name}, am a bear and I like honey.') def boar(self): print(f'I, {self.name}, am a boar and I like fruits.') bob = Foo('bob', 'bear') mark = Foo('mark', 'boar') bob.routine() mark.routine()
2. 支持外部传入自定义方法的安全版本
如果需要支持传入类外部的自定义方法,可以优化第二种方案,将func作为初始化参数传入并增加合法性校验:
from typing import Callable class Foo: def __init__(self, name: str, func: Callable[["Foo"], None]) -> None: self.name = name if not callable(func): raise TypeError("传入的func必须是可调用对象") # 包装一层保证func调用时可以拿到当前实例的self self._func = func def routine(self): print('fluff') self._func(self) # 类外定义的方法 def bear(self): print(f'I, {self.name}, am a bear and I like honey.') # 自定义外部方法 def fox(self): print(f'I, {self.name}, am a fox and I like grapes.') bob = Foo('bob', bear) kiki = Foo('kiki', fox) bob.routine() kiki.routine()
不可行方案报错原因说明
你列的几个不可行方案报错原因分别是:
- 直接传
bear:全局作用域不存在bear变量,该方法是类的成员,全局无法直接访问 - 实例化时传
self.bear:self是类内部的变量,实例化的外部调用场景无法拿到self - 传
Kim.bear:实例化语句执行完成前Kim变量还未创建完成,无法提前访问实例的属性 - 传
Foo.bear:这是未绑定实例的类方法,调用时需要手动传入self参数,直接调用就会提示缺少self参数
内容的提问来源于stack exchange,提问作者Ariadne Paradis
相关产品推荐
相关产品推荐

