首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Python 编程必备知识

生成器

生成器算得上是Python中最吸引人的特性之一,生成器其实是一种特殊的迭代器,但不需要写__iter__()和__next__()方法了,只需要一个yiled关键字即可。python中的 yield 关键字, 用于构建生成器(generator), 其作用与迭代器一样. 还以斐波那契数列为例:

def Fibonacci(): prevous, current = 0, 1 while True: yield current prevous, current = current, current + prevous

所有的生成器都是迭代器, 都实现了迭代器的接口。 一般地,只要python函数的定义体中使用了 yield 关键字, 该函数就是生成器函数. 调用生成器函数时, 会返回一个生成器对象。也就是说, 生成器函数是生成器工厂。

生成器函数会创建一个生成器对象, 包装生成器函数的定义体. 把生成器传给 next(…) 函数时, 生成器函数会向前执行函数体中下一个 yield 语句, 返回产出的值, 并在函数定义体的当前位置暂停.

需要注意的是, 在协程中, yield 通常出现在表达式的右边(data = yield), 可以产出值, 也可以不产出(如果yield后面没有表达式, 那么会出None)。 协程可能会从调用方接收数据, 调用方把数据提供给协程使用 通过的是 .send(data) 方法. 而不是 next(…) . 通常, 调用方会把值推送给协程.

生成器调用方是一直获取数据, 而协程调用方可以向它传入数据, 协程也不一定要产出数据。不管数据如何流动, yield 都是一种流程控制工具, 使用它可以实现写作式多任务即,协程可以把控制器让步给中心调度程序, 从而激活其他的协程.

描述符

描述符是一种创建托管属性的方法,托管属性还可用于保护属性不受修改,或自动更新某个依赖属性的值。描述符是一种在多个属性上重复利用同一个存取逻辑的方式,能劫持那些本应对于self.__dict__的操作。在其他编程语言中,描述符被称作 setter 和 getter,用于获得 (Get) 和设置 (Set) 一个私有变量。Python 没有私有变量的概念,而描述符可以作为一种 Python 的方式来实现与私有变量类似的功能。

静态方法、类方法、property都是构建描述符的类。创建描述符的方式主要有3种:

1.创建一个类并覆盖任意一个描述符方法:__set__、__ get__ 和 __delete__。当需要某个描述符跨多个不同的类和属性的时候,例如类型验证,则使用该方法,例如:

class MyNameDescriptor(object): def __init__(self): self._myname = '' def __get__(self, instance, owner): return self._myname def __set__(self, instance, myname): self._myname = myname.getText() def __delete__(self, instance): del self._myname

2.使用属性类型可以更加简单、灵活地创建描述符。通过使用 property(),可以轻松地为任意属性创建可用的描述符。

class Student(object): def __init__(self): self._sname = '' def fget(self): return self._sname def fset(self, value): self._sname = value.title() def fdel(self): del self._sname name = property(fget, fset, fdel, "This is the property.")

3.使用属性描述符,它结合了属性类型方法和 Python装饰器。

class Student(object): def __init__(self): self._sname = '' @property def name(self): return self._sname @name.setter def name(self, value): self._sname = value.title() @name.deleter def name(self): del self._sname

另外,还可以在运行时动态创建描述符。 描述符有很多经典的应用,例如Protobuf。

装饰器

装饰器(Decorator)是可调用的对象, 其参数是另一个函数(被装饰的函数). 装饰器可能会处理被装饰的函数, 然后把它返回, 或者将其替换成另一个函数或可调用对象.实际上装饰器就是一个高阶函数,它接收一个函数作为参数,然后返回一个新函数。

装饰器有两大特征:

把被装饰的函数替换成其他函数

装饰器在加载模块时立即执行

python内置了三个用于装饰方法的函数: property、classmethod 和 staticmethod. 当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常适合使用。

如果一个函数被多个装饰器修饰,其实应该是该函数先被最里面的装饰器修饰,变成另一个函数后,再次被装饰器修饰。例如:

def second(func): print "running 2nd decorator" def wrapper(): func() return wrapperdef fisrt(func): print "running 1st decorator" def wrapper(): func() return wrapper@second@firstdef myfunction(): print "running myfunction"

就扩展功能而言,装饰器模式比子类化更加灵活。

