python学习day4

一、装饰器

  • 定义:本质是函数,用来装饰其他函数
  • 原则:
    1)不能修改被装饰函数的源代码
    2)不能修改被装饰函数的调用方式

1.1函数调用顺序

其他高级语言类似,Python不允许在函数未声明之前,对其进行引用或者调用

  • 错误示范
  1. def foo():
  2. print 'in the foo'
  3. bar()
  4. foo()
  5. 报错:
  6. in the foo
  7. Traceback (most recent call last):
  8. File "<pyshell#13>", line 1, in <module>
  9. foo()
  10. File "<pyshell#12>", line 3, in foo
  11. bar()
  12. NameError: global name 'bar' is not defined
  1. def foo():
  2. print 'foo'
  3. bar()
  4. foo()
  5. def bar():
  6. print 'bar'
  7. 报错:NameError: global name 'bar' is not defined
  • 正确示范:(注意,python为解释执行,函数foo在调用前已经声明了bar和foo,所以bar和foo无顺序之分)
  1. def bar():
  2. print 'in the bar'
  3. def foo():
  4. print 'in the foo'
  5. bar()
  6. foo()
  1. def foo():
  2. print 'in the foo'
  3. bar()
  4. def bar():
  5. print 'in the bar'
  6. foo()

1.2高阶函数

  • 高阶函数的特点
    1)某一函数当做参数传入另一个函数中
    2)函数的返回值包含n个函数,n>0
  • 高阶函数示范
  1. def bar():
  2. print 'in the bar'
  3. def foo(func):
  4. res=func()
  5. return res
  6. foo(bar) #此处将bar这个函数的内存空间传给了foo这个函数作为参数
  • 高阶函数的牛逼之处
  1. def foo(func):
  2. return func
  3. print 'Function body is %s' %(foo(bar))
  4. print 'Function name is %s' %(foo(bar).func_name)
  5. foo(bar)()
  6. #foo(bar)() 等同于bar=foo(bar)然后bar()
  7. bar=foo(bar)
  8. bar()

1.3内嵌函数和变量作用域

在一个函数体内创建另外一个函数,这种函数就叫内嵌函数(基于python支持静态嵌套域)

  • 函数嵌套示范
  1. def foo():
  2. def bar():
  3. print 'in the bar'
  4. bar()
  5. foo()
  6. # bar()

局部作用域和全局作用域的访问顺序

  1. x=0
  2. def grandpa():
  3. # x=1
  4. def dad():
  5. x=2
  6. def son():
  7. x=3
  8. print x
  9. son()
  10. dad()
  11. grandpa()

局部变量修改对全局变量的影响

  1. y=10
  2. # def test():
  3. # y+=1
  4. # print y
  5. def test():
  6. # global y
  7. y=2
  8. print y
  9. test()
  10. print y
  11. def dad():
  12. m=1
  13. def son():
  14. n=2
  15. print '--->',m + n
  16. print '-->',m
  17. son()
  18. dad()

1.4闭包

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是 closure

  1. def counter(start_num=0):
  2. count=[start_num]
  3. def incr():
  4. count[0]+=1
  5. return count[0]
  6. return incr
  7. print counter()
  8. print counter()()
  9. print counter()()
  10. c=counter()
  11. print c()
  12. print c()

1.5内嵌函数+高阶函数+闭包=》装饰器

  • 范例1:函数参数固定
  1. def decorartor(func):
  2. def wrapper(n):
  3. print 'starting'
  4. func(n)
  5. print 'stopping'
  6. return wrapper
  7. def test(n):
  8. print 'in the test arg is %s' %n
  9. decorartor(test)('alex')
  • 范例二:函数参数不固定
  1. def decorartor(func):
  2. def wrapper(*args,**kwargs):
  3. print 'starting'
  4. func(*args,**kwargs)
  5. print 'stopping'
  6. return wrapper
  7. def test(n,x=1):
  8. print 'in the test arg is %s' %n
  9. decorartor(test)('alex',x=2)
  • 范例三:无参装饰器
  1. import time
  2. def decorator(func):
  3. def wrapper(*args,**kwargs):
  4. start=time.time()
  5. func(*args,**kwargs)
  6. stop=time.time()
  7. print 'run time is %s ' %(stop-start)
  8. print timeout
  9. return wrapper
  10. @decorator
  11. def test(list_test):
  12. for i in list_test:
  13. time.sleep(0.1)
  14. print '-'*20,i
  15. #decorator(test)(range(10))
  16. test(range(10))
  • 范例四:有参装饰器
  1. import time
  2. def timer(timeout=0):
  3. def decorator(func):
  4. def wrapper(*args,**kwargs):
  5. start=time.time()
  6. func(*args,**kwargs)
  7. stop=time.time()
  8. print 'run time is %s ' %(stop-start)
  9. print timeout
  10. return wrapper
  11. return decorator
  12. @timer(2)
  13. def test(list_test):
  14. for i in list_test:
  15. time.sleep(0.1)
  16. print '-'*20,i
  17. #timer(timeout=10)(test)(range(10))
  18. test(range(10))

