介绍Python中的一些高级编程技巧


Posted in Python onApril 02, 2015

 正文:

本文展示一些高级的Python设计结构和它们的使用方法。在日常工作中,你可以根据需要选择合适的数据结构,例如对快速查找性的要求、对数据一致性的要求或是对索引的要求等,同时也可以将各种数据结构合适地结合在一起,从而生成具有逻辑性并易于理解的数据模型。Python的数据结构从句法上来看非常直观,并且提供了大量的可选操作。这篇指南尝试将大部分常用的数据结构知识放到一起,并且提供对其最佳用法的探讨。
推导式(Comprehensions)

如果你已经使用了很长时间的Python,那么你至少应该听说过列表推导(list comprehensions)。这是一种将for循环、if表达式以及赋值语句放到单一语句中的一种方法。换句话说,你能够通过一个表达式对一个列表做映射或过滤操作。

一个列表推导式包含以下几个部分:

  •     一个输入序列
  •     一个表示输入序列成员的变量
  •     一个可选的断言表达式
  •     一个将输入序列中满足断言表达式的成员变换成输出列表成员的输出表达式

举个例子,我们需要从一个输入列表中将所有大于0的整数平方生成一个新的序列,你也许会这么写:
 

num = [1, 4, -5, 10, -7, 2, 3, -1]
filtered_and_squared = []
 
for number in num:
 if number > 0:
 filtered_and_squared.append(number ** 2)
print filtered_and_squared
 
# [1, 16, 100, 4, 9]

很简单是吧?但是这就会有4行代码,两层嵌套外加一个完全不必要的append操作。而如果使用filter、lambda和map函数,则能够将代码大大简化:
 

num = [1, 4, -5, 10, -7, 2, 3, -1]
filtered_and_squared = map(lambda x: x ** 2, filter(lambda x: x > 0, num))
print filtered_and_squared
 
# [1, 16, 100, 4, 9]

嗯,这么一来代码就会在水平方向上展开。那么是否能够继续简化代码呢?列表推导能够给我们答案:
 

num = [1, 4, -5, 10, -7, 2, 3, -1]
filtered_and_squared = [ x**2 for x in num if x > 0]
print filtered_and_squared
 
# [1, 16, 100, 4, 9]
  •     迭代器(iterator)遍历输入序列num的每个成员x
  •     断言式判断每个成员是否大于零
  •     如果成员大于零,则被交给输出表达式,平方之后成为输出列表的成员。

列表推导式被封装在一个列表中,所以很明显它能够立即生成一个新列表。这里只有一个type函数调用而没有隐式调用lambda函数,列表推导式正是使用了一个常规的迭代器、一个表达式和一个if表达式来控制可选的参数。

另一方面,列表推导也可能会有一些负面效应,那就是整个列表必须一次性加载于内存之中,这对上面举的例子而言不是问题,甚至扩大若干倍之后也都不是问题。但是总会达到极限,内存总会被用完。

针对上面的问题,生成器(Generator)能够很好的解决。生成器表达式不会一次将整个列表加载到内存之中,而是生成一个生成器对象(Generator objector),所以一次只加载一个列表元素。

生成器表达式同列表推导式有着几乎相同的语法结构,区别在于生成器表达式是被圆括号包围,而不是方括号:
 

num = [1, 4, -5, 10, -7, 2, 3, -1]
filtered_and_squared = ( x**2 for x in num if x > 0 )
print filtered_and_squared
 
# <generator object <genexpr> at 0x00583E18>
 
for item in filtered_and_squared:
 print item
 
# 1, 16, 100 4,9

这比列表推导效率稍微提高一些,让我们再一次改造一下代码:

num = [1, 4, -5, 10, -7, 2, 3, -1]
 
def square_generator(optional_parameter):
 return (x ** 2 for x in num if x > optional_parameter)
 
print square_generator(0)
# <generator object <genexpr> at 0x004E6418>
 
# Option I
for k in square_generator(0):
 print k
# 1, 16, 100, 4, 9
 