在设计模式中,具体的装饰器实例要包装具体组件的实例,即装饰器和所装饰的组件接口一致,对使用该组件的客户端透明,并将客户端的请求转发给该组件,并且可能在转发前后执行一些额外的操作,透明性使得可以递归嵌套多个装饰器,从而可以添加任意多个功能。装饰器模式和Python装饰器之间并不是一对一的等价关系,Python装饰器函数更为强大,不仅仅可以实现装饰器模式。

Lambda

Python 不是纯萃的函数式编程语言,但本身提供了一些函数式编程的特性,像 map、reduce、filter等都支持函数作为参数,lambda 函数函数则是函数式编程中的翘楚。

Lambda 函数又称匿名函数,在某种意义上,return语句隐含在lambda中。和其他很多语言相比,Python 的 lambda 限制很多,最严重的是它只能由一条表达式组成。lambda规范必须包含只有一个表达式,表达式必须返回一个值,由lambda创建一个匿名函数隐式地返回表达式的返回值。

在PySpark 中经常会用到使用Lambda 的操作,例如:

li = [1, 2, 3, 4, 5]### 列表中国年的每个元素加5map(lambda x: x+5, li)### 返回其中的偶数filter(lambda x: x % 2 == 0, li) # [2, 4]### 返回所有元素的乘积reduce(lambda x, y: x * y, li)

lambda 可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。

本质上,Lambda 函数是一个只与输入参数有关的抽象代码树片段。在很多语言里,lambda 函数的调用会被套上一层接口,还会形成闭包,在 lambda 函数构造的同时就可以完成,之后 lambda 函数内部就是完全静态的。而一般的函数还要加上存储局部变量的区域,对外部环境的操作,以及命名,大部分语言强制了一般函数必须与名字绑定。

线程

python是支持多线程的, python的线程就是C语言的一个pthread,并通过操作系统调度算法进行调度。 python 的thread模块是轻量级的,而threading模块是对thread做了一些封装,方便使用。threading 经常和Queue结合使用,Queue模块中提供了同步的、线程安全的队列类,包括FIFO队列,LIFO队列,和优先级队列等。这些队列都实现了锁,能够在多线程中直接使用,可以使用队列来实现线程间的同步。

运行线程(线程中包含name属性)的两种常用方式如下:

在构造函数中传入用于线程运行的函数

在子类中重写threading.Thread基类中run()方法(只需重写init()和run()方法)

实现一个守护线程的简单例子如下:

class MyThread(threading.Thread): def run(self): time.sleep(30) print 'thread %s finished.' % self.namedef MyDaemons(): print 'start thread:' for i in range(5): t = MyThread() t.setDaemon(1) t.start() print 'end thread.'if __name__ == '__main__': MyDaemons()

为了避免线程不同步造成数据不同步,可以对资源进行加锁,也就是访问资源的线程需要获得锁,才能访问。threading 模块中提供了一个 Lock 功能。从Python3.X开始,标准库为提供了concurrent.futures模块,其中的ThreadPoolExecutor和ProcessPoolExecutor两个类,实现了对threading和multiprocessing的进一步抽象,对编写线程池提供了直接支持。

线程在python 被诟病的是,由于GIL的机制致使多线程不能利用机器多核的特性。其实,GIL并不是Python的特性,只是在实现Python解析器(CPython)的时侯所引入的。尽管Python完全支持多线程编程, 但解释器的C语言实现部分在完全并行执行时并不是线程安全的,解释器被一个全局锁即GIL保护着,它确保任何时候都只有一个Python线程执行。

在多线程环境中,Python 虚拟机按以下方式执行:

设置GIL

切换到一个线程去执行

运行指定的字节码指令集合

线程主动让出控制

把线程设置完睡眠状态

解锁GIL

再次重复以上步骤

因此,Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。

GC

Python 中的GC为可配置的垃圾回收器提供了一个接口。通过它可以禁用回收器、调整回收频率以及设置debug选项,也为用户能够查看那些无法回收的对象。

需要了解GC 的两个重要函数是gc.collect() 和 gc.set_threshold()。

gc.collect([generation])触发回收行为,返回unreachable object的数量。generation可选参数,用于指定回收第几代垃圾回收,由此也可看出python使用的是分代垃圾回收。如果不提供参数,表示对整个堆进行回收,即Full GC。

