python中的那些“神器”

“武林至尊,宝刀屠龙,号令天下,莫敢不从,倚天不出,谁与争锋”,这是神器。不过今天要说的python中的“神器”就没有这么厉害了,这里要说的“神器”其实就是名称里面带了个“器”的,如下:

  1. 列表解析器
  2. 迭代器
  3. 生成器
  4. 装饰器

列表解析器

现在遇到了这样一个问题需要解决:“有一个数字的列表,要求对该列表中的奇数乘以2,返回处理完成后的列表(不改变原来列表的顺序,仅对列表中的奇数乘以2)”,比较传统的方法可能会是这样的:

def double_odd_number_list(odd_number_list):
    ret_value = []
    for number in odd_number_list:
        if number % 2:
            ret_value.append(number * 2)
        else:
            ret_value.append(number)
    return ret_value
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

测试数据:

if __name__ == ‘__main__‘:
    test_odd_number_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 24, 13, 28]
    print double_odd_number_list(test_odd_number_list)
  • 1
  • 2
  • 3

测试结果: 
 
这样做对于新手来说比较容易理解,但代码不够精简,我们来看看列表解析器怎样完成上面的工作:

print [(number*2) if number % 2 else number
           for number in test_odd_number_list]
  • 1
  • 2

代码说明:

  1. for number in test_odd_number_list迭代test_odd_number_list,对于test_odd_number_list中的每一个元素,如果为奇数(if分支)则往结果列表中放入number*2,否则放入number(else分支)

test_odd_number_list还是上面定义的,测试结果如下: 

上面讲的可能还满足不了你全部的需求,比如下面的情况: 

生成器表达式

列表解析器在生成的列表数据量很大时会占用很大的内存,它会一次性生成全部的数据保存到列表中并返回,无论是计算时间上还是内存消耗上,当数据量达到一定量级时都是无法接受的。一个改进方法就是使用生成器表达式,生成器表达式在语法上与列表解析唯一的区别就是讲[]换成了()。它会返回一个生成器,一次只计算一个值(延迟计算),如下图: 

迭代器

迭代器的详细内容可以参照我的另外一篇博客《python的迭代器

生成器

谈到生成器,不得不谈到python的一个关键字yield,python中关于生成器的定义是:“A function which returns an iterator”,该函数与普通还是除了包含yield关键字外没有任何区别。yield关键字的作用是挂起函数的执行,下次调用时所有环境恢复到挂起时的状态从挂起的地方继续执行,一般都是放到循环里。我们来看一个求阶乘的例子: 
 
有几点需要作出说明:

  1. 一个生成器函数中可以有多个yield函数,像上例
  2. 函数执行的顺序跟普通函数一致,可以理解成yield就是一个return,但return后该函数就挂起在该点,下次调用该函数时继续从该点开始执行。例如factorial(2),函数会直接执行到while循环(因为n!=0),第一次循环到yield时ret *= i,计算的结果是1,返回1并挂起,此时i=1,ret等于1,再一次调用时从yield的下一行执行,i变成2,小于等于传入的n=2,继续循环(没有从函数的头部执行),再到yield时ret=2,返回2并挂起,依次类推
  3. 通常比较少用,我第一次在代码中看到是ceilometer(openstack一个子项目)中

装饰器

装饰器,装点门面用,此处的门面就是函数,用来装饰函数或者方法,在python中我们习惯性的把绑定到实例上的叫方法,未绑定的叫函数,也就是说在类里面第一个参数为self的就是方法,在模块中的就是函数。python中的装饰器有两种:带参数的装饰器,不带参数的装饰器

不带参数的装饰器

我们先从语法上相对简单的不带参数的装饰器讲起。我们在评估性能时,可能会精确到某个具体的函数执行一次需要花费的时间,新手最新想到的应该是写一个脚本,调用前和调用后分别获取一下当前时间,两者之差就是该函数执行所消耗的时间,代码可能是这样的:

def test(ret_value, sleep_time):
    time.sleep(sleep_time)
    return ret_value

