总结:自动将函数对象添加到字典的bug
介绍
本文以ATM项目为背景,介绍一个比较实用的编程技巧,使用装饰器将项目中的指定函数添加到字典中。
利用字典通过key访问value的特点,实现用户输入编号,通过字典直接获取并调用编号对应的功能函数。
# 实现的目标:让用户输入使用功能的编号,程序调用相应的函数。
基础版-if条件判断
def login():
print('this is login function')
def register():
print('this is register function')
def transfer():
print('this is transfer function')
# 主程序
def atm():
desc = '1: 登录\n2:注册\n3:转账'
while 1:
print(desc)
cmd = input('请选择您要使用的功能编号:').strip()
if cmd == '1':
login()
elif cmd == '2':
register()
elif cmd == '3':
register()
else:
print('编号不存在,请重新选择')
if __name__ == '__main__':
atm()
# 基础版本,每个数字字符对应不同的函数,通过if-elif-else判断用户的选择,然后调用。
# 优点:简单明亮,思路清晰
# 缺点:代码重复,程序臃肿
提高版-函数字典
# 功能函数略.......
# 主程序
def atm():
# 函数字典
cmd_func = {
'1': ('登录', login),
'2': ('注册', register),
'3': ('转账', transfer),
}
while 1:
for k, v in cmd_func.items():
print(f'({k}){v[0]}', end='\t')
cmd = input('\n请选择您要使用的功能编号:').strip()
if cmd not in cmd_func:
print('编号不存在,请重新选择')
continue
# 编号存在字典中的情况
func = cmd_func.get(cmd)[1] # func是cmd_func字典中value的第二个元素
func() # 调用函数
if __name__ == '__main__':
atm()
# 提高版本,将每个功能函数放在一字典中,key是编号,值中包含有函数名,输入key就可以获取到值中的功能函数
# 优点:思路清晰,代码简洁,程序优美
# 缺点:需要手动创建一个函数字典
进阶版-自动生成函数字典
# 装饰器
def auto(desc):
from functools import wraps
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
index = len(cmd_func) + 1 # 调用全局函数字典(初始为空),自动计算编号
cmd_func[str(index)] = (desc, func) # 将编号,描述信息、函数加到字典中
return inner
return wrapper
@auto('登录')
def login():
print('this is login function')
@auto('注册')
def register():
print('this is register function')
@auto('转账')
def transfer():
print('this is transfer function')
# 自动调用被装饰函数的函数,需要手动排除全局名称空间中不需要的名字
def auto_append():
my_func = [v for k, v in globals().items() if callable(v) if k not in ['atm', 'auto', 'auto_append']]
for func in my_func: func()
# 函数字典
cmd_func = {}
# 主程序
def atm():
# 调用函数自动添加函数字典
auto_append()
while 1:
for k, v in cmd_func.items():
print(f'({k}){v[0]}', end='\t')
cmd = input('\n请选择您要使用的功能编号:').strip()
if cmd not in cmd_func:
print('编号不存在,请重新选择')
continue
# 编号存在字典中的情况
func = cmd_func.get(cmd)[1] # func是cmd_func字典中value的第二个元素
func() # 调用函数
if __name__ == '__main__':
atm()
# 进阶版-编写装饰器,每个被装饰的函数调用,就会被添加到一个字典中,然后再写一个自动发现这些他们的函数,自动调用
# 优点:使用字典函数时不需要手动添加
# 缺点:需要字典是全局变量,cmd_func = {},手动排除不需要的函数,功能函数少时提现不出它的优势
# 上面的情况是全部函数都在一个文件中;
# 如果功能函数在其他文件中,笔触采用导入的方式运行,这种情况使用自动添加函数字典就优势明显。
高级版-跨文件自动添加
这里使用简化版的软件开发目录规范,将不同功能的函数放在不同的文件中,并且所有的文件在一个文件夹下。
ATM/
|-- run.py # 程序启动文件
|-- atm.py # 主函数文件,即atm()函数
|--funcs.py # 存放 login()、register()、transfer()函数的文件
|--tools.py # 存放装饰器函数auto()、auto_append()函数的文件
run.py
from atm import atm # 导入atm.py下的atm()函数
if __name__ == '__main__':
atm() # 运行atm()函数
atm.py
from tools import auto_append # 从tools.py导入auto_append函数
# 函数字典,将login、register、transfer自动加到这个字典中
cmd_func = {}
# 主程序
def atm():
auto_append() # 调用auto_append函数自动添加函数字典
while 1:
for k, v in cmd_func.items():
print(f'({k}){v[0]}', end='\t')
cmd = input('\n请选择您要使用的功能编号:').strip()
if cmd not in cmd_func:
print('编号不存在,请重新选择')
continue
# 编号存在字典中的情况
func = cmd_func.get(cmd)[1] # func是cmd_func字典中value的第二个元素
func() # 调用函数
funcs.py
from tools import auto # 从tools.py导入auto装饰器
@auto('登录') # 装饰login
def login():
print('this is login function')
@auto('注册') # 装饰register
def register():
print('this is register function')
@auto('转账') # 装饰transfer
def transfer():
print('this is transfer function')
tools.py
from functools import wraps
def auto(desc): # 装饰器
from atm import cmd_func # 函数内导入,避免循环导入问题
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
index = len(cmd_func) + 1 # 访问atm.py文件的cmd_func字典
cmd_func[str(index)] = (desc, func) # 将函数自定添加到该字典中
return inner
return wrapper
def auto_append():
# 在当前局部名称空间中导入需要添加到字典里面的函数
from funcs import login, register, transfer
my_funcs = locals()
for func in my_funcs.values():
func() # 自动执行,即执行装饰器内的inner函数,自动添加到字典
运行run.py,程序结果界面如下,很完美对吧。通过上述操作,我们实现了自动将功能函数添加到字典中。