gc.set_threshold(threshold0[,threshold1[,threshold2)设置不同代的回收频率,GC会把生命周期不同的对象分别放到3种代去管理回收,generation 0即传说中的年轻代,generation 1为老年代等。

一般地,通过比较上次回收之后,比较分配的资源数和释放的资源数来决定是否启动回收,比如,当分配的资源减去释放的资源数超过阈值0时,回收年轻代的对象。相应的,可以通过gc.get_referents(*objs)得到对objs任一对象引用的所有对象列表。

在要求极限性能的情况下,并确保程序不会造成对象循环引用的时候,可以禁掉垃圾回收器。通过使用gc.disable(),可以禁掉自动垃圾回收器。

1. gc.enable():激活GC2. gc.disable():禁用GC3. gc.isenabled():检查是否激活

同时,可以用gc.set_debug(gc.DEBUG_LEAK)来调试有内存泄露的程序。除此之外,还有DEBUG_SAVEALL,该选项能够让被回收的对象保存在gc.garbage里面,以便检查。

调试

iPDB是一个不错的工具,通过 pip install ipdb 安装该工具,然后在你的代码中import ipdb; ipdb.set_trace(),然后在程序运行时,会获得一个交互式提示,每次执行程序的一行并且检查变量。示例代码如下:

import ipdbipdb.set_trace()ipdb.set_trace(context=5) # will show five lines of code # instead of the default three linesipdb.pm()ipdb.run('x[0] = 3')result = ipdb.runcall(function, arg0, arg1, kwarg='foo')result = ipdb.runeval('f(1,2) - 3')

另外,python内置了一个很好的追踪模块,当希望搞清其他程序的内部构造的时候,这个功能非常有用。

python -m trace --trace tracing.py

在一些场合,可以使用pycallgraph来追踪性能问题,它可以创建函数调用时间和次数的图表。同时,objgraph对于查找内存泄露非常有用。

当然, 在Python 程序员八荣八耻中谈到“以打印日志为荣 , 以单步跟踪为耻“,日志在很多时候都是调试的不二法门。

性能优化中的雕虫小技

从时空的角度看,优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率。

一个良好的算法往往对性能起到关键作用,因此性能改进的首要点是对算法的改进。在算法的时间复杂度排序上依次是:

O(1) -> O(log n) -> O(n) -> O(n log n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)

因此能在时间复杂度上对算法进行一定的改进,对性能的提高不言而喻。

Python 字典中查找操作的复杂度为O(1),而list 实际是个数组,在list 中查找需要遍历整个表,其复杂度为O(n),因此对成员的读操作字典要比列表 更快。在需要多数据成员进行频繁访问的时候,字典是一个较好的选择。set的union, intersection,difference操作要比list的迭代要快。因此如果涉及到求list交集,并集或者差的问题可以转换为set来操作。

对循环的优化所遵循的原则是尽量减少循环过程中的计算量,有多重循环的尽量将内层的计算提到上一层。 在循环的时候使用 xrange 而不是 range,因为 xrange() 在序列中每次调用只产生一个整数元素。而 range() 将直接返回完整的元素列表,用于循环时会有不必要的开销。另外,while 1 要比 while True 更快。另外,要充分利用Lazy if-evaluation的特性,也就是说如果存在条件表达式if x and y,在 x 为false的情况下y表达式的值将不再计算。

python中的字符串对象是不可改变的,因此对任何字符串的操作如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串,因此这种持续的copy会在一定程度上影响python的性能。因此,在字符串连接的使用尽量使用join()而不是+,当对字符串处理的时候,首选内置函数,对字符进行格式化比直接串联读取要快,尽量使用列表推导和生成器表达式。

优化的前提是需要了解性能瓶颈在什么地方,对于比较复杂的代码可以借助一些工具来定位,如profile。profile的使用非常简单,只需要在使用之前进行import即可。对于profile的剖析数据,如果以二进制文件的时候保存结果的时候,可以通过pstats模块进行文本报表分析,它支持多种形式的报表输出,是文本界面下一个较为实用的工具。

Python性能优化除了改进算法,选用合适的数据结构之外,还可以将关键python代码部分重写成C扩展模块,或者选用在性能上更为优化的解释器等。

强大的库

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180205A0LVYP00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券