if __name__ == ‘__main__‘:
    start = datetime.datetime.utcnow()
    test((1, 2, 3, 4, 5, 6), 3)
    end = datetime.datetime.utcnow()
    print (end - start).total_seconds()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

代码容易理解,但如果有很多个函数都需要做这种测试呢?是不是就需要重复写很多次的:

start = datetime.datetime.utcnow()
end = datetime.datetime.utcnow()
print (end - start).total_seconds()
  • 1
  • 2
  • 3

如果你想成为一个优秀的程序员,那么,尽你所能去避免做重复的事情,让你写的每一行代码发挥最大的价值,下面来看一个使用装饰器的例子:

import datetime
import time

__author__ = ‘Administrator‘

def time_counter(func):
    def doc_func(*args, **kwargs):
        start = datetime.datetime.utcnow()
        ret = func(*args, **kwargs)
        end = datetime.datetime.utcnow()
        return ret, start, end, (end - start).total_seconds()
    return doc_func

@time_counter
def test(ret_value, sleep_time):
    time.sleep(sleep_time)
    return ret_value

if __name__ == ‘__main__‘:
    print test((1, 2, 3, 4, 5, 6), 3)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

time_counter是一个装饰器,它跟普通函数的区别在于:

  1. 参数为函数对象(time_counter的参数func)
  2. 在函数里面定义了一个新的函数(time_counter中的doc_func),且该函数(time_counter中的doc_func)的参数列表与第1步中的函数对象(time_counter的参数func)的参数列表一致
  3. 函数最后返回了新定义的函数对象(time_counter中的doc_func)
  4. @time_counter def func(*args, **kwargs):等价于func = time_counter(func) 
    在任何需要计算执行时间的函数上使用该装饰器即可,如下:
@time_counter
def test(ret_value, sleep_time):
    time.sleep(sleep_time)
    return ret_value
  • 1
  • 2
  • 3
  • 4

当执行test函数时,会先将test函数对象作为参数传递到time_counter装饰器,也就是形式参数func,然后依次执行定义的doc_func中的全部操作,其中调用func的地方(ret = func(*args, **kwargs))会调用test函数,下面是上述代码的执行结果: 

带参数的装饰器

上面的装饰器,在使用时不需要传递任何参数,但有时这还不足以满足我们的需求,假设我们在一套测试框架中,测试用例可能有多种级别,完全测试时我们需要执行所有等级的测试用例,如果某次发布仅仅是修改了其中的一小块功能,对于未修改的部分,我们仅需要测试基本功能即可,例如级别大于等于2的。我们来看一下使用带参数的装饰器模拟实现该功能:

import datetime
import time

__author__ = ‘Administrator‘

def skip_unit_test(func):
    print ‘skip function:%s‘ % func.func_name

def unit_test_filter(level):
    def new_decorator(func):
        def final_func(*args, **kwargs):
            if level >= 2:
                start = datetime.datetime.utcnow()
                ret = func(*args, **kwargs)
                end = datetime.datetime.utcnow()
                print ret, start, end, (end - start).total_seconds()
            else:
                skip_unit_test(func)
        return final_func
    return new_decorator

@unit_test_filter(0)
def test_level_zero(ret_value, sleep_time):
    time.sleep(sleep_time)
    return ret_value

@unit_test_filter(1)
def test_level_one(ret_value, sleep_time):
    time.sleep(sleep_time)
    return ret_value

@unit_test_filter(2)
def test_level_two(ret_value, sleep_time):
    time.sleep(sleep_time)
    return ret_value

@unit_test_filter(3)
def test_level_three(ret_value, sleep_time):
    time.sleep(sleep_time)
    return ret_value

if __name__ == ‘__main__‘:
    test_level_zero((1, 2, 3, 4, 5, 6), 3)
    test_level_one((1, 2, 3, 4, 5, 6), 3)
    test_level_two((1, 2, 3, 4, 5, 6), 3)
    test_level_three((1, 2, 3, 4, 5, 6), 3)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

