├── README.md ├── main.py └── 廖雪峰python教程HTML版 ├── 0Python教程.html ├── 100Day 12 - 编写日志列表页.html ├── 101Day 13 - 提升开发效率.html ├── 102Day 14 - 完成Web App.html ├── 103Day 15 - 部署Web App.html ├── 104Day 16 - 编写移动App.html ├── 10使用list和tuple.html ├── 11条件判断和循环.html ├── 12使用dict和set.html ├── 13函数.html ├── 14调用函数.html ├── 15定义函数.html ├── 16函数的参数.html ├── 17递归函数.html ├── 18高级特性.html ├── 19切片.html ├── 1Python简介.html ├── 20迭代.html ├── 21列表生成式.html ├── 22生成器.html ├── 23函数式编程.html ├── 24高阶函数.html ├── 25匿名函数.html ├── 26装饰器.html ├── 27偏函数.html ├── 28模块.html ├── 29使用模块.html ├── 2安装Python.html ├── 30安装第三方模块.html ├── 31使用__future__.html ├── 32面向对象编程.html ├── 33类和实例.html ├── 34访问限制.html ├── 35继承和多态.html ├── 36获取对象信息.html ├── 37面向对象高级编程.html ├── 38使用__slots__.html ├── 39使用@property.html ├── 3Python解释器.html ├── 40多重继承.html ├── 41定制类.html ├── 42使用元类.html ├── 43错误、调试和测试.html ├── 44错误处理.html ├── 45调试.html ├── 46单元测试.html ├── 47文档测试.html ├── 48IO编程.html ├── 49文件读写.html ├── 4第一个Python程序.html ├── 50操作文件和目录.html ├── 51序列化.html ├── 52进程和线程.html ├── 53多进程.html ├── 54多线程.html ├── 55ThreadLocal.html ├── 56进程 vs. 线程.html ├── 57分布式进程.html ├── 58正则表达式.html ├── 59常用内建模块.html ├── 5使用文本编辑器.html ├── 60collections.html ├── 61base64.html ├── 62struct.html ├── 63hashlib.html ├── 64XML.html ├── 65HTMLParser.html ├── 66常用第三方模块.html ├── 67PIL.html ├── 68图形界面.html ├── 69网络编程.html ├── 6输入和输出.html ├── 70TCP IP简介.html ├── 71TCP编程.html ├── 72UDP编程.html ├── 73电子邮件.html ├── 74SMTP发送邮件.html ├── 75POP3收取邮件.html ├── 76访问数据库.html ├── 77使用SQLite.html ├── 78使用MySQL.html ├── 79使用SQLAlchemy.html ├── 7Python基础.html ├── 80Web开发.html ├── 81HTTP协议简介.html ├── 82HTML简介.html ├── 83WSGI接口.html ├── 84使用Web框架.html ├── 85使用模板.html ├── 86协程.html ├── 87gevent.html ├── 88实战.html ├── 89Day 1 - 搭建开发环境.html ├── 8数据类型和变量.html ├── 90Day 2 - 编写数据库模块.html ├── 91Day 3 - 编写ORM.html ├── 92Day 4 - 编写Model.html ├── 93Day 5 - 编写Web框架.html ├── 94Day 6 - 添加配置文件.html ├── 95Day 7 - 编写MVC.html ├── 96Day 8 - 构建前端.html ├── 97Day 9 - 编写API.html ├── 98Day 10 - 用户注册和登录.html ├── 99Day 11 - 编写日志创建页.html └── 9字符串和编码.html /README.md: -------------------------------------------------------------------------------- 1 | # 一个python爬虫小程序 2 | 3 | ## 起因 4 | 深夜忽然想下载一点电子书来扩充一下kindle,就想起来python学得太浅,什么“装饰器”啊、“多线程”啊都没有学到。 5 | 6 | 想到廖雪峰大神的python教程很经典、很著名。就想找找有木有pdf版的下载,结果居然没找到!!CSDN有个不完整的还骗走了我一个积分!!尼玛!! 7 | 8 | 怒了,准备写个程序直接去爬廖雪峰的教程,然后再html转成电子书。 9 | 10 | 11 | ## 过程 12 | 13 | 过程很有趣呢,用浅薄的python知识,写python程序,去爬python教程,来学习python。想想有点小激动…… 14 | 15 | 果然python很是方便,50行左右就OK了。直接贴代码: 16 | 17 | # coding:utf-8 18 | import urllib 19 | 20 | domain = 'http://www.liaoxuefeng.com' #廖雪峰的域名 21 | path = r'C:\Users\cyhhao2013\Desktop\temp\\' #html要保存的路径 22 | 23 | # 一个html的头文件 24 | input = open(r'C:\Users\cyhhao2013\Desktop\0.html', 'r') 25 | head = input.read() 26 | 27 | # 打开python教程主界面 28 | f = urllib.urlopen("http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000") 29 | home = f.read() 30 | f.close() 31 | 32 | # 替换所有空格回车(这样容易好获取url) 33 | geturl = home.replace("\n", "") 34 | geturl = geturl.replace(" ", "") 35 | 36 | # 得到包含url的字符串 37 | list = geturl.split(r'em;">') 41 | 42 | # 开始遍历url List 43 | for li in list: 44 | url = li.split(r'">')[0] 45 | url = domain + url #拼凑url 46 | print url 47 | f = urllib.urlopen(url) 48 | html = f.read() 49 | 50 | # 获得title为了写文件名 51 | title = html.split("")[1] 52 | title = title.split(" - 廖雪峰的官方网站")[0] 53 | 54 | # 要转一下码,不然加到路径里就悲剧了 55 | title = title.decode('utf-8').replace("/", " ") 56 | 57 | # 截取正文 58 | html = html.split(r'')[1] 59 | html = html.split(r'

您的支持是作者写作最大的动力!

')[0] 60 | html = html.replace(r'src="', 'src="' + domain) 61 | 62 | # 加上头和尾组成完整的html 63 | html = head + html+"" 64 | 65 | # 输出文件 66 | output = open(path + "%d" % list.index(li) + title + '.html', 'w') 67 | output.write(html) 68 | output.close() 69 | 70 | 简直,人生苦短我用python啊! 71 | 72 | # 最后 73 | 74 | 附上HTML转epub电子书格式在线链接:[html.toepub.com][2] 75 | 76 | 以及廖雪峰的教程:[链接][1] 77 | 廖雪峰的Git教程也非常非常的不错哦~ 78 | 79 | 80 | 81 | 82 | 顺便扩充一下自己的→_→[GitHub][3](爬下来的html也在github上哦~) 83 | 84 | 85 | [1]: http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000 86 | [2]: http://html.toepub.com/ 87 | [3]: https://github.com/cyhhao/PythonCrawler1 -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import urllib 3 | 4 | domain = 'http://www.liaoxuefeng.com' #廖雪峰的域名 5 | path = r'C:\Users\cyhhao2013\Desktop\temp\\' #html要保存的路径 6 | 7 | # 一个html的头文件 8 | input = open(r'C:\Users\cyhhao2013\Desktop\0.html', 'r') 9 | head = input.read() 10 | 11 | # 打开python教程主界面 12 | f = urllib.urlopen("http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000") 13 | home = f.read() 14 | f.close() 15 | 16 | # 替换所有空格回车(这样容易好获取url) 17 | geturl = home.replace("\n", "") 18 | geturl = geturl.replace(" ", "") 19 | 20 | # 得到包含url的字符串 21 | list = geturl.split(r'em;">') 25 | 26 | # 开始遍历url List 27 | for li in list: 28 | url = li.split(r'">')[0] 29 | url = domain + url #拼凑url 30 | print url 31 | f = urllib.urlopen(url) 32 | html = f.read() 33 | 34 | # 获得title为了写文件名 35 | title = html.split("")[1] 36 | title = title.split(" - 廖雪峰的官方网站")[0] 37 | 38 | # 要转一下码,不然加到路径里就悲剧了 39 | title = title.decode('utf-8').replace("/", " ") 40 | 41 | # 截取正文 42 | html = html.split(r'')[1] 43 | html = html.split(r'

您的支持是作者写作最大的动力!

')[0] 44 | html = html.replace(r'src="', 'src="' + domain) 45 | 46 | # 加上头和尾组成完整的html 47 | html = head + html+"" 48 | 49 | # 输出文件 50 | output = open(path + "%d" % list.index(li) + title + '.html', 'w') 51 | output.write(html) 52 | output.close() -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/0Python教程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Python教程

8 |
33602次阅读
9 |
10 |

这是小白的Python新手教程。

11 |

Python是一种计算机程序设计语言。你可能已经听说过很多种流行的编程语言,比如非常难学的C语言,非常流行的Java语言,适合初学者的Basic语言,适合网页编程的JavaScript语言,等等。

12 |

那Python是一种什么语言?

13 |

首选,我们普及一下编程语言的基础知识。用任何编程语言来开发程序,都是为了让计算机干活,比如下载一个MP3,编写一个文档等等,而计算机干活的CPU只认识机器指令,所以,尽管不同的编程语言差异极大,最后都得“翻译”成CPU可以执行的机器指令。而不同的编程语言,干同一个活,编写的代码量,差距也很大。

14 |

比如,完成同一个任务,C语言要写1000行代码,Java只需要写100行,而Python可能只要20行。

15 |

所以Python是一种相当高级的语言。

16 |

你也许会问,代码少还不好?代码少的代价是运行速度慢,C程序运行1秒钟,Java程序可能需要2秒,而Python程序可能就需要10秒。

17 |

那是不是越低级的程序越难学,越高级的程序越简单?表面上来说,是的,但是,在非常高的抽象计算中,高级的Python程序设计也是非常难学的,所以,高级程序语言不等于简单。

18 |

但是,对于初学者和完成普通任务,Python语言是非常简单易用的。连Google都在大规模使用Python,你就不用担心学了会没用。

19 |

用Python可以做什么?可以做日常任务,比如自动备份你的MP3;可以做网站,很多著名的网站包括YouTube就是Python写的;可以做网络游戏的后台,很多在线游戏的后台都是Python开发的。总之就是能干很多很多事啦。

20 |

Python当然也有不能干的事情,比如写操作系统,这个只能用C语言写;写手机应用,只能用Objective-C(针对iPhone)和Java(针对Android);写3D游戏,最好用C或C++。

21 |

如果你是小白用户,满足以下条件:

22 |
    23 |
  • 会使用电脑,但从来没写过程序;
  • 24 |
  • 还记得初中数学学的方程式和一点点代数知识;
  • 25 |
  • 想从编程小白变成专业的软件架构师;
  • 26 |
  • 每天能抽出半个小时学习。
  • 27 |
28 |

不要再犹豫了,这个教程就是为你准备的!

29 |

准备好了吗?

30 |

challenge-accepted

31 |

关于作者

32 |

廖雪峰,十年软件开发经验,业余产品经理,精通Java/Python/Ruby/Visual Basic/Objective C等,对开源框架有深入研究,著有《Spring 2.0核心技术与最佳实践》一书,多个业余开源项目托管在GitHub,欢迎微博交流:

33 |

34 |
35 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/101Day 13 - 提升开发效率.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 13 - 提升开发效率

8 |
83次阅读
9 |
10 |

现在,我们已经把一个Web App的框架完全搭建好了,从后端的API到前端的MVVM,流程已经跑通了。

11 |

在继续工作前,注意到每次修改Python代码,都必须在命令行先Ctrl-C停止服务器,再重启,改动才能生效。

12 |

在开发阶段,每天都要修改、保存几十次代码,每次保存都手动来这么一下非常麻烦,严重地降低了我们的开发效率。有没有办法让服务器检测到代码修改后自动重新加载呢?

13 |

Django的开发环境在Debug模式下就可以做到自动重新加载,如果我们编写的服务器也能实现这个功能,就能大大提升开发效率。

14 |

可惜的是,Django没把这个功能独立出来,不用Django就享受不到,怎么办?

15 |

其实Python本身提供了重新载入模块的功能,但不是所有模块都能被重新载入。另一种思路是检测www目录下的代码改动,一旦有改动,就自动重启服务器。

16 |

按照这个思路,我们可以编写一个辅助程序pymonitor.py,让它启动wsgiapp.py,并时刻监控www目录下的代码改动,有改动时,先把当前wsgiapp.py进程杀掉,再重启,就完成了服务器进程的自动重启。

17 |

要监控目录文件的变化,我们也无需自己手动定时扫描,Python的第三方库watchdog可以利用操作系统的API来监控目录文件的变化,并发送通知。我们先用easy_install安装:

18 |
$ easy_install watchdog
 19 | 

利用watchdog接收文件变化的通知,如果是.py文件,就自动重启wsgiapp.py进程。

20 |

利用Python自带的subprocess实现进程的启动和终止,并把输入输出重定向到当前进程的输入输出中:

