Python线程为什么搞个setDaemon

前言使用 Python 都不会错过线程这个知识,但是每次谈到线程,大家都下意识说 GIL 全局锁, 但其实除了这个老生常谈的话题,还有很多有价值的东西可以探索的,譬如:setDaemon()。 线程的使用 与 存在的问题我们会写这样的代码来启动多线程: import timeimport threadingdef test(): while True: print threading.currentThread() time.sleep(1)if __name__ == '__main__': t1 = threading.Thread(target=test) t2 = threading.Thread(target=test) t1.start() t2.start()输出: ^C<Thread(Thread-2, started 123145414086656)><Thread(Thread-1, started 123145409880064)>^C^C^C^C^C^C<Thread(Thread-2, started 123145414086656)> # ctrl-c 多次都无法中断 <Thread(Thread-1, started 123145409880064)>^C<Thread(Thread-1, started 123145409880064)> <Thread(Thread-2, started 123145414086656)><Thread(Thread-1, started 123145409880064)> <Thread(Thread-2, started 123145414086656)><Thread(Thread-2, started 123145414086656)><Thread(Thread-1, started 123145409880064)>...(两个线程竞相打印)通过 Threading 我们可以很简单的实现并发的需求,但是同时也给我们带来了一个大难题: 怎么退出呢? 在上面的程序运行中,我已经尝试按了多次的 ctrl-c,都无法中断这程序工作的热情!最后是迫不得已用 kill 才结束。 那么怎样才能可以避免这种问题呢?或者说,怎样才能在主线程退出的时候,子线程也自动退出呢? 守护线程有过相似经验的老司机肯定就知道,setDaemon() 将线程搞成 守护线程 不就得了呗: ...

July 13, 2019 · 3 min · jiezi

Redash开发环境搭建

开发环境搭建步骤建议使用mac和ubuntu系统,同事刚开始使用windows安装,过程中遇到很多问题,临时改用ubuntu系统,本文适用mac和ubuntu系统。1.github获取代码及安装需要的python包https://github.com/getredash/…以下安装过程使用master分支作为开发环境版本,release/6.0.x也适用。2.修改Python解释器版本redash适用python2.7开发。3.创建application4.创建数据库并建表本地安装pgsql并创建数据库,安装pgsql的过程此处省略,可以百度查下。修改redash下面settings/init.py数据库连接信息Terminal运行表创建语句./manage.py database create_tables5.本地安装redis过程读者可以百度查下,本地安装redis之后redash关于redis的配置不需要修改。6.打包前端Terminal运行npm install,install 完成后运行运行npm run watch或者运行npm run build7.Terminal运行命令启动celerycelery如果不启动,在查询sql的时候,查询会一直处于等待状态。celery worker –app=redash.worker –beat -Qscheduled_queries,queries,celery -c2启动成功后显示如下:8.运行application完成注册之后浏览data source笔者在data source遇到个问题,安装完之后仅显示部分数据源,没有mysql,hive等。经排查发现mysqldb,hive模块引入失败运行pip install MySQLdb报如下错误Could not find a version that satisfies the requirement MySQLdb (from versions: )No matching distribution found for MySQLdb可以参考篇文章https://blog.51cto.com/legeha…Terminal运行 pip install MySQL-python成功后显示如下:笔者运行时本地已安装好mysql,同事使用Ubuntu,遇到了文章中同样的问题,他本地环境没有安装mysql,安装mysql之后,运行 pip install MySQL-python,也顺利完成。注意:MySQL-python 安装完成之后需要重启celery,否则在创建好数据源之后查询的时候会报如下错误:Error running query: ‘NoneType’ object has no attribute ‘annotate_query’到此为止,redash安装成功。

March 1, 2019 · 1 min · jiezi

Python2.x与3.x版本区别