看起来比较复杂,我们来总结一下带参数装饰器的特性:

    1. 是一个函数,有一个或者多个参数,返回一个定义的子函数对象
    2. 函数体中定义一个子函数,子函数的参数是函数对象,同“不带参数装饰器”中的func,子函数仍然返回定义的孙子函数对象
    3. 子函数中再定义一个孙子函数,孙子函数的参数列表与子函数参数func的参数列表一致,并在其中做一些处理,且最终会调用到被装饰的函数(单次装饰的函数可能不会被调用)
    4. 在使用时需要按照装饰器函数的参数列表来传递参数,如例子中的:@unit_test_filter(0)等 
      下面附上上述例子的执行结果: 
时间: 2024-10-06 01:20:07

python中的那些“神器”的相关文章

一文看懂命令行参数的用法——Python中的getopt神器

一文看懂命令行参数的用法--Python中的getopt神器 参考原文:Python模块之命令行参数解析 - 每天进步一点点!!! - 博客园 https://www.cnblogs.com/madsnotes/articles/5687079.htmlpython getopt使用 - tianzhu123的专栏 - CSDN博客 https://blog.csdn.net/tianzhu123/article/details/7655499在运行程序时,可能需要根据不同的条件,输入不同的命令

正则表达式与Python中re模块的使用

正则表达式与Python中re模块的使用 最近做了点爬虫,正则表达式使用的非常多,用Python做的话会用到re模块. 另外,给大家介绍一个在线测试正则表达式的神器网站:http://tool.chinaz.com/regex 大家可以去这里练习正则表达式 下面总结一下正则表达式与re模块的基础与使用. 正则表达式 使用场景 关于正则表达式的基本概念这里就不赘述了,大家可以去各种百科里查找它的定义.正则的使用场景主要分为两个: 一是:检测某一段字符串是否符合规则,也就是我们常说的"校验"

走入计算机的第四十天(python中sockserver模块)

一.Python中的sockserver模块 1.该模块与sock模块不同之处是该模块自动帮我们分装好了一些功能,让我们在编程的时候直接调用这些功能就可以了,节省了编程步骤. 2.如图所示 注释:上图为服务端设置 该模块的操作方法比较死板,我们只要会熟悉的使用他就可以了.

python中if __name__ == '__main__':

Using a module's __name__ Example? 8.2.? Using a module's __name__ #!/usr/bin/python # Filename: using_name.py if __name__ == '__main__': print 'This program is being run by itself' else: print 'I am being imported from another module' Output $ pytho

关于Python中的yield

关于Python中的yield http://www.cnblogs.com/tqsummer/archive/2010/12/27/1917927.html http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/ 一.迭代器(iterator) 在Python中,for循环可以用于Python中的任何类型,包括列表.元祖等等,实际上,for循环可用于任何“可迭代对象”,这其实就是迭代器 迭代器是一个实现了迭代器协议

Python中字符串格式化如何实现?

Python开发中字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存. This PEP proposes a new system for built-in string formatting operations, intended as a replacement for the existing '%' string formatting operator. 1.百分号方式 %[(na

python 中*args 和 **kwargs

简单的可以理解为python 中给函数传递的可变参数,args 是 列表的形式.kwargs 是 key,value的形式,也就是python 中的字典. *args 必须出现在**kwargs 的前边,否则会抛异常. 1 def test(*args, **kwargs): 2 print args 3 print kwargs 1 if __name__ == '__main__': 2 print '---test 1---' 3 test(1, 2, 3) 4 print '---tes

python中super出现的TypeError: must be type, not classobj 原因及解决

执行一下代码,出现错误,TypeError: must be type, not classobj class A():    def __init__(self):        print("Enter A")        print("Leave A") class B(A):    def __init__(self):        print("Enter B")        super(B, self).__init__()  

python中的切片问题

对于在一个字符串中选取几个字符,不同的语言有不同的解决方案,python 中就有了切片的方法.    在list中,如下:     s=list(range(1,101))    如果想要选取偶数个数字(或者选取偶数),可以用循环的方法:但是方法臃肿,比较"笨"    但是python中给出的切片方法是更加的优雅的,如下:    L=list(range(1,101))    print(L[0])    print(L[0:10])#输出结果是[1, 2, 3, 4, 5, 6, 7