# Option II
g = list(square_generator(0))
print g
# [1, 16, 100, 4, 9]

除非特殊的原因,应该经常在代码中使用生成器表达式。但除非是面对非常大的列表,否则是不会看出明显区别的。

下例使用zip()函数一次处理两个或多个列表中的元素:
 

alist = ['a1', 'a2', 'a3']
blist = ['1', '2', '3']
 
for a, b in zip(alist, blist):
 print a, b
 
# a1 1
# a2 2
# a3 3

再来看一个通过两阶列表推导式遍历目录的例子:
 

import os
def tree(top):
 for path, names, fnames in os.walk(top):
 for fname in fnames:
  yield os.path.join(path, fname)
 
for name in tree('C:\Users\XXX\Downloads\Test'):
 print name

装饰器(Decorators)

装饰器为我们提供了一个增加已有函数或类的功能的有效方法。听起来是不是很像Java中的面向切面编程(Aspect-Oriented Programming)概念?两者都很简单,并且装饰器有着更为强大的功能。举个例子,假定你希望在一个函数的入口和退出点做一些特别的操作(比如一些安全、追踪以及锁定等操作)就可以使用装饰器。

装饰器是一个包装了另一个函数的特殊函数:主函数被调用,并且其返回值将会被传给装饰器,接下来装饰器将返回一个包装了主函数的替代函数,程序的其他部分看到的将是这个包装函数。
 

def timethis(func):
 '''
 Decorator that reports the execution time.
 '''
 pass
 
@timethis
def countdown(n):
 while n > 0:
 n -= 1

语法糖@标识了装饰器。

好了,让我们回到刚才的例子。我们将用装饰器做一些更典型的操作:

import time
from functools import wraps
 
def timethis(func):
 '''
 Decorator that reports the execution time.
 '''
 @wraps(func)
 def wrapper(*args, **kwargs):
 start = time.time()
 result = func(*args, **kwargs)
 end = time.time()
 print(func.__name__, end-start)
 return result
 return wrapper
 
@timethis
def countdown(n):
 while n > 0:
 n -= 1
 
countdown(100000)
 
# ('countdown', 0.006999969482421875)

当你写下如下代码时:
 

@timethis
def countdown(n):

意味着你分开执行了以下步骤:
 

def countdown(n):
...
countdown = timethis(countdown)

装饰器函数中的代码创建了一个新的函数(正如此例中的wrapper函数),它用 *args 和 **kwargs 接收任意的输入参数,并且在此函数内调用原函数并且返回其结果。你可以根据自己的需要放置任何额外的代码(例如本例中的计时操作),新创建的包装函数将作为结果返回并取代原函数。
 

@decorator
def function():
 print("inside function")

当编译器查看以上代码时,function()函数将会被编译,并且函数返回对象将会被传给装饰器代码,装饰器将会在做完相关操作之后用一个新的函数对象代替原函数。

装饰器代码是什么样的?大部分的例子都是将装饰器定义为函数,而我发觉将装饰器定义成类更容易理解其功能,并且这样更能发挥装饰器机制的威力。

对装饰器的类实现唯一要求是它必须能如函数一般使用,也就是说它必须是可调用的。所以,如果想这么做这个类必须实现__call__方法。

这样的装饰器应该用来做些什么?它可以做任何事,但通常它用在当你想在一些特殊的地方使用原函数时,但这不是必须的,例如:

class decorator(object):
 
 def __init__(self, f):
 print("inside decorator.__init__()")
 f() # Prove that function definition has completed
 
 def __call__(self):
 print("inside decorator.__call__()")
 
@decorator
def function():
 print("inside function()")
 
print("Finished decorating function()")
 
function()
 
# inside decorator.__init__()
# inside function()
# Finished decorating function()
# inside decorator.__call__()

