Python进阶学习小结-1

2016-4-3 pierre

关于Python进阶

众所周知,Python入门是非常快的。记得我当时就看了一张十分钟入门Python的图,图片地址,再看看别人的代码,就开始尝试写起了Python。无论是普通的Python脚本、爬虫、Python web,以及Python科学计算,我们使用和喜欢使用Python的原因就是因为Python语言简单、清晰,写起来很随意。
但是随着不断深入,我们写Python的时候会不自主的带入一些与整个语言不搭的写法,而Python本身非常优秀的一些用法却被我们忽略。在阅读一些优秀Python开源项目时,往往发现自己写的Python和那些规范的进阶写法的Python差别很大,仿佛不是同一种语言是的。
前几天在公司内部KM上看到一篇Python进阶的翻译文章。单身狗清明节也没啥事情可干,就慢慢阅读,并生成这个笔记,和相关的联系代码(详见我的github)。
全系列文章可能3-4篇,希望大家读完,都可以写出逼格满满的Python代码。
Ps.此处的进阶是相对Python最基本语法而言,如果想要更进一步,利用Python开发类似Openstack这种的话,推荐《Python高手之路》,英文名字《The Hacker‘s Guide To Python》。

本文主要内容

  • *args和**kwargs
  • Debugging部分
  • 生成器部分
  • Map和Filiter部分
  • set部分
  • 装饰器部分

目录基本和那本书的内容一致,但是整合了我对文章阅读的一些理解,配合代码理解,有助于后续代码的学习。

魔法变量:*args和**kwargs

在阅读一些比较大的项目时,会看到很多*args和*kwargs这种魔法变量,如果没有系统学习很容易云里雾里。它们主要将不定量的参数传递给一个函数。*args发送一个非键值对的可变数量的参数列表给一个函数,*kwargs允许你将不定长度的键值对(key,value), 作为参数传递给一个函数。

  1. def test_args_kwargs(arg1, arg2, arg3):
  2. print "arg1:", arg1
  3. print "arg2:", arg2
  4. print "arg3:", arg3
  5. def test_args_1(*argv):
  6. for arg in argv:
  7. print "arg from argv", arg
  8. def test_args(f_arg, *argv):
  9. print "first normal arg:", f_arg
  10. for arg in argv:
  11. print "another arg from argv:", arg
  12. def test_kwargs(**kwargs):
  13. for key, value in kwargs.items():
  14. print "{0} == {1}".format(key, value)
  15. args = ("two", 3, 5)
  16. test_args_kwargs(*args)
  17. # 输出
  18. # arg1: two
  19. # arg2: 3
  20. # arg3: 5
  21. test_args_1('1','2')
  22. # 输出
  23. # arg from argv 1
  24. # arg from argv 2
  25. test_args('1','2','3')
  26. # 输出
  27. # first normal arg: 1
  28. # another arg from argv: 2
  29. # another arg from argv: 3
  30. kwargs = {"arg3": 3,"arg2": "two"}
  31. test_kwargs(**kwargs)
  32. # 输出
  33. # arg2 == two
  34. # arg3 == 3

如代码所示:*args和 **kwargs是很容易将不定量参数传递给函数的,通过函数等量参数声明或者for循环方式一一读出。此外,如果函数里有 test_args(f_arg, *argv)这种,可以直接额外直接读取出第一个传过去的参数。不过,通过函数等量参数声明的方法感觉不是特别实用,个人感觉也一般场景下应该不会使用。
*args和 **kwargs是下面装饰器的基础,所以需要特别注意。

调试

利用好调试,能大大提高你捕捉代码Bug的。大部分新人忽略了Python debugger(pdb)的重要性。此处建议大家阅读官方文档(其实我自己还没认真阅读完,平时代码量过小,基本靠肉眼debug)。
参考:https://docs.python.org/2/library/pdb.html Or https://docs.python.org/3/library/pdb.html+
一个参考代码

  1. import pdb
  2. def make_break():
  3. pdb.set_trace()
  4. return "I don't have time"
  5. print (make_break())

在运行时马上进入debugger模式,接下来就是debugger模式模式下的一系列命令:
c: 继续执行
w: 显示当前正在执行的代码行的上下文信息
a: 打印当前函数的参数列表
s: 执行当前代码行,并停在第一个能停的地方(相当于单步进入)
n: 继续执行到当前函数的下一行,或者当前行直接返回(单步跳过)