1.6装饰器应用案例

装饰器功能:函数超时则终止

  1. # -*- coding: utf-8 -*-
  2. from threading import Thread
  3. import time
  4. class TimeoutException(Exception):
  5. pass
  6. ThreadStop = Thread._Thread__stop#获取私有函数
  7. def timelimited(timeout):
  8. def decorator(function):
  9. def decorator2(*args,**kwargs):
  10. class TimeLimited(Thread):
  11. def __init__(self,_error= None,):
  12. Thread.__init__(self)
  13. self._error = _error
  14. def run(self):
  15. try:
  16. self.result = function(*args,**kwargs)
  17. except Exception,e:
  18. self._error =e
  19. def _stop(self):
  20. if self.isAlive():
  21. ThreadStop(self)
  22. t = TimeLimited()
  23. t.start()
  24. t.join(timeout)
  25. if isinstance(t._error,TimeoutException):
  26. t._stop()
  27. raise TimeoutException('timeout for %s' % (repr(function)))
  28. if t.isAlive():
  29. t._stop()
  30. raise TimeoutException('timeout for %s' % (repr(function)))
  31. if t._error is None:
  32. return t.result
  33. return decorator2
  34. return decorator
  35. @timelimited(2)
  36. def fn_1(secs):
  37. time.sleep(secs)
  38. return 'Finished'
  39. if __name__ == "__main__":
  40. print fn_1(4)