Python的3.0版本,常被称为Python 3000,或简称Py3k。相对于Python的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0在设计的时候没有考虑向下相容。许多针对早期Python版本设计的程式都无法在Python 3.0上正常执行。为了照顾现有程式,Python 2.6作为一个过渡版本,基本使用了Python 2.x的语法和库,同时考虑了向Python 3.0的迁移,允许使用部分Python 3.0的语法与函数。新的Python程式建议使用Python 3.0版本的语法。除非执行环境无法安装Python 3.0或者程式本身使用了不支援Python 3.0的第三方库。目前不支援Python 3.0的第三方库有Twisted, py2exe, PIL等。大多数第三方库都正在努力地相容Python 3.0版本。即使无法立即使用Python 3.0,也建议编写相容Python 3.0版本的程式,然后使用Python 2.6, Python 2.7来执行。Python 3.0的变化主要在以下几个方面:print 函数print语句没有了,取而代之的是print()函数。 Python 2.6与Python 2.7部分地支持这种形式的print语法。在Python 2.6与Python 2.7里面,以下三种形式是等价的:print “fish"print (“fish”) #注意print后面有个空格print(“fish”) #print()不能带有任何其它参数然而,Python 2.6实际已经支持新的print()语法:from future import print_functionprint(“fish”, “panda”, sep=’, ‘)UnicodePython 2 有 ASCII str() 类型,unicode() 是单独的,不是 byte 类型。现在, 在 Python 3,我们最终有了 Unicode (utf-8) 字符串,以及一个字节类:byte 和 bytearrays。由于 Python3.X 源码文件默认使用utf-8编码,这就使得以下代码是合法的:>>> 中国 = ‘china’ >>>print(中国) chinaPython 2.x>>> str = “我爱北京天安门”>>> str’\xe6\x88\x91\xe7\x88\xb1\xe5\x8c\x97\xe4\xba\xac\xe5\xa4\xa9\xe5\xae\x89\xe9\x97\xa8’>>> str = u"我爱北京天安门”>>> stru’\u6211\u7231\u5317\u4eac\u5929\u5b89\u95e8’Python 3.x>>> str = “我爱北京天安门”>>> str’我爱北京天安门’除法运算Python中的除法较其它语言显得非常高端,有套很复杂的规则。Python中的除法有两个运算符,/和//首先来说/除法:在python 2.x中/除法就跟我们熟悉的大多数语言,比如Java啊C啊差不多,整数相除的结果是一个整数,把小数部分完全忽略掉,浮点数除法会保留小数点的部分得到一个浮点数的结果。在python 3.x中/除法不再这么做了,对于整数之间的相除,结果也会是浮点数。Python 2.x:>>> 1 / 2 0 >>> 1.0 / 2.0 0.5 Python 3.x: >>> 1/2 0.5而对于//除法,这种除法叫做floor除法,会对除法的结果自动进行一个floor操作,在python 2.x和python 3.x中是一致的。python 2.x:>>> -1 // 2-1python 3.x:>>> -1 // 2-1注意的是并不是舍弃小数部分,而是执行 floor 操作,如果要截取整数部分,那么需要使用 math 模块的 trunc 函数python 3.x:>>> import math>>> math.trunc(1 / 2)0>>> math.trunc(-1 / 2)0异常在 Python 3 中处理异常也轻微的改变了,在 Python 3 中我们现在使用 as 作为关键词。捕获异常的语法由 except exc, var 改为 except exc as var。使用语法except (exc1, exc2) as var可以同时捕获多种类别的异常。 Python 2.6已经支持这两种语法。在2.x时代,所有类型的对象都是可以被直接抛出的,在3.x时代,只有继承自BaseException的对象才可以被抛出。2.x raise语句使用逗号将抛出对象类型和参数分开,3.x取消了这种奇葩的写法,直接调用构造函数抛出对象即可。在2.x时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在3.x中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。xrange在 Python 2 中 xrange() 创建迭代对象的用法是非常流行的。比如: for 循环或者是列表/集合/字典推导式。这个表现十分像生成器(比如。“惰性求值”)。但是这个 xrange-iterable 是无穷的,意味着你可以无限遍历。由于它的惰性求值,如果你不得仅仅不遍历它一次,xrange() 函数 比 range() 更快(比如 for 循环)。尽管如此,对比迭代一次,不建议你重复迭代多次,因为生成器每次都从头开始。在 Python 3 中,range() 是像 xrange() 那样实现以至于一个专门的 xrange() 函数都不再存在(在 Python 3 中 xrange() 会抛出命名异常)。import timeitn = 10000def test_range(n): return for i in range(n): passdef test_xrange(n): for i in xrange(n): pass Python 2print ‘Python’, python_version()print ‘\ntiming range()’ %timeit test_range(n)print ‘\n\ntiming xrange()’ %timeit test_xrange(n)Python 2.7.6timing range()1000 loops, best of 3: 433 µs per looptiming xrange()1000 loops, best of 3: 350 µs per loopPython 3print(‘Python’, python_version())print(’\ntiming range()’)%timeit test_range(n)Python 3.4.1timing range()1000 loops, best of 3: 520 µs per loopprint(xrange(10))NameError Traceback (most recent call last)<ipython-input-5-5d8f9b79ea70> in <module>()—-> 1 print(xrange(10))NameError: name ‘xrange’ is not defined八进制字面量表示八进制数必须写成0o777,原来的形式0777不能用了;二进制必须写成0b111。新增了一个bin()函数用于将一个整数转换成二进制字串。 Python 2.6已经支持这两种语法。在Python 3.x中,表示八进制字面量的方式只有一种,就是0o1000。python 2.x>>> 0o1000512>>> 01000512python 3.x>>> 01000 File “<stdin>”, line 1 01000 ^SyntaxError: invalid token>>> 0o1000512不等运算符Python 2.x中不等于有两种写法 != 和 <>Python 3.x中去掉了<>, 只有!=一种写法,还好,我从来没有使用<>的习惯去掉了repr表达式Python 2.x 中反引号相当于repr函数的作用Python 3.x 中去掉了``这种写法,只允许使用repr函数,这样做的目的是为了使代码看上去更清晰么?不过我感觉用repr的机会很少,一般只在debug的时候才用,多数时候还是用str函数来用字符串描述对象。def sendMail(from_: str, to: str, title: str, body: str) -> bool: pass多个模块被改名(根据PEP8)旧的名字 新的名字_winreg winregConfigParser configparsercopy_reg copyregQueue queueSocketServer socketserverrepr reprlibStringIO模块现在被合并到新的io模组内。 new, md5, gopherlib等模块被删除。 Python 2.6已经支援新的io模组。httplib, BaseHTTPServer, CGIHTTPServer, SimpleHTTPServer, Cookie, cookielib被合并到http包内。取消了exec语句,只剩下exec()函数。 Python 2.6已经支援exec()函数。5.数据类型1)Py3.X去除了long类型,现在只有一种整型——int,但它的行为就像2.X版本的long2)新增了bytes类型,对应于2.X版本的八位串,定义一个bytes字面量的方法如下:>>> b = b’china’ >>> type(b) <type ‘bytes’> str对象和bytes对象可以使用.encode() (str -> bytes) or .decode() (bytes -> str)方法相互转化。>>> s = b.decode() >>> s ‘china’ >>> b1 = s.encode() >>> b1 b’china'3)dict的.keys()、.items 和.values()方法返回迭代器,而之前的iterkeys()等函数都被废弃。同时去掉的还有 dict.has_key(),用 in替代它吧 。 ...