由于自己对调试这块用的不多,先跳过不细讲。

生成器 generator

  1. def generator_function():
  2. for i in range(2):
  3. yield i
  4. gen = generator_function()
  5. print next(gen)
  6. print next(gen)
  7. # print next(gen)
  8. # yield掉所有value时候,会触发stopIteration
  9. for item in generator_function():
  10. print item

如上图代码:生成器也是一种迭代,但是你只能迭代一次,它并不会将所有值放入内存中,而是运行时慢慢生成(yield)出来。注意:在不停next的时候,会到达yield所有的值,这时候会触发stopIteration,而采用for循环时不会产生。
这里的例子可能不是很实用,有种多此一举的感觉。其实生成器最佳应用场景:你不想将所有计算出来的大量结果集分配在内存当中,特别是结果集里包含大量循环,因为这样会造成大量的循环。
python2里的一些标准库函数会返回列表,而python3都修改成生成器,因为生成器更节省资源。在生成器对你有意义时候使用它,会非常有效。

map和filter

Map会将一个函数映射到一个输入列表的所有元素上。可以将一个list的参数传到函数里执行,甚至将一个列表的函数一一执行。有种只有想不到没有实现不了的感觉。

  1. # 匿名函数配合map
  2. items = [1, 2, 3, 4, 5]
  3. squared = list(map(lambda x: x**2, items))
  4. print squared
  5. # 一列表的函数。
  6. def multiply(x):
  7. return (x*x)
  8. def add(x):
  9. return (x+x)
  10. funcs = [multiply, add]
  11. for i in range(5):
  12. value = list(map(lambda x: x(i), funcs))
  13. print value

filter能创建一个列表,其中每个元素都是对一个函数能返回True。

  1. number_list = range(-5, 5)
  2. less_than_zero = list(filter(lambda x: x<0, number_list))
  3. print less_than_zero

集合:set

归纳起来就两点:

  • set 集合,与list相似,但是不能包含重复的值
  • set 可以比较方便的进行集合运算
    利用这个特性,可以实现一些神奇的功能:
  1. set(one_list) #将list中重复的值去掉
  2. duplicates = set([x for x in one_list if one_list.count(x)>1]) #找出list中重复的值
  3. input_set.intersection(valid) #求input_set和valid这个两个set的交集
  4. input_set.difference(valid) #求input_set和valid的差集

装饰器:decorator

终于到了本片文章最重要,最具Python的部分了。
看过Django和Flask的同学对装饰器肯定不陌生,像在flask中,路由什么的都是通过装饰器来实现的。
其实装饰器自身就是一个函数,它的作用就是改变其他函数的行为。听起来云里雾里,有种嵌入式编程中“中断”的味道。
看如下这个复杂一点的代码(利用生成器实现复杂的日志功能):

  1. from functools import wraps
  2. def logit(logfile='out.log'):
  3. def logging_decorator(func):
  4. @wraps(func)
  5. def wrapped_function(*args, **kwargs):
  6. log_string = func.__name__+" was called"
  7. print log_string
  8. with open(logfile,'a') as opened_file:
  9. opened_file.write(log_string+'\n')
  10. return wrapped_function
  11. return logging_decorator
  12. @logit()
  13. def myfunc1():
  14. pass
  15. myfunc1()
  16. @logit(logfile='out2.log')
  17. def myfunc2():
  18. pass
  19. myfunc2()

在Python中所有的一切都是对象,这一点在此不细说,通过对象的思维才能理解装饰器。上面代码中,定义logit这个装饰器,设置wraps时的场景,以及修改后的行为。通过在函数上方@logit()标识,可以实现对日志内容区分和日志文件名的区分。
不过上述代码有个小问题,就是如何写日志的同时确保函数内容实现?应该修改后可以实现。
ps.这个问题已经解决,只需要在@wraps(func)前运行func()就能实现相关。

当然,可以把装饰器写成装饰器类的形式,这样形式更简洁,更清晰。



来自为知笔记(Wiz)


发表评论:

Powered by emlog 陕ICP备15016021 sitemap