译者注:
1. 语法糖@decorator相当于function=decorator(function),在此调用decorator的__init__打印“inside decorator.__init__()”
2. 随后执行f()打印“inside function()”
3. 随后执行“print(“Finished decorating function()”)”
4. 最后在调用function函数时,由于使用装饰器包装,因此执行decorator的__call__打印 “inside decorator.__call__()”。

一个更实际的例子:

def decorator(func):
 def modify(*args, **kwargs):
 variable = kwargs.pop('variable', None)
 print variable
 x,y=func(*args, **kwargs)
 return x,y
 return modify
 
@decorator
def func(a,b):
 print a**2,b**2
 return a**2,b**2
 
func(a=4, b=5, variable="hi")
func(a=4, b=5)
 
# hi
# 16 25
# None
# 16 25

上下文管理库(ContextLib)

contextlib模块包含了与上下文管理器和with声明相关的工具。通常如果你想写一个上下文管理器,则你需要定义一个类包含__enter__方法以及__exit__方法,例如:
 

import time
class demo:
 def __init__(self, label):
 self.label = label
 
 def __enter__(self):
 self.start = time.time()
 
 def __exit__(self, exc_ty, exc_val, exc_tb):
 end = time.time()
 print('{}: {}'.format(self.label, end - self.start))

完整的例子在此:

import time
 
class demo:
 def __init__(self, label):
 self.label = label
 
 def __enter__(self):
 self.start = time.time()
 
 def __exit__(self, exc_ty, exc_val, exc_tb):
 end = time.time()
 print('{}: {}'.format(self.label, end - self.start))
 
with demo('counting'):
 n = 10000000
 while n > 0:
 n -= 1
 
# counting: 1.36000013351

上下文管理器被with声明所激活,这个API涉及到两个方法。
1. __enter__方法,当执行流进入with代码块时,__enter__方法将执行。并且它将返回一个可供上下文使用的对象。
2. 当执行流离开with代码块时,__exit__方法被调用,它将清理被使用的资源。

利用@contextmanager装饰器改写上面那个例子:

from contextlib import contextmanager
import time
 
@contextmanager
def demo(label):
 start = time.time()
 try:
 yield
 finally:
 end = time.time()
 print('{}: {}'.format(label, end - start))
 
with demo('counting'):
 n = 10000000
 while n > 0:
 n -= 1
 
# counting: 1.32399988174

看上面这个例子,函数中yield之前的所有代码都类似于上下文管理器中__enter__方法的内容。而yield之后的所有代码都如__exit__方法的内容。如果执行过程中发生了异常,则会在yield语句触发。
描述器(Descriptors)

描述器决定了对象属性是如何被访问的。描述器的作用是定制当你想引用一个属性时所发生的操作。

