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

 正文:

本文展示一些高级的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的测试。

观察者

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

构造函数

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

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

(0)

相关推荐

  • Python字符串中查找子串小技巧

    惭愧啊,今天写了个查找子串的Python程序被BS了- 如果让你写一个程序检查字符串s2中是不是包含有s1.也许你会很直观的写下下面的代码: 复制代码 代码如下: #determine whether s1 is a substring of s2 def isSubstring1(s1,s2):     tag = False     len1 = len(s1)     len2 = len(s2)     for i in range(0,len2):         if s2[i] =

  • 总结Python编程中三条常用的技巧

    在 python 代码中可以看到一些常见的 trick,在这里做一个简单的小结. json 字符串格式化 在开发 web 应用的时候经常会用到 json 字符串,但是一段比较长的 json 字符串是可读性较差的,不容易看出来里面结构的. 这时候就可以用 python 来把 json 字符串漂亮的打印出来. root@Exp-1:/tmp# cat json.txt {"menu": {"breakfast": {"English Muffin":

  • 收藏整理的一些Python常用方法和技巧

    1. 逆转字符串的三种方法 1.1. 模拟C++中方法, 定义一个空字符串来实现 通过设置一个空字符串, 然后讲参数中的字符串从后往前遍历, 使用字符串的加法合并为新的字符串 复制代码 代码如下: def reverse(text) :     str = ''     index = len(text) - 1     while index >= 0 :         str += text[index]         index -= 1     return str 1.2. 使用切

  • Python合并多个装饰器小技巧

    django程序,需要写很多api,每个函数都需要几个装饰器,例如 复制代码 代码如下: @csrf_exempt  @require_POST  def  foo(request):      pass 既然那么多个方法都需要写2个装饰器,或者多个,有啥办法把多个合并成一行呢? 上面的函数执行过程应该是 复制代码 代码如下: csrf_exempt(require_POST(foo)) 修改成 复制代码 代码如下: def compose(*funs):      def deco(f): 

  • 低版本中Python除法运算小技巧

    首先要说的是python中的除法运算,在python 2.5版本中存在两种除法运算,即所谓的true除法和floor除法.当使用x/y形式进行除法运算时,如果x和y都是整形,那么运算的会对结果进行截取,取运算的整数部分,比如2/3的运算结果是0:如果x和y中有一个是浮点数,那么会进行所谓的true除法,比如2.0/3的结果是 0.66666666666666663.另外一种除法是采用x//y的形式,那么这里采用的是所谓floor除法,即得到不大于结果的最大整数值,这个运算时与操作数无关的.比如2

  • Python性能优化技巧

    Python是一门非常酷的语言,因为很少的Python代码可以在短时间内做很多事情,并且,Python很容易就能支持多任务和多重处理. py 1.关键代码可以依赖于扩展包 Python使许多编程任务变得简单,但是对于很关键的任务并不总是提供最好的性能.使用C.C++或者机器语言扩展包来执行关键任务能极大改善性能.这些包是依赖于平台的,也就是说,你必须使用特定的.与你使用的平台相关的包.简而言之,该解决方案提供了一些应用程序的可移植性,以换取性能,您可以获得只有通过直接向底层主机编程.下面这些扩展

  • 给Python初学者的一些编程技巧

    交换变量   x = 6 y = 5 x, y = y, x print x >>> 5 print y >>> 6 if 语句在行内 print "Hello" if True else "World" >>> Hello 连接 下面的最后一种方式在绑定两个不同类型的对象时显得很cool. nfc = ["Packers", "49ers"] afc = ["R

  • Python常用小技巧总结

    本文实例总结了Python常用的小技巧.分享给大家供大家参考.具体分析如下: 1. 获取本地mac地址: import uuid mac = uuid.uuid1().hex[-12:] print(mac) 运行结果:e0cb4e077585 2. del 的使用 a = ['b','c','d'] del a[0] print(a)# 输出 ['c', 'd'] a = ['b','c','d'] del a[0:2] # 删除从第1个元素开始,到第2个元素 print(a)# 输出 ['d

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

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

  • Python中字符串的常见操作技巧总结

    本文实例总结了Python中字符串的常见操作技巧.分享给大家供大家参考,具体如下: 反转一个字符串 >>> S = 'abcdefghijklmnop' >>> S[::-1] 'ponmlkjihgfedcba' 这种用法叫做three-limit slices 除此之外,还可以使用slice对象,例如 >>> 'spam'[slice(None, None, -1)] >>> unicode码与字符(single-characte

  • Python 中 list 的各项操作技巧

    最近在学习 python 语言.大致学习了 python 的基础语法.觉得 python 在数据处理中的地位和它的 list 操作密不可分. 特学习了相关的基础操作并在这里做下笔记. ''' Python --version Python 2.7.11 Quote : https://docs.python.org/2/tutorial/datastructures.html#more-on-lists Add by camel97 2017-04 ''' list.append(x) #在列表

  • python 中的9个实用技巧,助你提高开发效率

    整理字符串输入 整理用户输入的问题在编程过程中极为常见.通常情况下,将字符转换为小写或大写就够了,有时你可以使用正则表达式模块「Regex」完成这项工作.但是如果问题很复杂,可能有更好的方法来解决: user_input = "This string has some whitespaces... " character_map = { ord( ) : , ord( ) : , ord( ) : None } user_input.translate(character_map) #

  • 在Python中f-string的几个技巧,你都知道吗

    目录 最基础用法 自记录表达式 多行f-string 在f-string中格式化日期 控制浮点数精度 标准化显示宽度 修改为左对齐 设置科学计数法格式 控制有效数字位数 f-string想必很多Python用户都基础性的使用过,作为Python3.6版本开始引入的特性,通过它我们可以更加方便地向字符串中嵌入自定义内容,但f-string真正蕴含的功能远比大多数用户知道的要丰富,今天我们就来一起get它们~ 最基础用法 f-string最基础的用法很简单,如下例所示,在前缀f的字符串中向{}内直接

  • Python中最大最小赋值小技巧(分享)

    码代码时,有时候需要根据比较大小分别赋值: import random seq = [random.randint(0, 1000) for _ in range(100)] #方法1: xmax, xmin = max(seq), min(seq) #方法2: xmax, *_, xmin = sorted(seq) 从上面这个来看,看不出来方法2的优势来,不过我们常用的是比较两个数的大小,并选取: dx, dy = random.sample(seq, 2) #方法1: dx, dy = m

  • 全面介绍python中很常用的单元测试框架unitest

    1.unitest主要功能模块介绍 unitest主要包含TestCase.TestSuite.TestLoader.TextTestRunner.TextTestResult这几个功能模块. TestCase:一个TestCase实例就是一个测试用例,一个测试用例就是一个完整的测试流程,包括测试前环境的搭建,测试代码的执行,以及测试后环境的还原或者销毁.元测试的本质也就在这里,一个测试用例是一个完整的测试单元,可以对某一具体问题进行检查验证. TestSuite:多个测试用例集合在一起就是Te

  • 详细介绍Python中的set集合

    目录 Python中的set集合 一.集合是什么? 二.set集合怎么用? 1.创建set集合 2.删除set集合 3.访问set集合元素 4.删除集合中的元素 5.向集合中添加元素 三.set集合的交并补 1.交集 2.并集 3.差集 四.set中的其他方法 五.frozenset 集合 Python中的set集合 一.集合是什么? 集合是什么呢?相信读者朋友们哪怕是没有用过集合这个数据类型.也一定在数学课堂上听过集合这个名词.数学中的集合是一个基本概念,说白了一堆不重复的数字可以组成一个集合

  • 关于Python中空格字符串处理的技巧总结

    前言 大家应该都知道字符串处理,是任何语言最常用到的. 其中就经常会碰到,对字符串中的空格处理,比如:去除前后空格,去除全部空格,或者以空格为分隔符来处理. 好在Python中字符串有很多方法,比如lstrip() ,  rstrip() ,  strip()来去除字符串前后空格,借助split()对字符来分隔: 实在不行,还可以借助于re模块的sub函数来替换. 下面列举下,各种情况下的处理技巧,通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,话不多说了,来一起看看详细的介绍吧. [

  • Python中的super()面向对象编程

    目录 Python super()面向对象编程 一.为什么要用 super() 二.什么是 super 三.继承中使用 super 1.实例方法使用 super 2.构造方法使用 super 四.多继承中使用 super Python super()面向对象编程 一.为什么要用 super() 当子类重写了父类方法时,又想调用父类的同名方法时,就需要用到 super() 二.什么是 super 在 Python 中,super 是一个特殊的类 super() 就是使用 super 类创建出来的对

随机推荐