不过值得注意的有如下几点:
我们是将字典
cmd_func放在了atm.py文件的全局名称空间中,函数装饰器auto在使用它的时候需要从atm.py文件中导入cmd_func。此处需要注意的是循环导入问题。这种情况下解决循环导入问题有两中方案。方案1:在auto函数内导入from atm import cmd_func;方案2:tools.py文件顶部导入import atm,auto函数内使用atm.cmd_func如果将字典
cmd_func放在一个独立的py文件中,而不是放在atm.py文件中,那就很方便了,只要在需要它的位置导入并引用就好了,不会造成循环导入问题。函数auto_append内导入login、register、transfer函数,局部名称空间的名字。
补充
说到这里就可以结束了,因为上面的操作很流畅的实现了我们目的。这种操作在功能数量多,跨文件导入时,非常实用。
在刚要结束的时候,我突然想试试,如果不通过run.py文件调用atm函数,而是直接运行atm.py文件,调用atm函数,是不是也可以?于是,在atm.py文件末尾增加if __name__ == '__main__': atm()
from tools import auto_append # 从tools.py导入auto_append函数
# 函数字典,将login、register、transfer自动加到这个字典中
cmd_func = {}
# 主程序
def atm():
auto_append() # 调用auto_append函数自动添加函数字典
while 1:
for k, v in cmd_func.items():
print(f'({k}){v[0]}', end='\t')
cmd = input('\n请选择您要使用的功能编号:').strip()
if cmd not in cmd_func:
print('编号不存在,请重新选择')
continue
# 编号存在字典中的情况
func = cmd_func.get(cmd)[1] # func是cmd_func字典中value的第二个元素
func() # 调用函数
if __name__ == '__main__': # 增加了这个判断,不影响该文件被导入执行
atm()
执行atm.py文件,调用atm函数,结果。。。。。字典居然是空的