21 |
#!/usr/bin/env python
 22 | import os, sys, time, subprocess
 23 | 
 24 | from watchdog.observers import Observer
 25 | from watchdog.events import FileSystemEventHandler
 26 | 
 27 | def log(s):
 28 |     print '[Monitor] %s' % s
 29 | 
 30 | class MyFileSystemEventHander(FileSystemEventHandler):
 31 |     def __init__(self, fn):
 32 |         super(MyFileSystemEventHander, self).__init__()
 33 |         self.restart = fn
 34 | 
 35 |     def on_any_event(self, event):
 36 |         if event.src_path.endswith('.py'):
 37 |             log('Python source file changed: %s' % event.src_path)
 38 |             self.restart()
 39 | 
 40 | command = ['echo', 'ok']
 41 | process = None
 42 | 
 43 | def kill_process():
 44 |     global process
 45 |     if process:
 46 |         log('Kill process [%s]...' % process.pid)
 47 |         process.kill()
 48 |         process.wait()
 49 |         log('Process ended with code %s.' % process.returncode)
 50 |         process = None
 51 | 
 52 | def start_process():
 53 |     global process, command
 54 |     log('Start process %s...' % ' '.join(command))
 55 |     process = subprocess.Popen(command, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
 56 | 
 57 | def restart_process():
 58 |     kill_process()
 59 |     start_process()
 60 | 
 61 | def start_watch(path, callback):
 62 |     observer = Observer()
 63 |     observer.schedule(MyFileSystemEventHander(restart_process), path, recursive=True)
 64 |     observer.start()
 65 |     log('Watching directory %s...' % path)
 66 |     start_process()
 67 |     try:
 68 |         while True:
 69 |             time.sleep(0.5)
 70 |     except KeyboardInterrupt:
 71 |         observer.stop()
 72 |     observer.join()
 73 | 
 74 | if __name__ == '__main__':
 75 |     argv = sys.argv[1:]
 76 |     if not argv:
 77 |         print('Usage: ./pymonitor your-script.py')
 78 |         exit(0)
 79 |     if argv[0]!='python':
 80 |         argv.insert(0, 'python')
 81 |     command = argv
 82 |     path = os.path.abspath('.')
 83 |     start_watch(path, None)
 84 | 

一共50行左右的代码,就实现了Debug模式的自动重新加载。用下面的命令启动服务器:

85 |
$ python pymonitor.py wsgiapp.py
 86 | 

或者给pymonitor.py加上可执行权限,启动服务器:

87 |
$ ./pymonitor.py wsgiapp.py
 88 | 

在编辑器中打开一个py文件,修改后保存,看看命令行输出,是不是自动重启了服务器:

89 |
$ ./pymonitor.py wsgiapp.py 
 90 | [Monitor] Watching directory /Users/michael/Github/awesome-python-webapp/www...
 91 | [Monitor] Start process python wsgiapp.py...
 92 | ...
 93 | INFO:root:application (/Users/michael/Github/awesome-python-webapp/www) will start at 0.0.0.0:9000...
 94 | [Monitor] Python source file changed: /Users/michael/Github/awesome-python-webapp/www/apis.py
 95 | [Monitor] Kill process [2747]...
 96 | [Monitor] Process ended with code -9.
 97 | [Monitor] Start process python wsgiapp.py...
 98 | ...
 99 | INFO:root:application (/Users/michael/Github/awesome-python-webapp/www) will start at 0.0.0.0:9000...
100 | 

现在,只要一保存代码,就可以刷新浏览器看到效果,大大提升了开发效率。

101 |
102 | 103 |
104 | 105 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/102Day 14 - 完成Web App.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 14 - 完成Web App

8 |
78次阅读
9 |
10 |

在Web App框架和基本流程跑通后,剩下的工作全部是体力活了:在Debug开发模式下完成后端所有API、前端所有页面。我们需要做的事情包括:

11 |

对URL/manage/进行拦截,检查当前用户是否是管理员身份:

12 |
@interceptor('/manage/')
13 | def manage_interceptor(next):
14 |     user = ctx.request.user
15 |     if user and user.admin:
16 |         return next()
17 |     raise seeother('/signin')
18 | 

后端API包括:

19 |
    20 |
  • 获取日志:GET /api/blogs

    21 |
  • 22 |
  • 创建日志:POST /api/blogs

    23 |
  • 24 |
  • 修改日志:POST /api/blogs/:blog_id

    25 |
  • 26 |
  • 删除日志:POST /api/blogs/:blog_id/delete

    27 |
  • 28 |
  • 获取评论:GET /api/comments

    29 |
  • 30 |
  • 创建评论:POST /api/blogs/:blog_id/comments

    31 |
  • 32 |
  • 删除评论:POST /api/comments/:comment_id/delete

    33 |
  • 34 |
  • 创建新用户:POST /api/users

    35 |
  • 36 |
  • 获取用户:GET /api/users

    37 |
  • 38 |
39 |

管理页面包括:

40 |
    41 |
  • 评论列表页:GET /manage/comments

    42 |
  • 43 |
  • 日志列表页:GET /manage/blogs

    44 |
  • 45 |
  • 创建日志页:GET /manage/blogs/create

    46 |
  • 47 |
  • 修改日志页:GET /manage/blogs/

    48 |
  • 49 |
  • 用户列表页:GET /manage/users

    50 |
  • 51 |
52 |

用户浏览页面包括:

53 |
    54 |
  • 注册页:GET /register

    55 |
  • 56 |
  • 登录页:GET /signin

    57 |
  • 58 |
  • 注销页:GET /signout

    59 |
  • 60 |
  • 首页:GET /

    61 |
  • 62 |
  • 日志详情页:GET /blog/:blog_id

    63 |
  • 64 |
65 |

把所有的功能实现,我们第一个Web App就宣告完成!

66 |
67 | 68 |
69 | 70 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/104Day 16 - 编写移动App.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 16 - 编写移动App

8 |
112次阅读
9 |
10 |

网站部署上线后,还缺点啥呢?

11 |

在移动互联网浪潮席卷而来的今天,一个网站没有上线移动App,出门根本不好意思跟人打招呼。

12 |

所以,awesome-python-webapp必须得有一个移动App版本!

13 |

开发iPhone版本

14 |

我们首先来看看如何开发iPhone App。前置条件:一台Mac电脑,安装XCode和最新的iOS SDK。

15 |

在使用MVVM编写前端页面时,我们就能感受到,用REST API封装网站后台的功能,不但能清晰地分离前端页面和后台逻辑,现在这个好处更加明显,移动App也可以通过REST API从后端拿到数据。

16 |

我们来设计一个简化版的iPhone App,包含两个屏幕:列出最新日志和阅读日志的详细内容:

17 |

awesomepy-iphone-app

18 |

只需要调用API:/api/blogs

19 |

在XCode中完成App编写:

20 |

awesomepy-iphone-app-xcode

21 |

由于我们的教程是Python,关于如何开发iOS,请移步Develop Apps for iOS

22 |

点击下载iOS App源码

23 |

如何编写Android App?这个当成作业了。

24 |
25 | 26 |
27 | 28 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/11条件判断和循环.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

条件判断和循环

8 |
4143次阅读
9 |
10 |

条件判断

11 |

计算机之所以能做很多自动化的任务,因为它可以自己做条件判断。

12 |

比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:

13 |
age = 20
 14 | if age >= 18:
 15 |     print 'your age is', age
 16 |     print 'adult'
 17 | 

根据Python的缩进规则,如果if语句判断是True,就把缩进的两行print语句执行了,否则,什么也不做。

18 |

也可以给if添加一个else语句,意思是,如果if判断是False,不要执行if的内容,去把else执行了:

19 |
age = 3
 20 | if age >= 18:
 21 |     print 'your age is', age
 22 |     print 'adult'
 23 | else:
 24 |     print 'your age is', age
 25 |     print 'teenager'
 26 | 

注意不要少写了冒号:

27 |

当然上面的判断是很粗略的,完全可以用elif做更细致的判断:

28 |
age = 3
 29 | if age >= 18:
 30 |     print 'adult'
 31 | elif age >= 6:
 32 |     print 'teenager'
 33 | else:
 34 |     print 'kid'
 35 | 

elifelse if的缩写,完全可以有多个elif,所以if语句的完整形式就是:

36 |
if <条件判断1>:
 37 |     <执行1>
 38 | elif <条件判断2>:
 39 |     <执行2>
 40 | elif <条件判断3>:
 41 |     <执行3>
 42 | else:
 43 |     <执行4>
 44 | 

if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elifelse,所以,请测试并解释为什么下面的程序打印的是teenager

45 |
age = 20
 46 | if age >= 6:
 47 |     print 'teenager'
 48 | elif age >= 18:
 49 |     print 'adult'
 50 | else:
 51 |     print 'kid'
 52 | 

if判断条件还可以简写,比如写:

53 |
if x:
 54 |     print 'True'
 55 | 

只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False

56 |

循环

57 |

Python的循环有两种,一种是for...in循环,依次把list或tuple中的每个元素迭代出来,看例子:

58 |
names = ['Michael', 'Bob', 'Tracy']
 59 | for name in names:
 60 |     print name
 61 | 

执行这段代码,会依次打印names的每一个元素:

62 |
Michael
 63 | Bob
 64 | Tracy
 65 | 

所以for x in ...循环就是把每个元素代入变量x,然后执行缩进块的语句。

66 |

再比如我们想计算1-10的整数之和,可以用一个sum变量做累加:

67 |
sum = 0
 68 | for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
 69 |     sum = sum + x
 70 | print sum
 71 | 

如果要计算1-100的整数之和,从1写到100有点困难,幸好Python提供一个range()函数,可以生成一个整数序列,比如range(5)生成的序列是从0开始小于5的整数:

72 |
>>> range(5)
 73 | [0, 1, 2, 3, 4]
 74 | 

range(101)就可以生成0-100的整数序列,计算如下:

75 |
sum = 0
 76 | for x in range(101):
 77 |     sum = sum + x
 78 | print sum
 79 | 

请自行运行上述代码,看看结果是不是当年高斯同学心算出的5050。

80 |

第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:

81 |
sum = 0
 82 | n = 99
 83 | while n > 0:
 84 |     sum = sum + n
 85 |     n = n - 2
 86 | print sum
 87 | 

在循环内部变量n不断自减,直到变为-1时,不再满足while条件,循环退出。

88 |

再议raw_input

89 |

最后看一个有问题的条件判断。很多同学会用raw_input()读取用户的输入,这样可以自己输入,程序运行得更有意思:

90 |
birth = raw_input('birth: ')
 91 | if birth < 2000:
 92 |     print '00前'
 93 | else:
 94 |     print '00后'
 95 | 

输入1982,结果却显示00后,这么简单的判断Python也能搞错?

96 |

当然不是Python的问题,在Python的交互式命令行下打印birth看看:

97 |
>>> birth
 98 | '1982'
 99 | >>> '1982' < 2000
100 | False
101 | >>> 1982 < 2000
102 | True
103 | 

原因找到了!原来从raw_input()读取的内容永远以字符串的形式返回,把字符串和整数比较就不会得到期待的结果,必须先用int()把字符串转换为我们想要的整型:

104 |
birth = int(raw_input('birth: '))
105 | 

再次运行,就可以得到正确地结果。但是,如果输入abc呢?又会得到一个错误信息:

106 |
Traceback (most recent call last):
107 |   ...
108 | ValueError: invalid literal for int() with base 10: 'abc'
109 | 

原来int()发现一个字符串并不是合法的数字时就会报错,程序就退出了。

110 |

如何检查并捕获程序运行期的错误呢?后面的错误和调试会讲到。

111 |

小结

112 |

条件判断可以让计算机自己做选择,Python的if...elif...else很灵活。

113 |

python-if

114 |

循环是让计算机做重复任务的有效的方法,有些时候,如果代码写得有问题,会让程序陷入“死循环”,也就是永远循环下去。这时可以用Ctrl+C退出程序,或者强制结束Python进程。

115 |

请试写一个死循环程序。

116 |
117 | 118 |
119 | 120 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/13函数.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

函数

8 |
2258次阅读
9 |
10 |

我们知道圆的面积计算公式为:

11 |

S = πr2

12 |

当我们知道半径r的值时,就可以根据公式计算出面积。假设我们需要计算3个不同大小的圆的面积:

13 |
r1 = 12.34
14 | r2 = 9.08
15 | r3 = 73.1
16 | s1 = 3.14 * r1 * r1
17 | s2 = 3.14 * r2 * r2
18 | s3 = 3.14 * r3 * r3
19 | 

当代码出现有规律的重复的时候,你就需要当心了,每次写3.14 * x * x不仅很麻烦,而且,如果要把3.14改成3.14159265359的时候,得全部替换。

20 |

有了函数,我们就不再每次写s = 3.14 * x * x,而是写成更有意义的函数调用s = area_of_circle(x),而函数area_of_circle本身只需要写一次,就可以多次调用。

21 |

基本上所有的高级语言都支持函数,Python也不例外。Python不但能非常灵活地定义函数,而且本身内置了很多有用的函数,可以直接调用。

22 |

抽象

23 |

抽象是数学中非常常见的概念。举个例子:

24 |

计算数列的和,比如:1 + 2 + 3 + ... + 100,写起来十分不方便,于是数学家发明了求和符号∑,可以把1 + 2 + 3 + ... + 100记作:

25 |

100

26 |

n

27 |

n=1

28 |

这种抽象记法非常强大,因为我们看到∑就可以理解成求和,而不是还原成低级的加法运算。

29 |

而且,这种抽象记法是可扩展的,比如:

30 |

100

31 |

(n2+1)

32 |

n=1

33 |

还原成加法运算就变成了:

34 |

(1 x 1 + 1) + (2 x 2 + 1) + (3 x 3 + 1) + ... + (100 x 100 + 1)

35 |

可见,借助抽象,我们才能不关心底层的具体计算过程,而直接在更高的层次上思考问题。

36 |

写计算机程序也是一样,函数就是最基本的一种代码抽象的方式。

37 |
38 | 39 |
40 | 41 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/14调用函数.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

调用函数

8 |
3223次阅读
9 |
10 |

Python内置了很多有用的函数,我们可以直接调用。

11 |

要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数abs,只有一个参数。可以直接从Python的官方网站查看文档:

12 |

http://docs.python.org/2/library/functions.html#abs

13 |

也可以在交互式命令行通过help(abs)查看abs函数的帮助信息。

14 |

调用abs函数:

15 |
>>> abs(100)
16 | 100
17 | >>> abs(-20)
18 | 20
19 | >>> abs(12.34)
20 | 12.34
21 | 

调用函数的时候,如果传入的参数数量不对,会报TypeError的错误,并且Python会明确地告诉你:abs()有且仅有1个参数,但给出了两个:

22 |
>>> abs(1, 2)
23 | Traceback (most recent call last):
24 |   File "<stdin>", line 1, in <module>
25 | TypeError: abs() takes exactly one argument (2 given)
26 | 

如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError的错误,并且给出错误信息:str是错误的参数类型:

27 |
>>> abs('a')
28 | Traceback (most recent call last):
29 |   File "<stdin>", line 1, in <module>
30 | TypeError: bad operand type for abs(): 'str'
31 | 

而比较函数cmp(x, y)就需要两个参数,如果x<y,返回-1,如果x==y,返回0,如果x>y,返回1

32 |
>>> cmp(1, 2)
33 | -1
34 | >>> cmp(2, 1)
35 | 1
36 | >>> cmp(3, 3)
37 | 0
38 | 

数据类型转换

39 |

Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数:

40 |
>>> int('123')
41 | 123
42 | >>> int(12.34)
43 | 12
44 | >>> float('12.34')
45 | 12.34
46 | >>> str(1.23)
47 | '1.23'
48 | >>> unicode(100)
49 | u'100'
50 | >>> bool(1)
51 | True
52 | >>> bool('')
53 | False
54 | 

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

55 |
>>> a = abs # 变量a指向abs函数
56 | >>> a(-1) # 所以也可以通过a调用abs函数
57 | 1
58 | 

小结

59 |

调用Python的函数,需要根据函数定义,传入正确的参数。如果函数调用出错,一定要学会看错误信息,所以英文很重要!

60 |
61 | 62 |
63 | 64 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/15定义函数.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

定义函数

8 |
3020次阅读
9 |
10 |

在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

11 |

我们以自定义一个求绝对值的my_abs函数为例:

12 |
def my_abs(x):
13 |     if x >= 0:
14 |         return x
15 |     else:
16 |         return -x
17 | 

请自行测试并调用my_abs看看返回结果是否正确。

18 |

请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。

19 |

如果没有return语句,函数执行完毕后也会返回结果,只是结果为None

20 |

return None可以简写为return

21 |

空函数

22 |

如果想定义一个什么事也不做的空函数,可以用pass语句:

23 |
def nop():
24 |     pass
25 | 

pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

26 |

pass还可以用在其他语句里,比如:

27 |
if age >= 18:
28 |     pass
29 | 

缺少了pass,代码运行就会有语法错误。

30 |

参数检查

31 |

调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError

32 |
>>> my_abs(1, 2)
33 | Traceback (most recent call last):
34 |   File "<stdin>", line 1, in <module>
35 | TypeError: my_abs() takes exactly 1 argument (2 given)
36 | 

但是如果参数类型不对,Python解释器就无法帮我们检查。试试my_abs和内置函数abs的差别:

37 |
>>> my_abs('A')
38 | 'A'
39 | >>> abs('A')
40 | Traceback (most recent call last):
41 |   File "<stdin>", line 1, in <module>
42 | TypeError: bad operand type for abs(): 'str'
43 | 

当传入了不恰当的参数时,内置函数abs会检查出参数错误,而我们定义的my_abs没有参数检查,所以,这个函数定义不够完善。

44 |

让我们修改一下my_abs的定义,对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance实现:

45 |
def my_abs(x):
46 |     if not isinstance(x, (int, float)):
47 |         raise TypeError('bad operand type')
48 |     if x >= 0:
49 |         return x
50 |     else:
51 |         return -x
52 | 

添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个错误:

53 |
>>> my_abs('A')
54 | Traceback (most recent call last):
55 |   File "<stdin>", line 1, in <module>
56 |   File "<stdin>", line 3, in my_abs
57 | TypeError: bad operand type
58 | 

错误和异常处理将在后续讲到。

59 |

返回多个值

60 |

函数可以返回多个值吗?答案是肯定的。

61 |

比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:

62 |
import math
63 | 
64 | def move(x, y, step, angle=0):
65 |     nx = x + step * math.cos(angle)
66 |     ny = y - step * math.sin(angle)
67 |     return nx, ny
68 | 

这样我们就可以同时获得返回值:

69 |
>>> x, y = move(100, 100, 60, math.pi / 6)
70 | >>> print x, y
71 | 151.961524227 70.0
72 | 

但其实这只是一种假象,Python函数返回的仍然是单一值:

73 |
>>> r = move(100, 100, 60, math.pi / 6)
74 | >>> print r
75 | (151.96152422706632, 70.0)
76 | 

原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。

77 |

小结

78 |

定义函数时,需要确定函数名和参数个数;

79 |

如果有必要,可以先对参数的数据类型做检查;

80 |

函数体内部可以用return随时返回函数结果;

81 |

函数执行完毕也没有return语句时,自动return None

82 |

函数可以同时返回多个值,但其实就是一个tuple。

83 |
84 | 85 |
86 | 87 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/18高级特性.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

高级特性

8 |
1889次阅读
9 |
10 |

掌握了Python的数据类型、语句和函数,基本上就可以编写出很多有用的程序了。

11 |

比如构造一个1, 3, 5, 7, ..., 99的列表,可以通过循环实现:

12 |
L = []
13 | n = 1
14 | while n <= 99:
15 |     L.append(n)
16 |     n = n + 2
17 | 

取list的前一半的元素,也可以通过循环实现。

18 |

但是在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。

19 |

基于这一思想,我们来介绍Python中非常有用的高级特性,一行代码能实现的功能,决不写5行代码。

20 |
21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/19切片.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

切片

8 |
2384次阅读
9 |
10 |

取一个list或tuple的部分元素是非常常见的操作。比如,一个list如下:

11 |
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
12 | 

取前3个元素,应该怎么做?

13 |

笨办法:

14 |
>>> [L[0], L[1], L[2]]
15 | ['Michael', 'Sarah', 'Tracy']
16 | 

之所以是笨办法是因为扩展一下,取前N个元素就没辙了。

17 |

取前N个元素,也就是索引为0-(N-1)的元素,可以用循环:

18 |
>>> r = []
19 | >>> n = 3
20 | >>> for i in range(n):
21 | ...     r.append(L[i])
22 | ... 
23 | >>> r
24 | ['Michael', 'Sarah', 'Tracy']
25 | 

对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。

26 |

对应上面的问题,取前3个元素,用一行代码就可以完成切片:

27 |
>>> L[0:3]
28 | ['Michael', 'Sarah', 'Tracy']
29 | 

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。

30 |

如果第一个索引是0,还可以省略:

31 |
>>> L[:3]
32 | ['Michael', 'Sarah', 'Tracy']
33 | 

也可以从索引1开始,取出2个元素出来:

34 |
>>> L[1:3]
35 | ['Sarah', 'Tracy']
36 | 

类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片,试试:

37 |
>>> L[-2:]
38 | ['Bob', 'Jack']
39 | >>> L[-2:-1]
40 | ['Bob']
41 | 

记住倒数第一个元素的索引是-1

42 |

切片操作十分有用。我们先创建一个0-99的数列:

43 |
>>> L = range(100)
44 | >>> L
45 | [0, 1, 2, 3, ..., 99]
46 | 

可以通过切片轻松取出某一段数列。比如前10个数:

47 |
>>> L[:10]
48 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
49 | 

后10个数:

50 |
>>> L[-10:]
51 | [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
52 | 

前11-20个数:

53 |
>>> L[10:20]
54 | [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
55 | 

前10个数,每两个取一个:

56 |
>>> L[:10:2]
57 | [0, 2, 4, 6, 8]
58 | 

所有数,每5个取一个:

59 |
>>> L[::5]
60 | [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
61 | 

甚至什么都不写,只写[:]就可以原样复制一个list:

62 |
>>> L[:]
63 | [0, 1, 2, 3, ..., 99]
64 | 

tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:

65 |
>>> (0, 1, 2, 3, 4, 5)[:3]
66 | (0, 1, 2)
67 | 

字符串'xxx'或Unicode字符串u'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:

68 |
>>> 'ABCDEFG'[:3]
69 | 'ABC'
70 | >>> 'ABCDEFG'[::2]
71 | 'ACEG'
72 | 

在很多编程语言中,针对字符串提供了很多各种截取函数,其实目的就是对字符串切片。Python没有针对字符串的截取函数,只需要切片一个操作就可以完成,非常简单。

73 |

小结

74 |

有了切片操作,很多地方循环就不再需要了。Python的切片非常灵活,一行代码就可以实现很多行循环才能完成的操作。

75 |
76 | 77 |
78 | 79 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/1Python简介.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Python简介

8 |
6348次阅读
9 |
10 |

Python是著名的“龟叔”Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言。

11 |

现在,全世界差不多有600多种编程语言,但流行的编程语言也就那么20来种。如果你听说过TIOBE排行榜,你就能知道编程语言的大致流行程度。这是最近10年最常用的10种编程语言的变化图:

12 |

tpci_trends

13 |

总的来说,这几种编程语言各有千秋。C语言是可以用来编写操作系统的贴近硬件的语言,所以,C语言适合开发那些追求运行速度、充分发挥硬件性能的程序。而Python是用来编写应用程序的高级编程语言。

14 |

当你用一种语言开始作真正的软件开发时,你除了编写代码外,还需要很多基本的已经写好的现成的东西,来帮助你加快开发进度。比如说,要编写一个电子邮件客户端,如果先从最底层开始编写网络协议相关的代码,那估计一年半载也开发不出来。高级编程语言通常都会提供一个比较完善的基础代码库,让你能直接调用,比如,针对电子邮件协议的SMTP库,针对桌面环境的GUI库,在这些已有的代码库的基础上开发,一个电子邮件客户端几天就能开发出来。

15 |

Python就为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容,被形象地称作“内置电池(batteries included)”。用Python开发,许多功能不必从零编写,直接使用现成的即可。

16 |

除了内置的库外,Python还有大量的第三方库,也就是别人开发的,供你直接使用的东西。当然,如果你开发的代码通过很好的封装,也可以作为第三方库给别人使用。

17 |

许多大型网站就是用Python开发的,例如YouTube、Instagram,还有国内的豆瓣。很多大公司,包括Google、Yahoo等,甚至NASA(美国航空航天局)都大量地使用Python。

18 |

龟叔给Python的定位是“优雅”、“明确”、“简单”,所以Python程序看上去总是简单易懂,初学者学Python,不但入门容易,而且将来深入下去,可以编写那些非常非常复杂的程序。

19 |

总的来说,Python的哲学就是简单优雅,尽量写容易看明白的代码,尽量写少的代码。如果一个资深程序员向你炫耀他写的晦涩难懂、动不动就几万行的代码,你可以尽情地嘲笑他。

20 |

那Python适合开发哪些类型的应用呢?

21 |

首选是网络应用,包括网站、后台服务等等;

22 |

其次是许多日常需要的小工具,包括系统管理员需要的脚本任务等等;

23 |

另外就是把其他语言开发的程序再包装起来,方便使用。

24 |

最后说说Python的缺点。

25 |

任何编程语言都有缺点,Python也不例外。优点说过了,那Python有哪些缺点呢?

26 |

第一个缺点就是运行速度慢,和C程序相比非常慢,因为Python是解释型语言,你的代码在执行时会一行一行地翻译成CPU能理解的机器码,这个翻译过程非常耗时,所以很慢。而C程序是运行前直接编译成CPU能执行的机器码,所以非常快。

27 |

但是大量的应用程序不需要这么快的运行速度,因为用户根本感觉不出来。例如开发一个下载MP3的网络应用程序,C程序的运行时间需要0.001秒,而Python程序的运行时间需要0.1秒,慢了100倍,但由于网络更慢,需要等待1秒,你想,用户能感觉到1.001秒和1.1秒的区别吗?这就好比F1赛车和普通的出租车在北京三环路上行驶的道理一样,虽然F1赛车理论时速高达400公里,但由于三环路堵车的时速只有20公里,因此,作为乘客,你感觉的时速永远是20公里。

28 |

不要在意程序运行速度

29 |

第二个缺点就是代码不能加密。如果要发布你的Python程序,实际上就是发布源代码,这一点跟C语言不同,C语言不用发布源代码,只需要把编译后的机器码(也就是你在Windows上常见的xxx.exe文件)发布出去。要从机器码反推出C代码是不可能的,所以,凡是编译型的语言,都没有这个问题,而解释型的语言,则必须把源码发布出去。

30 |

这个缺点仅限于你要编写的软件需要卖给别人挣钱的时候。好消息是目前的互联网时代,靠卖软件授权的商业模式越来越少了,靠网站和移动应用卖服务的模式越来越多了,后一种模式不需要把源码给别人。

31 |

再说了,现在如火如荼的开源运动和互联网自由开放的精神是一致的,互联网上有无数非常优秀的像Linux一样的开源代码,我们千万不要高估自己写的代码真的有非常大的“商业价值”。那些大公司的代码不愿意开放的更重要的原因是代码写得太烂了,一旦开源,就没人敢用他们的产品了。

32 |

哪有功夫破解你的烂代码

33 |

当然,Python还有其他若干小缺点,请自行忽略,就不一一列举了。

34 |
35 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/20迭代.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

迭代

8 |
2256次阅读
9 |
10 |

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们成为迭代(Iteration)。

11 |

在Python中,迭代是通过for ... in来完成的,而很多语言比如C或者Java,迭代list是通过下标完成的,比如Java代码:

12 |
for (i=0; i<list.length; i++) {
13 |     n = list[i];
14 | }
15 | 

可以看出,Python的for循环抽象程度要高于Java的for循环,因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。

16 |

list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:

17 |
>>> d = {'a': 1, 'b': 2, 'c': 3}
18 | >>> for key in d:
19 | ...     print key
20 | ...
21 | a
22 | c
23 | b
24 | 

因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。

25 |

默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.itervalues(),如果要同时迭代key和value,可以用for k, v in d.iteritems()

26 |

由于字符串也是可迭代对象,因此,也可以作用于for循环:

27 |
>>> for ch in 'ABC':
28 | ...     print ch
29 | ...
30 | A
31 | B
32 | C
33 | 

所以,当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。

34 |

那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

35 |
>>> from collections import Iterable
36 | >>> isinstance('abc', Iterable) # str是否可迭代
37 | True
38 | >>> isinstance([1,2,3], Iterable) # list是否可迭代
39 | True
40 | >>> isinstance(123, Iterable) # 整数是否可迭代
41 | False
42 | 

最后一个小问题,如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

43 |
>>> for i, value in enumerate(['A', 'B', 'C']):
44 | ...     print i, value
45 | ...
46 | 0 A
47 | 1 B
48 | 2 C
49 | 

上面的for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:

50 |
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
51 | ...     print x, y
52 | ...
53 | 1 1
54 | 2 4
55 | 3 9
56 | 

小结

57 |

任何可迭代对象都可以作用于for循环,包括我们自定义的数据类型,只要符合迭代条件,就可以使用for循环。

58 |
59 | 60 |
61 | 62 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/21列表生成式.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

列表生成式

8 |
2223次阅读
9 |
10 |

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

11 |

举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用range(1, 11)

12 |
>>> range(1, 11)
13 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
14 | 

但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?方法一是循环:

15 |
>>> L = []
16 | >>> for x in range(1, 11):
17 | ...    L.append(x * x)
18 | ...
19 | >>> L
20 | [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
21 | 

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:

22 |
>>> [x * x for x in range(1, 11)]
23 | [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
24 | 

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。

25 |

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

26 |
>>> [x * x for x in range(1, 11) if x % 2 == 0]
27 | [4, 16, 36, 64, 100]
28 | 

还可以使用两层循环,可以生成全排列:

29 |
>>> [m + n for m in 'ABC' for n in 'XYZ']
30 | ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
31 | 

三层和三层以上的循环就很少用到了。

32 |

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

33 |
>>> import os # 导入os模块,模块的概念后面讲到
34 | >>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
35 | ['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']
36 | 

for循环其实可以同时使用两个甚至多个变量,比如dictiteritems()可以同时迭代key和value:

37 |
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
38 | >>> for k, v in d.iteritems():
39 | ...     print k, '=', v
40 | ... 
41 | y = B
42 | x = A
43 | z = C
44 | 

因此,列表生成式也可以使用两个变量来生成list:

45 |
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
46 | >>> [k + '=' + v for k, v in d.iteritems()]
47 | ['y=B', 'x=A', 'z=C']
48 | 

最后把一个list中所有的字符串变成小写:

49 |
>>> L = ['Hello', 'World', 'IBM', 'Apple']
50 | >>> [s.lower() for s in L]
51 | ['hello', 'world', 'ibm', 'apple']
52 | 

小结

53 |

运用列表生成式,可以快速生成list,可以通过一个list推导出另一个list,而代码却十分简洁。

54 |

思考:如果list中既包含字符串,又包含整数,由于非字符串类型没有lower()方法,所以列表生成式会报错:

55 |
>>> L = ['Hello', 'World', 18, 'Apple', None]
56 | >>> [s.lower() for s in L]
57 | Traceback (most recent call last):
58 |   File "<stdin>", line 1, in <module>
59 | AttributeError: 'int' object has no attribute 'lower'
60 | 

使用内建的isinstance函数可以判断一个变量是不是字符串:

61 |
>>> x = 'abc'
62 | >>> y = 123
63 | >>> isinstance(x, str)
64 | True
65 | >>> isinstance(y, str)
66 | False
67 | 

请修改列表生成式,通过添加if语句保证列表生成式能正确地执行。

68 |
69 | 70 |
71 | 72 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/22生成器.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

生成器

8 |
2195次阅读
9 |
10 |

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

11 |

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

12 |

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

13 |
>>> L = [x * x for x in range(10)]
 14 | >>> L
 15 | [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 16 | >>> g = (x * x for x in range(10))
 17 | >>> g
 18 | <generator object <genexpr> at 0x104feab40>
 19 | 

创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

20 |

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

21 |

如果要一个一个打印出来,可以通过generator的next()方法:

22 |
>>> g.next()
 23 | 0
 24 | >>> g.next()
 25 | 1
 26 | >>> g.next()
 27 | 4
 28 | >>> g.next()
 29 | 9
 30 | >>> g.next()
 31 | 16
 32 | >>> g.next()
 33 | 25
 34 | >>> g.next()
 35 | 36
 36 | >>> g.next()
 37 | 49
 38 | >>> g.next()
 39 | 64
 40 | >>> g.next()
 41 | 81
 42 | >>> g.next()
 43 | Traceback (most recent call last):
 44 |   File "<stdin>", line 1, in <module>
 45 | StopIteration
 46 | 

我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

47 |

当然,上面这种不断调用next()方法实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

48 |
>>> g = (x * x for x in range(10))
 49 | >>> for n in g:
 50 | ...     print n
 51 | ...
 52 | 0
 53 | 1
 54 | 4
 55 | 9
 56 | 16
 57 | 25
 58 | 36
 59 | 49
 60 | 64
 61 | 81
 62 | 

所以,我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。

63 |

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

64 |

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

65 |

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

66 |

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

67 |
def fib(max):
 68 |     n, a, b = 0, 0, 1
 69 |     while n < max:
 70 |         print b
 71 |         a, b = b, a + b
 72 |         n = n + 1
 73 | 

上面的函数可以输出斐波那契数列的前N个数:

74 |
>>> fib(6)
 75 | 1
 76 | 1
 77 | 2
 78 | 3
 79 | 5
 80 | 8
 81 | 

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

82 |

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print b改为yield b就可以了:

83 |
def fib(max):
 84 |     n, a, b = 0, 0, 1
 85 |     while n < max:
 86 |         yield b
 87 |         a, b = b, a + b
 88 |         n = n + 1
 89 | 

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

90 |
>>> fib(6)
 91 | <generator object fib at 0x104feaaa0>
 92 | 

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

93 |

举个简单的例子,定义一个generator,依次返回数字1,3,5:

94 |
>>> def odd():
 95 | ...     print 'step 1'
 96 | ...     yield 1
 97 | ...     print 'step 2'
 98 | ...     yield 3
 99 | ...     print 'step 3'
100 | ...     yield 5
101 | ...
102 | >>> o = odd()
103 | >>> o.next()
104 | step 1
105 | 1
106 | >>> o.next()
107 | step 2
108 | 3
109 | >>> o.next()
110 | step 3
111 | 5
112 | >>> o.next()
113 | Traceback (most recent call last):
114 |   File "<stdin>", line 1, in <module>
115 | StopIteration
116 | 

可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next()就报错。

117 |

回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

118 |

同样的,把函数改成generator后,我们基本上从来不会用next()来调用它,而是直接使用for循环来迭代:

119 |
>>> for n in fib(6):
120 | ...     print n
121 | ...
122 | 1
123 | 1
124 | 2
125 | 3
126 | 5
127 | 8
128 | 

小结

129 |

generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

130 |

要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

131 |
132 | 133 |
134 | 135 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/23函数式编程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

函数式编程

8 |
1978次阅读
9 |
10 |

函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

11 |

而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

12 |

我们首先要搞明白计算机(Computer)和计算(Compute)的概念。

13 |

在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。

14 |

而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。

15 |

对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

16 |

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

17 |

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

18 |

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

19 |
20 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/25匿名函数.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

匿名函数

8 |
1754次阅读
9 |
10 |

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

11 |

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

12 |
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
13 | [1, 4, 9, 16, 25, 36, 49, 64, 81]
14 | 

通过对比可以看出,匿名函数lambda x: x * x实际上就是:

15 |
def f(x):
16 |     return x * x
17 | 

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

18 |

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

19 |

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

20 |
>>> f = lambda x: x * x
21 | >>> f
22 | <function <lambda> at 0x10453d7d0>
23 | >>> f(5)
24 | 25
25 | 

同样,也可以把匿名函数作为返回值返回,比如:

26 |
def build(x, y):
27 |     return lambda: x * x + y * y
28 | 

小结

29 |

Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。

30 |
31 | 32 |
33 | 34 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/26装饰器.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

装饰器

8 |
2215次阅读
9 |
10 |

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

11 |
>>> def now():
 12 | ...     print '2013-12-25'
 13 | ...
 14 | >>> f = now
 15 | >>> f()
 16 | 2013-12-25
 17 | 

函数对象有一个__name__属性,可以拿到函数的名字:

18 |
>>> now.__name__
 19 | 'now'
 20 | >>> f.__name__
 21 | 'now'
 22 | 

现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

23 |

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

24 |
def log(func):
 25 |     def wrapper(*args, **kw):
 26 |         print 'call %s():' % func.__name__
 27 |         return func(*args, **kw)
 28 |     return wrapper
 29 | 

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

30 |
@log
 31 | def now():
 32 |     print '2013-12-25'
 33 | 

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

34 |
>>> now()
 35 | call now():
 36 | 2013-12-25
 37 | 

@log放到now()函数的定义处,相当于执行了语句:

38 |
now = log(now)
 39 | 

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

40 |

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

41 |

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

42 |
def log(text):
 43 |     def decorator(func):
 44 |         def wrapper(*args, **kw):
 45 |             print '%s %s():' % (text, func.__name__)
 46 |             return func(*args, **kw)
 47 |         return wrapper
 48 |     return decorator
 49 | 

这个3层嵌套的decorator用法如下:

50 |
@log('execute')
 51 | def now():
 52 |     print '2013-12-25'
 53 | 

执行结果如下:

54 |
>>> now()
 55 | execute now():
 56 | 2013-12-25
 57 | 

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

58 |
>>> now = log('execute')(now)
 59 | 

我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

60 |

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

61 |
>>> now.__name__
 62 | 'wrapper'
 63 | 

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

64 |

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

65 |
import functools
 66 | 
 67 | def log(func):
 68 |     @functools.wraps(func)
 69 |     def wrapper(*args, **kw):
 70 |         print 'call %s():' % func.__name__
 71 |         return func(*args, **kw)
 72 |     return wrapper
 73 | 

或者针对带参数的decorator:

74 |
import functools
 75 | 
 76 | def log(text):
 77 |     def decorator(func):
 78 |         @functools.wraps(func)
 79 |         def wrapper(*args, **kw):
 80 |             print '%s %s():' % (text, func.__name__)
 81 |             return func(*args, **kw)
 82 |         return wrapper
 83 |     return decorator
 84 | 

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

85 |

小结

86 |

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

87 |

decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

88 |

请编写一个decorator,能在函数调用的前后打印出'begin call''end call'的日志。

89 |

再思考一下能否写出一个@log的decorator,使它既支持:

90 |
@log
 91 | def f():
 92 |     pass
 93 | 

又支持:

94 |
@log('execute')
 95 | def f():
 96 |     pass
 97 | 
98 | 99 |
100 | 101 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/27偏函数.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

偏函数

8 |
1520次阅读
9 |
10 |

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

11 |

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:

12 |

int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

13 |
>>> int('12345')
14 | 12345
15 | 

int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

16 |
>>> int('12345', base=8)
17 | 5349
18 | >>> int('12345', 16)
19 | 74565
20 | 

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

21 |
def int2(x, base=2):
22 |     return int(x, base)
23 | 

这样,我们转换二进制就非常方便了:

24 |
>>> int2('1000000')
25 | 64
26 | >>> int2('1010101')
27 | 85
28 | 

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

29 |
>>> import functools
30 | >>> int2 = functools.partial(int, base=2)
31 | >>> int2('1000000')
32 | 64
33 | >>> int2('1010101')
34 | 85
35 | 

所以,简单总结functools.partial的作用就是,把一个函数的某些参数(不管有没有默认值)给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

36 |

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

37 |
>>> int2('1000000', base=10)
38 | 1000000
39 | 

最后,创建偏函数时,要从右到左固定参数,就是说,对于函数f(a1, a2, a3),可以固定a3,也可以固定a3a2,也可以固定a3a2a1,但不要跳着固定,比如只固定a1a3,把a2漏下了。如果这样做,调用新的函数会更复杂,可以自己试试。

40 |

小结

41 |

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

42 |
43 | 44 |
45 | 46 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/28模块.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

模块

8 |
1615次阅读
9 |
10 |

在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。

11 |

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。

12 |

使用模块有什么好处?

13 |

最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。

14 |

使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。点这里查看Python的所有内置函数。

15 |

你也许还想到,如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

16 |

举个例子,一个abc.py的文件就是一个名字叫abc的模块,一个xyz.py的文件就是一个名字叫xyz的模块。

17 |

现在,假设我们的abcxyz这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名,比如mycompany,按照如下目录存放:

18 |

mycompany

19 |

引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py模块的名字就变成了mycompany.abc,类似的,xyz.py的模块名变成了mycompany.xyz

20 |

请注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

21 |

类似的,可以有多级目录,组成多级层次的包结构。比如如下的目录结构:

22 |

mycompany-web

23 |

文件www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utilsmycompany.web.utils

24 |

mycompany.web也是一个模块,请指出该模块对应的.py文件。

25 |
26 | 27 |
28 | 29 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/29使用模块.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用模块

8 |
2307次阅读
9 |
10 |

Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。

11 |

我们以内建的sys模块为例,编写一个hello的模块:

12 |
#!/usr/bin/env python
 13 | # -*- coding: utf-8 -*-
 14 | 
 15 | ' a test module '
 16 | 
 17 | __author__ = 'Michael Liao'
 18 | 
 19 | import sys
 20 | 
 21 | def test():
 22 |     args = sys.argv
 23 |     if len(args)==1:
 24 |         print 'Hello, world!'
 25 |     elif len(args)==2:
 26 |         print 'Hello, %s!' % args[1]
 27 |     else:
 28 |         print 'Too many arguments!'
 29 | 
 30 | if __name__=='__main__':
 31 |     test()
 32 | 

第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;

33 |

第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

34 |

第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

35 |

以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。

36 |

后面开始就是真正的代码部分。

37 |

你可能注意到了,使用sys模块的第一步,就是导入该模块:

38 |
import sys
 39 | 

导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。

40 |

sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:

41 |

运行python hello.py获得的sys.argv就是['hello.py']

42 |

运行python hello.py Michael获得的sys.argv就是['hello.py', 'Michael]

43 |

最后,注意到这两行代码:

44 |
if __name__=='__main__':
 45 |     test()
 46 | 

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

47 |

我们可以用命令行运行hello.py看看效果:

48 |
$ python hello.py
 49 | Hello, world!
 50 | $ python hello.py Michael
 51 | Hello, Michael!
 52 | 

如果启动Python交互环境,再导入hello模块:

53 |
$ python
 54 | Python 2.7.5 (default, Aug 25 2013, 00:04:04) 
 55 | [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
 56 | Type "help", "copyright", "credits" or "license" for more information.
 57 | >>> import hello
 58 | >>>
 59 | 

导入时,没有打印Hello, word!,因为没有执行test()函数。

60 |

调用hello.test()时,才能打印出Hello, word!

61 |
>>> hello.test()
 62 | Hello, world!
 63 | 

别名

64 |

导入模块时,还可以使用别名,这样,可以在运行时根据当前环境选择最合适的模块。比如Python标准库一般会提供StringIOcStringIO两个库,这两个库的接口和功能是一样的,但是cStringIO是C写的,速度更快,所以,你会经常看到这样的写法:

65 |
try:
 66 |     import cStringIO as StringIO
 67 | except ImportError: # 导入失败会捕获到ImportError
 68 |     import StringIO
 69 | 

这样就可以优先导入cStringIO。如果有些平台不提供cStringIO,还可以降级使用StringIO。导入cStringIO时,用import ... as ...指定了别名StringIO,因此,后续代码引用StringIO即可正常工作。

70 |

还有类似simplejson这样的库,在Python 2.6之前是独立的第三方库,从2.6开始内置,所以,会有这样的写法:

71 |
try:
 72 |     import json # python >= 2.6
 73 | except ImportError:
 74 |     import simplejson as json # python <= 2.5
 75 | 

由于Python是动态语言,函数签名一致接口就一样,因此,无论导入哪个模块后续代码都能正常工作。

76 |

作用域

77 |

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。

78 |

正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;

79 |

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

80 |

类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

81 |

之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

82 |

private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:

83 |
def _private_1(name):
 84 |     return 'Hello, %s' % name
 85 | 
 86 | def _private_2(name):
 87 |     return 'Hi, %s' % name
 88 | 
 89 | def greeting(name):
 90 |     if len(name) > 3:
 91 |         return _private_1(name)
 92 |     else:
 93 |         return _private_2(name)
 94 | 

我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:

95 |

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

96 |
97 | 98 |
99 | 100 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/2安装Python.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

安装Python

8 |
6838次阅读
9 |
10 |

因为Python是跨平台的,它可以运行在Windows、Mac和各种Linux/Unix系统上。在Windows上写Python程序,放到Linux上也是能够运行的。

11 |

要开始学习Python编程,首先就得把Python安装到你的电脑里。安装后,你会得到Python解释器(就是负责运行Python程序的),一个命令行交互环境,还有一个简单的集成开发环境。

12 |

2.x还是3.x

13 |

目前,Python有两个版本,一个是2.x版,一个是3.x版,这两个版本是不兼容的,因为现在Python正在朝着3.x版本进化,在进化过程中,大量的针对2.x版本的代码要修改后才能运行,所以,目前有许多第三方库还暂时无法在3.x上使用。

14 |

为了保证你的程序能用到大量的第三方库,我们的教程仍以2.x版本为基础,确切地说,是2.7版本。请确保你的电脑上安装的Python版本是2.7.x,这样,你才能无痛学习这个教程。

15 |

在Mac上安装Python

16 |

如果你正在使用Mac,系统是OS X 10.8或者最新的10.9 Mavericks,恭喜你,系统自带了Python 2.7。如果你的系统版本低于10.8,请自行备份系统并免费升级到最新的10.9,就可以获得Python 2.7。

17 |

查看系统版本的办法是点击左上角的苹果图标,选择“关于本机”:

18 |

osx-10.9

19 |

在Linux上安装Python

20 |

如果你正在使用Linux,那我可以假定你有Linux系统管理经验,自行安装Python 2.7应该没有问题,否则,请换回Windows系统。

21 |

对于大量的目前仍在使用Windows的同学,如果短期内没有打算换Mac,就可以继续阅读以下内容。

22 |

在Windows上安装Python

23 |

首先,从Python的官方网站www.python.org下载最新的2.7.6版本,地址是这个:

24 |

http://www.python.org/ftp/python/2.7.6/python-2.7.6.msi

25 |

然后,运行下载的MSI安装包,不需要更改任何默认设置,直接一路点“Next”即可完成安装:

26 |

27 | 28 |

默认会安装到C:\Python27目录下,但是当你兴致勃勃地打开命令提示符窗口,敲入python后,会得到:

29 |
‘python’不是内部或外部命令,也不是可运行的程序或批处理文件。
30 | 

31 | 32 |

这是因为Windows会根据一个Path的环境变量设定的路径去查找python.exe,如果没找到,就会报错。解决办法是把python.exe所在的路径C:\Python27添加到Path中。

33 |

在控制面板中打开“系统属性”,点击“高级”,“环境变量”,打开“环境变量”窗口,在系统变量中,找到“Path”变量,然后点击“编辑”:

34 |

windows环境变量Path

35 |

在“编辑系统变量”的窗口中,可以看到,变量名是Path,在变量值的最后面,先添加一个分号“;”(注意用英文输入法,千万不要输入中文分号),再写上C:\Python27(如果安装的时候没有更改过安装目录),然后连续点“确定”,“确定”,“确定”把所有窗口都关掉。

36 |

37 | 38 |

现在,再打开一个新的命令行窗口(一定要关掉原来的命令行窗口,再新开一个),输入python:

39 |

python-command

40 |

看到上面的画面,就说明Python安装成功!

41 |

你看到提示符>>>就表示我们已经在Python交互式环境中了,可以输入任何Python代码,回车后会立刻得到执行结果。现在,输入exit()并回车,就可以退出Python交互式环境(直接关掉命令行窗口也可以!)。

42 |

43 | 44 |

小结

45 |

学会如何把Python安装到计算机中,并且熟练打开和退出Python交互式环境。

46 |
47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/30安装第三方模块.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

安装第三方模块

8 |
1986次阅读
9 |
10 |

在Python中,安装第三方模块,是通过setuptools这个工具完成的。

11 |

如果你正在使用Mac或Linux,安装setuptools本身这个步骤就可以跳过了。

12 |

如果你正在使用Windows,请首先从这个地址下载ez_setup.py

13 |

https://pypi.python.org/pypi/setuptools#windows

14 |

download_ez_setup.py

15 |

下载后,随便放到一个目录下,然后运行以下命令来安装setuptools:

16 |
python ez_setup.py
17 | 

18 | 19 |

在命令提示符窗口下尝试运行easy_install,Windows会提示未找到命令,原因是easy_install.exe所在路径还没有被添加到环境变量Path中。请添加C:\Python27\Scripts到环境变量Path

20 |

add-path-for-py-scripts

21 |

重新打开命令提示符窗口,就可以运行easy_install了:

22 |

现在,让我们来安装一个第三方库——Python Imaging Library,这是Python下非常强大的处理图像的工具库。一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Python Imaging Library的名称叫PIL,因此,安装Python Imaging Library的命令就是:

23 |
easy_install PIL
24 | 

25 | 26 |

耐心等待下载并安装后,就可以使用PIL了。

27 |

有了PIL,处理图片易如反掌。随便找个图片生成缩略图:

28 |
>>> import Image
29 | >>> im = Image.open('test.png')
30 | >>> print im.format, im.size, im.mode
31 | PNG (400, 300) RGB
32 | >>> im.thumbnail((200, 100))
33 | >>> im.save('thumb.jpg', 'JPEG')
34 | 

其他常用的第三方库还有MySQL的驱动:MySQL-python,用于科学计算的NumPy库:numpy,用于生成文本的模板工具Jinja2,等等。

35 |

模块搜索路径

36 |

当我们试图加载一个模块时,Python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错:

37 |
>>> import mymodule
38 | Traceback (most recent call last):
39 |   File "<stdin>", line 1, in <module>
40 | ImportError: No module named mymodule
41 | 

默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

42 |
>>> import sys
43 | >>> sys.path
44 | ['', '/Library/Python/2.7/site-packages/pycrypto-2.6.1-py2.7-macosx-10.9-intel.egg', '/Library/Python/2.7/site-packages/PIL-1.1.7-py2.7-macosx-10.9-intel.egg', ...]
45 | 

如果我们要添加自己的搜索目录,有两种方法:

46 |

一是直接修改sys.path,添加要搜索的目录:

47 |
>>> import sys
48 | >>> sys.path.append('/Users/michael/my_py_scripts')
49 | 

这种方法是在运行时修改,运行结束后失效。

50 |

第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。

51 |
52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/31使用__future__.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用__future__

8 |
1530次阅读
9 |
10 |

Python的每个新版本都会增加一些新的功能,或者对原来的功能作一些改动。有些改动是不兼容旧版本的,也就是在当前版本运行正常的代码,到下一个版本运行就可能不正常了。

11 |

从Python 2.7到Python 3.x就有不兼容的一些改动,比如2.x里的字符串用'xxx'表示str,Unicode字符串用u'xxx'表示unicode,而在3.x中,所有字符串都被视为unicode,因此,写u'xxx''xxx'是完全一致的,而在2.x中以'xxx'表示的str就必须写成b'xxx',以此表示“二进制字符串”。

12 |

要直接把代码升级到3.x是比较冒进的,因为有大量的改动需要测试。相反,可以在2.7版本中先在一部分代码中测试一些3.x的特性,如果没有问题,再移植到3.x不迟。

13 |

Python提供了__future__模块,把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性。举例说明如下:

14 |

为了适应Python 3.x的新的字符串的表示方法,在2.7版本的代码中,可以通过unicode_literals来使用Python 3.x的新的语法:

15 |
# still running on Python 2.7
16 | 
17 | from __future__ import unicode_literals
18 | 
19 | print '\'xxx\' is unicode?', isinstance('xxx', unicode)
20 | print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode)
21 | print '\'xxx\' is str?', isinstance('xxx', str)
22 | print 'b\'xxx\' is str?', isinstance(b'xxx', str)
23 | 

注意到上面的代码仍然在Python 2.7下运行,但结果显示去掉前缀u'a string'仍是一个unicode,而加上前缀bb'a string'才变成了str:

24 |
$ python task.py
25 | 'xxx' is unicode? True
26 | u'xxx' is unicode? True
27 | 'xxx' is str? False
28 | b'xxx' is str? True
29 | 

类似的情况还有除法运算。在Python 2.x中,对于除法有两种情况,如果是整数相除,结果仍是整数,余数会被扔掉,这种除法叫“地板除”:

30 |
>>> 10 / 3
31 | 3
32 | 

要做精确除法,必须把其中一个数变成浮点数:

33 |
>>> 10.0 / 3
34 | 3.3333333333333335
35 | 

而在Python 3.x中,所有的除法都是精确除法,地板除用//表示:

36 |
$ python3
37 | Python 3.3.2 (default, Jan 22 2014, 09:54:40) 
38 | [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
39 | Type "help", "copyright", "credits" or "license" for more information.
40 | >>> 10 / 3
41 | 3.3333333333333335
42 | >>> 10 // 3
43 | 3
44 | 

如果你想在Python 2.7的代码中直接使用Python 3.x的除法,可以通过__future__模块的division实现:

45 |
from __future__ import division
46 | 
47 | print '10 / 3 =', 10 / 3
48 | print '10.0 / 3 =', 10.0 / 3
49 | print '10 // 3 =', 10 // 3
50 | 

结果如下:

51 |
10 / 3 = 3.33333333333
52 | 10.0 / 3 = 3.33333333333
53 | 10 // 3 = 3
54 | 

小结

55 |

由于Python是由社区推动的开源并且免费的开发语言,不受商业公司控制,因此,Python的改进往往比较激进,不兼容的情况时有发生。Python为了确保你能顺利过渡到新版本,特别提供了__future__模块,让你在旧的版本中试验新版本的一些特性。

56 |
57 | 58 |
59 | 60 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/32面向对象编程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

面向对象编程

8 |
1814次阅读
9 |
10 |

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

11 |

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

12 |

而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

13 |

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

14 |

我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。

15 |

假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示:

16 |
std1 = { 'name': 'Michael', 'score': 98 }
17 | std2 = { 'name': 'Bob', 'score': 81 }
18 | 

而处理学生成绩可以通过函数实现,比如打印学生的成绩:

19 |
def print_score(std):
20 |     print '%s: %s' % (std['name'], std['score'])
21 | 

如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有namescore这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

22 |
class Student(object):
23 | 
24 |     def __init__(self, name, score):
25 |         self.name = name
26 |         self.score = score
27 | 
28 |     def print_score(self):
29 |         print '%s: %s' % (self.name, self.score)
30 | 

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:

31 |
bart = Student('Bart Simpson', 59)
32 | lisa = Student('Lisa Simpson', 87)
33 | bart.print_score()
34 | lisa.print_score()
35 | 

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student:

36 |

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。

37 |

面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

38 |

小结

39 |

数据封装、继承和多态是面向对象的三大特点,我们后面会详细讲解。

40 |
41 | 42 |
43 | 44 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/33类和实例.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

类和实例

8 |
2151次阅读
9 |
10 |

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

11 |

仍以Student类为例,在Python中,定义类是通过class关键字:

12 |
class Student(object):
13 |     pass
14 | 

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

15 |

定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:

16 |
>>> bart = Student()
17 | >>> bart
18 | <__main__.Student object at 0x10a67a590>
19 | >>> Student
20 | <class '__main__.Student'>
21 | 

可以看到,变量bart指向的就是一个Student的object,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。

22 |

可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

23 |
>>> bart.name = 'Bart Simpson'
24 | >>> bart.name
25 | 'Bart Simpson'
26 | 

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

27 |
class Student(object):
28 | 
29 |     def __init__(self, name, score):
30 |         self.name = name
31 |         self.score = score
32 | 

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

33 |

有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

34 |
>>> bart = Student('Bart Simpson', 59)
35 | >>> bart.name
36 | 'Bart Simpson'
37 | >>> bart.score
38 | 59
39 | 

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数和关键字参数。

40 |

数据封装

41 |

面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的namescore这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:

42 |
>>> def print_score(std):
43 | ...     print '%s: %s' % (std.name, std.score)
44 | ...
45 | >>> print_score(bart)
46 | Bart Simpson: 59
47 | 

但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:

48 |
class Student(object):
49 | 
50 |     def __init__(self, name, score):
51 |         self.name = name
52 |         self.score = score
53 | 
54 |     def print_score(self):
55 |         print '%s: %s' % (self.name, self.score)
56 | 

要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入:

57 |
>>> bart.print_score()
58 | Bart Simpson: 59
59 | 

这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出namescore,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

60 |

封装的另一个好处是可以给Student类增加新的方法,比如get_grade

61 |
class Student(object):
62 |     ...
63 | 
64 |     def get_grade(self):
65 |         if self.score >= 90:
66 |             return 'A'
67 |         elif self.score >= 60:
68 |             return 'B'
69 |         else:
70 |             return 'C'
71 | 

同样的,get_grade方法可以直接在实例变量上调用,不需要知道内部实现细节:

72 |
>>> bart.get_grade()
73 | 'A'
74 | 

小结

75 |

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都不相同;

76 |

通过在实例变量上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。

77 |

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:

78 |
>>> bart = Student('Bart Simpson', 59)
79 | >>> lisa=Student('Lisa Simpson', 87)
80 | >>> bart.age = 8
81 | >>> bart.age
82 | 8
83 | >>> lisa.age
84 | Traceback (most recent call last):
85 |   File "<stdin>", line 1, in <module>
86 | AttributeError: 'Student' object has no attribute 'age'
87 | 
88 | 89 |
90 | 91 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/34访问限制.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

访问限制

8 |
1428次阅读
9 |
10 |

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

11 |

但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:

12 |
>>> bart = Student('Bart Simpson', 98)
13 | >>> bart.score
14 | 98
15 | >>> bart.score = 59
16 | >>> bart.score
17 | 59
18 | 

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

19 |
class Student(object):
20 | 
21 |     def __init__(self, name, score):
22 |         self.__name = name
23 |         self.__score = score
24 | 
25 |     def print_score(self):
26 |         print '%s: %s' % (self.__name, self.__score)
27 | 

改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name实例变量.__score了:

28 |
>>> bart = Student('Bart Simpson', 98)
29 | >>> bart.__name
30 | Traceback (most recent call last):
31 |   File "<stdin>", line 1, in <module>
32 | AttributeError: 'Student' object has no attribute '__name'
33 | 

这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

34 |

但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

35 |
class Student(object):
36 |     ...
37 | 
38 |     def get_name(self):
39 |         return self.__name
40 | 
41 |     def get_score(self):
42 |         return self.__score
43 | 

如果又要允许外部代码修改score怎么办?可以给Student类增加set_score方法:

44 |
class Student(object):
45 |     ...
46 | 
47 |     def set_score(self, score):
48 |         self.__score = score
49 | 

你也许会问,原先那种直接通过bart.score = 59也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

50 |
class Student(object):
51 |     ...
52 | 
53 |     def set_score(self, score):
54 |         if 0 <= score <= 100:
55 |             self.__score = score
56 |         else:
57 |             raise ValueError('bad score')
58 | 

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

59 |

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

60 |

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

61 |
>>> bart._Student__name
62 | 'Bart Simpson'
63 | 

但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

64 |

总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

65 |
66 | 67 |
68 | 69 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/35继承和多态.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

继承和多态

8 |
1444次阅读
9 |
10 |

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

11 |

比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:

12 |
class Animal(object):
 13 |     def run(self):
 14 |         print 'Animal is running...'
 15 | 

当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:

16 |
class Dog(Animal):
 17 |     pass
 18 | 
 19 | class Cat(Animal):
 20 |     pass
 21 | 

对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。

22 |

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:

23 |
dog = Dog()
 24 | dog.run()
 25 | 
 26 | cat = Cat()
 27 | cat.run()
 28 | 

运行结果如下:

29 |
Animal is running...
 30 | Animal is running...
 31 | 

当然,也可以对子类增加一些方法,比如Dog类:

32 |
class Dog(Animal):
 33 |     def run(self):
 34 |         print 'Dog is running...'
 35 |     def eat(self):
 36 |         print 'Eating meat...'
 37 | 

继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...Cat is running...,因此,对Dog和Cat类改进如下:

38 |
class Dog(Animal):
 39 |     def run(self):
 40 |         print 'Dog is running...'
 41 | 
 42 | class Cat(Animal):
 43 |     def run(self):
 44 |         print 'Cat is running...'
 45 | 

再次运行,结果如下:

46 |
Dog is running...
 47 | Cat is running...
 48 | 

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

49 |

要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:

50 |
a = list() # a是list类型
 51 | b = Animal() # b是Animal类型
 52 | c = Dog() # c是Dog类型
 53 | 

判断一个变量是否是某个类型可以用isinstance()判断:

54 |
>>> isinstance(a, list)
 55 | True
 56 | >>> isinstance(b, Animal)
 57 | True
 58 | >>> isinstance(c, Dog)
 59 | True
 60 | 

看来a、b、c确实对应着list、Animal、Dog这3种类型。

61 |

但是等等,试试:

62 |
>>> isinstance(c, Animal)
 63 | True
 64 | 

看来c不仅仅是Dog,c还是Animal!

65 |

不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!

66 |

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:

67 |
>>> b = Animal()
 68 | >>> isinstance(b, Dog)
 69 | False
 70 | 

Dog可以看成Animal,但Animal不可以看成Dog。

71 |

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

72 |
def run_twice(animal):
 73 |     animal.run()
 74 |     animal.run()
 75 | 

当我们传入Animal的实例时,run_twice()就打印出:

76 |
>>> run_twice(Animal())
 77 | Animal is running...
 78 | Animal is running...
 79 | 

当我们传入Dog的实例时,run_twice()就打印出:

80 |
>>> run_twice(Dog())
 81 | Dog is running...
 82 | Dog is running...
 83 | 

当我们传入Cat的实例时,run_twice()就打印出:

84 |
>>> run_twice(Cat())
 85 | Cat is running...
 86 | Cat is running...
 87 | 

看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:

88 |
class Tortoise(Animal):
 89 |     def run(self):
 90 |         print 'Tortoise is running slowly...'
 91 | 

当我们调用run_twice()时,传入Tortoise的实例:

92 |
>>> run_twice(Tortoise())
 93 | Tortoise is running slowly...
 94 | Tortoise is running slowly...
 95 | 

你会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

96 |

多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

97 |

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

98 |

对扩展开放:允许新增Animal子类;

99 |

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

100 |

继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:

101 |

class-inheritance

102 |

小结

103 |

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写;

104 |

有了继承,才能有多态。在调用类实例方法的时候,尽量把变量视作父类类型,这样,所有子类类型都可以正常被接收;

105 |

旧的方式定义Python类允许不从object类继承,但这种编程方式已经严重不推荐使用。任何时候,如果没有合适的类可以继承,就继承自object类。

106 |
107 | 108 |
109 | 110 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/37面向对象高级编程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

面向对象高级编程

8 |
1100次阅读
9 |
10 |

数据封装、继承和多态只是面向对象程序设计中最基础的3个概念。在Python中,面向对象还有很多高级特性,允许我们写出非常强大的功能。

11 |

我们会讨论多重继承、定制类、元类等概念。

12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/38使用__slots__.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用__slots__

8 |
1418次阅读
9 |
10 |

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

11 |
>>> class Student(object):
12 | ...     pass
13 | ...
14 | 

然后,尝试给实例绑定一个属性:

15 |
>>> s = Student()
16 | >>> s.name = 'Michael' # 动态给实例绑定一个属性
17 | >>> print s.name
18 | Michael
19 | 

还可以尝试给实例绑定一个方法:

20 |
>>> def set_age(self, age): # 定义一个函数作为实例方法
21 | ...     self.age = age
22 | ...
23 | >>> from types import MethodType
24 | >>> s.set_age = MethodType(set_age, s, Student) # 给实例绑定一个方法
25 | >>> s.set_age(25) # 调用实例方法
26 | >>> s.age # 测试结果
27 | 25
28 | 

但是,给一个实例绑定的方法,对另一个实例是不起作用的:

29 |
>>> s2 = Student() # 创建新的实例
30 | >>> s2.set_age(25) # 尝试调用方法
31 | Traceback (most recent call last):
32 |   File "<stdin>", line 1, in <module>
33 | AttributeError: 'Student' object has no attribute 'set_age'
34 | 

为了给所有实例都绑定方法,可以给class绑定方法:

35 |
>>> def set_score(self, score):
36 | ...     self.score = score
37 | ...
38 | >>> Student.set_score = MethodType(set_score, None, Student)
39 | 

给class绑定方法后,所有实例均可调用:

40 |
>>> s.set_score(100)
41 | >>> s.score
42 | 100
43 | >>> s2.set_score(99)
44 | >>> s2.score
45 | 99
46 | 

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

47 |

使用__slots__

48 |

但是,如果我们想要限制class的属性怎么办?比如,只允许对Student实例添加nameage属性。

49 |

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class能添加的属性:

50 |
>>> class Student(object):
51 | ...     __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
52 | ...
53 | 

然后,我们试试:

54 |
>>> s = Student() # 创建新的实例
55 | >>> s.name = 'Michael' # 绑定属性'name'
56 | >>> s.age = 25 # 绑定属性'age'
57 | >>> s.score = 99 # 绑定属性'score'
58 | Traceback (most recent call last):
59 |   File "<stdin>", line 1, in <module>
60 | AttributeError: 'Student' object has no attribute 'score'
61 | 

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

62 |

使用__slots__要注意,__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的:

63 |
>>> class GraduateStudent(Student):
64 | ...     pass
65 | ...
66 | >>> g = GraduateStudent()
67 | >>> g.score = 9999
68 | 

除非在子类中也定义__slots__,这样,子类允许定义的属性就是自身的__slots__加上父类的__slots__

69 |
70 | 71 |
72 | 73 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/39使用@property.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用@property

8 |
1261次阅读
9 |
10 |

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:

11 |
s = Student()
12 | s.score = 9999
13 | 

这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:

14 |
class Student(object):
15 | 
16 |     def get_score(self):
17 |         return self._score
18 | 
19 |     def set_score(self, value):
20 |         if not isinstance(value, int):
21 |             raise ValueError('score must be an integer!')
22 |         if value < 0 or value > 100:
23 |             raise ValueError('score must between 0 ~ 100!')
24 |         self._score = value
25 | 

现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:

26 |
>>> s = Student()
27 | >>> s.set_score(60) # ok!
28 | >>> s.get_score()
29 | 60
30 | >>> s.set_score(9999)
31 | Traceback (most recent call last):
32 |   ...
33 | ValueError: score must between 0 ~ 100!
34 | 

但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。

35 |

有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!

36 |

还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

37 |
class Student(object):
38 | 
39 |     @property
40 |     def score(self):
41 |         return self._score
42 | 
43 |     @score.setter
44 |     def score(self, value):
45 |         if not isinstance(value, int):
46 |             raise ValueError('score must be an integer!')
47 |         if value < 0 or value > 100:
48 |             raise ValueError('score must between 0 ~ 100!')
49 |         self._score = value
50 | 

@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

51 |
>>> s = Student()
52 | >>> s.score = 60 # OK,实际转化为s.set_score(60)
53 | >>> s.score # OK,实际转化为s.get_score()
54 | 60
55 | >>> s.score = 9999
56 | Traceback (most recent call last):
57 |   ...
58 | ValueError: score must between 0 ~ 100!
59 | 

注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。

60 |

还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

61 |
class Student(object):
62 | 
63 |     @property
64 |     def birth(self):
65 |         return self._birth
66 | 
67 |     @birth.setter
68 |     def birth(self, value):
69 |         self._birth = value
70 | 
71 |     @property
72 |     def age(self):
73 |         return 2014 - self._birth
74 | 

上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

75 |

小结

76 |

@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

77 |
78 | 79 |
80 | 81 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/3Python解释器.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Python解释器

8 |
4050次阅读
9 |
10 |

当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。

11 |

由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种Python解释器。

12 |

CPython

13 |

当我们从Python官方网站下载并安装好Python 2.7后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。

14 |

CPython是使用最广的Python解释器。教程的所有代码也都在CPython下执行。

15 |

IPython

16 |

IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。

17 |

CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。

18 |

PyPy

19 |

PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。

20 |

绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点

21 |

Jython

22 |

Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

23 |

IronPython

24 |

IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

25 |

小结

26 |

Python的解释器很多,但使用最广泛的还是CPython。如果要和Java或.Net平台交互,最好的办法不是用Jython或IronPython,而是通过网络调用来交互,确保各程序之间的独立性。

27 |

本教程的所有代码只确保在CPython 2.7版本下运行。请务必在本地安装CPython(也就是从Python官方网站下载的安装程序)。

28 |

此外,教程还内嵌一个IPython的Web版本,用来在浏览器内练习执行一些Python代码。要注意两者功能一样,输入的代码一样,但是提示符有所不同。另外,不是所有代码都能在Web版本的IPython中执行,出于安全原因,很多操作(比如文件操作)是受限的,所以有些代码必须在本地环境执行代码。

29 |
30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/40多重继承.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

多重继承

8 |
1006次阅读
9 |
10 |

继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。

11 |

回忆一下Animal类层次的设计,假设我们要实现以下4种动物:

12 |
    13 |
  • Dog - 狗狗;
  • 14 |
  • Bat - 蝙蝠;
  • 15 |
  • Parrot - 鹦鹉;
  • 16 |
  • Ostrich - 鸵鸟。
  • 17 |
18 |

如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次:

19 |

animal-mb

20 |

但是如果按照“能跑”和“能飞”来归类,我们就应该设计出这样的类的层次:

21 |

animal-rf

22 |

如果要把上面的两种分类都包含进来,我们就得设计更多的层次:

23 |
    24 |
  • 哺乳类:能跑的哺乳类,能飞的哺乳类;
  • 25 |
  • 鸟类:能跑的鸟类,能飞的鸟类。
  • 26 |
27 |

这么一来,类的层次就复杂了:

28 |

animal-mb-rf

29 |

如果要再增加“宠物类”和“非宠物类”,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。

30 |

正确的做法是采用多重继承。首先,主要的类层次仍按照哺乳类和鸟类设计:

31 |
class Animal(object):
32 |     pass
33 | 
34 | # 大类:
35 | class Mammal(Animal):
36 |     pass
37 | 
38 | class Bird(Animal):
39 |     pass
40 | 
41 | # 各种动物:
42 | class Dog(Mammal):
43 |     pass
44 | 
45 | class Bat(Mammal):
46 |     pass
47 | 
48 | class Parrot(Bird):
49 |     pass
50 | 
51 | class Ostrich(Bird):
52 |     pass
53 | 

现在,我们要给动物再加上RunnableFlyable的功能,只需要先定义好RunnableFlyable的类:

54 |
class Runnable(object):
55 |     def run(self):
56 |         print('Running...')
57 | 
58 | class Flyable(object):
59 |     def fly(self):
60 |         print('Flying...')
61 | 

对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog

62 |
class Dog(Mammal, Runnable):
63 |     pass
64 | 

对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat

65 |
class Bat(Mammal, Flyable):
66 |     pass
67 | 

通过多重继承,一个子类就可以同时获得多个父类的所有功能。

68 |

Mixin

69 |

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为Mixin。

70 |

为了更好地看出继承关系,我们把RunnableFlyable改为RunnableMixinFlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin:

71 |
class Dog(Mammal, RunnableMixin, CarnivorousMixin):
72 |     pass
73 | 

Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。

74 |

Python自带的很多库也使用了Mixin。举个例子,Python自带了TCPServerUDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixinThreadingMixin提供。通过组合,我们就可以创造出合适的服务来。

75 |

比如,编写一个多进程模式的TCP服务,定义如下:

76 |
class MyTCPServer(TCPServer, ForkingMixin):
77 |     pass
78 | 

编写一个多线程模式的UDP服务,定义如下:

79 |
class MyUDPServer(UDPServer, ThreadingMixin):
80 |     pass
81 | 

如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixin

82 |
class MyTCPServer(TCPServer, CoroutineMixin):
83 |     pass
84 | 

这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。

85 |

小结

86 |

由于Python允许使用多重继承,因此,Mixin就是一种常见的设计。

87 |

只允许单一继承的语言(如Java)不能使用Mixin的设计。

88 |
89 | 90 |
91 | 92 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/43错误、调试和测试.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

错误、调试和测试

8 |
1088次阅读
9 |
10 |

在程序运行过程中,总会遇到各种各样的错误。

11 |

有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误我们通常称之为bug,bug是必须修复的。

12 |

有的错误是用户输入造成的,比如让用户输入email地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。

13 |

还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。

14 |

Python内置了一套异常处理机制,来帮助我们进行错误处理。

15 |

此外,我们也需要跟踪程序的执行,查看变量的值是否正确,这个过程称为调试。Python的pdb可以让我们以单步方式执行代码。

16 |

最后,编写测试也很重要。有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们编写的测试。

17 |
18 | 19 |
20 | 21 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/47文档测试.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

文档测试

8 |
403次阅读
9 |
10 |

如果你经常阅读Python的官方文档,可以看到很多文档都有示例代码。比如re模块就带了很多示例代码:

11 |
>>> import re
 12 | >>> m = re.search('(?<=abc)def', 'abcdef')
 13 | >>> m.group(0)
 14 | 'def'
 15 | 

可以把这些示例代码在Python的交互式环境下输入并执行,结果与文档中的示例代码显示的一致。

16 |

这些代码与其他说明可以写在注释中,然后,由一些工具来自动生成文档。既然这些代码本身就可以粘贴出来直接运行,那么,可不可以自动执行写在注释中的这些代码呢?

17 |

答案是肯定的。

18 |

当我们编写注释时,如果写上这样的注释:

19 |
def abs(n):
 20 |     '''
 21 |     Function to get absolute value of number.
 22 | 
 23 |     Example:
 24 | 
 25 |     >>> abs(1)
 26 |     1
 27 |     >>> abs(-1)
 28 |     1
 29 |     >>> abs(0)
 30 |     0
 31 |     '''
 32 |     return n if n >= 0 else (-n)
 33 | 

无疑更明确地告诉函数的调用者该函数的期望输入和输出。

34 |

并且,Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。

35 |

doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用...表示中间一大段烦人的输出。

36 |

让我们用doctest来测试上次编写的Dict类:

37 |
class Dict(dict):
 38 |     '''
 39 |     Simple dict but also support access as x.y style.
 40 | 
 41 |     >>> d1 = Dict()
 42 |     >>> d1['x'] = 100
 43 |     >>> d1.x
 44 |     100
 45 |     >>> d1.y = 200
 46 |     >>> d1['y']
 47 |     200
 48 |     >>> d2 = Dict(a=1, b=2, c='3')
 49 |     >>> d2.c
 50 |     '3'
 51 |     >>> d2['empty']
 52 |     Traceback (most recent call last):
 53 |         ...
 54 |     KeyError: 'empty'
 55 |     >>> d2.empty
 56 |     Traceback (most recent call last):
 57 |         ...
 58 |     AttributeError: 'Dict' object has no attribute 'empty'
 59 |     '''
 60 |     def __init__(self, **kw):
 61 |         super(Dict, self).__init__(**kw)
 62 | 
 63 |     def __getattr__(self, key):
 64 |         try:
 65 |             return self[key]
 66 |         except KeyError:
 67 |             raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
 68 | 
 69 |     def __setattr__(self, key, value):
 70 |         self[key] = value
 71 | 
 72 | if __name__=='__main__':
 73 |     import doctest
 74 |     doctest.testmod()
 75 | 

运行python mydict.py

76 |
$ python mydict.py
 77 | 

什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把__getattr__()方法注释掉,再运行就会报错:

78 |
$ python mydict.py
 79 | **********************************************************************
 80 | File "mydict.py", line 7, in __main__.Dict
 81 | Failed example:
 82 |     d1.x
 83 | Exception raised:
 84 |     Traceback (most recent call last):
 85 |       ...
 86 |     AttributeError: 'Dict' object has no attribute 'x'
 87 | **********************************************************************
 88 | File "mydict.py", line 13, in __main__.Dict
 89 | Failed example:
 90 |     d2.c
 91 | Exception raised:
 92 |     Traceback (most recent call last):
 93 |       ... 
 94 |     AttributeError: 'Dict' object has no attribute 'c'
 95 | **********************************************************************
 96 | 

注意到最后两行代码。当模块正常导入时,doctest不会被执行。只有在命令行运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。

97 |

小结

98 |

doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest。

99 |
100 | 101 |
102 | 103 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/48IO编程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

IO编程

8 |
1077次阅读
9 |
10 |

IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

11 |

比如你打开浏览器,访问新浪首页,浏览器这个程序就需要通过网络IO获取新浪的网页。浏览器首先会发送数据给新浪服务器,告诉它我想要首页的HTML,这个动作是往外发数据,叫Output,随后新浪服务器把网页发过来,这个动作是从外面接收数据,叫Input。所以,通常,程序完成IO操作会有Input和Output两个数据流。当然也有只用一个的情况,比如,从磁盘读取文件到内存,就只有Input操作,反过来,把数据写到磁盘文件里,就只是一个Output操作。

12 |

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。对于浏览网页来说,浏览器和新浪服务器之间至少需要建立两根水管,才可以既能发数据,又能收数据。

13 |

由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,怎么办呢?有两种办法:

14 |

第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;

15 |

另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。

16 |

同步和异步的区别就在于是否等待IO执行的结果。好比你去麦当劳点餐,你说“来个汉堡”,服务员告诉你,对不起,汉堡要现做,需要等5分钟,于是你站在收银台前面等了5分钟,拿到汉堡再去逛商场,这是同步IO。

17 |

你说“来个汉堡”,服务员告诉你,汉堡需要等5分钟,你可以先去逛商场,等做好了,我们再通知你,这样你可以立刻去干别的事情(逛商场),这是异步IO。

18 |

很明显,使用异步IO来编写程序性能会远远高于同步IO,但是异步IO的缺点是编程模型复杂。想想看,你得知道什么时候通知你“汉堡做好了”,而通知你的方法也各不相同。如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步IO的复杂度远远高于同步IO。

19 |

操作IO的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也不例外。我们后面会详细讨论Python的IO编程接口。

20 |

注意,本章的IO编程都是同步模式,异步IO由于复杂度太高,后续涉及到服务器端程序开发时我们再讨论。

21 |
22 | 23 |
24 | 25 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/49文件读写.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

文件读写

8 |
1714次阅读
9 |
10 |

读写文件是最常见的IO操作。Python内置了读写文件的函数,用法和C是兼容的。

11 |

读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

12 |

读文件

13 |

要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符:

14 |
>>> f = open('/Users/michael/test.txt', 'r')
15 | 

标示符'r'表示读,这样,我们就成功地打开了一个文件。

16 |

如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在:

17 |
>>> f=open('/Users/michael/notfound.txt', 'r')
18 | Traceback (most recent call last):
19 |   File "<stdin>", line 1, in <module>
20 | IOError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt'
21 | 

如果文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示:

22 |
>>> f.read()
23 | 'Hello, world!'
24 | 

最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:

25 |
>>> f.close()
26 | 

由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现:

27 |
try:
28 |     f = open('/path/to/file', 'r')
29 |     print f.read()
30 | finally:
31 |     if f:
32 |         f.close()
33 | 

但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:

34 |
with open('/path/to/file', 'r') as f:
35 |     print f.read()
36 | 

这和前面的try ... finally是一样的,但是代码更佳简洁,并且不必调用f.close()方法。

37 |

调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。

38 |

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:

39 |
for line in f.readlines():
40 |     print(line.strip()) # 把末尾的'\n'删掉
41 | 

file-like Object

42 |

open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。

43 |

StringIO就是在内存中创建的file-like Object,常用作临时缓冲。

44 |

二进制文件

45 |

前面讲的默认都是读取文本文件,并且是ASCII编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件即可:

46 |
>>> f = open('/Users/michael/test.jpg', 'rb')
47 | >>> f.read()
48 | '\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节
49 | 

字符编码

50 |

要读取非ASCII编码的文本文件,就必须以二进制模式打开,再解码。比如GBK编码的文件:

51 |
>>> f = open('/Users/michael/gbk.txt', 'rb')
52 | >>> u = f.read().decode('gbk')
53 | >>> u
54 | u'\u6d4b\u8bd5'
55 | >>> print u
56 | 测试
57 | 

如果每次都这么手动转换编码嫌麻烦(写程序怕麻烦是好事,不怕麻烦就会写出又长又难懂又没法维护的代码),Python还提供了一个codecs模块帮我们在读文件时自动转换编码,直接读出unicode:

58 |
import codecs
59 | with codecs.open('/Users/michael/gbk.txt', 'r', 'gbk') as f:
60 |     f.read() # u'\u6d4b\u8bd5'
61 | 

写文件

62 |

写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:

63 |
>>> f = open('/Users/michael/test.txt', 'w')
64 | >>> f.write('Hello, world!')
65 | >>> f.close()
66 | 

你可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:

67 |
with open('/Users/michael/test.txt', 'w') as f:
68 |     f.write('Hello, world!')
69 | 

要写入特定编码的文本文件,请效仿codecs的示例,写入unicode,由codecs自动转换成指定编码。

70 |

小结

71 |

在Python中,文件读写是通过open()函数打开的文件对象完成的。使用with语句操作文件IO是个好习惯。

72 |
73 | 74 |
75 | 76 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/4第一个Python程序.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

第一个Python程序

8 |
6402次阅读
9 |
10 |

现在,了解了如何启动和退出Python的交互式环境,我们就可以正式开始编写Python代码了。

11 |

在写代码之前,请千万不要用“复制”-“粘贴”把代码从页面粘贴到你自己的电脑上。写程序也讲究一个感觉,你需要一个字母一个字母地把代码自己敲进去,在敲代码的过程中,初学者经常会敲错代码,所以,你需要仔细地检查、对照,才能以最快的速度掌握如何写程序。

12 |

在交互式环境的提示符>>>下,直接输入代码,按回车,就可以立刻得到代码执行结果。现在,试试输入100+200,看看计算结果是不是300:

13 |
>>> 100+200
14 | 300
15 | 

很简单吧,任何有效的数学计算都可以算出来。

16 |

如果要让Python打印出指定的文字,可以用print语句,然后把希望打印的文字用单引号或者双引号括起来,但不能混用单引号和双引号:

17 |
>>> print 'hello, world'
18 | hello, world
19 | 

这种用单引号或者双引号括起来的文本在程序中叫字符串,今后我们还会经常遇到。

20 |

最后,用exit()退出Python,我们的第一个Python程序完成!唯一的缺憾是没有保存下来,下次运行时还要再输入一遍代码。

21 |

22 | 23 |

小结

24 |

在Python交互式命令行下,可以直接输入代码,然后执行,并立刻得到结果。

25 |

simpson-coding

26 |
27 | 28 |
29 | 30 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/50操作文件和目录.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

操作文件和目录

8 |
1111次阅读
9 |
10 |

如果我们要操作文件、目录,可以在命令行下面输入操作系统提供的各种命令来完成。比如dircp等命令。

11 |

如果要在Python程序中执行这些目录和文件的操作怎么办?其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数,Python内置的os模块也可以直接调用操作系统提供的接口函数。

12 |

打开Python交互式命令行,我们来看看如何使用os模块的基本功能:

13 |
>>> import os
14 | >>> os.name # 操作系统名字
15 | 'posix'
16 | 

如果是posix,说明系统是LinuxUnixMac OS X,如果是nt,就是Windows系统。

17 |

要获取详细的系统信息,可以调用uname()函数:

18 |
>>> os.uname()
19 | ('Darwin', 'iMac.local', '13.3.0', 'Darwin Kernel Version 13.3.0: Tue Jun  3 21:27:35 PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64', 'x86_64')
20 | 

环境变量

21 |

在操作系统中定义的环境变量,全部保存在os.environ这个dict中,可以直接查看:

22 |
>>> os.environ
23 | {'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...}
24 | 

要获取某个环境变量的值,可以调用os.getenv()函数:

25 |
>>> os.getenv('PATH')
26 | '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
27 | 

操作文件和目录

28 |

操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中,这一点要注意一下。查看、创建和删除目录可以这么调用:

29 |
# 查看当前目录的绝对路径:
30 | >>> os.path.abspath('.')
31 | '/Users/michael'
32 | # 在某个目录下创建一个新目录,
33 | # 首先把新目录的完整路径表示出来:
34 | >>> os.path.join('/Users/michael', 'testdir')
35 | '/Users/michael/testdir'
36 | # 然后创建一个目录:
37 | >>> os.mkdir('/Users/michael/testdir')
38 | # 删掉一个目录:
39 | >>> os.rmdir('/Users/michael/testdir')
40 | 

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下,os.path.join()返回这样的字符串:

41 |
part-1/part-2
42 | 

而Windows下会返回这样的字符串:

43 |
part-1\part-2
44 | 

同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:

45 |
>>> os.path.split('/Users/michael/testdir/file.txt')
46 | ('/Users/michael/testdir', 'file.txt')
47 | 

os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便:

48 |
>>> os.path.splitext('/path/to/file.txt')
49 | ('/path/to/file', '.txt')
50 | 

这些合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。

51 |

文件操作使用下面的函数。假定当前目录下有一个test.txt文件:

52 |
# 对文件重命名:
53 | >>> os.rename('test.txt', 'test.py')
54 | # 删掉文件:
55 | >>> os.remove('test.py')
56 | 

但是复制文件的函数居然在os模块中不存在!原因是复制文件并非由操作系统提供的系统调用。理论上讲,我们通过上一节的读写文件可以完成文件复制,只不过要多写很多代码。

57 |

幸运的是shutil模块提供了copyfile()的函数,你还可以在shutil模块中找到很多实用函数,它们可以看做是os模块的补充。

58 |

最后看看如何利用Python的特性来过滤文件。比如我们要列出当前目录下的所有目录,只需要一行代码:

59 |
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
60 | ['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Adlm', 'Applications', 'Desktop', ...]
61 | 

要列出所有的.py文件,也只需一行代码:

62 |
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
63 | ['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
64 | 

是不是非常简洁?

65 |

小结

66 |

Python的os模块封装了操作系统的目录和文件操作,要注意这些函数有的在os模块中,有的在os.path模块中。

67 |

练习:编写一个search(s)的函数,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出完整路径:

68 |
$ python search.py test
69 | unit_test.log
70 | py/test.py
71 | py/test_os.py
72 | my/logs/unit-test-result.txt
73 | 
74 | 75 |
76 | 77 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/52进程和线程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

进程和线程

8 |
1069次阅读
9 |
10 |

很多同学都听说过,现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。

11 |

什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

12 |

现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?

13 |

答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

14 |

真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

15 |

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

16 |

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

17 |

由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

18 |

我们前面编写的所有的Python程序,都是执行单任务的进程,也就是只有一个线程。如果我们要同时执行多个任务怎么办?

19 |

有两种解决方案:

20 |

一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。

21 |

还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。

22 |

当然还有第三种方法,就是启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。

23 |

总结一下就是,多任务的实现有3种方式:

24 |
    25 |
  • 多进程模式;
  • 26 |
  • 多线程模式;
  • 27 |
  • 多进程+多线程模式。
  • 28 |
29 |

同时执行多个任务通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务1必须暂停等待任务2完成后才能继续执行,有时,任务3和任务4又不能同时执行,所以,多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。

30 |

因为复杂度高,调试困难,所以,不是迫不得已,我们也不想编写多任务。但是,有很多时候,没有多任务还真不行。想想在电脑上看电影,就必须由一个线程播放视频,另一个线程播放音频,否则,单线程实现的话就只能先把视频播放完再播放音频,或者先把音频播放完再播放视频,这显然是不行的。

31 |

Python既支持多进程,又支持多线程,我们会讨论如何编写这两种多任务程序。

32 |

小结

33 |

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

34 |

多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂。

35 |
36 | 37 |
38 | 39 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/55ThreadLocal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

ThreadLocal

8 |
754次阅读
9 |
10 |

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。

11 |

但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦:

12 |
def process_student(name):
13 |     std = Student(name)
14 |     # std是局部变量,但是每个函数都要用它,因此必须传进去:
15 |     do_task_1(std)
16 |     do_task_2(std)
17 | 
18 | def do_task_1(std):
19 |     do_subtask_1(std)
20 |     do_subtask_2(std)
21 | 
22 | def do_task_2(std):
23 |     do_subtask_2(std)
24 |     do_subtask_2(std)
25 | 

每个函数一层一层调用都这么传参数那还得了?用全局变量?也不行,因为每个线程处理不同的Student对象,不能共享。

26 |

如果用一个全局dict存放所有的Student对象,然后以thread自身作为key获得线程对应的Student对象如何?

27 |
global_dict = {}
28 | 
29 | def std_thread(name):
30 |     std = Student(name)
31 |     # 把std放到全局变量global_dict中:
32 |     global_dict[threading.current_thread()] = std
33 |     do_task_1()
34 |     do_task_2()
35 | 
36 | def do_task_1():
37 |     # 不传入std,而是根据当前线程查找:
38 |     std = global_dict[threading.current_thread()]
39 |     ...
40 | 
41 | def do_task_2():
42 |     # 任何函数都可以查找出当前线程的std变量:
43 |     std = global_dict[threading.current_thread()]
44 |     ...
45 | 

这种方式理论上是可行的,它最大的优点是消除了std对象在每层函数中的传递问题,但是,每个函数获取std的代码有点丑。

46 |

有没有更简单的方式?

47 |

ThreadLocal应运而生,不用查找dictThreadLocal帮你自动做这件事:

48 |
import threading
49 | 
50 | # 创建全局ThreadLocal对象:
51 | local_school = threading.local()
52 | 
53 | def process_student():
54 |     print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name)
55 | 
56 | def process_thread(name):
57 |     # 绑定ThreadLocal的student:
58 |     local_school.student = name
59 |     process_student()
60 | 
61 | t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
62 | t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
63 | t1.start()
64 | t2.start()
65 | t1.join()
66 | t2.join()
67 | 

执行结果:

68 |
Hello, Alice (in Thread-A)
69 | Hello, Bob (in Thread-B)
70 | 

全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。

71 |

可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。

72 |

ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

73 |
74 | 75 |
76 | 77 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/56进程 vs. 线程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

进程 vs. 线程

8 |
722次阅读
9 |
10 |

我们介绍了多进程和多线程,这是实现多任务最常用的两种方式。现在,我们来讨论一下这两种方式的优缺点。

11 |

首先,要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。

12 |

如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。

13 |

如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。

14 |

多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。

15 |

多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。

16 |

多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程。

17 |

在Windows下,多线程的效率比多进程要高,所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题,IIS的稳定性就不如Apache。为了缓解这个问题,IIS和Apache现在又有多进程+多线程的混合模式,真是把问题越搞越复杂。

18 |

线程切换

19 |

无论是多进程还是多线程,只要数量一多,效率肯定上不去,为什么呢?

20 |

我们打个比方,假设你不幸正在准备中考,每天晚上需要做语文、数学、英语、物理、化学这5科的作业,每项作业耗时1小时。

21 |

如果你先花1小时做语文作业,做完了,再花1小时做数学作业,这样,依次全部做完,一共花5小时,这种方式称为单任务模型,或者批处理任务模型。

22 |

假设你打算切换到多任务模型,可以先做1分钟语文,再切换到数学作业,做1分钟,再切换到英语,以此类推,只要切换速度足够快,这种方式就和单核CPU执行多任务是一样的了,以幼儿园小朋友的眼光来看,你就正在同时写5科作业。

23 |

但是,切换作业是有代价的,比如从语文切到数学,要先收拾桌子上的语文书本、钢笔(这叫保存现场),然后,打开数学课本、找出圆规直尺(这叫准备新环境),才能开始做数学作业。操作系统在切换进程或者线程时也是一样的,它需要先保存当前执行的现场环境(CPU寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,但是也需要耗费时间。如果有几千个任务同时进行,操作系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。

24 |

所以,多任务一旦多到一个限度,就会消耗掉系统所有的资源,结果效率急剧下降,所有任务都做不好。

25 |

计算密集型 vs. IO密集型

26 |

是否采用多任务的第二个考虑是任务的类型。我们可以把任务分为计算密集型和IO密集型。

27 |

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

28 |

计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

29 |

第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

30 |

IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

31 |

异步IO

32 |

考虑到CPU和IO之间巨大的速度差异,一个任务在执行的过程中大部分时间都在等待IO操作,单进程单线程模型会导致别的任务无法并行执行,因此,我们才需要多进程模型或者多线程模型来支持多任务并发执行。

33 |

现代操作系统对IO操作已经做了巨大的改进,最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。由于系统总的进程数量十分有限,因此操作系统调度非常高效。用异步IO编程模型来实现多任务是一个主要的趋势。

34 |

对应到Python语言,单进程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。我们会在后面讨论如何编写协程。

35 |
36 | 37 |
38 | 39 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/59常用内建模块.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

常用内建模块

8 |
708次阅读
9 |
10 |

Python之所以自称“batteries included”,就是因为内置了许多非常有用的模块,无需额外安装和配置,即可直接使用。

11 |

本章将介绍一些常用的内建模块。

12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/5使用文本编辑器.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用文本编辑器

8 |
6524次阅读
9 |
10 |

在Python的交互式命令行写程序,好处是一下就能得到结果,坏处是没法保存,下次还想运行的时候,还得再敲一遍。

11 |

所以,实际开发的时候,我们总是使用一个文本编辑器来写代码,写完了,保存为一个文件,这样,程序就可以反复运行了。

12 |

现在,我们就把上次的'hello, world'程序用文本编辑器写出来,保存下来。

13 |

所以问题又变成了:用什么文本编辑器?

14 |

推荐两款文本编辑器:

15 |

一个是Sublime Text,免费使用,但是不付费会弹出提示框:

16 |

sublimetext

17 |

一个是Notepad++,免费使用,有中文界面:

18 |

notepad++

19 |

请注意,用哪个都行,但是绝对不能用Word和Windows自带的记事本。Word保存的不是纯文本文件,而记事本会自作聪明地在文件开始的地方加上几个特殊字符(UTF-8 BOM),结果会导致程序运行出现莫名其妙的错误。

20 |

安装好文本编辑器后,输入以下代码:

21 |
print 'hello, world'
22 | 

注意print前面不要有任何空格。然后,选择一个目录,例如C:\Workspace,把文件保存为hello.py,就可以打开命令行窗口,把当前目录切换到hello.py所在目录,就可以运行这个程序了:

23 |
C:\Workspace>python hello.py
24 | hello, world
25 | 

也可以保存为别的名字,比如abc.py,但是必须要以.py结尾,其他的都不行。此外,文件名只能是英文字母、数字和下划线的组合。

26 |

27 | 28 |

如果当前目录下没有hello.py这个文件,运行python hello.py就会报错:

29 |
python hello.py
30 | python: can't open file 'hello.py': [Errno 2] No such file or directory
31 | 

报错的意思就是,无法打开hello.py这个文件,因为文件不存在。这个时候,就要检查一下当前目录下是否有这个文件了。

32 |

请注意区分命令行模式和Python交互模式:

33 |

cmd-vs-py

34 |

看到类似C:\>是在Windows提供的命令行模式,看到>>>是在Python交互式环境下。

35 |

在命令行模式下,可以执行python进入Python交互式环境,也可以执行python hello.py运行一个.py文件,但是在Python交互式环境下,只能输入Python代码执行。

36 |

直接运行py文件

37 |

还有同学问,能不能像.exe文件那样直接运行.py文件呢?在Windows上是不行的,但是,在Mac和Linux上是可以的,方法是在.py文件的第一行加上:

38 |
#!/usr/bin/env python
39 | 

然后,通过命令:

40 |
$ chmod a+x hello.py
41 | 

就可以直接运行hello.py了,比如在Mac下运行:

42 |

run-python-in-shell

43 |

小结

44 |

用文本编辑器写Python程序,然后保存为后缀为.py的文件,就可以用Python直接运行这个程序了。

45 |

用Python开发程序,完全可以一边在文本编辑器里写代码,一边开一个交互式命令窗口,在写代码的过程中,把部分代码粘到命令行去验证,事半功倍!前提是得有个27'的超大显示器!

46 |
47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/61base64.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

base64

8 |
627次阅读
9 |
10 |

Base64是一种用64个字符来表示任意二进制数据的方法。

11 |

用记事本打开exejpgpdf这些文件时,我们都会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符,所以,如果要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法。Base64是一种最常见的二进制编码方法。

12 |

Base64的原理很简单,首先,准备一个包含64个字符的数组:

13 |
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
14 | 

然后,对二进制数据进行处理,每3个字节一组,一共是3x8=24bit,划为4组,每组正好6个bit:

15 |

base64-encode

16 |

这样我们得到4个数字作为索引,然后查表,获得相应的4个字符,就是编码后的字符串。

17 |

所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。

18 |

如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。

19 |

Python内置的base64可以直接进行base64的编解码:

20 |
>>> import base64
21 | >>> base64.b64encode('binary\x00string')
22 | 'YmluYXJ5AHN0cmluZw=='
23 | >>> base64.b64decode('YmluYXJ5AHN0cmluZw==')
24 | 'binary\x00string'
25 | 

由于标准的Base64编码后可能出现字符+/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+/分别变成-_

26 |
>>> base64.b64encode('i\xb7\x1d\xfb\xef\xff')
27 | 'abcd++//'
28 | >>> base64.urlsafe_b64encode('i\xb7\x1d\xfb\xef\xff')
29 | 'abcd--__'
30 | >>> base64.urlsafe_b64decode('abcd--__')
31 | 'i\xb7\x1d\xfb\xef\xff'
32 | 

还可以自己定义64个字符的排列顺序,这样就可以自定义Base64编码,不过,通常情况下完全没有必要。

33 |

Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行。

34 |

Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容等。

35 |

由于=字符也可能出现在Base64编码中,但=用在URL、Cookie里面会造成歧义,所以,很多Base64编码后会把=去掉:

36 |
# 标准Base64:
37 | 'abcd' -> 'YWJjZA=='
38 | # 自动去掉=:
39 | 'abcd' -> 'YWJjZA'
40 | 

去掉=后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以正常解码了。

41 |

请写一个能处理去掉=的base64解码函数:

42 |
>>> base64.b64decode('YWJjZA==')
43 | 'abcd'
44 | >>> base64.b64decode('YWJjZA')
45 | Traceback (most recent call last):
46 |   ...
47 | TypeError: Incorrect padding
48 | >>> safe_b64decode('YWJjZA')
49 | 'abcd'
50 | 

小结

51 |

Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。

52 |
53 | 54 |
55 | 56 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/62struct.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

struct

8 |
630次阅读
9 |
10 |

准确地讲,Python没有专门处理字节的数据类型。但由于str既是字符串,又可以表示字节,所以,字节数组=str。而在C语言中,我们可以很方便地用struct、union来处理字节,以及字节和int,float的转换。

11 |

在Python中,比方说要把一个32位无符号整数变成字节,也就是4个长度的str,你得配合位运算符这么写:

12 |
>>> n = 10240099
13 | >>> b1 = chr((n & 0xff000000) >> 24)
14 | >>> b2 = chr((n & 0xff0000) >> 16)
15 | >>> b3 = chr((n & 0xff00) >> 8)
16 | >>> b4 = chr(n & 0xff)
17 | >>> s = b1 + b2 + b3 + b4
18 | >>> s
19 | '\x00\x9c@c'
20 | 

非常麻烦。如果换成浮点数就无能为力了。

21 |

好在Python提供了一个struct模块来解决str和其他二进制数据类型的转换。

22 |

structpack函数把任意数据类型变成字符串:

23 |
>>> import struct
24 | >>> struct.pack('>I', 10240099)
25 | '\x00\x9c@c'
26 | 

pack的第一个参数是处理指令,'>I'的意思是:

27 |

>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。

28 |

后面的参数个数要和处理指令一致。

29 |

unpackstr变成相应的数据类型:

30 |
>>> struct.unpack('>IH', '\xf0\xf0\xf0\xf0\x80\x80')
31 | (4042322160, 32896)
32 | 

根据>IH的说明,后面的str依次变为I:4字节无符号整数和H:2字节无符号整数。

33 |

所以,尽管Python不适合编写底层操作字节流的代码,但在对性能要求不高的地方,利用struct就方便多了。

34 |

struct模块定义的数据类型可以参考Python官方文档:

35 |

https://docs.python.org/2/library/struct.html#format-characters

36 |

Windows的位图文件(.bmp)是一种非常简单的文件格式,我们来用struct分析一下。

37 |

首先找一个bmp文件,没有的话用“画图”画一个。

38 |

读入前30个字节来分析:

39 |
>>> s = '\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'
40 | 

BMP格式采用小端方式存储数据,文件头的结构按顺序如下:

41 |

两个字节:'BM'表示Windows位图,'BA'表示OS/2位图; 42 | 一个4字节整数:表示位图大小; 43 | 一个4字节整数:保留位,始终为0; 44 | 一个4字节整数:实际图像的偏移量; 45 | 一个4字节整数:Header的字节数; 46 | 一个4字节整数:图像宽度; 47 | 一个4字节整数:图像高度; 48 | 一个2字节整数:始终为1; 49 | 一个2字节整数:颜色数。

50 |

所以,组合起来用unpack读取:

51 |
>>> struct.unpack('<ccIIIIIIHH', s)
52 | ('B', 'M', 691256, 0, 54, 40, 640, 360, 1, 24)
53 | 

结果显示,'B''M'说明是Windows位图,位图大小为640x360,颜色数为24。

54 |

请编写一个bmpinfo.py,可以检查任意文件是否是位图文件,如果是,打印出图片大小和颜色数。

55 |
56 | 57 |
58 | 59 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/63hashlib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

hashlib

8 |
520次阅读
9 |
10 |

摘要算法简介

11 |

Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。

12 |

什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。

13 |

举个例子,你写了一篇文章,内容是一个字符串'how to use python hashlib - by Michael',并附上这篇文章的摘要是'2d73d4f15c0db7f5ecb321b6a65e5d6d'。如果有人篡改了你的文章,并发表为'how to use python hashlib - by Bob',你可以一下子指出Bob篡改了你的文章,因为根据'how to use python hashlib - by Bob'计算出的摘要不同于原始文章的摘要。

14 |

可见,摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。

15 |

摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

16 |

我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:

17 |
import hashlib
18 | 
19 | md5 = hashlib.md5()
20 | md5.update('how to use md5 in python hashlib?')
21 | print md5.hexdigest()
22 | 

计算结果如下:

23 |
d26a53750bc40b38b65a520292f69306
24 | 

如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:

25 |
md5 = hashlib.md5()
26 | md5.update('how to use md5 in ')
27 | md5.update('python hashlib?')
28 | print md5.hexdigest()
29 | 

试试改动一个字母,看看计算的结果是否完全不同。

30 |

MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。

31 |

另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:

32 |
import hashlib
33 | 
34 | sha1 = hashlib.sha1()
35 | sha1.update('how to use sha1 in ')
36 | sha1.update('python hashlib?')
37 | print sha1.hexdigest()
38 | 

SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。

39 |

比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,而且摘要长度更长。

40 |

有没有可能两个不同的数据通过某个摘要算法得到了相同的摘要?完全有可能,因为任何摘要算法都是把无限多的数据集合映射到一个有限的集合中。这种情况称为碰撞,比如Bob试图根据你的摘要反推出一篇文章'how to learn hashlib in python - by Bob',并且这篇文章的摘要恰好和你的文章完全一致,这种情况也并非不可能出现,但是非常非常困难。

41 |

摘要算法应用

42 |

摘要算法能应用到什么地方?举个常用例子:

43 |

任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:

44 |
name    | password
45 | --------+----------
46 | michael | 123456
47 | bob     | abc999
48 | alice   | alice2008
49 | 

如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。

50 |

正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:

51 |
username | password
52 | ---------+---------------------------------
53 | michael  | e10adc3949ba59abbe56e057f20f883e
54 | bob      | 878ef96e86145580c38c87f0410ad153
55 | alice    | 99b1c2188db85afee403b1536010c2c9
56 | 

当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。

57 |

练习:根据用户输入的口令,计算出存储在数据库中的MD5口令:

58 |
def calc_md5(password):
59 |     pass
60 | 

存储MD5的好处是即使运维人员能访问数据库,也无法获知用户的明文口令。

61 |

练习:设计一个验证用户登录的函数,根据用户输入的口令是否正确,返回True或False:

62 |
db = {
63 |     'michael': 'e10adc3949ba59abbe56e057f20f883e',
64 |     'bob': '878ef96e86145580c38c87f0410ad153',
65 |     'alice': '99b1c2188db85afee403b1536010c2c9'
66 | }
67 | 
68 | def login(user, password):
69 |     pass
70 | 

采用MD5存储口令是否就一定安全呢?也不一定。假设你是一个黑客,已经拿到了存储MD5口令的数据库,如何通过MD5反推用户的明文口令呢?暴力破解费事费力,真正的黑客不会这么干。

71 |

考虑这么个情况,很多用户喜欢用123456888888password这些简单的口令,于是,黑客可以事先计算出这些常用口令的MD5值,得到一个反推表:

72 |
'e10adc3949ba59abbe56e057f20f883e': '123456'
73 | '21218cca77804d2ba1922c33e0151105': '888888'
74 | '5f4dcc3b5aa765d61d8327deb882cf99': 'password'
75 | 

这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。

76 |

对于用户来讲,当然不要使用过于简单的口令。但是,我们能否在程序设计上对简单口令加强保护呢?

77 |

由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:

78 |
def calc_md5(password):
79 |     return get_md5(password + 'the-Salt')
80 | 

经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。

81 |

但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的MD5呢?

82 |

如果假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。

83 |

练习:根据用户输入的登录名和口令模拟用户注册,计算更安全的MD5:

84 |
db = {}
85 | 
86 | def register(username, password):
87 |     db[username] = get_md5(password + username + 'the-Salt')
88 | 

然后,根据修改后的MD5算法实现用户登录的验证:

89 |
def login(username, password):
90 |     pass
91 | 

小结

92 |

摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

93 |
94 | 95 |
96 | 97 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/64XML.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

XML

8 |
319次阅读
9 |
10 |

XML虽然比JSON复杂,在Web中应用也不如以前多了,不过仍有很多地方在用,所以,有必要了解如何操作XML。

11 |

DOM vs SAX

12 |

操作XML有两种方法:DOM和SAX。DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。SAX是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件。

13 |

正常情况下,优先考虑SAX,因为DOM实在太占内存。

14 |

在Python中使用SAX解析XML非常简洁,通常我们关心的事件是start_elementend_elementchar_data,准备好这3个函数,然后就可以解析xml了。

15 |

举个例子,当SAX解析器读到一个节点时:

16 |
<a href="/">python</a>
17 | 

会产生3个事件:

18 |
    19 |
  1. start_element事件,在读取<a href="/">时;

    20 |
  2. 21 |
  3. char_data事件,在读取python时;

    22 |
  4. 23 |
  5. end_element事件,在读取</a>时。

    24 |
  6. 25 |
26 |

用代码实验一下:

27 |
from xml.parsers.expat import ParserCreate
28 | 
29 | class DefaultSaxHandler(object):
30 |     def start_element(self, name, attrs):
31 |         print('sax:start_element: %s, attrs: %s' % (name, str(attrs)))
32 | 
33 |     def end_element(self, name):
34 |         print('sax:end_element: %s' % name)
35 | 
36 |     def char_data(self, text):
37 |         print('sax:char_data: %s' % text)
38 | 
39 | xml = r'''<?xml version="1.0"?>
40 | <ol>
41 |     <li><a href="/python">Python</a></li>
42 |     <li><a href="/ruby">Ruby</a></li>
43 | </ol>
44 | '''
45 | handler = DefaultSaxHandler()
46 | parser = ParserCreate()
47 | parser.returns_unicode = True
48 | parser.StartElementHandler = handler.start_element
49 | parser.EndElementHandler = handler.end_element
50 | parser.CharacterDataHandler = handler.char_data
51 | parser.Parse(xml)
52 | 

当设置returns_unicode为True时,返回的所有element名称和char_data都是unicode,处理国际化更方便。

53 |

需要注意的是读取一大段字符串时,CharacterDataHandler可能被多次调用,所以需要自己保存起来,在EndElementHandler里面再合并。

54 |

除了解析XML外,如何生成XML呢?99%的情况下需要生成的XML结构都是非常简单的,因此,最简单也是最有效的生成XML的方法是拼接字符串:

55 |
L = []
56 | L.append(r'<?xml version="1.0"?>')
57 | L.append(r'<root>')
58 | L.append(encode('some & data'))
59 | L.append(r'</root>')
60 | return ''.join(L)
61 | 

如果要生成复杂的XML呢?建议你不要用XML,改成JSON。

62 |

小结

63 |

解析XML时,注意找出自己感兴趣的节点,响应事件时,把节点数据保存起来。解析完毕后,就可以处理数据。

64 |

练习一下解析Yahoo的XML格式的天气预报,获取当天和最近几天的天气:

65 |
http://weather.yahooapis.com/forecastrss?u=c&w=2151330
66 | 

参数w是城市代码,要查询某个城市代码,可以在weather.yahoo.com搜索城市,浏览器地址栏的URL就包含城市代码。

67 |
68 | 69 |
70 | 71 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/65HTMLParser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

HTMLParser

8 |
337次阅读
9 |
10 |

如果我们要编写一个搜索引擎,第一步是用爬虫把目标网站的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻、图片还是视频。

11 |

假设第一步已经完成了,第二步应该如何解析HTML呢?

12 |

HTML本质上是XML的子集,但是HTML的语法没有XML那么严格,所以不能用标准的DOM或SAX来解析HTML。

13 |

好在Python提供了HTMLParser来非常方便地解析HTML,只需简单几行代码:

14 |
from HTMLParser import HTMLParser
15 | from htmlentitydefs import name2codepoint
16 | 
17 | class MyHTMLParser(HTMLParser):
18 | 
19 |     def handle_starttag(self, tag, attrs):
20 |         print('<%s>' % tag)
21 | 
22 |     def handle_endtag(self, tag):
23 |         print('</%s>' % tag)
24 | 
25 |     def handle_startendtag(self, tag, attrs):
26 |         print('<%s/>' % tag)
27 | 
28 |     def handle_data(self, data):
29 |         print('data')
30 | 
31 |     def handle_comment(self, data):
32 |         print('<!-- -->')
33 | 
34 |     def handle_entityref(self, name):
35 |         print('&%s;' % name)
36 | 
37 |     def handle_charref(self, name):
38 |         print('&#%s;' % name)
39 | 
40 | parser = MyHTMLParser()
41 | parser.feed('<html><head></head><body><p>Some <a href=\"#\">html</a> tutorial...<br>END</p></body></html>')
42 | 

feed()方法可以多次调用,也就是不一定一次把整个HTML字符串都塞进去,可以一部分一部分塞进去。

43 |

特殊字符有两种,一种是英文表示的&nbsp;,一种是数字表示的&#1234;,这两种字符都可以通过Parser解析出来。

44 |

小结

45 |

找一个网页,例如https://www.python.org/events/python-events/,用浏览器查看源码并复制,然后尝试解析一下HTML,输出Python官网发布的会议时间、名称和地点。

46 |
47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/66常用第三方模块.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

常用第三方模块

8 |
768次阅读
9 |
10 |

除了内建的模块外,Python还有大量的第三方模块。

11 |

基本上,所有的第三方模块都会在PyPI - the Python Package Index上注册,只要找到对应的模块名字,即可用easy_install或者pip安装。

12 |

本章介绍常用的第三方模块。

13 |
14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/67PIL.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

PIL

8 |
239次阅读
9 |
10 |

PIL:Python Imaging Library,已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。

11 |

安装PIL

12 |

在Debian/Ubuntu Linux下直接通过apt安装:

13 |
$ sudo apt-get install python-imaging
14 | 

Mac和其他版本的Linux可以直接使用easy_install或pip安装,安装前需要把编译环境装好:

15 |
$ sudo easy_install PIL
16 | 

如果安装失败,根据提示先把缺失的包(比如openjpeg)装上。

17 |

Windows平台就去PIL官方网站下载exe安装包。

18 |

操作图像

19 |

来看看最常见的图像缩放操作,只需三四行代码:

20 |
import Image
21 | 
22 | # 打开一个jpg图像文件,注意路径要改成你自己的:
23 | im = Image.open('/Users/michael/test.jpg')
24 | # 获得图像尺寸:
25 | w, h = im.size
26 | # 缩放到50%:
27 | im.thumbnail((w//2, h//2))
28 | # 把缩放后的图像用jpeg格式保存:
29 | im.save('/Users/michael/thumbnail.jpg', 'jpeg')
30 | 

其他功能如切片、旋转、滤镜、输出文字、调色板等一应俱全。

31 |

比如,模糊效果也只需几行代码:

32 |
import Image, ImageFilter
33 | 
34 | im = Image.open('/Users/michael/test.jpg')
35 | im2 = im.filter(ImageFilter.BLUR)
36 | im2.save('/Users/michael/blur.jpg', 'jpeg')
37 | 

效果如下:

38 |

PIL-blur

39 |

PIL的ImageDraw提供了一系列绘图方法,让我们可以直接绘图。比如要生成字母验证码图片:

40 |
import Image, ImageDraw, ImageFont, ImageFilter
41 | import random
42 | 
43 | # 随机字母:
44 | def rndChar():
45 |     return chr(random.randint(65, 90))
46 | 
47 | # 随机颜色1:
48 | def rndColor():
49 |     return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))
50 | 
51 | # 随机颜色2:
52 | def rndColor2():
53 |     return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
54 | 
55 | # 240 x 60:
56 | width = 60 * 4
57 | height = 60
58 | image = Image.new('RGB', (width, height), (255, 255, 255))
59 | # 创建Font对象:
60 | font = ImageFont.truetype('Arial.ttf', 36)
61 | # 创建Draw对象:
62 | draw = ImageDraw.Draw(image)
63 | # 填充每个像素:
64 | for x in range(width):
65 |     for y in range(height):
66 |         draw.point((x, y), fill=rndColor())
67 | # 输出文字:
68 | for t in range(4):
69 |     draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2())
70 | # 模糊:
71 | image = image.filter(ImageFilter.BLUR)
72 | image.save('code.jpg', 'jpeg');
73 | 

我们用随机颜色填充背景,再画上文字,最后对图像进行模糊,得到验证码图片如下:

74 |

验证码

75 |

如果运行的时候报错:

76 |
IOError: cannot open resource
77 | 

这是因为PIL无法定位到字体文件的位置,可以根据操作系统提供绝对路径,比如:

78 |
'/Library/Fonts/Arial.ttf'
79 | 

要详细了解PIL的强大功能,请请参考PIL官方文档:

80 |

http://effbot.org/imagingbook/

81 |
82 | 83 |
84 | 85 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/68图形界面.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

图形界面

8 |
1595次阅读
9 |
10 |

Python支持多种图形界面的第三方库,包括:

11 |
    12 |
  • Tk

    13 |
  • 14 |
  • wxWidgets

    15 |
  • 16 |
  • Qt

    17 |
  • 18 |
  • GTK

    19 |
  • 20 |
21 |

等等。

22 |

但是Python自带的库是支持Tk的Tkinter,使用Tkinter,无需安装任何包,就可以直接使用。本章简单介绍如何使用Tkinter进行GUI编程。

23 |

Tkinter

24 |

我们来梳理一下概念:

25 |

我们编写的Python代码会调用内置的Tkinter,Tkinter封装了访问Tk的接口;

26 |

Tk是一个图形库,支持多个操作系统,使用Tcl语言开发;

27 |

Tk会调用操作系统提供的本地GUI接口,完成最终的GUI。

28 |

所以,我们的代码只需要调用Tkinter提供的接口就可以了。

29 |

第一个GUI程序

30 |

使用Tkinter十分简单,我们来编写一个GUI版本的“Hello, world!”。

31 |

第一步是导入Tkinter包的所有内容:

32 |
from Tkinter import *
33 | 

第二步是从Frame派生一个Application类,这是所有Widget的父容器:

34 |
class Application(Frame):
35 |     def __init__(self, master=None):
36 |         Frame.__init__(self, master)
37 |         self.pack()
38 |         self.createWidgets()
39 | 
40 |     def createWidgets(self):
41 |         self.helloLabel = Label(self, text='Hello, world!')
42 |         self.helloLabel.pack()
43 |         self.quitButton = Button(self, text='Quit', command=self.quit)
44 |         self.quitButton.pack()
45 | 

在GUI中,每个Button、Label、输入框等,都是一个Widget。Frame则是可以容纳其他Widget的Widget,所有的Widget组合起来就是一棵树。

46 |

pack()方法把Widget加入到父容器中,并实现布局。pack()是最简单的布局,grid()可以实现更复杂的布局。

47 |

createWidgets()方法中,我们创建一个Label和一个Button,当Button被点击时,触发self.quit()使程序退出。

48 |

第三步,实例化Application,并启动消息循环:

49 |
app = Application()
50 | # 设置窗口标题:
51 | app.master.title('Hello World')
52 | # 主消息循环:
53 | app.mainloop()
54 | 

GUI程序的主线程负责监听来自操作系统的消息,并依次处理每一条消息。因此,如果消息处理非常耗时,就需要在新线程中处理。

55 |

运行这个GUI程序,可以看到下面的窗口:

56 |

tk-hello-world

57 |

点击“Quit”按钮或者窗口的“x”结束程序。

58 |

输入文本

59 |

我们再对这个GUI程序改进一下,加入一个文本框,让用户可以输入文本,然后点按钮后,弹出消息对话框。

60 |
from Tkinter import *
61 | import tkMessageBox
62 | 
63 | class Application(Frame):
64 |     def __init__(self, master=None):
65 |         Frame.__init__(self, master)
66 |         self.pack()
67 |         self.createWidgets()
68 | 
69 |     def createWidgets(self):
70 |         self.nameInput = Entry(self)
71 |         self.nameInput.pack()
72 |         self.alertButton = Button(self, text='Hello', command=self.hello)
73 |         self.alertButton.pack()
74 | 
75 |     def hello(self):
76 |         name = self.nameInput.get() or 'world'
77 |         tkMessageBox.showinfo('Message', 'Hello, %s' % name)
78 | 

当用户点击按钮时,触发hello(),通过self.nameInput.get()获得用户输入的文本后,使用tkMessageBox.showinfo()可以弹出消息对话框。

79 |

程序运行结果如下:

80 |

tk-say-hello

81 |

小结

82 |

Python内置的Tkinter可以满足基本的GUI程序的要求,如果是非常复杂的GUI程序,建议用操作系统原生支持的语言和库来编写。

83 |

源码参考:https://github.com/michaelliao/learn-python/tree/master/gui

84 |
85 | 86 |
87 | 88 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/69网络编程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

网络编程

8 |
784次阅读
9 |
10 |

自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。

11 |

计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。

12 |

举个例子,当你使用浏览器访问新浪网时,你的计算机就和新浪的某台服务器通过互联网连接起来了,然后,新浪的服务器把网页内容作为数据通过互联网传输到你的电脑上。

13 |

由于你的电脑上可能不止浏览器,还有QQ、Skype、Dropbox、邮件客户端等,不同的程序连接的别的计算机也会不同,所以,更确切地说,网络通信是两台计算机上的两个进程之间的通信。比如,浏览器进程和新浪服务器上的某个Web服务进程在通信,而QQ进程是和腾讯的某个服务器上的某个进程在通信。

14 |

网络通信就是两个进程在通信

15 |

网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。

16 |

本章我们将详细介绍Python网络编程的概念和最主要的两种网络类型的编程。

17 |
18 | 19 |
20 | 21 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/6输入和输出.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

输入和输出

8 |
5905次阅读
9 |
10 |

输出

11 |

print加上字符串,就可以向屏幕上输出指定的文字。比如输出'hello, world',用代码实现如下:

12 |
>>> print 'hello, world'
13 | 

print语句也可以跟上多个字符串,用逗号“,”隔开,就可以连成一串输出:

14 |
>>> print 'The quick brown fox', 'jumps over', 'the lazy dog'
15 | The quick brown fox jumps over the lazy dog
16 | 

print会依次打印每个字符串,遇到逗号“,”会输出一个空格,因此,输出的字符串是这样拼起来的:

17 |

print-howto

18 |

print也可以打印整数,或者计算结果:

19 |
>>> print 300
20 | 300
21 | >>> print 100 + 200
22 | 300
23 | 

因此,我们可以把计算100 + 200的结果打印得更漂亮一点:

24 |
>>> print '100 + 200 =', 100 + 200
25 | 100 + 200 = 300
26 | 

注意,对于100 + 200,Python解释器自动计算出结果300,但是,'100 + 200 ='是字符串而非数学公式,Python把它视为字符串,请自行解释上述打印结果。

27 |

输入

28 |

现在,你已经可以用print输出你想要的结果了。但是,如果要让用户从电脑输入一些字符怎么办?Python提供了一个raw_input,可以让用户输入字符串,并存放到一个变量里。比如输入用户的名字:

29 |
>>> name = raw_input()
30 | Michael
31 | 

当你输入name = raw_input()并按下回车后,Python交互式命令行就在等待你的输入了。这时,你可以输入任意字符,然后按回车后完成输入。

32 |

输入完成后,不会有任何提示,Python交互式命令行又回到>>>状态了。那我们刚才输入的内容到哪去了?答案是存放到name变量里了。可以直接输入name查看变量内容:

33 |
>>> name
34 | 'Michael'
35 | 

什么是变量?请回忆初中数学所学的代数基础知识:

36 |

设正方形的边长为a,则正方形的面积为a x a。把边长a看做一个变量,我们就可以根据a的值计算正方形的面积,比如:

37 |

若a=2,则面积为a x a = 2 x 2 = 4;

38 |

若a=3.5,则面积为a x a = 3.5 x 3.5 = 12.25。

39 |

在计算机程序中,变量不仅可以为整数或浮点数,还可以是字符串,因此,name作为一个变量就是一个字符串。

40 |

要打印出name变量的内容,除了直接写name然后按回车外,还可以用print语句:

41 |
>>> print name
42 | Michael
43 | 

有了输入和输出,我们就可以把上次打印'hello, world'的程序改成有点意义的程序了:

44 |
name = raw_input()
45 | print 'hello,', name
46 | 

运行上面的程序,第一行代码会让用户输入任意字符作为自己的名字,然后存入name变量中;第二行代码会根据用户的名字向用户说hello,比如输入Michael

47 |
C:\Workspace> python hello.py
48 | Michael
49 | hello, Michael
50 | 

但是程序运行的时候,没有任何提示信息告诉用户:“嘿,赶紧输入你的名字”,这样显得很不友好。幸好,raw_input可以让你显示一个字符串来提示用户,于是我们把代码改成:

51 |
name = raw_input('please enter your name: ')
52 | print 'hello,', name
53 | 

再次运行这个程序,你会发现,程序一运行,会首先打印出please enter your name:,这样,用户就可以根据提示,输入名字后,得到hello, xxx的输出:

54 |
C:\Workspace> python hello.py
55 | please enter your name: Michael
56 | hello, Michael
57 | 

每次运行该程序,根据用户输入的不同,输出结果也会不同。

58 |

在命令行下,输入和输出就是这么简单。

59 |

小结

60 |

任何计算机程序都是为了执行一个特定的任务,有了输入,用户才能告诉计算机程序所需的信息,有了输出,程序运行后才能告诉用户任务的结果。

61 |

输入是Input,输出是Output,因此,我们把输入输出统称为Input/Output,或者简写为IO。

62 |

raw_inputprint是在命令行下面最基本的输入和输出,但是,用户也可以通过其他更高级的图形界面完成输入和输出,比如,在网页上的一个文本框输入自己的名字,点击“确定”后在网页上看到输出信息。

63 |
64 | 65 |
66 | 67 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/70TCP IP简介.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

TCP/IP简介

8 |
1117次阅读
9 |
10 |

虽然大家现在对互联网很熟悉,但是计算机网络的出现比互联网要早很多。

11 |

计算机为了联网,就必须规定通信协议,早期的计算机网络,都是由各厂商自己规定一套协议,IBM、Apple和Microsoft都有各自的网络协议,互不兼容,这就好比一群人有的说英语,有的说中文,有的说德语,说同一种语言的人可以交流,不同的语言之间就不行了。

12 |

为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议簇(Internet Protocol Suite)就是通用协议标准。Internet是由inter和net两个单词组合起来的,原意就是连接“网络”的网络,有了Internet,任何私有网络,只要支持这个协议,就可以联入互联网。

13 |

因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议。

14 |

通信的时候,双方必须知道对方的标识,好比发邮件必须知道对方的邮件地址。互联网上每个计算机的唯一标识就是IP地址,类似123.123.123.123。如果一台计算机同时接入到两个或更多的网络,比如路由器,它就会有两个或多个IP地址,所以,IP地址对应的实际上是计算机的网络接口,通常是网卡。

15 |

IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。

16 |

internet-computers

17 |

TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。

18 |

许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。

19 |

一个IP包除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。

20 |

端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个IP包来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。

21 |

一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。

22 |

了解了TCP/IP协议的基本概念,IP地址和端口的概念,我们就可以开始进行网络编程了。

23 |
24 | 25 |
26 | 27 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/72UDP编程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

UDP编程

8 |
572次阅读
9 |
10 |

TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

11 |

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

12 |

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

13 |

我们来看看如何通过UDP协议传输数据。和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口:

14 |
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
15 | # 绑定端口:
16 | s.bind(('127.0.0.1', 9999))
17 | 

创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据:

18 |
print 'Bind UDP on 9999...'
19 | while True:
20 |     # 接收数据:
21 |     data, addr = s.recvfrom(1024)
22 |     print 'Received from %s:%s.' % addr
23 |     s.sendto('Hello, %s!' % data, addr)
24 | 

recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。

25 |

注意这里省掉了多线程,因为这个例子很简单。

26 |

客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据:

27 |
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
28 | for data in ['Michael', 'Tracy', 'Sarah']:
29 |     # 发送数据:
30 |     s.sendto(data, ('127.0.0.1', 9999))
31 |     # 接收数据:
32 |     print s.recv(1024)
33 | s.close()
34 | 

从服务器接收数据仍然调用recv()方法。

35 |

仍然用两个命令行分别启动服务器和客户端测试,结果如下:

36 |

client-server

37 |

小结

38 |

UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口可以各自绑定。

39 |

源码参考:https://github.com/michaelliao/learn-python/tree/master/socket

40 |
41 | 42 |
43 | 44 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/73电子邮件.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

电子邮件

8 |
473次阅读
9 |
10 |

Email的历史比Web还要久远,直到现在,Email也是互联网上应用非常广泛的服务。

11 |

几乎所有的编程语言都支持发送和接收电子邮件,但是,先等等,在我们开始编写代码之前,有必要搞清楚电子邮件是如何在互联网上运作的。

12 |

我们来看看传统邮件是如何运作的。假设你现在在北京,要给一个香港的朋友发一封信,怎么做呢?

13 |

首先你得写好信,装进信封,写上地址,贴上邮票,然后就近找个邮局,把信仍进去。

14 |

信件会从就近的小邮局转运到大邮局,再从大邮局往别的城市发,比如先发到天津,再走海运到达香港,也可能走京九线到香港,但是你不用关心具体路线,你只需要知道一件事,就是信件走得很慢,至少要几天时间。

15 |

信件到达香港的某个邮局,也不会直接送到朋友的家里,因为邮局的叔叔是很聪明的,他怕你的朋友不在家,一趟一趟地白跑,所以,信件会投递到你的朋友的邮箱里,邮箱可能在公寓的一层,或者家门口,直到你的朋友回家的时候检查邮箱,发现信件后,就可以取到邮件了。

16 |

电子邮件的流程基本上也是按上面的方式运作的,只不过速度不是按天算,而是按秒算。

17 |

现在我们回到电子邮件,假设我们自己的电子邮件地址是me@163.com,对方的电子邮件地址是friend@sina.com(注意地址都是虚构的哈),现在我们用Outlook或者Foxmail之类的软件写好邮件,填上对方的Email地址,点“发送”,电子邮件就发出去了。这些电子邮件软件被称为MUA:Mail User Agent——邮件用户代理。

18 |

Email从MUA发出去,不是直接到达对方电脑,而是发到MTA:Mail Transfer Agent——邮件传输代理,就是那些Email服务提供商,比如网易、新浪等等。由于我们自己的电子邮件是163.com,所以,Email首先被投递到网易提供的MTA,再由网易的MTA发到对方服务商,也就是新浪的MTA。这个过程中间可能还会经过别的MTA,但是我们不关心具体路线,我们只关心速度。

19 |

Email到达新浪的MTA后,由于对方使用的是@sina.com的邮箱,因此,新浪的MTA会把Email投递到邮件的最终目的地MDA:Mail Delivery Agent——邮件投递代理。Email到达MDA后,就静静地躺在新浪的某个服务器上,存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称之为电子邮箱。

20 |

同普通邮件类似,Email不会直接到达对方的电脑,因为对方电脑不一定开机,开机也不一定联网。对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。

21 |

所以,一封电子邮件的旅程就是:

22 |
发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
23 | 

有了上述基本概念,要编写程序来发送和接收邮件,本质上就是:

24 |
    25 |
  1. 编写MUA把邮件发到MTA;

    26 |
  2. 27 |
  3. 编写MUA从MDA上收邮件。

    28 |
  4. 29 |
30 |

发邮件时,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一个MTA也是用SMTP协议。

31 |

收邮件时,MUA和MDA使用的协议有两种:POP:Post Office Protocol,目前版本是3,俗称POP3;IMAP:Internet Message Access Protocol,目前版本是4,优点是不但能取邮件,还可以直接操作MDA上存储的邮件,比如从收件箱移到垃圾箱,等等。

32 |

邮件客户端软件在发邮件时,会让你先配置SMTP服务器,也就是你要发到哪个MTA上。假设你正在使用163的邮箱,你就不能直接发到新浪的MTA上,因为它只服务新浪的用户,所以,你得填163提供的SMTP服务器地址:smtp.163.com,为了证明你是163的用户,SMTP服务器还要求你填写邮箱地址和邮箱口令,这样,MUA才能正常地把Email通过SMTP协议发送到MTA。

33 |

类似的,从MDA收邮件时,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你收取你的邮件,所以,Outlook之类的邮件客户端会要求你填写POP3或IMAP服务器地址、邮箱地址和口令,这样,MUA才能顺利地通过POP或IMAP协议从MDA取到邮件。

34 |

在使用Python收发邮件前,请先准备好至少两个电子邮件,如xxx@163.comxxx@sina.comxxx@qq.com等,注意两个邮箱不要用同一家邮件服务商。

35 |
36 | 37 |
38 | 39 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/76访问数据库.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

访问数据库

8 |
598次阅读
9 |
10 |

程序运行的时候,数据都是在内存中的。当程序终止的时候,通常都需要将数据保存到磁盘上,无论是保存到本地磁盘,还是通过网络保存到服务器上,最终都会将数据写入磁盘文件。

11 |

而如何定义数据的存储格式就是一个大问题。如果我们自己来定义存储格式,比如保存一个班级所有学生的成绩单:

12 | 13 | 14 | 15 | 16 | 17 | 18 |
名字成绩
Michael99
Bob85
Bart59
Lisa87
19 | 20 |

你可以用一个文本文件保存,一行保存一个学生,用,隔开:

21 |
Michael,99
22 | Bob,85
23 | Bart,59
24 | Lisa,87
25 | 

你还可以用JSON格式保存,也是文本文件:

26 |
[
27 |     {"name":"Michael","score":99},
28 |     {"name":"Bob","score":85},
29 |     {"name":"Bart","score":59},
30 |     {"name":"Lisa","score":87}
31 | ]
32 | 

你还可以定义各种保存格式,但是问题来了:

33 |

存储和读取需要自己实现,JSON还是标准,自己定义的格式就各式各样了;

34 |

不能做快速查询,只有把数据全部读到内存中才能自己遍历,但有时候数据的大小远远超过了内存(比如蓝光电影,40GB的数据),根本无法全部读入内存。

35 |

为了便于程序保存和读取数据,而且,能直接通过条件快速查询到指定的数据,就出现了数据库(Database)这种专门用于集中存储和查询的软件。

36 |

数据库软件诞生的历史非常久远,早在1950年数据库就诞生了。经历了网状数据库,层次数据库,我们现在广泛使用的关系数据库是20世纪70年代基于关系模型的基础上诞生的。

37 |

关系模型有一套复杂的数学理论,但是从概念上是十分容易理解的。举个学校的例子:

38 |

假设某个XX省YY市ZZ县第一实验小学有3个年级,要表示出这3个年级,可以在Excel中用一个表格画出来:

39 |

grade

40 |

每个年级又有若干个班级,要把所有班级表示出来,可以在Excel中再画一个表格:

41 |

class

42 |

这两个表格有个映射关系,就是根据Grade_ID可以在班级表中查找到对应的所有班级:

43 |

grade-classes

44 |

也就是Grade表的每一行对应Class表的多行,在关系数据库中,这种基于表(Table)的一对多的关系就是关系数据库的基础。

45 |

根据某个年级的ID就可以查找所有班级的行,这种查询语句在关系数据库中称为SQL语句,可以写成:

46 |
SELECT * FROM classes WHERE grade_id = '1';
47 | 

结果也是一个表:

48 |
---------+----------+----------
49 | grade_id | class_id | name
50 | ---------+----------+----------
51 | 1        | 11       | 一年级一班
52 | ---------+----------+----------
53 | 1        | 12       | 一年级二班
54 | ---------+----------+----------
55 | 1        | 13       | 一年级三班
56 | ---------+----------+----------
57 | 

类似的,Class表的一行记录又可以关联到Student表的多行记录:

58 |

class-students

59 |

由于本教程不涉及到关系数据库的详细内容,如果你想从零学习关系数据库和基本的SQL语句,推荐Coursera课程:

60 |

英文:https://www.coursera.org/course/db

61 |

中文:http://c.open.163.com/coursera/courseIntro.htm?cid=12

62 |

NoSQL

63 |

你也许还听说过NoSQL数据库,很多NoSQL宣传其速度和规模远远超过关系数据库,所以很多同学觉得有了NoSQL是否就不需要SQL了呢?千万不要被他们忽悠了,连SQL都不明白怎么可能搞明白NoSQL呢?

64 |

数据库类别

65 |

既然我们要使用关系数据库,就必须选择一个关系数据库。目前广泛使用的关系数据库也就这么几种:

66 |

付费的商用数据库:

67 |
    68 |
  • Oracle,典型的高富帅;

    69 |
  • 70 |
  • SQL Server,微软自家产品,Windows定制专款;

    71 |
  • 72 |
  • DB2,IBM的产品,听起来挺高端;

    73 |
  • 74 |
  • Sybase,曾经跟微软是好基友,后来关系破裂,现在家境惨淡。

    75 |
  • 76 |
77 |

这些数据库都是不开源而且付费的,最大的好处是花了钱出了问题可以找厂家解决,不过在Web的世界里,常常需要部署成千上万的数据库服务器,当然不能把大把大把的银子扔给厂家,所以,无论是Google、Facebook,还是国内的BAT,无一例外都选择了免费的开源数据库:

78 |
    79 |
  • MySQL,大家都在用,一般错不了;

    80 |
  • 81 |
  • PostgreSQL,学术气息有点重,其实挺不错,但知名度没有MySQL高;

    82 |
  • 83 |
  • sqlite,嵌入式数据库,适合桌面和移动应用。

    84 |
  • 85 |
86 |

作为Python开发工程师,选择哪个免费数据库呢?当然是MySQL。因为MySQL普及率最高,出了错,可以很容易找到解决方法。而且,围绕MySQL有一大堆监控和运维的工具,安装和使用很方便。

87 |

为了能继续后面的学习,你需要从MySQL官方网站下载并安装MySQL Community Server 5.6,这个版本是免费的,其他高级版本是要收钱的(请放心,收钱的功能我们用不上)。

88 |
89 | 90 |
91 | 92 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/77使用SQLite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用SQLite

8 |
765次阅读
9 |
10 |

SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。

11 |

Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。

12 |

在使用SQLite前,我们先要搞清楚几个概念:

13 |

表是数据库中存放关系数据的集合,一个数据库里面通常都包含多个表,比如学生的表,班级的表,学校的表,等等。表和表之间通过外键关联。

14 |

要操作关系数据库,首先需要连接到数据库,一个数据库连接称为Connection;

15 |

连接到数据库后,需要打开游标,称之为Cursor,通过Cursor执行SQL语句,然后,获得执行结果。

16 |

Python定义了一套操作数据库的API接口,任何数据库要连接到Python,只需要提供符合Python标准的数据库驱动即可。

17 |

由于SQLite的驱动内置在Python标准库中,所以我们可以直接来操作SQLite数据库。

18 |

我们在Python交互式命令行实践一下:

19 |
# 导入SQLite驱动:
20 | >>> import sqlite3
21 | # 连接到SQLite数据库
22 | # 数据库文件是test.db
23 | # 如果文件不存在,会自动在当前目录创建:
24 | >>> conn = sqlite3.connect('test.db')
25 | # 创建一个Cursor:
26 | >>> cursor = conn.cursor()
27 | # 执行一条SQL语句,创建user表:
28 | >>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
29 | <sqlite3.Cursor object at 0x10f8aa260>
30 | # 继续执行一条SQL语句,插入一条记录:
31 | >>> cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')
32 | <sqlite3.Cursor object at 0x10f8aa260>
33 | # 通过rowcount获得插入的行数:
34 | >>> cursor.rowcount
35 | 1
36 | # 关闭Cursor:
37 | >>> cursor.close()
38 | # 提交事务:
39 | >>> conn.commit()
40 | # 关闭Connection:
41 | >>> conn.close()
42 | 

我们再试试查询记录:

43 |
>>> conn = sqlite3.connect('test.db')
44 | >>> cursor = conn.cursor()
45 | # 执行查询语句:
46 | >>> cursor.execute('select * from user where id=?', '1')
47 | <sqlite3.Cursor object at 0x10f8aa340>
48 | # 获得查询结果集:
49 | >>> values = cursor.fetchall()
50 | >>> values
51 | [(u'1', u'Michael')]
52 | >>> cursor.close()
53 | >>> conn.close()
54 | 

使用Python的DB-API时,只要搞清楚Connection和Cursor对象,打开后一定记得关闭,就可以放心地使用。

55 |

使用Cursor对象执行insertupdatedelete语句时,执行结果由rowcount返回影响的行数,就可以拿到执行结果。

56 |

使用Cursor对象执行select语句时,通过featchall()可以拿到结果集。结果集是一个list,每个元素都是一个tuple,对应一行记录。

57 |

如果SQL语句带有参数,那么需要把参数按照位置传递给execute()方法,有几个?占位符就必须对应几个参数,例如:

58 |
cursor.execute('select * from user where id=?', '1')
59 | 

SQLite支持常见的标准SQL语句以及几种常见的数据类型。具体文档请参阅SQLite官方网站。

60 |

小结

61 |

在Python中操作数据库时,要先导入数据库对应的驱动,然后,通过Connection对象和Cursor对象操作数据。

62 |

要确保打开的Connection对象和Cursor对象都正确地被关闭,否则,资源就会泄露。

63 |

如何才能确保出错的情况下也关闭掉Connection对象和Cursor对象呢?请回忆try...catch...finally...的用法。

64 |
65 | 66 |
67 | 68 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/78使用MySQL.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用MySQL

8 |
1079次阅读
9 |
10 |

MySQL是Web世界中使用最广泛的数据库服务器。SQLite的特点是轻量级、可嵌入,但不能承受高并发访问,适合桌面和移动应用。而MySQL是为服务器端设计的数据库,能承受高并发访问,同时占用的内存也远远大于SQLite。

11 |

此外,MySQL内部有多种数据库引擎,最常用的引擎是支持数据库事务的InnoDB。

12 |

安装MySQL

13 |

可以直接从MySQL官方网站下载最新的Community Server 5.6.x版本。MySQL是跨平台的,选择对应的平台下载安装文件,安装即可。

14 |

安装时,MySQL会提示输入root用户的口令,请务必记清楚。如果怕记不住,就把口令设置为password

15 |

在Windows上,安装时请选择UTF-8编码,以便正确地处理中文。

16 |

在Mac或Linux上,需要编辑MySQL的配置文件,把数据库默认的编码全部改为UTF-8。MySQL的配置文件默认存放在/etc/my.cnf或者/etc/mysql/my.cnf

17 |
[client]
18 | default-character-set = utf8
19 | 
20 | [mysqld]
21 | default-storage-engine = INNODB
22 | character-set-server = utf8
23 | collation-server = utf8_general_ci
24 | 

重启MySQL后,可以通过MySQL的客户端命令行检查编码:

25 |
$ mysql -u root -p
26 | Enter password: 
27 | Welcome to the MySQL monitor...
28 | ...
29 | 
30 | mysql> show variables like '%char%';
31 | +--------------------------+--------------------------------------------------------+
32 | | Variable_name            | Value                                                  |
33 | +--------------------------+--------------------------------------------------------+
34 | | character_set_client     | utf8                                                   |
35 | | character_set_connection | utf8                                                   |
36 | | character_set_database   | utf8                                                   |
37 | | character_set_filesystem | binary                                                 |
38 | | character_set_results    | utf8                                                   |
39 | | character_set_server     | utf8                                                   |
40 | | character_set_system     | utf8                                                   |
41 | | character_sets_dir       | /usr/local/mysql-5.1.65-osx10.6-x86_64/share/charsets/ |
42 | +--------------------------+--------------------------------------------------------+
43 | 8 rows in set (0.00 sec)
44 | 

看到utf8字样就表示编码设置正确。

45 |

安装MySQL驱动

46 |

由于MySQL服务器以独立的进程运行,并通过网络对外服务,所以,需要支持Python的MySQL驱动来连接到MySQL服务器。

47 |

目前,有两个MySQL驱动:

48 |
    49 |
  • mysql-connector-python:是MySQL官方的纯Python驱动;

    50 |
  • 51 |
  • MySQL-python:是封装了MySQL C驱动的Python驱动。

    52 |
  • 53 |
54 |

可以把两个都装上,使用的时候再决定用哪个:

55 |
$ easy_install mysql-connector-python
56 | $ easy_install MySQL-python
57 | 

我们以mysql-connector-python为例,演示如何连接到MySQL服务器的test数据库:

58 |
# 导入MySQL驱动:
59 | >>> import mysql.connector
60 | # 注意把password设为你的root口令:
61 | >>> conn = mysql.connector.connect(user='root', password='password', database='test', use_unicode=True)
62 | >>> cursor = conn.cursor()
63 | # 创建user表:
64 | >>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
65 | # 插入一行记录,注意MySQL的占位符是%s:
66 | >>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])
67 | >>> cursor.rowcount
68 | 1
69 | # 提交事务:
70 | >>> conn.commit()
71 | >>> cursor.close()
72 | # 运行查询:
73 | >>> cursor = conn.cursor()
74 | >>> cursor.execute('select * from user where id = %s', '1')
75 | >>> values = cursor.fetchall()
76 | >>> values
77 | [(u'1', u'Michael')]
78 | # 关闭Cursor和Connection:
79 | >>> cursor.close()
80 | True
81 | >>> conn.close()
82 | 

由于Python的DB-API定义都是通用的,所以,操作MySQL的数据库代码和SQLite类似。

83 |

小结

84 |
    85 |
  • MySQL的SQL占位符是%s

    86 |
  • 87 |
  • 通常我们在连接MySQL时传入use_unicode=True,让MySQL的DB-API始终返回Unicode。

    88 |
  • 89 |
90 |
91 | 92 |
93 | 94 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/79使用SQLAlchemy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用SQLAlchemy

8 |
502次阅读
9 |
10 |

数据库表是一个二维表,包含多行多列。把一个表的内容用Python的数据结构表示出来的话,可以用一个list表示多行,list的每一个元素是tuple,表示一行记录,比如,包含idnameuser表:

11 |
[
 12 |     ('1', 'Michael'),
 13 |     ('2', 'Bob'),
 14 |     ('3', 'Adam')
 15 | ]
 16 | 

Python的DB-API返回的数据结构就是像上面这样表示的。

17 |

但是用tuple表示一行很难看出表的结构。如果把一个tuple用class实例来表示,就可以更容易地看出表的结构来:

18 |
class User(object):
 19 |     def __init__(self, id, name):
 20 |         self.id = id
 21 |         self.name = name
 22 | 
 23 | [
 24 |     User('1', 'Michael'),
 25 |     User('2', 'Bob'),
 26 |     User('3', 'Adam')
 27 | ]
 28 | 

这就是传说中的ORM技术:Object-Relational Mapping,把关系数据库的表结构映射到对象上。是不是很简单?

29 |

但是由谁来做这个转换呢?所以ORM框架应运而生。

30 |

在Python中,最有名的ORM框架是SQLAlchemy。我们来看看SQLAlchemy的用法。

31 |

首先通过easy_install或者pip安装SQLAlchemy:

32 |
$ easy_install sqlalchemy
 33 | 

然后,利用上次我们在MySQL的test数据库中创建的user表,用SQLAlchemy来试试:

34 |

第一步,导入SQLAlchemy,并初始化DBSession:

35 |
# 导入:
 36 | from sqlalchemy import Column, String, create_engine
 37 | from sqlalchemy.orm import sessionmaker
 38 | from sqlalchemy.ext.declarative import declarative_base
 39 | 
 40 | # 创建对象的基类:
 41 | Base = declarative_base()
 42 | 
 43 | # 定义User对象:
 44 | class User(Base):
 45 |     # 表的名字:
 46 |     __tablename__ = 'user'
 47 | 
 48 |     # 表的结构:
 49 |     id = Column(String(20), primary_key=True)
 50 |     name = Column(String(20))
 51 | 
 52 | # 初始化数据库连接:
 53 | engine = create_engine('mysql+mysqlconnector://root:password@localhost:3306/test')
 54 | # 创建DBSession类型:
 55 | DBSession = sessionmaker(bind=engine)
 56 | 

以上代码完成SQLAlchemy的初始化和具体每个表的class定义。如果有多个表,就继续定义其他class,例如School:

57 |
class School(Base):
 58 |     __tablename__ = 'school'
 59 |     id = ...
 60 |     name = ...
 61 | 

create_engine()用来初始化数据库连接。SQLAlchemy用一个字符串表示连接信息:

62 |
'数据库类型+数据库驱动名称://用户名:口令@机器地址:端口号/数据库名'
 63 | 

你只需要根据需要替换掉用户名、口令等信息即可。

64 |

下面,我们看看如何向数据库表中添加一行记录。

65 |

由于有了ORM,我们向数据库表中添加一行记录,可以视为添加一个User对象:

66 |
# 创建session对象:
 67 | session = DBSession()
 68 | # 创建新User对象:
 69 | new_user = User(id='5', name='Bob')
 70 | # 添加到session:
 71 | session.add(new_user)
 72 | # 提交即保存到数据库:
 73 | session.commit()
 74 | # 关闭session:
 75 | session.close()
 76 | 

可见,关键是获取session,然后把对象添加到session,最后提交并关闭。Session对象可视为当前数据库连接。

77 |

如何从数据库表中查询数据呢?有了ORM,查询出来的可以不再是tuple,而是User对象。SQLAlchemy提供的查询接口如下:

78 |
# 创建Session:
 79 | session = DBSession()
 80 | # 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
 81 | user = session.query(User).filter(User.id=='5').one()
 82 | # 打印类型和对象的name属性:
 83 | print 'type:', type(user)
 84 | print 'name:', user.name
 85 | # 关闭Session:
 86 | session.close()
 87 | 

运行结果如下:

88 |
type: <class '__main__.User'>
 89 | name: Bob
 90 | 

可见,ORM就是把数据库表的行与相应的对象建立关联,互相转换。

91 |

由于关系数据库的多个表还可以用外键实现一对多、多对多等关联,相应地,ORM框架也可以提供两个对象之间的一对多、多对多等功能。

92 |

例如,如果一个User拥有多个Book,就可以定义一对多关系如下:

93 |
class User(Base):
 94 |     __tablename__ = 'user'
 95 | 
 96 |     id = Column(String(20), primary_key=True)
 97 |     name = Column(String(20))
 98 |     # 一对多:
 99 |     books = relationship('Book')
100 | 
101 | class Book(Base):
102 |     __tablename__ = 'book'
103 | 
104 |     id = Column(String(20), primary_key=True)
105 |     name = Column(String(20))
106 |     # “多”的一方的book表是通过外键关联到user表的:
107 |     user_id = Column(String(20), ForeignKey('user.id'))
108 | 

当我们查询一个User对象时,该对象的books属性将返回一个包含若干个Book对象的list。

109 |

小结

110 |

ORM框架的作用就是把数据库表的一行记录与一个对象互相做自动转换。

111 |

正确使用ORM的前提是了解关系数据库的原理。

112 |
113 | 114 |
115 | 116 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/7Python基础.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Python基础

8 |
4280次阅读
9 |
10 |

Python是一种计算机编程语言。计算机编程语言和我们日常使用的自然语言有所不同,最大的区别就是,自然语言在不同的语境下有不同的理解,而计算机要根据编程语言执行任务,就必须保证编程语言写出的程序决不能有歧义,所以,任何一种编程语言都有自己的一套语法,编译器或者解释器就是负责把符合语法的程序代码转换成CPU能够执行的机器码,然后执行。Python也不例外。

11 |

Python的语法比较简单,采用缩进方式,写出来的代码就像下面的样子:

12 |
# print absolute value of an integer:
13 | a = 100
14 | if a >= 0:
15 |     print a
16 | else:
17 |     print -a
18 | 

#开头的语句是注释,注释是给人看的,可以是任意内容,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号“:”结尾时,缩进的语句视为代码块。

19 |

缩进有利有弊。好处是强迫你写出格式化的代码,但没有规定缩进是几个空格还是Tab。按照约定俗成的管理,应该始终坚持使用4个空格的缩进。

20 |

缩进的另一个好处是强迫你写出缩进较少的代码,你会倾向于把一段很长的代码拆分成若干函数,从而得到缩进较少的代码。

21 |

缩进的坏处就是“复制-粘贴”功能失效了,这是最坑爹的地方。当你重构代码时,粘贴过去的代码必须重新检查缩进是否正确。此外,IDE很难像格式化Java代码那样格式化Python代码。

22 |

最后,请务必注意,Python程序是大小写敏感的,如果写错了大小写,程序会报错。

23 |
24 | 25 |
26 | 27 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/80Web开发.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Web开发

8 |
747次阅读
9 |
10 |

最早的软件都是运行在大型机上的,软件使用者通过“哑终端”登陆到大型机上去运行软件。后来随着PC机的兴起,软件开始主要运行在桌面上,而数据库这样的软件运行在服务器端,这种Client/Server模式简称CS架构。

11 |

随着互联网的兴起,人们发现,CS架构不适合Web,最大的原因是Web应用程序的修改和升级非常迅速,而CS架构需要每个客户端逐个升级桌面App,因此,Browser/Server模式开始流行,简称BS架构。

12 |

在BS架构下,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web页面,并把Web页面展示给用户即可。

13 |

当然,Web页面也具有极强的交互性。由于Web页面是用HTML编写的,而HTML具备超强的表现力,并且,服务器端升级后,客户端无需任何部署就可以使用到新的版本,因此,BS架构迅速流行起来。

14 |

今天,除了重量级的软件如Office,Photoshop等,大部分软件都以Web形式提供。比如,新浪提供的新闻、博客、微博等服务,均是Web应用。

15 |

Web应用开发可以说是目前软件开发中最重要的部分。Web开发也经历了好几个阶段:

16 |
    17 |
  1. 静态Web页面:由文本编辑器直接编辑并生成静态的HTML页面,如果要修改Web页面的内容,就需要再次编辑HTML源文件,早期的互联网Web页面就是静态的;

    18 |
  2. 19 |
  3. CGI:由于静态Web页面无法与用户交互,比如用户填写了一个注册表单,静态Web页面就无法处理。要处理用户发送的动态数据,出现了Common Gateway Interface,简称CGI,用C/C++编写。

    20 |
  4. 21 |
  5. ASP/JSP/PHP:由于Web应用特点是修改频繁,用C/C++这样的低级语言非常不适合Web开发,而脚本语言由于开发效率高,与HTML结合紧密,因此,迅速取代了CGI模式。ASP是微软推出的用VBScript脚本编程的Web开发技术,而JSP用Java来编写脚本,PHP本身则是开源的脚本语言。

    22 |
  6. 23 |
  7. MVC:为了解决直接用脚本语言嵌入HTML导致的可维护性差的问题,Web应用也引入了Model-View-Controller的模式,来简化Web开发。ASP发展为ASP.Net,JSP和PHP也有一大堆MVC框架。

    24 |
  8. 25 |
26 |

目前,Web开发技术仍在快速发展中,异步开发、新的MVVM前端技术层出不穷。

27 |

Python的诞生历史比Web还要早,由于Python是一种解释型的脚本语言,开发效率高,所以非常适合用来做Web开发。

28 |

Python有上百种Web开发框架,有很多成熟的模板技术,选择Python开发Web应用,不但开发效率高,而且运行速度快。

29 |

本章我们会详细讨论Python Web开发技术。

30 |
31 | 32 |
33 | 34 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/82HTML简介.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

HTML简介

8 |
694次阅读
9 |
10 |

网页就是HTML?这么理解大概没错。因为网页中不但包含文字,还有图片、视频、Flash小游戏,有复杂的排版、动画效果,所以,HTML定义了一套语法规则,来告诉浏览器如何把一个丰富多彩的页面显示出来。

11 |

HTML长什么样?上次我们看了新浪首页的HTML源码,如果仔细数数,竟然有6000多行!

12 |

所以,学HTML,就不要指望从新浪入手了。我们来看看最简单的HTML长什么样:

13 |
<html>
14 | <head>
15 |   <title>Hello</title>
16 | </head>
17 | <body>
18 |   <h1>Hello, world!</h1>
19 | </body>
20 | </html>
21 | 

可以用文本编辑器编写HTML,然后保存为hello.html,双击或者把文件拖到浏览器中,就可以看到效果:

22 |

hello.html

23 |

HTML文档就是一系列的Tag组成,最外层的Tag是<html>。规范的HTML也包含<head>...</head><body>...</body>(注意不要和HTTP的Header、Body搞混了),由于HTML是富文档模型,所以,还有一系列的Tag用来表示链接、图片、表格、表单等等。

24 |

CSS简介

25 |

CSS是Cascading Style Sheets(层叠样式表)的简称,CSS用来控制HTML里的所有元素如何展现,比如,给标题元素<h1>加一个样式,变成48号字体,灰色,带阴影:

26 |
<html>
27 | <head>
28 |   <title>Hello</title>
29 |   <style>
30 |     h1 {
31 |       color: #333333;
32 |       font-size: 48px;
33 |       text-shadow: 3px 3px 3px #666666;
34 |     }
35 |   </style>
36 | </head>
37 | <body>
38 |   <h1>Hello, world!</h1>
39 | </body>
40 | </html>
41 | 

效果如下:

42 |

hello-css

43 |

JavaScript简介

44 |

JavaScript虽然名称有个Java,但它和Java真的一点关系没有。JavaScript是为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以从外部链接到HTML中。如果我们希望当用户点击标题时把标题变成红色,就必须通过JavaScript来实现:

45 |
<html>
46 | <head>
47 |   <title>Hello</title>
48 |   <style>
49 |     h1 {
50 |       color: #333333;
51 |       font-size: 48px;
52 |       text-shadow: 3px 3px 3px #666666;
53 |     }
54 |   </style>
55 |   <script>
56 |     function change() {
57 |       document.getElementsByTagName('h1')[0].style.color = '#ff0000';
58 |     }
59 |   </script>
60 | </head>
61 | <body>
62 |   <h1 onclick="change()">Hello, world!</h1>
63 | </body>
64 | </html>
65 | 

效果如下:

66 |

hello-js-change-color

67 |

小结

68 |

如果要学习Web开发,首先要对HTML、CSS和JavaScript作一定的了解。HTML定义了页面的内容,CSS来控制页面元素的样式,而JavaScript负责页面的交互逻辑。

69 |

讲解HTML、CSS和JavaScript就可以写3本书,对于优秀的Web开发人员来说,精通HTML、CSS和JavaScript是必须的,这里推荐一个在线学习网站w3schools:

70 |

http://www.w3schools.com/

71 |

以及一个对应的中文版本:

72 |

http://www.w3school.com.cn/

73 |

当我们用Python或者其他语言开发Web应用时,我们就是要在服务器端动态创建出HTML,这样,浏览器就会向不同的用户显示出不同的Web页面。

74 |
75 | 76 |
77 | 78 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/83WSGI接口.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

WSGI接口

8 |
951次阅读
9 |
10 |

了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是:

11 |
    12 |
  1. 浏览器发送一个HTTP请求;

    13 |
  2. 14 |
  3. 服务器收到请求,生成一个HTML文档;

    15 |
  4. 16 |
  5. 服务器把HTML文档作为HTTP响应的Body发送给浏览器;

    17 |
  6. 18 |
  7. 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。

    19 |
  8. 20 |
21 |

所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。

22 |

如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。

23 |

正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。

24 |

这个接口就是WSGI:Web Server Gateway Interface。

25 |

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:

26 |
def application(environ, start_response):
27 |     start_response('200 OK', [('Content-Type', 'text/html')])
28 |     return '<h1>Hello, web!</h1>'
29 | 

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

30 |
    31 |
  • environ:一个包含所有HTTP请求信息的dict对象;

    32 |
  • 33 |
  • start_response:一个发送HTTP响应的函数。

    34 |
  • 35 |
36 |

application()函数中,调用:

37 |
start_response('200 OK', [('Content-Type', 'text/html')])
38 | 

就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个strtuple表示。

39 |

通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。

40 |

然后,函数的返回值'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器。

41 |

有了WSGI,我们关心的就是如何从environ这个dict对象拿到HTTP请求信息,然后构造HTML,通过start_response()发送Header,最后返回Body。

42 |

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。

43 |

不过,等等,这个application()函数怎么调用?如果我们自己调用,两个参数environstart_response我们没法提供,返回的str也没法发给浏览器。

44 |

所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的application()函数真的可以把HTML输出到浏览器,所以,要赶紧找一个最简单的WSGI服务器,把我们的Web应用程序跑起来。

45 |

好消息是Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。

46 |

运行WSGI服务

47 |

我们先编写hello.py,实现Web应用程序的WSGI处理函数:

48 |
# hello.py
49 | 
50 | def application(environ, start_response):
51 |     start_response('200 OK', [('Content-Type', 'text/html')])
52 |     return '<h1>Hello, web!</h1>'
53 | 

然后,再编写一个server.py,负责启动WSGI服务器,加载application()函数:

54 |
# server.py
55 | # 从wsgiref模块导入:
56 | from wsgiref.simple_server import make_server
57 | # 导入我们自己编写的application函数:
58 | from hello import application
59 | 
60 | # 创建一个服务器,IP地址为空,端口是8000,处理函数是application:
61 | httpd = make_server('', 8000, application)
62 | print "Serving HTTP on port 8000..."
63 | # 开始监听HTTP请求:
64 | httpd.serve_forever()
65 | 

确保以上两个文件在同一个目录下,然后在命令行输入python server.py来启动WSGI服务器:

66 |

wsgiref-start

67 |

注意:如果8000端口已被其他程序占用,启动将失败,请修改成其他端口。

68 |

启动成功后,打开浏览器,输入http://localhost:8000/,就可以看到结果了:

69 |

hello-web

70 |

在命令行可以看到wsgiref打印的log信息:

71 |

wsgiref-log

72 |

Ctrl+C终止服务器。

73 |

如果你觉得这个Web应用太简单了,可以稍微改造一下,从environ里读取PATH_INFO,这样可以显示更加动态的内容:

74 |
# hello.py
75 | 
76 | def application(environ, start_response):
77 |     start_response('200 OK', [('Content-Type', 'text/html')])
78 |     return '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
79 | 

你可以在地址栏输入用户名作为URL的一部分,将返回Hello, xxx!

80 |

hello-michael

81 |

是不是有点Web App的感觉了?

82 |

小结

83 |

无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。

84 |

复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。

85 |
86 | 87 |
88 | 89 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/84使用Web框架.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

使用Web框架

8 |
1015次阅读
9 |
10 |

了解了WSGI框架,我们发现:其实一个Web App,就是写一个WSGI的处理函数,针对每个HTTP请求进行响应。

11 |

但是如何处理HTTP请求不是问题,问题是如何处理100个不同的URL。

12 |

每一个URL可以对应GET和POST请求,当然还有PUT、DELETE等请求,但是我们通常只考虑最常见的GET和POST请求。

13 |

一个最简单的想法是从environ变量里取出HTTP请求的信息,然后逐个判断:

14 |
def application(environ, start_response):
15 |     method = environ['REQUEST_METHOD']
16 |     path = environ['PATH_INFO']
17 |     if method=='GET' and path=='/':
18 |         return handle_home(environ, start_response)
19 |     if method=='POST' and path='/signin':
20 |         return handle_signin(environ, start_response)
21 |     ...
22 | 

只是这么写下去代码是肯定没法维护了。

23 |

代码这么写没法维护的原因是因为WSGI提供的接口虽然比HTTP接口高级了不少,但和Web App的处理逻辑比,还是比较低级,我们需要在WSGI接口之上能进一步抽象,让我们专注于用一个函数处理一个URL,至于URL到函数的映射,就交给Web框架来做。

24 |

由于用Python开发一个Web框架十分容易,所以Python有上百个开源的Web框架。这里我们先不讨论各种Web框架的优缺点,直接选择一个比较流行的Web框架——Flask来使用。

25 |

用Flask编写Web App比WSGI接口简单(这不是废话么,要是比WSGI还复杂,用框架干嘛?),我们先用easy_install或者pip安装Flask:

26 |
$ easy_install flask
27 | 

然后写一个app.py,处理3个URL,分别是:

28 |
    29 |
  • GET /:首页,返回Home

    30 |
  • 31 |
  • GET /signin:登录页,显示登录表单;

    32 |
  • 33 |
  • POST /signin:处理登录表单,显示登录结果。

    34 |
  • 35 |
36 |

注意噢,同一个URL/signin分别有GET和POST两种请求,映射到两个处理函数中。

37 |

Flask通过Python的装饰器在内部自动地把URL和函数给关联起来,所以,我们写出来的代码就像这样:

38 |
from flask import Flask
39 | from flask import request
40 | 
41 | app = Flask(__name__)
42 | 
43 | @app.route('/', methods=['GET', 'POST'])
44 | def home():
45 |     return '<h1>Home</h1>'
46 | 
47 | @app.route('/signin', methods=['GET'])
48 | def signin_form():
49 |     return '''<form action="/signin" method="post">
50 |               <p><input name="username"></p>
51 |               <p><input name="password" type="password"></p>
52 |               <p><button type="submit">Sign In</button></p>
53 |               </form>'''
54 | 
55 | @app.route('/signin', methods=['POST'])
56 | def signin():
57 |     # 需要从request对象读取表单内容:
58 |     if request.form['username']=='admin' and request.form['password']=='password':
59 |         return '<h3>Hello, admin!</h3>'
60 |     return '<h3>Bad username or password.</h3>'
61 | 
62 | if __name__ == '__main__':
63 |     app.run()
64 | 

运行python app.py,Flask自带的Server在端口5000上监听:

65 |
$ python app.py 
66 |  * Running on http://127.0.0.1:5000/
67 | 

打开浏览器,输入首页地址http://localhost:5000/

68 |

flask-home

69 |

首页显示正确!

70 |

再在浏览器地址栏输入http://localhost:5000/signin,会显示登录表单:

71 |

flask-signin-form

72 |

输入预设的用户名admin和口令password,登录成功:

73 |

flask-signin-ok

74 |

输入其他错误的用户名和口令,登录失败:

75 |

flask-signin-failed

76 |

实际的Web App应该拿到用户名和口令后,去数据库查询再比对,来判断用户是否能登录成功。

77 |

除了Flask,常见的Python Web框架还有:

78 |
    79 |
  • Django:全能型Web框架;

    80 |
  • 81 |
  • web.py:一个小巧的Web框架;

    82 |
  • 83 |
  • Bottle:和Flask类似的Web框架;

    84 |
  • 85 |
  • Tornado:Facebook的开源异步Web框架。

    86 |
  • 87 |
88 |

当然了,因为开发Python的Web框架也不是什么难事,我们后面也会自己开发一个Web框架。

89 |

小结

90 |

有了Web框架,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数,这样,编写Web App就更加简单了。

91 |

在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。Flask通过request.form['name']来获取表单的内容。

92 |
93 | 94 |
95 | 96 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/86协程.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

协程

8 |
577次阅读
9 |
10 |

协程,又称微线程,纤程。英文名Coroutine。

11 |

协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。

12 |

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。

13 |

所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。

14 |

子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

15 |

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

16 |

注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

17 |
def A():
 18 |     print '1'
 19 |     print '2'
 20 |     print '3'
 21 | 
 22 | def B():
 23 |     print 'x'
 24 |     print 'y'
 25 |     print 'z'
 26 | 

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:

27 |
1
 28 | 2
 29 | x
 30 | y
 31 | 3
 32 | z
 33 | 

但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

34 |

看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

35 |

最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

36 |

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

37 |

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

38 |

Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。

39 |

来看例子:

40 |

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

41 |

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

42 |
import time
 43 | 
 44 | def consumer():
 45 |     r = ''
 46 |     while True:
 47 |         n = yield r
 48 |         if not n:
 49 |             return
 50 |         print('[CONSUMER] Consuming %s...' % n)
 51 |         time.sleep(1)
 52 |         r = '200 OK'
 53 | 
 54 | def produce(c):
 55 |     c.next()
 56 |     n = 0
 57 |     while n < 5:
 58 |         n = n + 1
 59 |         print('[PRODUCER] Producing %s...' % n)
 60 |         r = c.send(n)
 61 |         print('[PRODUCER] Consumer return: %s' % r)
 62 |     c.close()
 63 | 
 64 | if __name__=='__main__':
 65 |     c = consumer()
 66 |     produce(c)
 67 | 

执行结果:

68 |
[PRODUCER] Producing 1...
 69 | [CONSUMER] Consuming 1...
 70 | [PRODUCER] Consumer return: 200 OK
 71 | [PRODUCER] Producing 2...
 72 | [CONSUMER] Consuming 2...
 73 | [PRODUCER] Consumer return: 200 OK
 74 | [PRODUCER] Producing 3...
 75 | [CONSUMER] Consuming 3...
 76 | [PRODUCER] Consumer return: 200 OK
 77 | [PRODUCER] Producing 4...
 78 | [CONSUMER] Consuming 4...
 79 | [PRODUCER] Consumer return: 200 OK
 80 | [PRODUCER] Producing 5...
 81 | [CONSUMER] Consuming 5...
 82 | [PRODUCER] Consumer return: 200 OK
 83 | 

注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:

84 |
    85 |
  1. 首先调用c.next()启动生成器;

    86 |
  2. 87 |
  3. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

    88 |
  4. 89 |
  5. consumer通过yield拿到消息,处理,又通过yield把结果传回;

    90 |
  6. 91 |
  7. produce拿到consumer处理的结果,继续生产下一条消息;

    92 |
  8. 93 |
  9. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

    94 |
  10. 95 |
96 |

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

97 |

最后套用Donald Knuth的一句话总结协程的特点:

98 |

“子程序就是协程的一种特例。”

99 |
100 | 101 |
102 | 103 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/87gevent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

gevent

8 |
376次阅读
9 |
10 |

Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。

11 |

gevent是第三方库,通过greenlet实现协程,其基本思想是:

12 |

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

13 |

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:

14 |
from gevent import monkey; monkey.patch_socket()
15 | import gevent
16 | 
17 | def f(n):
18 |     for i in range(n):
19 |         print gevent.getcurrent(), i
20 | 
21 | g1 = gevent.spawn(f, 5)
22 | g2 = gevent.spawn(f, 5)
23 | g3 = gevent.spawn(f, 5)
24 | g1.join()
25 | g2.join()
26 | g3.join()
27 | 

运行结果:

28 |
<Greenlet at 0x10e49f550: f(5)> 0
29 | <Greenlet at 0x10e49f550: f(5)> 1
30 | <Greenlet at 0x10e49f550: f(5)> 2
31 | <Greenlet at 0x10e49f550: f(5)> 3
32 | <Greenlet at 0x10e49f550: f(5)> 4
33 | <Greenlet at 0x10e49f910: f(5)> 0
34 | <Greenlet at 0x10e49f910: f(5)> 1
35 | <Greenlet at 0x10e49f910: f(5)> 2
36 | <Greenlet at 0x10e49f910: f(5)> 3
37 | <Greenlet at 0x10e49f910: f(5)> 4
38 | <Greenlet at 0x10e49f4b0: f(5)> 0
39 | <Greenlet at 0x10e49f4b0: f(5)> 1
40 | <Greenlet at 0x10e49f4b0: f(5)> 2
41 | <Greenlet at 0x10e49f4b0: f(5)> 3
42 | <Greenlet at 0x10e49f4b0: f(5)> 4
43 | 

可以看到,3个greenlet是依次运行而不是交替运行。

44 |

要让greenlet交替运行,可以通过gevent.sleep()交出控制权:

45 |
def f(n):
46 |     for i in range(n):
47 |         print gevent.getcurrent(), i
48 |         gevent.sleep(0)
49 | 

执行结果:

50 |
<Greenlet at 0x10cd58550: f(5)> 0
51 | <Greenlet at 0x10cd58910: f(5)> 0
52 | <Greenlet at 0x10cd584b0: f(5)> 0
53 | <Greenlet at 0x10cd58550: f(5)> 1
54 | <Greenlet at 0x10cd584b0: f(5)> 1
55 | <Greenlet at 0x10cd58910: f(5)> 1
56 | <Greenlet at 0x10cd58550: f(5)> 2
57 | <Greenlet at 0x10cd58910: f(5)> 2
58 | <Greenlet at 0x10cd584b0: f(5)> 2
59 | <Greenlet at 0x10cd58550: f(5)> 3
60 | <Greenlet at 0x10cd584b0: f(5)> 3
61 | <Greenlet at 0x10cd58910: f(5)> 3
62 | <Greenlet at 0x10cd58550: f(5)> 4
63 | <Greenlet at 0x10cd58910: f(5)> 4
64 | <Greenlet at 0x10cd584b0: f(5)> 4
65 | 

3个greenlet交替运行,

66 |

把循环次数改为500000,让它们的运行时间长一点,然后在操作系统的进程管理器中看,线程数只有1个。

67 |

当然,实际代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时,gevent自动切换,代码如下:

68 |
from gevent import monkey; monkey.patch_all()
69 | import gevent
70 | import urllib2
71 | 
72 | def f(url):
73 |     print('GET: %s' % url)
74 |     resp = urllib2.urlopen(url)
75 |     data = resp.read()
76 |     print('%d bytes received from %s.' % (len(data), url))
77 | 
78 | gevent.joinall([
79 |         gevent.spawn(f, 'https://www.python.org/'),
80 |         gevent.spawn(f, 'https://www.yahoo.com/'),
81 |         gevent.spawn(f, 'https://github.com/'),
82 | ])
83 | 

运行结果:

84 |
GET: https://www.python.org/
85 | GET: https://www.yahoo.com/
86 | GET: https://github.com/
87 | 45661 bytes received from https://www.python.org/.
88 | 14823 bytes received from https://github.com/.
89 | 304034 bytes received from https://www.yahoo.com/.
90 | 

从结果看,3个网络操作是并发执行的,而且结束顺序不同,但只有一个线程。

91 |

小结

92 |

使用gevent,可以获得极高的并发性能,但gevent只能在Unix/Linux下运行,在Windows下不保证正常安装和运行。

93 |

由于gevent是基于IO切换的协程,所以最神奇的是,我们编写的Web App代码,不需要引入gevent的包,也不需要改任何代码,仅仅在部署的时候,用一个支持gevent的WSGI服务器,立刻就获得了数倍的性能提升。具体部署方式可以参考后续“实战”-“部署Web App”一节。

94 |
95 | 96 |
97 | 98 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/88实战.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

实战

8 |
1044次阅读
9 |
10 |

看完了教程,是不是有这么一种感觉:看的时候觉得很简单,照着教程敲代码也没啥大问题。

11 |

于是准备开始独立写代码,就发现不知道从哪开始下手了。

12 |

这种情况是完全正常的。好比学写作文,学的时候觉得简单,写的时候就无从下笔了。

13 |

虽然这个教程是面向小白的零基础Python教程,但是我们的目标不是学到60分,而是学到90分。

14 |

所以,用Python写一个真正的Web App吧!

15 |

目标

16 |

我们设定的实战目标是一个Blog网站,包含日志、用户和评论3大部分。

17 |

很多童鞋会想,这是不是太简单了?

18 |

比如webpy.org上就提供了一个Blog的例子,目测也就100行代码。

19 |

但是,这样的页面:

20 |

simple-hello-world

21 |

你拿得出手么?

22 |

我们要写出用户真正看得上眼的页面,首页长得像这样:

23 |

awesomepy-home-blogs

24 |

评论区:

25 |

awesomepy-comments

26 |

还有极其强大的后台管理页面:

27 |

awesomepy-manage-blogs

28 |

是不是一下子变得高端大气上档次了?

29 |

项目名称

30 |

必须是高端大气上档次的名称,命名为awesome-python-webapp

31 |

项目计划

32 |

项目计划开发周期为16天。每天,你需要完成教程中的内容。如果你觉得编写代码难度实在太大,可以参考一下当天在GitHub上的代码。

33 |

第N天的代码在https://github.com/michaelliao/awesome-python-webapp/tree/day-N上。比如第1天就是:

34 |

https://github.com/michaelliao/awesome-python-webapp/tree/day-01

35 |

以此类推。

36 |

要预览awesome-python-webapp的最终页面效果,请猛击:

37 |

awesome.liaoxuefeng.com

38 |
39 | 40 |
41 | 42 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/89Day 1 - 搭建开发环境.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 1 - 搭建开发环境

8 |
3068次阅读
9 |
10 |

搭建开发环境

11 |

首先,确认系统安装的Python版本是2.7.x:

12 |
$ python --version
13 | Python 2.7.5
14 | 

然后,安装开发Web App需要的第三方库:

15 |

前端模板引擎jinja2:

16 |
$ easy_install jinja2
17 | 

MySQL 5.x数据库,从官方网站下载并安装,安装完毕后,请务必牢记root口令。为避免遗忘口令,建议直接把root口令设置为password

18 |

MySQL的Python驱动程序mysql-connector-python:

19 |
$ easy_install mysql-connector-python
20 | 

项目结构

21 |

选择一个工作目录,然后,我们建立如下的目录结构:

22 |
awesome-python-webapp/   <-- 根目录
23 | |
24 | +- backup/               <-- 备份目录
25 | |
26 | +- conf/                 <-- 配置文件
27 | |
28 | +- dist/                 <-- 打包目录
29 | |
30 | +- www/                  <-- Web目录,存放.py文件
31 | |  |
32 | |  +- static/            <-- 存放静态文件
33 | |  |
34 | |  +- templates/         <-- 存放模板文件
35 | |
36 | +- LICENSE               <-- 代码LICENSE
37 | 

创建好项目的目录结构后,建议同时建立Git仓库并同步至GitHub,保证代码修改的安全。

38 |

要了解Git和GitHub的用法,请移步Git教程

39 |

开发工具

40 |

自备,推荐用Sublime Text。

41 |
42 | 43 |
44 | 45 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/91Day 3 - 编写ORM.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 3 - 编写ORM

8 |
687次阅读
9 |
10 |

有了db模块,操作数据库直接写SQL就很方便。但是,我们还缺少ORM。如果有了ORM,就可以用类似这样的语句获取User对象:

11 |
user = User.get('123')
 12 | 

而不是写SQL然后再转换成User对象:

13 |
u = db.select_one('select * from users where id=?', '123')
 14 | user = User(**u)
 15 | 

所以我们开始编写ORM模块:transwarp.orm

16 |

设计ORM接口

17 |

和设计db模块类似,设计ORM也是从上层调用者角度来设计。

18 |

我们先考虑如何定义一个User对象,然后把数据库表users和它关联起来。

19 |
from transwarp.orm import Model, StringField, IntegerField
 20 | 
 21 | class User(Model):
 22 |     __table__ = 'users'
 23 |     id = IntegerField(primary_key=True)
 24 |     name = StringField()
 25 | 

注意到定义在User类中的__table__idname是类的属性,不是实例的属性。所以,在类级别上定义的属性用来描述User对象和表的映射关系,而实例属性必须通过__init__()方法去初始化,所以两者互不干扰:

26 |
# 创建实例:
 27 | user = User(id=123, name='Michael')
 28 | # 存入数据库:
 29 | user.insert()
 30 | 

实现ORM模块

31 |

有了定义,我们就可以开始实现ORM模块。

32 |

首先要定义的是所有ORM映射的基类Model

33 |
class Model(dict):
 34 |     __metaclass__ = ModelMetaclass
 35 | 
 36 |     def __init__(self, **kw):
 37 |         super(Model, self).__init__(**kw)
 38 | 
 39 |     def __getattr__(self, key):
 40 |         try:
 41 |             return self[key]
 42 |         except KeyError:
 43 |             raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
 44 | 
 45 |     def __setattr__(self, key, value):
 46 |         self[key] = value
 47 | 

Modeldict继承,所以具备所有dict的功能,同时又实现了特殊方法__getattr__()__setattr__(),所以又可以像引用普通字段那样写:

48 |
>>> user['id']
 49 | 123
 50 | >>> user.id
 51 | 123
 52 | 

Model只是一个基类,如何将具体的子类如User的映射信息读取出来呢?答案就是通过metaclass:ModelMetaclass

53 |
class ModelMetaclass(type):
 54 |     def __new__(cls, name, bases, attrs):
 55 |         mapping = ... # 读取cls的Field字段
 56 |         primary_key = ... # 查找primary_key字段
 57 |         __table__ = cls.__talbe__ # 读取cls的__table__字段
 58 |         # 给cls增加一些字段:
 59 |         attrs['__mapping__'] = mapping
 60 |         attrs['__primary_key__'] = __primary_key__
 61 |         attrs['__table__'] = __table__
 62 |         return type.__new__(cls, name, bases, attrs)
 63 | 

这样,任何继承自Model的类(比如User),会自动通过ModelMetaclass扫描映射关系,并存储到自身的class中。

64 |

然后,我们往Model类添加class方法,就可以让所有子类调用class方法:

65 |
class Model(dict):
 66 | 
 67 |     ...
 68 | 
 69 |     @classmethod
 70 |     def get(cls, pk):
 71 |         d = db.select_one('select * from %s where %s=?' % (cls.__table__, cls.__primary_key__.name), pk)
 72 |         return cls(**d) if d else None
 73 | 

User类就可以通过类方法实现主键查找:

74 |
user = User.get('123')
 75 | 

Model类添加实例方法,就可以让所有子类调用实例方法:

76 |
class Model(dict):
 77 | 
 78 |     ...
 79 | 
 80 |     def insert(self):
 81 |         params = {}
 82 |         for k, v in self.__mappings__.iteritems():
 83 |             params[v.name] = getattr(self, k)
 84 |         db.insert(self.__table__, **params)
 85 |         return self
 86 | 

这样,就可以把一个User实例存入数据库:

87 |
user = User(id=123, name='Michael')
 88 | user.insert()
 89 | 

最后一步是完善ORM,对于查找,我们可以实现以下方法:

90 |
    91 |
  • find_first()

    92 |
  • 93 |
  • find_all()

    94 |
  • 95 |
  • find_by()

    96 |
  • 97 |
98 |

对于count,可以实现:

99 |
    100 |
  • count_all()

    101 |
  • 102 |
  • count_by()

    103 |
  • 104 |
105 |

以及update()delete()方法。

106 |

最后看看我们实现的ORM模块一共多少行代码?加上注释和doctest才仅仅300多行。用Python写一个ORM是不是很容易呢?

107 |
108 | 109 |
110 | 111 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/94Day 6 - 添加配置文件.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 6 - 添加配置文件

8 |
234次阅读
9 |
10 |

有了Web框架和ORM框架,我们就可以开始装配App了。

11 |

通常,一个Web App在运行时都需要读取配置文件,比如数据库的用户名、口令等,在不同的环境中运行时,Web App可以通过读取不同的配置文件来获得正确的配置。

12 |

由于Python本身语法简单,完全可以直接用Python源代码来实现配置,而不需要再解析一个单独的.properties或者.yaml等配置文件。

13 |

默认的配置文件应该完全符合本地开发环境,这样,无需任何设置,就可以立刻启动服务器。

14 |

我们把默认的配置文件命名为config_default.py

15 |
# config_default.py
16 | 
17 | configs = {
18 |     'db': {
19 |         'host': '127.0.0.1',
20 |         'port': 3306,
21 |         'user': 'www-data',
22 |         'password': 'www-data',
23 |         'database': 'awesome'
24 |     },
25 |     'session': {
26 |         'secret': 'AwEsOmE'
27 |     }
28 | }
29 | 

上述配置文件简单明了。但是,如果要部署到服务器时,通常需要修改数据库的host等信息,直接修改config_default.py不是一个好办法,更好的方法是编写一个config_override.py,用来覆盖某些默认设置:

30 |
# config_override.py
31 | 
32 | configs = {
33 |     'db': {
34 |         'host': '192.168.0.100'
35 |     }
36 | }
37 | 

config_default.py作为开发环境的标准配置,把config_override.py作为生产环境的标准配置,我们就可以既方便地在本地开发,又可以随时把应用部署到服务器上。

38 |

应用程序读取配置文件需要优先从config_override.py读取。为了简化读取配置文件,可以把所有配置读取到统一的config.py中:

39 |
# config.py
40 | configs = config_default.configs
41 | 
42 | try:
43 |     import config_override
44 |     configs = merge(configs, config_override.configs)
45 | except ImportError:
46 |     pass
47 | 

这样,我们就完成了App的配置。

48 |
49 | 50 |
51 | 52 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/95Day 7 - 编写MVC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 7 - 编写MVC

8 |
310次阅读
9 |
10 |

现在,ORM框架、Web框架和配置都已就绪,我们可以开始编写一个最简单的MVC,把它们全部启动起来。

11 |

通过Web框架的@decorator和ORM框架的Model支持,可以很容易地编写一个处理首页URL的函数:

12 |
# urls.py
13 | from transwarp.web import get, view
14 | from models import User, Blog, Comment
15 | 
16 | @view('test_users.html')
17 | @get('/')
18 | def test_users():
19 |     users = User.find_all()
20 |     return dict(users=users)
21 | 

@view指定的模板文件是test_users.html,所以我们在模板的根目录templates下创建test_users.html

22 |
<!DOCTYPE html>
23 | <html>
24 | <head>
25 |     <meta charset="utf-8" />
26 |     <title>Test users - Awesome Python Webapp</title>
27 | </head>
28 | <body>
29 |     <h1>All users</h1>
30 |     {% for u in users %}
31 |     <p>{{ u.name }} / {{ u.email }}</p>
32 |     {% endfor %}
33 | </body>
34 | </html>
35 | 

接下来,我们创建一个Web App的启动文件wsgiapp.py,负责初始化数据库、初始化Web框架,然后加载urls.py,最后启动Web服务:

36 |
# wsgiapp.py
37 | import logging; logging.basicConfig(level=logging.INFO)
38 | import os
39 | 
40 | from transwarp import db
41 | from transwarp.web import WSGIApplication, Jinja2TemplateEngine
42 | 
43 | from config import configs
44 | 
45 | # 初始化数据库:
46 | db.create_engine(**configs.db)
47 | 
48 | # 创建一个WSGIApplication:
49 | wsgi = WSGIApplication(os.path.dirname(os.path.abspath(__file__)))
50 | # 初始化jinja2模板引擎:
51 | template_engine = Jinja2TemplateEngine(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates'))
52 | wsgi.template_engine = template_engine
53 | 
54 | # 加载带有@get/@post的URL处理函数:
55 | import urls
56 | wsgi.add_module(urls)
57 | 
58 | # 在9000端口上启动本地测试服务器:
59 | if __name__ == '__main__':
60 |     wsgi.run(9000)
61 | 

如果一切顺利,可以用命令行启动Web服务器:

62 |
$ python wsgiapp.py
63 | 

然后,在浏览器中访问http://localhost:9000/

64 |

如果数据库的users表什么内容也没有,你就无法在浏览器中看到循环输出的内容。可以自己在MySQL的命令行里给users表添加几条记录,然后再访问:

65 |

awesomepy-all-users

66 |
67 | 68 |
69 | 70 | -------------------------------------------------------------------------------- /廖雪峰python教程HTML版/97Day 9 - 编写API.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Day 9 - 编写API

8 |
374次阅读
9 |
10 |

自从Roy Fielding博士在2000年他的博士论文中提出REST(Representational State Transfer)风格的软件架构模式后,REST就基本上迅速取代了复杂而笨重的SOAP,成为Web API的标准了。

11 |

什么是Web API呢?

12 |

如果我们想要获取一篇Blog,输入http://localhost:9000/blog/123,就可以看到id为123的Blog页面,但这个结果是HTML页面,它同时混合包含了Blog的数据和Blog的展示两个部分。对于用户来说,阅读起来没有问题,但是,如果机器读取,就很难从HTML中解析出Blog的数据。

13 |

如果一个URL返回的不是HTML,而是机器能直接解析的数据,这个URL就可以看成是一个Web API。比如,读取http://localhost:9000/api/blogs/123,如果能直接返回Blog的数据,那么机器就可以直接读取。

14 |

REST就是一种设计API的模式。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,以JSON格式编写的REST风格的API具有简单、易读、易用的特点。

15 |

编写API有什么好处呢?由于API就是把Web App的功能全部封装了,所以,通过API操作数据,可以极大地把前端和后端的代码隔离,使得后端代码易于测试,前端代码编写更简单。

16 |

一个API也是一个URL的处理函数,我们希望能直接通过一个@api来把函数变成JSON格式的REST API,这样,获取注册用户可以用一个API实现如下:

17 |
@api
18 | @get('/api/users')
19 | def api_get_users():
20 |     users = User.find_by('order by created_at desc')
21 |     # 把用户的口令隐藏掉:
22 |     for u in users:
23 |         u.password = '******'
24 |     return dict(users=users)
25 | 

所以,@api这个decorator只要编写好了,就可以把任意的URL处理函数变成API调用。

26 |

新建一个apis.py,编写@api负责把函数的返回结果序列化为JSON:

27 |
def api(func):
28 |     @functools.wraps(func)
29 |     def _wrapper(*args, **kw):
30 |         try:
31 |             r = json.dumps(func(*args, **kw))
32 |         except APIError, e:
33 |             r = json.dumps(dict(error=e.error, data=e.data, message=e.message))
34 |         except Exception, e:
35 |             r = json.dumps(dict(error='internalerror', data=e.__class__.__name__, message=e.message))
36 |         ctx.response.content_type = 'application/json'
37 |         return r
38 |     return _wrapper
39 | 

@api需要对Error进行处理。我们定义一个APIError,这种Error是指API调用时发生了逻辑错误(比如用户不存在),其他的Error视为Bug,返回的错误代码为internalerror

40 |

客户端调用API时,必须通过错误代码来区分API调用是否成功。错误代码是用来告诉调用者出错的原因。很多API用一个整数表示错误码,这种方式很难维护错误码,客户端拿到错误码还需要查表得知错误信息。更好的方式是用字符串表示错误代码,不需要看文档也能猜到错误原因。

41 |

可以在浏览器直接测试API,例如,输入http://localhost:9000/api/users,就可以看到返回的JSON:

42 |

api-result

43 |
44 | 45 |
46 | 47 | --------------------------------------------------------------------------------