二、生成器

  通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
  所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
  要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

  1. >>> L = [x * x for x in range(10)]
  2. >>> L
  3. [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  4. >>> g = (x * x for x in range(10))
  5. >>> g
  6. <generator object <genexpr> at 0x1022ef630>

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

  1. >>> next(g)
  2. 0
  3. >>> next(g)
  4. 1
  5. >>> next(g)
  6. 4
  7. >>> next(g)
  8. 9
  9. >>> next(g)
  10. 16
  11. >>> next(g)
  12. 25
  13. >>> next(g)
  14. 36
  15. >>> next(g)
  16. 49
  17. >>> next(g)
  18. 64
  19. >>> next(g)
  20. 81
  21. >>> next(g)
  22. Traceback (most recent call last):
  23. File "<stdin>", line 1, in <module>
  24. StopIteration

v我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

  1. >>> g = (x * x for x in range(10))
  2. >>> for n in g:
  3. ... print(n)
  4. ...
  5. 0
  6. 1
  7. 4
  8. 9
  9. 16
  10. 25
  11. 36
  12. 49
  13. 64
  14. 81

所以,我们创建了一个generator后,基本上永远不会调用__next__,而是通过for循环来迭代它,并且不需要关心StopIteration的错误。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, …
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

  1. def fib(max):
  2. n, a, b = 0, 0, 1
  3. while n < max:
  4. print(b)
  5. a, b = b, a + b
  6. n = n + 1
  7. return 'done'
  • 注意赋值语句:
  1. a, b = b, a + b
  • 上述赋值语句相当于
  1. t = (b, a + b) # t是一个tuple
  2. a = t[0]
  3. b = t[1]

但不必显式写出临时变量t就可以赋值。
上面的函数可以输出斐波那契数列的前N个数:

  1. >>> fib(10)
  2. 1
  3. 1
  4. 2
  5. 3
  6. 5
  7. 8
  8. 13
  9. 21
  10. 34
  11. 55
  12. done

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

  1. def fib(max):
  2. n,a,b = 0,0,1
  3. while n < max:
  4. #print(b)
  5. yield b
  6. a,b = b,a+b
  7. n += 1
  8. return 'done'

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

  1. >>> f = fib(6)
  2. >>> f
  3. <generator object fib at 0x104feaaa0>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

  1. data = fib(10)
  2. print(data)
  3. print(data.__next__())
  4. print(data.__next__())
  5. print("干点别的事")
  6. print(data.__next__())
  7. print(data.__next__())
  8. print(data.__next__())
  9. print(data.__next__())
  10. print(data.__next__())
  11. #输出
  12. <generator object fib at 0x101be02b0>
  13. 1
  14. 1
  15. 干点别的事
  16. 2
  17. 3
  18. 5
  19. 8
  20. 13

在上面fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

  1. >>> for n in fib(6):
  2. ... print(n)
  3. ...
  4. 1
  5. 1
  6. 2
  7. 3
  8. 5
  9. 8

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

  1. >>> g = fib(6)
  2. >>> while True:
  3. ... try:
  4. ... x = next(g)
  5. ... print('g:', x)
  6. ### ... except StopIteration as e: #关于如何捕获错误,后面的错误处理还会详细讲解。
  7. ... print('Generator return value:', e.value)
  8. ... break
  9. ...
  10. g: 1
  11. g: 1
  12. g: 2
  13. g: 3
  14. g: 5
  15. g: 8
  16. Generator return value: done

还可通过yield实现在单线程的情况下实现并发运算的效果

  1. #_*_coding:utf-8_*_
  2. __author__ = 'Alex Li'
  3. import time
  4. def consumer(name):
  5. print("%s 准备吃包子啦!" %name)
  6. while True:
  7. baozi = yield
  8. print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
  9. def producer(name):
  10. c = consumer('A')
  11. c2 = consumer('B')
  12. c.__next__()
  13. c2.__next__()
  14. print("老子开始准备做包子啦!")
  15. for i in range(10):
  16. time.sleep(1)
  17. print("做了2个包子!")
  18. c.send(i)
  19. c2.send(i)
  20. producer("alex")
  21. 通过生成器实现协程并行运算

三、 迭代器

我们已经知道,可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:

  1. >>> from collections import Iterable
  2. >>> isinstance([], Iterable)
  3. True
  4. >>> isinstance({}, Iterable)
  5. True
  6. >>> isinstance('abc', Iterable)
  7. True
  8. >>> isinstance((x for x in range(10)), Iterable)
  9. True
  10. >>> isinstance(100, Iterable)
  11. False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

  • 可以被__next__函数调用并不断返回下一个值的对象称为迭代器:Iterator。
    可以使用isinstance()判断一个对象是否是Iterator对象:
  1. >>> from collections import Iterator
  2. >>> isinstance((x for x in range(10)), Iterator)
  3. True
  4. >>> isinstance([], Iterator)
  5. False
  6. >>> isinstance({}, Iterator)
  7. False
  8. >>> isinstance('abc', Iterator)
  9. False

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:

  1. >>> isinstance(iter([]), Iterator)
  2. True
  3. >>> isinstance(iter('abc'), Iterator)
  4. True

你可能会问,为什么list、dict、str等数据类型不是Iterator?

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结:

  • 凡是可作用于for循环的对象都是Iterable类型;
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
    *集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
    Python的for循环本质上就是通过不断调用next()函数实现的,例如:
  1. for x in [1, 2, 3, 4, 5]:
  2. pass
  3. ```
  4. 上述代码实际上完全等价于:
  5. ```py
  6. # 首先获得Iterator对象:
  7. it = iter([1, 2, 3, 4, 5])
  8. # 循环:
  9. while True:
  10. try:
  11. # 获得下一个值:
  12. x = it__next__
  13. except StopIteration:
  14. # 遇到StopIteration就退出循环
  15. break
  16. ```
  17. # 四、json和pickle序列化
  18. * json,用于字符串 python数据类型间进行转换
  19. * pickle,用于python特有的类型 python的数据类型间进行转换
  20. Json模块提供了四个功能:dumpsdumploadsload
  21. pickle模块提供了四个功能:dumpsdumploadsload
  22. ![](d0769950-5afc-4f47-b142-56bf7a186eb5_files/425762-20151114231017087-842020084.png)
  23. # 五软件目录结构规范
  24. ## 5.1为什么要设计好目录结构?
  25. "设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。对于这种风格上的规范,一直都存在两种态度:
  26. * 一类同学认为,这种个人风格问题"无关紧要"。理由是能让程序work就好,风格问题根本不是问题。
  27. * 另一类同学认为,规范化能更好的控制程序结构,让程序具有更高的可读性。
  28. 我是比较偏向于后者的,因为我是前一类同学思想行为下的直接受害者。我曾经维护过一个非常不好读的项目,其实现的逻辑并不复杂,但是却耗费了我非常长的时间去理解它想表达的意思。从此我个人对于提高项目可读性、可维护性的要求就很高了。"项目目录结构"其实也是属于"可读性和可维护性"的范畴,我们设计一个层次清晰的目录结构,就是为了达到以下两点:
  29. * 可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。
  30. * 可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。
  31. **所以,我认为,保持一个层次清晰的目录结构是有必要的。更何况组织一个良好的工程目录,其实是一件很简单的事儿**
  32. ## 5.2目录组织方式
  33. 关于如何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构。在Stackoverflow的这个问题上,能看到大家对Python目录结构的讨论。
  34. 这里面说的已经很好了,我也不打算重新造轮子列举各种不同的方式,这里面我说一下我的理解和体会。
  35. 假设你的项目名为foo, 我比较建议的最方便快捷目录结构这样就足够了:
  36. ```PY
  37. Foo/
  38. |-- bin/
  39. | |-- foo
  40. |
  41. |-- foo/
  42. | |-- tests/
  43. | | |-- __init__.py
  44. | | |-- test_main.py
  45. | |
  46. | |-- __init__.py
  47. | |-- main.py
  48. |
  49. |-- docs/
  50. | |-- conf.py
  51. | |-- abc.rst
  52. |
  53. |-- setup.py
  54. |-- requirements.txt
  55. |-- README

简要解释一下:

  • bin/: 存放项目的一些可执行文件,当然你可以起名script/之类的也行。
  • foo/: 存放项目的所有源代码。(1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录tests/存放单元测试代码; (3) 程序的入口最好命名为main.py。
  • docs/: 存放一些文档。
  • setup.py: 安装、部署、打包的脚本。
  • requirements.txt: 存放软件依赖的外部Python包列表。
  • README: 项目说明文件。
    除此之外,有一些方案给出了更加多的内容。比如LICENSE.txt,ChangeLog.txt文件等,我没有列在这里,因为这些东西主要是项目开源的时候需要用到。如果你想写一个开源软件,目录该如何组织,可以参考这篇文章。

5.3对这些目录的理解和个人要求

5.3.1关于README的内容

这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。
它需要说明以下几个事项:

  • 软件定位,软件的基本功能。
  • 运行代码的方法: 安装环境、启动命令等。
  • 简要的使用说明。
  • 代码目录结构说明,更详细点可以说明软件的基本原理。
    常见问题说明。
    小结:我觉得有以上几点是比较好的一个README。在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。
    可以参考Redis源码中Readme的写法,这里面简洁但是清晰的描述了Redis功能和源码结构。

5.3.2关于setup.py

一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情。这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。
我刚开始接触Python写项目的时候,安装环境、部署代码、运行程序这个过程全是手动完成,遇到过以下问题:

  • 安装环境时经常忘了最近又添加了一个新的Python包,结果一到线上运行,程序就出错了。
  • Python包的版本依赖问题,有时候我们程序中使用的是一个版本的Python包,但是官方的已经是最新的包了,通过手动安装就可能装错了。
  • 如果依赖的包很多的话,一个一个安装这些依赖是很费时的事情。
  • 新同学开始写项目的时候,将程序跑起来非常麻烦,因为可能经常忘了要怎么安装各种依赖。
    setup.py可以将这些事情自动化起来,提高效率、减少出错的概率。”复杂的东西自动化,能自动化的东西一定要自动化。”是一个非常好的习惯。
    setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,可以参考一下Python的一个Web框架,flask是如何写的: setup.py
    当然,简单点自己写个安装脚本(deploy.sh)替代setup.py也未尝不可。

5.3.3关于requirements.txt

这个文件存在的目的是:

  • 方便开发者维护软件的包依赖。将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包。
  • 方便读者明确项目使用了哪些Python包。
    这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以简单的通过 pip install -r requirements.txt来把所有Python包依赖都装好了。具体格式说明: 点这里。

5.3.4关于配置文件的使用方法

很多项目对配置文件的使用做法是:

  • 配置文件写在一个或多个python文件中,比如此处的conf.py。
  • 项目中哪个模块用到这个配置文件就直接通过import conf这种形式来在代码中使用配置。
    这种做法我不太赞同:
  • 这让单元测试变得困难(因为模块内部依赖了外部配置)
  • 另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。
  • 程序组件可复用性太差,因为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖conf.py这个文件。
    我认为配置的使用,更好的方式如下
  • 模块的配置都是可以灵活配置的,不受外部配置文件的影响。
  • 程序的配置也是可以灵活控制的。
    能够佐证这个思想的是,用过nginx和mysql的同学都知道,nginx、mysql这些程序都可以自由的指定用户配置。
    所以,不应当在代码中直接import conf来使用配置文件。上面目录结构中的conf.py,是给出的一个配置样例,不是在写死在程序中直接引用的配置文件。可以通过给main.py启动参数指定配置路径的方式来让程序读取配置内容。当然,这里的conf.py你可以换个类似的名字,比如settings.py。或者你也可以使用其他格式的内容来编写配置文件,比如settings.yaml之类的。
0
未经许可,不得转载,否则将受到作者追究,博主联系方式见首页右上角
  • 转载请注明来源:python学习day4
  • 本文永久链接地址:http://www.52devops.com/chuck/794.html

该文章由 发布

这货来去如风,什么鬼都没留下!!!
发表我的评论
取消评论
代码 贴图 加粗 链接 删除线 签到

(1)条精彩评论:
  1. 匿名
    甲:对不起,我的鸡没圈好,跑出来弄坏了你种的菜。 乙:没关系,我的狗已经把你的鸡吃了。 甲:噢!怪不得我从狗的肚子里发现了鸡骨头。 乙:……? http://xcyxbz.com http://xcyxbz.com
    匿名2016-11-03 21:28 回复