December 19, 2018 · 2 min · jiezi

Python: 浅谈函数局部变量小知识

前言这两天在 CodeReview 时,看到这样的代码# 伪代码import somelibclass A(object): def load_project(self): self.project_code_to_name = {} for project in somelib.get_all_projects(): self.project_code_to_name[project] = project …意图很简单,就是将 somelib.get_all_projects 获取的项目塞入的 self.project_code_to_name然而印象中这个是有优化空间的,于是提出调整方案:import somelibclass A(object): def load_project(self): project_code_to_name = {} for project in somelib.get_all_projects(): project_code_to_name[project] = project self.project_code_to_name = project_code_to_name …方案很简单,就是先定义局部变量 project_code_to_name,操作完,再赋值到self.project_code_to_name。在后面的测试,也确实发现这样是会好点,那么结果知道了,接下来肯定是想探索原因的!局部变量其实在网上很多地方,甚至很多书上都有讲过一个观点:访问局部变量速度要快很多,粗看好像好有道理,然后又看到下面贴了一大堆测试数据,虽然不知道是什么,但这是真的屌,记住再说,管他呢!但是实际上这个观点还是有一定的局限性,并不是放诸四海皆准。所以先来理解下这句话吧,为什么大家都喜欢这样说。先看段代码理解下什么是局部变量:#coding: utf8a = 1def test(b): c = ’test’ print a # 全局变量 print b # 局部变量 print c # 局部变量test(3)# 输出13test简单来说,局部变量就是只作用于所在的函数域,超过作用域就被回收理解了什么是局部变量,就需要谈谈 Python 函数 和 局部变量 的爱恨情仇,因为如果不搞清楚这个,是很难感受到到底快在哪里;为避免枯燥,以上述的代码来阐述吧,顺便附上 test 函数执行 的 dis 的解析:# CALL_FUNCTION 5 0 LOAD_CONST 1 (’test’) 3 STORE_FAST 1 (c) 6 6 LOAD_GLOBAL 0 (a) 9 PRINT_ITEM 10 PRINT_NEWLINE 7 11 LOAD_FAST 0 (b) 14 PRINT_ITEM 15 PRINT_NEWLINE 8 16 LOAD_FAST 1 (c) 19 PRINT_ITEM 20 PRINT_NEWLINE 21 LOAD_CONST 0 (None) 24 RETURN_VALUE在上图中比较清楚能看到 a、b、c 分别对应的指令块,每一块的第一行都是 LOAD_XXX,顾名思义,是说明这些变量是从哪个地方获取的。LOAD_GLOBAL 毫无疑问是全局,但是 LOAD_FAST 是什么鬼?似乎应该叫LOAD_LOCAL 吧?然而事实就是这么神奇,人家就真的是叫 LOAD_FAST,因为局部变量是从一个叫 fastlocals 的数组里面读,所以名字也就这样叫了(我猜的)。那么主角来了,我们要重点理解这个,因为这个确实还挺有意思。Python 函数执行Python 函数的构建和运行,说复杂不复杂,说简单也不简单,因为它需要区分很多情况,比方说需要区分 函数 和 方法,再而区分是有无参数,有什么参数,有木有变长参数,有木有关键参数。全部展开仔细讲是不可能的啦,不过可以简单图解下大致的流程(忽略参数变化细节):一路顺流而下,直达 fast_function,它在这里的调用是:// ceval.c -> call_functionx = fast_function(func, pp_stack, n, na, nk);参数解释下:func: 传入的 test;pp_stack: 近似理解调用栈 (py方式);na: 位置参数个数;nk: 关键字个数;n = na + 2 * nk;那么下一步就看看 fast_function 要做什么吧。初始化一波定义 co 来存放 test 对象里面的 func_code定义 globals 来存放 test 对象里面的 func_globals (字典)定义 argdefs 来存放 test 对象里面的 func_defaults (构建函数时的关键字参数默认值)来个判断,如果 argdefs 为空 && 传入的位置参数个数 == 函数定义时候的位置形参个数 && 没有传入关键字参数那就用 当前线程状态、co 、globals 来新建栈对象 f;定义fastlocals ( fastlocals = f->f_localsplus; );把 传入的参数全部塞进去 fastlocals那么问题来了,怎么塞?怎么找到传入了什么鬼参数:这个问题还是只能有 dis 来解答:我们知道现在这步是在 CALL_FUNCTION 里面进行的,所以塞参数的动作,肯定是在此之前的,所以: 12 27 LOAD_NAME 2 (test) 30 LOAD_CONST 4 (3) 33 CALL_FUNCTION 1 36 POP_TOP 37 LOAD_CONST 1 (None) 40 RETURN_VALUE在 CALL_FUNCTION 上面就看到 30 LOAD_CONST 4 (3),有兴趣的童鞋可以试下多传几个参数,就会发现传入的参数,是依次通过LOAD_CONST 这样的方式加载进来,所以如何找参数的问题就变得呼之欲出了;// fast_function 函数fastlocals = f->f_localsplus;stack = (*pp_stack) - n; for (i = 0; i < n; i++) { Py_INCREF(*stack); fastlocals[i] = *stack++; }这里出现的 n 还记得怎么来的吗?回顾上面有个 n = na + 2 * nk; ,能想起什么吗?其实这个地方就是简单的通过将 pp_stack 偏移 n 字节 找到一开始塞入参数的位置。那么问题来了,如果 n 是 位置参数个数 + 关键字参数,那么 2 * nk 是什么意思?其实这答案很简单,那就是 关键字参数字节码 是属于带参数字节码, 是占 2字节。到了这里,栈对象 f 的 f_localsplus 也登上历史舞台了,只是此时的它,还只是一个未经人事的少年,还需历练。做好这些动作,终于来到真正执行函数的地方了: PyEval_EvalFrameEx,在这里,需要先交代下,有个和 PyEval_EvalFrameEx 很像的,叫 PyEval_EvalCodeEx,虽然长得像,但是人家干得活更多了。请看回前面的 fast_function 开始那会有个判断,我们上面说得是判断成立的,也就是最简单的函数执行情况。如果函数传入多了关键字参数或者其他情况,那就复杂很多了,此时就需要由 PyEval_EvalCodeEx 处理一波,再执行 PyEval_EvalFrameEx。PyEval_EvalFrameEx 主要的工作就是解析字节码,像刚才的那些 CALL_FUNCTION,LOAD_FAST 等等,都是由它解析和处理的,它的本质就是一个死循环,然后里面有一堆 swith - case,这基本也就是 Python 的运行本质了。f_localsplus 存 和 取讲了这么长的一堆,算是把 Python 最基本的 函数调用过程简单扫了个盲,现在才开始探索主题。。为了简单阐述,直接引用名词:fastlocals, 其中 fastlocals = f->f_localsplus刚才只是简单看到了,Python 会把传入的参数,以此塞入 fastlocals 里面去,那么毋庸置疑,传入的位置参数,必然属于局部变量了,那么关键字参数呢?那肯定也是局部变量,因为它们都被特殊对待了嘛。那么除了函数参数之外,必然还有函数内部的赋值咯? 这块字节码也一早在上面给出了:# CALL_FUNCTION 5 0 LOAD_CONST 1 (’test’) 3 STORE_FAST 1 (c)这里出现了新的字节码 STORE_FAST,一起来看看实现把:# PyEval_EvalFrameEx 庞大 switch-case 的其中一个分支: PREDICTED_WITH_ARG(STORE_FAST); TARGET(STORE_FAST) { v = POP(); SETLOCAL(oparg, v); FAST_DISPATCH(); }# 因为有涉及到宏,就顺便给出:#define GETLOCAL(i) (fastlocals[i])#define SETLOCAL(i, value) do { PyObject *tmp = GETLOCAL(i); GETLOCAL(i) = value; Py_XDECREF(tmp); } while (0)简单解释就是,将 POP() 获得的值 v,塞到 fastlocals 的 oparg 位置上。此处,v 是 “test”, oparg 就是 1。用图表示就是:有童鞋可能会突然懵了,为什么突然来了个 b ?我们又需要回到上面看 test 函数是怎样定义的:// 我感觉往回看的概率超低的,直接给出算了def test(b): c = ’test’ print b # 局部变量 print c # 局部变量看到函数定义其实都应该知道了,因为 b 是传的参数啊,老早就塞进去了~那存储知道了,那么怎么取呢?同样也是这段代码的字节码:22 LOAD_FAST 1 (c)虽然这个用脚趾头想想都知道原理是啥,但公平起见还是给出相应的代码:# PyEval_EvalFrameEx 庞大 switch-case 的其中一个分支:TARGET(LOAD_FAST){ x = GETLOCAL(oparg); if (x != NULL) { Py_INCREF(x); PUSH(x); FAST_DISPATCH(); } format_exc_check_arg(PyExc_UnboundLocalError, UNBOUNDLOCAL_ERROR_MSG, PyTuple_GetItem(co->co_varnames, oparg)); break;}直接用 GETLOCAL 通过索引在数组里取值了。到了这里,应该也算是把 f_localsplus 讲明白了。这个地方不难,其实一般而言是不会被提及到这个,因为一般来说忽略即可了,但是如果说想在性能方面讲究点,那么这个小知识就不得忽视了。变量使用姿势因为是面向对象,所以我们都习惯了通过 class 的方式,对于下面的使用方式,也是随手就来:class SS(object): def init(self): self.fuck = {} def test(self): print self.fuck这种方式一般是没什么问题的,也很规范。到那时如果是下面的操作,那就有问题了:class SS(object): def init(self): self.fuck = {} def test(self): num = 10 for i in range(num): self.fuck[i] = i这段代码的性能损耗,会随着 num 的值增大而增大, 如果下面循环中还要涉及到更多类属性的读取、修改等等,那影响就更大了这个类属性如果换成 全局变量,也会存在类似的问题,只是说在操作类属性会比操作全局变量要频繁得多。我们直接看看两者的差距有多大把?import timeitclass SS(object): def test(self): num = 100 self.fuck = {} # 为了公平,每次执行都同样初始化新的 {} for i in range(num): self.fuck[i] = i def test_local(self): num = 100 fuck = {} # 为了公平,每次执行都同样初始化新的 {} for i in range(num): fuck[i] = i self.fuck = fucks = SS()print timeit.timeit(stmt=s.test_local)print timeit.timeit(stmt=s.test)通过上图可以看出,随着 num 的值越大,for 循环的次数就越多,那么两者的差距也就越大了。那么为什么会这样,也是在字节码可以看出写端倪:// s.test >> 28 FOR_ITER 19 (to 50) 31 STORE_FAST 2 (i) 8 34 LOAD_FAST 2 (i) 37 LOAD_FAST 0 (self) 40 LOAD_ATTR 0 (hehe) 43 LOAD_FAST 2 (i) 46 STORE_SUBSCR 47 JUMP_ABSOLUTE 28 >> 50 POP_BLOCK// s.test_local >> 25 FOR_ITER 16 (to 44) 28 STORE_FAST 3 (i) 14 31 LOAD_FAST 3 (i) 34 LOAD_FAST 2 (hehe) 37 LOAD_FAST 3 (i) 40 STORE_SUBSCR 41 JUMP_ABSOLUTE 25 >> 44 POP_BLOCK 15 >> 45 LOAD_FAST 2 (hehe) 48 LOAD_FAST 0 (self) 51 STORE_ATTR 1 (hehe)上面两段就是两个方法的 for block 内容,大家对比下就会知道, s.test 相比于 s.test_local, 多了个 LOAD_ATTR 放在 FOR_ITER 和 POP_BLOCK 之间。这说明什么呢? 这说明,在每次循环时,s.test 都需要 LOAD_ATTR,很自然的,我们需要看看这个是干什么的:TARGET(LOAD_ATTR){ w = GETITEM(names, oparg); v = TOP(); x = PyObject_GetAttr(v, w); Py_DECREF(v); SET_TOP(x); if (x != NULL) DISPATCH(); break; }# 相关宏定义#define GETITEM(v, i) PyTuple_GetItem((v), (i)) 这里出现了一个陌生的变量 name, 这是什么?其实这个就是每个 codeobject 所维护的一个 名字数组,基本上每个块所使用到的字符串,都会在这里面存着,同样也是有序的:// PyCodeObject 结构体成员PyObject co_names; / list of strings (names used) */那么 LOAD_ATTR 的任务就很清晰了:先从名字列表里面取出字符串,结果就是 “hehe”, 然后通过 PyObject_GetAttr 去查找,在这里就是在 s 实例中去查找。且不说查找效率如何,光多了这一步,都能失之毫厘差之千里了,当然这是在频繁操作次数比较多的情况下。所以我们在一些会频繁操作 类/实例属性 的情况下,应该是先把 属性 取出来存到 局部变量,然后用 局部变量 来完成操作。最后视情况把变动更新到 属性 上。结语其实相比变量,在函数和方法的使用上面更有学问,更值得探索,因为那个原理和表面看起来差别更大,下次有机会再探讨。平时工作多注意下,才能使得我们的 PY 能够稍微快点点点点点。 欢迎各位大神指点交流, QQ讨论群: 258498217 **转载请注明来源: https://segmentfault.com/a/11… ...

September 2, 2018 · 4 min · jiezi