比如mac环境下在某个路径下面跑celery的任务,celery -A msg_task worker 对于这一段语句的理解其实就是用后面的这些参数跑起来celery的可执行文件;那具体是怎样跑起来的呢,分为如下的四个步骤。
一:mac的环境变量
mac 一般使用bash作为默认shell,Mac系统的环境变量,加载顺序为:
Ⅰ.(系统级别,系统启动就会加载)
/etc/profile /etc/paths
Ⅱ.(当前用户级的环境变量,因为~文件夹本身就是登录人的home目录,根据shell的不同,加载的配置文件可能不一样,比如如果用的是zsh的话,那这边的第一个文件就会是~/.zshrc;然后如果其中的~/.bash_profile文件存在,后面的文件就会忽略不读,如果不存在,才会读后面的文件)
~/.bash_profile ~/.bash_login ~/.profile
Ⅲ.(bash shell打开的时候载入的)
~/.bashrc
比如我的电脑上面看见/etc/path文件里面的内容是
/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
然后在~/.zprofile的内容是PATH="/Library/Frameworks/Python.framework/Versions/3.4/bin:${PATH}"
最后在~/.zshrc的内容是export PATH=/usr/local/sbin:$HOME/bin:/usr/local/bin:$PATH
所以最后我们在终端里面打印环境变量PATH,使用echo $PATH 得到的信息是/usr/local/sbin:/Users/sunmenghua/bin:/usr/local/bin:/Library/Frameworks/Python.framework/Versions/3.4/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin,这就对应上了
二:找到可执行文件
在/usr/local/bin【路径1】和/Library/Frameworks/Python.framework/Versions/3.4/bin【路径2】路径下面,我们都发现了celery
然而执行celery -A msg_task worker的时候,发现,执行有报错,说【路径1】下面的celery是一个错误的编译器(bad interpreter),并且/usr/ocal/opt/python3/bin/python3.5不存在,所以在【路径2】找到对应的可执行文件
三:分析可执行文件
打开文件,能够定位到入口是在celery模块中__main__.py里面的面函数,看到的内容是:
1 import re 2 import sys 3 from celery.__main__ import main 4 if __name__ == '__main__': 5 sys.argv[0] = re.sub(r'(-script.pyw|.exe)?$', '', sys.argv[0]) # 正则匹配与替换 6 sys.exit(main()) # 退出时运行从celery模块的__main__.py里面的main函数
篇外话:发现了一个比较有用处的模块,就是sys模块。之前我写脚本的时候,都是使用的tornado.options模块。使用形式是:
1 from tornado.options import options, define # 引入模块 2 define("action", default="userinc", help="update static module") # 定义传进来的参数 3 if __name__ == "__main__": 4 options.parse_command_line() # 分析命令行里面传进来的所有参数,默认是sys.argv,然后会丢掉第一个参数sys.argv[0],因为它是程序名 5 action=options.action 然后调用形式就是 python3 test.py --action=test,经过追踪tornado的源码,知道这个模块其实是在sys.args进行了一层封装
四:找到python3模块
通过import celery ,然后help(celery),
Ⅰ.找到模块的源码所在地方为/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/celery/;
Ⅱ.用sublime打开对应的文件夹,先找__main__.py,在根据导入模块找到celery/bin/celery.py里面的main函数
Ⅲ.找到执行方法是在class CeleryCommand的execute_from_commandline;
Ⅳ.继续向上追溯,执行方法在celery.bin.base里面的execute_from_commandline;
Ⅴ.打印调试,真正读参数是在setup_app_from_command(argv)
Ⅵ.打印调试,往下走到self.find_app(app);
Ⅶ.调用的是from celery.app.utils import find_app方法传给他的两个参数,一个app是上面传下来的参数,另一个是symbol_by_name,这个参数是一个函数,搞得非常复杂,简直太复杂了,绕的一大圈。
###这里尝试描述一下: ##A.find_app(app, symbol_by_name=self.symbol_by_name)使用的是当前类里面的函数; ##B.from celery.utils import imports ##def symbol_by_name(self, name, imp=imports.import_from_cwd): ##return imports.symbol_by_name(name, imp=imp) ##C0.但是在celery/utils.py里面又有from celery.utils.imports import import_from_cwd,symbol_by_name ##D0.继续去celery/utils/imports.py里面又发现:from kombu.utils.imports import symbol_by_name ##E0.找kombu/utils/imports.py,确实是最后的导入是在这边,可是这里的逻辑是如果没有传导入函数的话,那就用importlib.import_module做导入函数,否则用调用的时候传的导入函数;好吧,那我们使用的时候,其实在步骤B里面有传入一个导入函数,那我们就继续追着B看一下,这个导入函数是哪里传入进来的; ##C1:跟C0一样 ##D1:像B的这种形式,那是没有传参数的,所以,这边得到的这个函数也是importlib.import_module
上面的追踪不继续描述了,言语难以表达。
不过在追踪的过程中,还是有几点理解。
1.函数不调用就不执行。
写一个简化的调用。
def t1(): print('t1') def main(c=t1): c() print('main') if __name__ == '__main__': main() |
def t1(): print('t1') def main(c=t1): print('main') if __name__ == '__main__': main() |
第一个的打印是t1和main,第二个的打印是main
2.后面跟别人聊,本来我以为这样子的代码应该算是一种比较糟糕的代码;但是被告知,这种难以理解代码的产生是面向对象编程和产品迭代产生的;调用某个新的模块的时候,不需要知道他的内部实现原理,只需要他的参数和返回,然后如果想拓展他,就直接拓展接口就好了。
但我还是不太理解,后面再想想吧。