如何实现addOrUpdate方法:仅更新指定参数且保留其余参数
实现支持创建/更新Feature实例的函数
需求说明
需要编写一个函数,同时满足两个功能:
- 创建
Feature类的新实例,并且支持为实例参数指定默认值 - 修改现有
Feature实例的指定参数,未明确指定的参数保持原有值不变
问题示例代码
以下代码展示了当前实现的问题:当更新现有实例时,未指定的参数会被函数默认值覆盖,而不是保留原有值。
class Feature: def __init__(self, title='DefaultTitle', number=-1): self.title = title self.number = number def addOrUpdateFeature( existingFeature=None, title='NewTitle', number=0): if existingFeature: existingFeature.title = title existingFeature.number = number return True else: return Feature(title=title, number=number) # 测试1:创建title='featureA'、number=1的新实例 a = addOrUpdateFeature(title='featureA', number=1) print(f'part 1: title={a.title} number={a.number}') # 测试2:尝试仅修改实例的number,保留title不变 # 问题:当前代码会将title覆盖为函数默认值'NewTitle' addOrUpdateFeature(existingFeature=a, number=2) print(f'part 2: title={a.title} number={a.number}')
运行结果
PS C:\Users\caver\Documents\GitHub> python argsTest.py part 1: title=featureA number=1 part 2: title=NewTitle number=2
现有方案的局限
可以通过获取函数调用时的实际传入参数列表来解决问题,但这种方法需要将函数签名改为**kwargs,虽然调用方式友好,但函数签名不够直观简洁。
解决方案
方案一:使用**kwargs实现简洁调用(推荐)
直接通过关键字参数接收需要修改或设置的参数,创建实例时自动使用Feature类的默认值,更新时仅修改传入的参数:
class Feature: def __init__(self, title='DefaultTitle', number=-1): self.title = title self.number = number def addOrUpdateFeature(existingFeature=None, **kwargs): if existingFeature: # 仅更新传入的参数,其余参数保持不变 for key, value in kwargs.items(): if hasattr(existingFeature, key): setattr(existingFeature, key, value) return True else: # 创建新实例,传入的参数优先,未传入的使用Feature的默认值 return Feature(**kwargs) # 测试验证 a = addOrUpdateFeature(title='featureA', number=1) print(f'part 1: title={a.title} number={a.number}') # 输出:part 1: title=featureA number=1 addOrUpdateFeature(existingFeature=a, number=2) print(f'part 2: title={a.title} number={a.number}') # 输出:part 2: title=featureA number=2 # 测试创建默认实例 b = addOrUpdateFeature() print(f'part 3: title={b.title} number={b.number}') # 输出:part 3: title=DefaultTitle number=-1
方案二:保留显式参数签名(借助inspect模块)
如果希望保留函数的显式参数签名,同时区分调用时的传入参数和默认值,可以使用inspect模块:
import inspect class Feature: def __init__(self, title='DefaultTitle', number=-1): self.title = title self.number = number def addOrUpdateFeature(existingFeature=None, title='NewTitle', number=0): # 获取函数签名和调用时的实际参数 sig = inspect.signature(addOrUpdateFeature) frame = inspect.currentframe() args_info = inspect.getargvalues(frame) bound_args = sig.bind(*args_info.args) bound_args.apply_defaults() # 筛选出调用者显式传入的参数(排除existingFeature) passed_params = {} for param_name, param in sig.parameters.items(): if param_name == 'existingFeature': continue # 判断参数是否是调用者显式传入的(而非使用默认值) if args_info.locals[param_name] != param.default: passed_params[param_name] = args_info.locals[param_name] if existingFeature: # 仅更新显式传入的参数 for key, value in passed_params.items(): setattr(existingFeature, key, value) return True else: # 创建新实例时,优先使用传入的参数,否则用Feature的默认值 feature_params = {} feature_init_sig = inspect.signature(Feature.__init__) for param_name in ['title', 'number']: if param_name in passed_params: feature_params[param_name] = passed_params[param_name] else: # 获取Feature类的默认值 feature_params[param_name] = feature_init_sig.parameters[param_name].default return Feature(**feature_params) # 测试验证 a = addOrUpdateFeature(title='featureA', number=1) print(f'part 1: title={a.title} number={a.number}') # 输出:part 1: title=featureA number=1 addOrUpdateFeature(existingFeature=a, number=2) print(f'part 2: title={a.title} number={a.number}') # 输出:part 2: title=featureA number=2 # 测试创建默认实例 b = addOrUpdateFeature() print(f'part 3: title={b.title} number={b.number}') # 输出:part 3: title=DefaultTitle number=-1
方案对比
- 方案一:代码简洁,调用方式灵活,完全满足需求,推荐使用。可以通过添加文档字符串来补充参数说明,弥补签名不直观的问题。
- 方案二:保留了显式的参数签名,可读性更强,但代码相对复杂,依赖
inspect模块。
内容的提问来源于stack exchange,提问作者Tom Grundy
相关产品推荐
相关产品推荐