构建描述器的方法是至少定义以下三个方法中的一个。需要注意,下文中的instance是包含被访问属性的对象实例,而owner则是被描述器修辞的类。

    __get__(self, instance, owner) ? 这个方法是当属性被通过(value = obj.attr)的方式获取时调用,这个方法的返回值将被赋给请求此属性值的代码部分。
    __set__(self, instance, value) ? 这个方法是当希望设置属性的值(obj.attr = ‘value')时被调用,该方法不会返回任何值。
    __delete__(self, instance) ? 当从一个对象中删除一个属性时(del obj.attr),调用此方法。

译者注:对于instance和owner的理解,考虑以下代码:
 

class Celsius(object):
 def __init__(self, value=0.0):
 self.value = float(value)
 def __get__(self, instance, owner):
 return self.value
 def __set__(self, instance, value):
 self.value = float(value)
 
class Temperature(object):
 celsius = Celsius()
 
temp=Temperature()
temp.celsius #calls Celsius.__get__

上例中,instance指的是temp,而owner则是Temperature。

LazyLoading Properties例子:
 

import weakref
 
class lazyattribute(object):
 def __init__(self, f):
 self.data = weakref.WeakKeyDictionary()
 self.f = f
 def __get__(self, obj, cls):
 if obj not in self.data:
  self.data[obj] = self.f(obj)
 return self.data[obj]
 
class Foo(object):
 @lazyattribute
 def bar(self):
 print "Being lazy"
 return 42
 
f = Foo()
 
print f.bar
# Being lazy
# 42
 
print f.bar
# 42

描述器很好的总结了Python中的绑定方法(bound method)这个概念,绑定方法是经典类(classic classes)的实现核心。在经典类中,当在一个对象实例的字典中没有找到某个属性时,会继续到类的字典中查找,然后再到基类的字典中,就这么一直递归的查找下去。如果在类字典中找到这个属性,解释器会检查找到的对象是不是一个Python函数对象。如果是,则返回的并不是这个对象本身,而是返回一个柯里化(currying function)的包装器对象。当调用这个包装器时,它会首先在参数列表之前插入实例,然后再调用原函数。

译者注:
1. 柯里化 ? http://zh.wikipedia.org/wiki/%E6%9F%AF%E9%87%8C%E5%8C%96
2. function,method,bound method及unbound method的区别。首先,函数(function)是由def或lambda创建的。当一个函数在class语句块中定义或是由type来创建时,它会转成一个非绑定方法(unbound method),而当通过类实例(instance)来访问此方法的时候,它将转成绑定方法(bound method),绑定方法会自动将实例作为第一个参数传入方法。综上所述,方法是出现在类中的函数,绑定方法是一个绑定了具体实例的方法,反之则是非绑定方法。

综上,描述器被赋值给类,而这些特殊的方法就在属性被访问的时候根据具体的访问类型自动地调用。
元类(MetaClasses)

元类提供了一个改变Python类行为的有效方式。

元类的定义是“一个类的类”。任何实例是它自己的类都是元类。
 

class demo(object):
 pass
 
obj = demo()
 
print "Class of obj is {0}".format(obj.__class__)
print "Class of obj is {0}".format(demo.__class__)
 
# Class of obj is <class '__main__.demo'>
# Class of obj is <type 'type'>

在上例中,我们定义了一个类demo,并且生成了一个该类的对象obj。首先,可以看到obj的__class__是demo。有意思的来了,那么demo的class又是什么呢?可以看到demo的__class__是type。

所以说type是python类的类,换句话说,上例中的obj是一个demo的对象,而demo本身又是type的一个对象。

所以说type就是一个元类,而且是python中最常见的元类,因为它使python中所有类的默认元类。

因为元类是类的类,所以它被用来创建类(正如类是被用来创建对象的一样)。但是,难道我们不是通过一个标准的类定义来创建类的么?的确是这样,但是python内部的运作机制如下:

  •         当看见一个类定义,python会收集所有属性到一个字典中。
  •         当类定义结束,python将决定类的元类,我们就称它为Meta吧。
  •         最后,python执行Meta(name, bases, dct),其中:

a. Meta是元类,所以这个调用是实例化它。
b. name是新建类的类名。
c. bases是新建类的基类元组
d. dct将属性名映射到对象,列出所有的类属性。

那么如何确定一个类(A)的元类呢?简单来说,如果一个类(A)自身或其基类(Base_A)之一有__metaclass__属性存在,则这个类(A/Base_A)就是类(A)的元类。否则type就将是类(A)的元类。
模式(Patterns)

“请求宽恕比请求许可更容易(EFAP)”

这个Python设计原则是这么说的“请求宽恕比请求许可更容易(EFAP)”。不提倡深思熟虑的设计思路,这个原则是说应该尽量去尝试,如果遇到错误,则给予妥善的处理。Python有着强大的异常处理机制可以支持这种尝试,这些机制帮助程序员开发出更为稳定,容错性更高的程序。

单例

单例是指只能同时存在一个的实例对象。Python提供了很多方法来实现单例。

Null对象

Null对象能够用来代替None类型以避免对None的测试。

观察者

观察者模式允许多个对象访问同一份数据。

构造函数

构造函数的参数经常被赋值给实例的变量。这种模式能够用一行代码替代多个手动赋值语句。
总结

谢谢阅读,如有疑问,请留言讨论。

Python 相关文章推荐
pyqt4教程之实现windows窗口小示例分享
Mar 07 Python
Python数据类型详解(四)字典:dict
May 12 Python
Python解析命令行读取参数--argparse模块使用方法
Jan 23 Python
tensorflow TFRecords文件的生成和读取的方法
Feb 06 Python
python 2.7.14安装图文教程
Apr 08 Python
Python+AutoIt实现界面工具开发过程详解
Aug 07 Python
利用Python的sympy包求解一元三次方程示例
Nov 22 Python
python绘制规则网络图形实例
Dec 09 Python
pytorch加载自己的图像数据集实例
Jul 07 Python
套娃式文件夹如何通过Python批量处理
Aug 23 Python
关于探究python中sys.argv时遇到的问题详解
Feb 23 Python
简单且有用的Python数据分析和机器学习代码
Jul 02 Python
用Python代码来解图片迷宫的方法整理
Apr 02 #Python
在Python3中使用asyncio库进行快速数据抓取的教程
Apr 02 #Python
Python中的Classes和Metaclasses详解
Apr 02 #Python
详解Python中的装饰器、闭包和functools的教程
Apr 02 #Python
详解Python的迭代器、生成器以及相关的itertools包
Apr 02 #Python
用Python实现通过哈希算法检测图片重复的教程
Apr 02 #Python
仅用500行Python代码实现一个英文解析器的教程
Apr 02 #Python
You might like
PHP在弹框中获取foreach中遍历的id值并传递给地址栏
2017/06/13 PHP
kindeditor 加入七牛云上传的实例讲解
2017/11/12 PHP
Laravel登录失败次数限制的实现方法
2020/08/26 PHP
基于jQuery替换table中的内容并显示进度条的代码
2011/08/02 Javascript
2种jQuery 实现刮刮卡效果
2015/02/01 Javascript
JS实现的另类手风琴效果网页内容切换代码
2015/09/08 Javascript
jquery.validate 自定义验证方法及validate相关参数
2016/01/18 Javascript
MVC+jQuery.Ajax异步实现增删改查和分页
2020/12/22 Javascript
详解webpack异步加载业务模块
2017/06/23 Javascript
使用clipboard.js实现复制功能的示例代码
2017/10/16 Javascript
vue移动端轻量级的轮播组件实现代码
2018/07/12 Javascript
使用Vuex解决Vue中的身份验证问题
2018/09/28 Javascript
puppeteer实现html截图的示例代码
2019/01/10 Javascript
javascrit中undefined和null的区别详解
2019/04/07 Javascript
如何对react hooks进行单元测试的方法
2019/08/14 Javascript
vue输入节流,避免实时请求接口的实例代码
2019/10/30 Javascript
JavaScript手写数组的常用函数总结
2020/11/22 Javascript
跟老齐学Python之永远强大的函数
2014/09/14 Python
开源Web应用框架Django图文教程
2017/03/09 Python
Python下实现的RSA加密/解密及签名/验证功能示例
2017/07/17 Python
python数据结构链表之单向链表(实例讲解)
2017/07/25 Python
django初始化数据库的实例
2018/05/27 Python
python assert的用处示例详解
2019/04/01 Python
Python批量查询关键词微信指数实例方法
2019/06/27 Python
django框架中ajax的使用及避开CSRF 验证的方式详解
2019/12/11 Python
Python导入模块包原理及相关注意事项
2020/03/25 Python
枚举与#define宏的区别
2014/04/30 面试题
管理站站长岗位职责
2013/11/27 职场文书
保护环境建议书
2014/03/12 职场文书
2014年关于两会精神的心得体会
2014/03/17 职场文书
正科级干部考察材料
2014/05/29 职场文书
市场营销专业应届生自荐信
2014/06/19 职场文书
工作表扬信
2015/01/17 职场文书
居安思危观后感
2015/06/11 职场文书
2016年大学光棍节活动总结
2016/04/05 职场文书
python ansible自动化运维工具执行流程
2021/06/24 Python