也就是说,我的这些操作,就是啥也没做。但我明明做了呀。
这个问题,我研究了半天愣是没想明白。后来跟着debug调试,后来发现其实原因很简单。
使用run.py文件导入atm函数这种方式执行atm函数时
被导入的模块atm已经在导入模块的时候会执行atm.py文件,执行完毕后atm.py模块名称空间里面就有了一个名字cmd_func这个字典。
后面执行atm函数内部的auto_append函数,会进入装饰器auto内部,装饰器内部遇到 from atm import cmd_func时会再次导入atm模块,但因为atm模块已经被加载到内存了,所以此时不会再执行atm.py文件,而是直接在内存中找到了cmd_func这个名字。后面装饰器内部使用的都是这个相同的字典,即atm.py文件内的字典。
直接执行atm.py文件,调用atm函数的方式情况却不同。
执行atm.py文件,程序走到cmd_func = {}处,程序会为这个字典开辟一个内存空间。
后面执行atm函数,进入atm函数内部,遇到auto_append(),就会跳转到auto_append函数内部。
auto_append内部因为导入login,会进入funcs.py文件内,由于装饰器的存在,程序又会进入装饰器auto函数内。
auto内就比较关键了,因为要使用cmd_func字典,程序需要再次从atm.py文件导入这个字典。
现在,内存中是没有atm这个模块的(因为还在执行这个文件)。根据导入模块时的搜索路径(内存、内置、sys.path),此时就会再次执行atm.py文件,遇到cmd_func={},就会再次在内存中开辟一块空间存这个字典。
所以我们在装饰器中使用的字典是第二次生成的字典,而atm函数内部使用的字典却是第一次生成的那个字典。他们在不同的作用域里面。
所以这个问题的关键在于:两种方式导入atm.py这个模块的先后顺序不同,造成第一种方式整个程序使用的是一个字典;而第二方式开辟了两个字典,我们操作的是后来的第二个字典,而程序使用的却是第一个。
核心知识点是模块导入时的搜索顺序:内存、内置、sys.path路径
总结:自动将函数对象添加到字典的bug的更多相关文章
- 认识js函数对象(Function Object)
认识函数对象(Function Object) 可以用function关键字定义一个函数,对于每个函数可以为其指定一个函数名,通过函 数名来进行调用.这些都是代码给用户的印象,而在JavaScript ...
- js函数对象
函数是进行模块化程序设计的基础,编写复杂的Ajax应用程序,必须对函数有更深入的了解. javascript中的函数不同于其他的语言,每个函数都是作为一个对象被维护和运行的.通过函数对象的性质,可以很 ...
- 关于js函数对象的理解
js中函数和对象的关系: 什么是对象?根据W3C上面的解释JS中所有事物都是对象,对象是拥有属性和方法的数据,由此可以看出除了基 本值类型不是对象(number.string.Boolean.Unde ...
- javascript 函数对象
http://hi.baidu.com/gdancer/blog/item/a59e2c12479b4e54f919b814.html jQuery的一些写法就是基于这篇文章的原理的.. 函数 ...
- js 函数对象
函数是进行模块化程序设计的基础,编写复杂的Ajax应用程序,必须对函数有更深入的了解: javaScript中的函数不同于其他的语言,每个函数都是作为一个对象被维护和运行的,通过函数对象的性质,可以很 ...
- python12--字符串的比较 函数的默认值的细节 三元表达式 函数对象 名称空间 作用域 列表与字典的推导式 四则运算 函数的嵌套
复习 1.字符串的比较; 2.函数的参数; ******实参与形参的分类: 3.函数的嵌套调用: # 字符串的比较# -- 按照从左往右比较每一个字符,通过字符对应的ascii进行比较 ...
- day12函数,三元表达式 ,列表推导式 ,字典推导式,函数对象,名称空间与作用域,函数的嵌套定义
复习 # 字符串的比较 # -- 按照从左往右比较每一个字符,通过字符对应的ascii进行比较 # 函数的参数 # 1)实参与形参: # -- 形参:在函数定义时()中出现的参数 # -- 实参:在函 ...
- python之三元表达式,列表|字典推导式,函数对象
#### 三元表达式: 就是if....else...的语法糖 # -- 1) 只能解决if...else...结构,其他if分支结构都不管 # -- 2)一个分支提供一个结果: 如果一个分支提供了多 ...
- python之函数对象、函数嵌套、名称空间与作用域、装饰器
一 函数对象 一 函数是第一类对象,即函数可以当作数据传递 #1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素 二 利用该特性,优雅的取代多分支的if de ...
随机推荐
- vue——一个页面实现音乐播放器
请忽略下面这段文字年关将至,时间好歹又多出了些许.却不敢过度消遣.岁月未曾饶过我,我亦不想饶过岁月.且将它塞得膨胀,让这一年看似加更充实.不曾料想我一个爱些风花雪月.研墨行歌之人,却做起了碼农这一行当 ...
- vue 实现 裁切图片 同时有放大、缩小、旋转功能
实现效果: 裁切指定区域内的图片 旋转图片 放大图片 输出bolb 格式数据 提供给 formData 对象 效果图 大概原理: 利用h5 FileReader 对象, 获取 <input ty ...
- python笔记26
一.今日内容 python中的方法 python中的方法+正则表达式的新内容 #分组 #分组命名 #引用分组 #爬虫的小例子 1.转义符 如:\n--->\\n--->print('\\n ...
- Linux——如何将Red Hat Enterprise Linux 6的语言改为中文?
第一步,打开终端,输入su -,获取超级用户权限,输入密码. 第二步,输入cd /etc/sysconfig,进入设置目录. 第三步,输入vi i18n,进入到配置文件. 第四步,按 ‘i’键,进入编 ...
- yolo3各部分代码详解(超详细)
0.摘要 最近一段时间在学习yolo3,看了很多博客,理解了一些理论知识,但是学起来还是有些吃力,之后看了源码,才有了更进一步的理解.在这里,我不在赘述网络方面的代码,网络方面的代码比较容易理解,下面 ...
- openwrt 上的 upnp wifi 音频推送 gmediarender
首先是必须启用的模块 Libraries ---> <*> libupnp Sound ---> <*> alsa-utils<*> madplay-a ...
- 1.JVM中的五大内存区域划分详解及快速扫盲
本博客参考<深入理解Java虚拟机>这本书 视频及电子书详见:https://shimo.im/docs/HP6qqHx38xCJwcv9/ 一.快速扫盲 1. JVM是什么 JVM是 ...
- XSS(跨站脚本攻击)详解
跨站脚本攻击XSS(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS.恶意攻击者往Web ...
- 我用STM32MP1做了个疫情监控平台4—功能完善界面重新设计
目录 前言 界面展示 新增功能 API 接口说明 多个接口数据的获取和解析 FontAwesome字体图标库的使用 代码下载 系列教程 @ 前言 之前我用STM32MP1和Qt实现了疫情监控平台,系列 ...
- 《前端之路》- TypeScript (三) ES5 中实现继承、类以及原理
目录 一.先讲讲 ES5 中构造函数(类)静态方法和多态 1-1 JS 中原型以及原型链 例子一 1-2 JS 中原型以及原型链中,我们常见的 constructor.prototype.**prot ...