├── .gitignore ├── .idea ├── .gitignore ├── from-python-to-django.iml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.md ├── SUMMARY.md ├── a-byte-of-python3 ├── about.md ├── about_python.md ├── basics.md ├── control_flow.md ├── data_structures.md ├── dedication.md ├── exceptions.md ├── feedback.md ├── first_steps.md ├── firststep01.png ├── firststep02.png ├── firststep03.png ├── firststep04.png ├── floss.md ├── functions.md ├── hello.png ├── index.md ├── install-on-win10.jpg ├── installation.md ├── io.md ├── modules.md ├── more.md ├── mymodule.py ├── oop.md ├── op_exp.md ├── preface.md ├── problem_solving.md ├── revision_history.md ├── stdlib.md ├── test.py ├── translation_howto.md ├── translations.md └── what_next.md ├── book.json ├── byte-of-python-chinese-edition.pdf ├── cover.jpg ├── django-step-by-step ├── chapter01.md ├── chapter01.png ├── chapter02.md ├── chapter0201.png ├── chapter0202.png ├── chapter03.md ├── chapter03.png ├── chapter04.md ├── chapter05.md ├── chapter06.md ├── chapter0601.png ├── chapter0602.png ├── chapter0603.png ├── chapter0604.png ├── chapter0605.png ├── chapter07.md ├── chapter0701.png ├── chapter0702.png ├── chapter0703.png ├── chapter0704.png ├── chapter0705.png ├── chapter0706.png ├── chapter0707.png ├── chapter08.md ├── chapter09.md ├── chapter0901.png ├── chapter0902.png ├── chapter10.md ├── chapter1001.png ├── chapter11.md ├── chapter1101.png ├── chapter1102.png ├── chapter12.md ├── chapter13.md ├── chapter14.md ├── chapter15.md ├── chapter16.md ├── chapter1601.png ├── chapter17.md └── index.md ├── django-tips └── python_rules.md ├── hello.py ├── introduction ├── env.md ├── first_django.png ├── hello-world.png ├── home-screenshot-win.png ├── installpython.png ├── using_python.jpg ├── virtualenv.png └── vscode1.png └── 封面.png /.gitignore: -------------------------------------------------------------------------------- 1 | # This is a comment 2 | 3 | # Ignore the file test.md 4 | test.md 5 | 6 | # Ignore everything in the directory "bin" 7 | bin/* 8 | 9 | # Book build output 10 | _book 11 | 12 | # Visual Studio Code 13 | .vscode 14 | 15 | # MAC OS files 16 | .DS_Store 17 | 18 | # Python Virtualenv 19 | venv 20 | .venv 21 | *.pyc 22 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/from-python-to-django.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 从Python到Django入门教程(2024版) 2 | -------------------------------------------------- 3 | 4 | ## 前言 5 | 6 | ​Python是一门面向对象的编程语言,它相对于其他语言,更加易学、易读,非常适合快速开发。Python在实践中主要有以下优点: 7 | 8 | * 容易上手,教材众多,平台支持广泛。 9 | * 可以很容易支持大数据的应用。 10 | * 可以很容易的支持人工智能的应用。 11 | * 可以很容易的迁移到IoT平台,开发物联网应用。 12 | 13 | Django是一个开放源代码的Web应用框架,由Python写成。采用了MVC(模型M,视图V和控制器C)的软件设计模式。Django在实践中主要有以下优点: 14 | 15 | * 在Python各种web框架中,Django的文档最完善、市场占有率最高、招聘职位最多! 16 | * 有一个开箱即用的Admin平台,可以省去开发后台管理平台的工作。 17 | * 有完整的开发体系,包括Web开发、REST接口开发、SQL数据库ORM、NoSQL数据库。 18 | * ORM模型可以自动修改数据库结构,方便进行快速迭代。 19 | 20 | 本文是从Python到Django的入门教程,给零起点的程序员一个完整的学习路径 21 | 22 | ``` 23 | Python 3.9--->Django 4.2 24 | ``` 25 | 26 | ## 开发环境 27 | 28 | 本文的范例基于Visual Studio Code编辑器完成的。因此,你需要准备以下这些软件: 29 | 30 | * [VS Code](https://code.visualstudio.com/)编辑器,还需要安装Python扩展。 31 | 32 | * [Python的安装文件](https://www.python.org/downloads/),根据你自己的操作系统安装,一直"下一步"就能搞定。 33 | 34 | * 用virtualenv搭建Django的开发环境,后文会详细描述。 35 | 36 | ## 开始学习 37 | 38 | 根据自己的情况选择从哪里开始学习,如果你已经有了一定的基础,你可以选择跳过某些章节,对于大多数初学者来讲,学习的路径如下: 39 | 40 | ### 1. [搭建Python开发环境](https://www.kancloud.cn/borisliu/from-python-to-django/1539197) 41 | ### 2. [简明Python教程](https://www.kancloud.cn/borisliu/from-python-to-django/1539198) 42 | ### 3. [Django Step By Step](https://www.kancloud.cn/borisliu/from-python-to-django/1539224) 43 | 44 | --- 45 | 46 | ![人生苦短,我用Python](./introduction/using_python.jpg) 47 | 48 | ### by 49 | 50 | 本书托管在[GitHub](https://gitee.com/borisliu/from-python-to-django), 51 | 如果有问题请在线[提交](https://gitee.com/borisliu/from-python-to-django/issues)。 52 | 53 | ### 废话少说,开始吧! 54 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | * [开始学习](README.md) 2 | * [搭建Python开发环境](introduction/env.md) 3 | * [简明Python教程](a-byte-of-python3/index.md) 4 | * [致敬](a-byte-of-python3/dedication.md) 5 | * [前言](a-byte-of-python3/preface.md) 6 | * [关于Python](a-byte-of-python3/about_python.md) 7 | * [安装](a-byte-of-python3/installation.md) 8 | * [第一步](a-byte-of-python3/first_steps.md) 9 | * [基础](a-byte-of-python3/basics.md) 10 | * [运算符和表达式](a-byte-of-python3/op_exp.md) 11 | * [控制流](a-byte-of-python3/control_flow.md) 12 | * [函数](a-byte-of-python3/functions.md) 13 | * [模块](a-byte-of-python3/modules.md) 14 | * [数据结构](a-byte-of-python3/data_structures.md) 15 | * [实战案例](a-byte-of-python3/problem_solving.md) 16 | * [面向对象编程](a-byte-of-python3/oop.md) 17 | * [输入与输出](a-byte-of-python3/io.md) 18 | * [异常处理](a-byte-of-python3/exceptions.md) 19 | * [标准库](a-byte-of-python3/stdlib.md) 20 | * [更多](a-byte-of-python3/more.md) 21 | * [继续学习](a-byte-of-python3/what_next.md) 22 | * [附录:免费/自由和开放源码软件](a-byte-of-python3/floss.md) 23 | * [附录: 关于](a-byte-of-python3/about.md) 24 | * [附录: 版本历史](a-byte-of-python3/revision_history.md) 25 | * [附录: 翻译](a-byte-of-python3/translations.md) 26 | * [附录: 参与翻译工作](a-byte-of-python3/translation_howto.md) 27 | * [反馈](a-byte-of-python3/feedback.md) 28 | * [Django Step Sy Step](django-step-by-step/index.md) 29 | * [第一讲 从简单到复杂](django-step-by-step/chapter01.md) 30 | * [第二讲 做加法的例子](django-step-by-step/chapter02.md) 31 | * [第三讲 使用Template](django-step-by-step/chapter03.md) 32 | * [第四讲 生成csv格式文件](django-step-by-step/chapter04.md) 33 | * [第五讲 session示例](django-step-by-step/chapter05.md) 34 | * [第六讲 wiki的例子](django-step-by-step/chapter06.md) 35 | * [第七讲 通讯录的例子](django-step-by-step/chapter07.md) 36 | * [第八讲 文件导入和导出](django-step-by-step/chapter08.md) 37 | * [第九讲 通讯录的美化](django-step-by-step/chapter09.md) 38 | * [第十讲 扩展django的模板](django-step-by-step/chapter10.md) 39 | * [第十一讲 用户管理](django-step-by-step/chapter11.md) 40 | * [第十二讲 搜索和部署](django-step-by-step/chapter12.md) 41 | * [第十三讲 Ajax的实现(一)](django-step-by-step/chapter13.md) 42 | * [第十四讲 Ajax的实现(二)](django-step-by-step/chapter14.md) 43 | * [第十五讲 i18n的一个简单实现](django-step-by-step/chapter15.md) 44 | * [第十六讲 自定义Calendar Tag](django-step-by-step/chapter16.md) 45 | * [第十七讲 View,Template和Tag](django-step-by-step/chapter17.md) 46 | * [Django开发实战](django-tips/index.md) 47 | * [Python开发规范](django-tips/python_rules.md) 48 | * [Django项目的gitignore](django-tips/gitignore.md) 49 | * [怎样配置开发环境的settings](django-tips/settings.md) 50 | * [如何使用Django和Vue.js构建项目](django-tips/vue.md) 51 | * [使用WebSocket开发网页聊天室](django-tips/websocket.md) 52 | * [怎样使Django Admin显示中文](django-tips/admin_chinese.md) 53 | * [怎样使Model在Admin界面中显示中文](django-tips/model_chinese.md) 54 | * [使用Django Admin怎样上传并显示图片](django-tips/admin_upload_img.md) 55 | * [解决Django模板和Vue指令花括号冲突的问题](django-tips/braces_conflict.md) 56 | * [使用Django和Vue开发微信公众号](django-tips/wechat_h5.md) 57 | * [使用Django和Vue调用微信JSSDK开发微信支付](django-tips/wechat_pay.md) 58 | -------------------------------------------------------------------------------- /a-byte-of-python3/about.md: -------------------------------------------------------------------------------- 1 | # 附录: 关于 2 | 3 | Almost all of the software that I have used in the creation of this book are [FLOSS](./floss.md#floss). 4 | 5 | ## Birth of the Book 6 | 7 | In the first draft of this book, I had used Red Hat 9.0 Linux as the foundation of my setup and in the sixth draft, I used Fedora Core 3 Linux as the basis of my setup. 8 | 9 | Initially, I was using KWord to write the book (as explained in the [history lesson](./revision_history.md#history-lesson)). 10 | 11 | ## Teenage Years 12 | 13 | Later, I switched to DocBook XML using Kate but I found it too tedious. So, I switched to OpenOffice which was just excellent with the level of control it provided for formatting as well as the PDF generation, but it produced very sloppy HTML from the document. 14 | 15 | Finally, I discovered XEmacs and I rewrote the book from scratch in DocBook XML (again) after I decided that this format was the long term solution. 16 | 17 | In the sixth draft, I decided to use Quanta+ to do all the editing. The standard XSL stylesheets that came with Fedora Core 3 Linux were being used. However, I had written a CSS document to give color and style to the HTML pages. I had also written a crude lexical analyzer, in Python of course, which automatically provides syntax highlighting to all the program listings. 18 | 19 | For the seventh draft, I was using [MediaWiki](http://www.mediawiki.org) as the basis of my setup. I used to edit everything online and the readers can directly read/edit/discuss within the wiki website, but I ended up spending more time fighting spam than writing. 20 | 21 | For the eight draft, I used [Vim]({{ book.vimBookUrl }}), [Pandoc](http://johnmacfarlane.net/pandoc/README.html), and Mac OS X. 22 | 23 | For the ninth draft, I switched to [AsciiDoc format](http://asciidoctor.org/docs/what-is-asciidoc/) and used [Emacs 24.3](http://www.masteringemacs.org/articles/2013/03/11/whats-new-emacs-24-3/), 24 | [tomorrow theme](https://github.com/chriskempson/tomorrow-theme), 25 | [Fira Mono font](https://www.mozilla.org/en-US/styleguide/products/firefox-os/typeface/#download-primary) and [adoc-mode](https://github.com/sensorflo/adoc-mode/wiki) to write. 26 | 27 | ## Now 28 | 29 | 2016: I got tired of several minor rendering issues in AsciiDoctor, like the `++` in `C/C++` would disappear and it was hard to keep track of escaping such minor things. Plus, I had become reluctant to edit the text because of the complex Asciidoc format. 30 | 31 | For the tenth draft, I switched to writing in Markdown + [GitBook](https://www.gitbook.com) format, using the [Spacemacs editor](http://spacemacs.org). 32 | 33 | ## About the Author 34 | 35 | See {{ book.authorUrl }} 36 | 37 | -------------------------------------------------- 38 | 39 | ### 继续阅读[附录: 关于](about.md) -------------------------------------------------------------------------------- /a-byte-of-python3/about_python.md: -------------------------------------------------------------------------------- 1 | # 关于Python 2 | 3 | 编程语言中有很少能像Python这样,即 _简单易学_ 又 _功能强大_。在学习的过程中,我们会惊喜地发现,我们可以很轻松的专注于问题的解决方案,而不是你正在使用的编程语言的语法以及结构。 4 | 5 | Python的官方介绍: 6 | 7 | > Python是一个简单易学、功能强大的编程语言。它的数据结构非常高效,对面向对象编程的实现也足够简单。Python优美的语法和动态类型,连同解释型特性一起,使其在多个平台的许多领域都成为脚本处理以及快速应用开发的理想语言。 8 | 9 | 在下一章,我将更详细地讨论这些特性。 10 | 11 | ## 名字背后的故事 12 | 13 | Python语言的发明人Guido van Rossum以BBC的电视剧《Monty Python's Flying Circus》给这个语言命名。他不是特别喜欢那些为了食物而杀死动物的蛇,这些蛇会用它们长长的身体缠绕住那些动物从而勒死它们。 14 | 15 | ## Python的特点 16 | 17 | ### 简单 18 | 19 | Python语言非常简约。阅读优秀的Python程序感觉就像阅读英语,而且是语法非常严格的英语。Python的这种伪代码特性是其最大强项之一,它可让你专注于解决问题的办法而不是语言本身。 20 | 21 | ### 容易学习 22 | 23 | 正如你即将看到的,Python非常容易上手。就像刚刚提到的,Python具有格外简单的语法。 24 | 25 | ### 免费开源 26 | 27 | Python是一个 _FLOSS_(自由与开源软件)的例子。在一些简单的条款之下,你可以自由地分发这个软件的拷贝,阅读其源代码,修改它,或者将其一部分用到新的开源软件中。FLOSS基于共享知识社区的概念。这是Python的优势之一:它由Python社区创建并不断改进,这个社区希望Python变得越来越好。 28 | 29 | 30 | ### 高级语言 31 | 32 | 当你使用Python编写程序时,你永远不需要关心底层的细节,比如你的程序如何管理内存的使用等。 33 | 34 | ### 可移植 35 | 36 | 基于其开放源代码的特性,Python已经被移植到许多操作系统平台。只要在编程时避免使用与特定操作系统绑定的特性,你的Python程序可以不加修改地运行在这其中的任意平台。 37 | 38 | 你可以在GNU/Linux、Windows、FreeBSD、Macintosh、Solaris、OS/2、Amiga、AROS、AS/400、BeOS、OS/390、z/OS、Palm OS、QNX、VMS、Psion、Acorn RISC OS、VxWorks、PlayStation、Sharp Zaurus、Windows CE,甚至PocketPC平台上使用Python。 39 | 40 | 你甚至可以使用[Kivy](http://kivy.org)平台为iOS(iPhone、iPad)和Android创建游戏。 41 | 42 | ### 解释型 43 | 44 | 这个特性解释起来稍微复杂一点。 45 | 46 | 使用编译型语言(像C或者C++)编写的程序,会由编译器根据一系列的配置和选项,将源代码(如C或者C++)转换成一种电脑能够识别的语言(二进制代码,也就是0和1)。在运行程序时,由加载程序将编译后的二进制代码从硬盘复制到内存,然后开始运行。 47 | 48 | 而Python不需要编译成二进制代码。你只需从源代码直接运行程序。在运行的时候,Python将源代码转换成一种称为字节码的中间格式,再将其翻译计算机可以识别的二进制代码,然后开始运行。事实上,这一切都让Python的使用更为简单,因为你不必担心程序的编译、链接加载等问题。这也使得你的Python程序更易于移植,因为你只需要复制你的Python程序到另外一台计算机,它就可以工作了! 49 | 50 | ### 面向对象 51 | 52 | Python supports procedure-oriented programming as well as object-oriented programming (OOP). In _procedure-oriented_ languages, the program is built around procedures or functions which are nothing but reusable pieces of programs. In _object-oriented_ languages, the program is built around objects which combine data and functionality. Python has a very powerful but simplistic way of doing OOP, especially when compared to big languages like C++ or Java. 53 | Python同时支持面向过程和面向对象编程。在 _面向过程_ 编程的时候,程序围绕着过程或函数(可重复使用的程序片段)构建。在 _面向对象_ 编程的时候,程序围绕着对象(数据和功能的组合)构建。相比于C++或者Java这种大型语言来说,Python具有强大且简洁的面向对象编程的方式。 54 | 55 | ### 可扩展 56 | 57 | 如果有一段代码很关键,需要能够快速运行,或者是想要编写一些不愿开放的算法,你可以使用C或C++完成那部分程序,然后从你的Python程序中调用。 58 | 59 | ### 可嵌入 60 | 61 | 你可以将Python嵌入到C/C++程序,让你的程序的用户获得 _脚本化_ 的能力。 62 | 63 | ### 庞大的扩展库 64 | 65 | Python标准库非常庞大。它能够帮助你完成许多工作,包括正则表达式、生成文档、单元测试、线程、数据库、网页浏览器、CGI(公共网关接口)、FTP(文件传输协议)、电子邮件、XML(可扩展标记语言)、XML-RPC(远程方法调用)、HTML(超文本标记语言)、WAV(音频格式)文件、加密、GUI(图形用户界面)以及其它与特定操作系统相关的代码。这里需要强调一下,只要安装了Python,上面这些功能都可以做到。这被称作Python的 _开箱即用_ 哲学。 66 | 67 | 除了标准库,还有各式各样的其它高质量的扩展库,可以在[Python包索引](http://pypi.python.org/pypi)中找到它们。 68 | 69 | ### 小结 70 | 71 | Python的确是一个令人激动的、功能强大的语言。Python将性能和特性结合得恰到好处,让我们使用Python编程既有趣又简单。 72 | 73 | ## Python 3 vs 2 74 | 75 | 如果你不关心Python 2和Python 3的区别,可以跳过这一节。但是必须知道你正在使用的Python版本。本书使用Python 3完成。 76 | 77 | 一旦你充分地理解或学习使用了两个版本中的一个,你会很容易了解两个版本之间的区别,也就可以轻而易举地切换到另外一个版本进行编程。重点是学习编程和理解Python语言的核心,这也是本书的目标。一旦你达成这个目标,你可以很容易的根据情况选择使用Python 2或Python 3。 78 | 79 | 关于Python 2和Python 3的详细区别,请参考: 80 | - [Python 2的未来](http://lwn.net/Articles/547191/) 81 | - [将Python 2代码移植到Python 3](https://docs.python.org/3/howto/pyporting.html) 82 | - [怎样书写能够同时运行在Python2 and 3中的代码](https://wiki.python.org/moin/PortingToPy3k/BilingualQuickRef) 83 | - [使用Python 3: 深度指南](http://python3porting.com) 84 | 85 | ## 看看其他程序员怎么说的 86 | 87 | You may find it interesting to read what great hackers like Eric S. Raymond (ESR) have to say about Python: 88 | 89 | - _Eric S. Raymond_ is the author of "The Cathedral and the Bazaar" and is also the person who coined the term _Open Source_. He says that [Python has become his favorite programming language](http://www.python.org/about/success/esr/). This article was the real inspiration for my first brush with Python. 90 | - _Bruce Eckel_ is the author of the famous 'Thinking in Java' and 'Thinking in C++' books. He says that no language has made him more productive than Python. He says that Python is perhaps the only language that focuses on making things easier for the programmer. Read the [complete interview](http://www.artima.com/intv/aboutme.html) for more details. 91 | - _Peter Norvig_ is a well-known Lisp author and Director of Search Quality at Google (thanks to Guido van Rossum for pointing that out). He says that [writing Python is like writing in pseudocode](https://news.ycombinator.com/item?id=1803815). He says that Python has always been an integral part of Google. You can actually verify this statement by looking at the [Google Jobs](http://www.google.com/jobs/index.html) page which lists Python knowledge as a requirement for software engineers. 92 | 93 | 或许你感兴趣顶尖的黑客,比如Eric S. Raymond (ESR),关于Python是怎么说的: 94 | 95 | > _Eric S. Raymond_,是《The Cathedral and the Bazaar》的作者,也是发明 _开放源代码_ 这一术语的人。他说,[Python已经成为他最喜欢的编程语言](http://www.python.org/about/success/esr/)。这篇文章给了我关注Python的第一个灵感。 96 | 97 | > _Bruce Eckel_,是著名的《Thinking in Java》和《Thinking in C++》的作者。他说,没有什么语言能比Python更能令他高效。他说Python或许是唯一让程序员工作更简单的编程语言。完整的采访请参考[这里](http://www.artima.com/intv/aboutme.html)。 98 | 99 | > _Peter Norvig_,是另一个著名编程语言Lisp的作者,Google搜索质量主管(感谢Guido van Rossum指出)。他说[用Python编程就像是在写诗一样](https://news.ycombinator.com/item?id=1803815)。他还说,Python一直是Google代码的重要组成部分。你可以通过查看[Google Jobs](http://www.google.com/jobs/index.html)验证这句话。从这个页面可以看出,Python知识是Google招聘软件工程师的必备技能。 100 | -------------------------------------------------------------------------------- /a-byte-of-python3/basics.md: -------------------------------------------------------------------------------- 1 | # 基础 2 | 3 | 只能输出`Hello World`是不够的,我们需要更加复杂的功能,比如我们输入一些内容,处理它,然后输出。在Python中,我们使用常量和变量可以实现这些。除此之外,在这一章我们还将学习一些其他的概念。 4 | 5 | ## 注释 6 | 7 | _注释_ 是`#`符号右边的内容,有了注释可以帮助其他人阅读你的程序。 8 | 9 | 例如: 10 | 11 | ```python 12 | print('hello world') # 注意:print是一个函数 13 | ``` 14 | 15 | 或者: 16 | 17 | ```python 18 | # 注意:print是一个函数 19 | print('Hello World') 20 | ``` 21 | 22 | 程序中的注释应该尽可能的多,可以是以下内容: 23 | 24 | - 解释假设 25 | - 解释重要决策 26 | - 解释重要细节 27 | - 解释你在试图解决的问题 28 | - 解释程序中你在努力克服的问题,等等。 29 | 30 | [代码表示方法,注释表示原因。](http://www.codinghorror.com/blog/2006/12/code-tells-you-how-comments-tell-you-why.html) 31 | 32 | 程序的易读性非常重要,可以帮助阅读你的代码的人理解它的功能。而且,这个人很有可能是六个月之后的你自己! 33 | 34 | ## 字面值 35 | 36 | 字面值可以是数字,例如`5`、`1.23`,也可以是`这是一个字符串`或`它一个字符串!`这样的字符串。 37 | 38 | 它被称为字面值,因为它是 _字面_ 的^__^。直接使用它的字面值。`2`就是那个自然数`2`,没有其他的含义,它是一个 _常数_,它的值是不能改变的。因此这类数值被称为字面值。 39 | 40 | ## 数字 41 | 42 | 数字主要有两种类型--整数和浮点数。 43 | 44 | 整数例如`2`。 45 | 46 | 浮点数例如`3.23`和`52.3E-4`。符号`E`表示10的次方。在这种情况下,`52.3E-4`的意思是`52.3的-4次方`。 47 | 48 | > **有经验的程序员要注意:** 49 | > Python中没有单独的`long`长整型。`int`整型可以是任意大小的整数。 50 | 51 | ## 字符串 52 | 53 | 字符串是 _字符_ 的 _序列_,大部分情况下字符串就是一串单词。 54 | 55 | 字符串几乎会出现在你写的每一个Python程序中,因此请仔细阅读以下内容。 56 | 57 | ### 单引号 58 | 59 | 你可以使用单引号指定字符串,例如`'Quote me on this'`。所有的空白,包括空格和制表符(TAB键)都按原样保留。 60 | 61 | ### 双引号 62 | 63 | Python中双引号和单引号一样,可以用来指定字符串。例如`"What's your name?"` 64 | 65 | ### 三重引号 66 | 67 | 您可以使用三重引号-(`"""`或`'''`)指定多行字符串。在三重引号中您可以随意使用单引号和双引号。例如: 68 | 69 | ```python 70 | '''这是一个多行字符串。这是第一行。 71 | 这是第二行。 72 | 我问:“你叫什么名字?” 73 | 他回答:“邦德,詹姆士·邦德”。 74 | ''' 75 | ``` 76 | 77 | #### 字符串是不可改变的 78 | 79 | 这意味着,一旦您创建了一个字符串,你就不能改变它。虽然这看起来有些刻板,实际上未必。在后面的章节我们写了很多程序之后,将会明白这并不是一个限制。 80 | 81 | > **C/C++程序员要注意:** 82 | > 在Python中没有单独的`char`(字符型)数据。对于编程来说它并不是必须的,我相信你也不会想念它。 83 | 84 | > **Perl/PHP程序员要注意:** 85 | > 记住,单引号字符串和双引号字符串是相同的——使用方式完全一致。 86 | 87 | #### 字符串格式化 88 | 89 | 有时候我们需要使用某些信息构建字符串。这时我们可以使用`format()`方法。 90 | 91 | 保存下面几行代码到文件`str_format.py`中: 92 | 93 | ```python 94 | age = 20 95 | name = 'Swaroop' 96 | 97 | print('{0} was {1} years old when he wrote this book'.format(name, age)) 98 | print('Why is {0} playing with that python?'.format(name)) 99 | ``` 100 | 101 | 输出结果为: 102 | 103 | ``` 104 | python str_format.py 105 | Swaroop was 20 years old when he wrote this book 106 | Why is Swaroop playing with that python? 107 | ``` 108 | 109 | **它是如何工作的:** 110 | 111 | 我们可以对特定的格式的字符串调用format方法,将格式中指定的位置用适当的参数取代。 112 | 113 | 我们可以看到,在字符串的第一个位置,我们使用`{0}`对应于变量`name`,这是format方法的第一个参数。以此类推,第二个位置`{1}`对应于`age`,这是format方法的第二个参数。注意,Python从0开始计数,这意味着第一位置的索引是0,第二个位置的索引是1,以此类推。 114 | 115 | 当然,我们也可以使用字符串的连接达到上述目的 116 | 117 | ```python 118 | name + 'is' + str(age) + 'years old' 119 | ``` 120 | 121 | 首先,这样写代码不那么优雅且容易出错。其次,`format`方法会自动将参数转换为字符串,而不是显式地转换为字符串。最后,当使用的`format`方法,我们无需关注所使用的变量就可以改变信息的内容,反之亦然。 122 | 123 | 还要注意,这些数字(索引)都是可选的,所以你也可以写成: 124 | 125 | ```python 126 | age = 20 127 | name = 'Swaroop' 128 | 129 | print('{} was {} years old when he wrote this book'.format(name, age)) 130 | print('Why is {} playing with that python?'.format(name)) 131 | ``` 132 | 133 | 输出结果与上一版程序相同。 134 | 135 | 我们还可以给参数命名: 136 | 137 | ```python 138 | age = 20 139 | name = 'Swaroop' 140 | 141 | print('{name} was {age} years old when he wrote this book'.format(name=name, age=age)) 142 | print('Why is {name} playing with that python?'.format(name=name)) 143 | ``` 144 | 145 | 输出结果仍然与上一版程序相同。 146 | 147 | 从Python 3.6版本开始,增加了一种更加简洁的方法处理参数的命名,叫做"f-strings": 148 | 149 | ```python 150 | age = 20 151 | name = 'Swaroop' 152 | 153 | print(f'{name} was {age} years old when he wrote this book') # 注意字符串前面的'f' 154 | print(f'Why is {name} playing with that python?') # 注意字符串前面的'f' 155 | ``` 156 | 157 | 输出结果依然与上一版程序相同。 158 | 159 | Python在`format`方法中会使用每个参数值替代格式中的对应的位置。关于格式,下面有更详细的说明: 160 | 161 | ```python 162 | # 用 (.) 表示浮点数的精度,下面代码的结果为'0.333' 163 | print('{0:.3f}'.format(1.0/3)) 164 | # 两侧补充下划线,中间是指定的字符串 165 | # (^) 表示一共有11个字符,下面代码的结果为'___hello___' 166 | print('{0:_^11}'.format('hello')) 167 | # 基于参数的格式化,下面代码的结果为'Swaroop wrote A Byte of Python' 168 | print('{name} wrote {book}'.format(name='Swaroop', book='A Byte of Python')) 169 | ``` 170 | 171 | 输出为: 172 | 173 | ``` 174 | 0.333 175 | ___hello___ 176 | Swaroop wrote A Byte of Python 177 | ``` 178 | 179 | 既然我们正在讨论格式化,大家应该也已经注意到了:`print`函数每次都会在输出的最后增加一个回车(`\n`) ,这保证了我们重复调用`print`函数每次都能输出在不同的行。如果想要去掉这个回车,可以给`print`函数一个`end`参数并设置为空即可,例如: 180 | 181 | ```python 182 | print('a', end='') 183 | print('b', end='') 184 | ``` 185 | 186 | 输出为: 187 | 188 | ``` 189 | ab 190 | ``` 191 | 192 | 也可以设置`end`为空格: 193 | 194 | ```python 195 | print('a', end=' ') 196 | print('b', end=' ') 197 | print('c') 198 | ``` 199 | 200 | 输出为: 201 | 202 | ``` 203 | a b c 204 | ``` 205 | 206 | ### 转义字符 207 | 208 | 如果你想要一个带单引号 (`'`)的字符串,你会怎么写呢?例如:你想输出`"What's your name?"`。 你不能写成`'What's your name?'`, 因为python会搞不清字符串是从哪开始又是从哪结束。所以你需要告诉python字符串中间的单引号并不是字符串的结束标志。 利用 _转义字符_ 可以做到这点。 将单引号替换成 `\'` - 注意反斜杠,这样字符串就变成`'What\'s your name?'`了. 209 | 210 | 另一个办法是使用双引号`"What's your name?"`。类似的,当你需要双引号的时候和单引号的情况类似,必须加上反斜杠 `\"`。而反斜杠也必须写成`\\`。 211 | 212 | 如果你需要一个两行字符串呢? 一个办法是使用前面提到的三引号,另外一个办法是使用换行的转义字符`\n`开始新的一行,例如: 213 | 214 | ```python 215 | 'This is the first line\nThis is the second line' 216 | ``` 217 | 218 | 还有一个有用的转义字符`\t`,表示Tab。转义字符太多了,本文只列举了最常用的。 219 | 220 | 另一个值得注意的地方是,在一个字符串末尾的反斜杠表示这个字符串与下一行的字符串要合并使用,中间没有回车, 例如: 221 | 222 | ```python 223 | "This is the first sentence. \ 224 | This is the second sentence." 225 | ``` 226 | 227 | 上面的字符串等价于 228 | 229 | ```python 230 | "This is the first sentence. This is the second sentence." 231 | ``` 232 | 233 | ### 原始字符串 234 | 235 | 如果你不希望Python对字符串进行特别处理, 例如不希望使用转义字符, 可以为字符串加上前缀`r`或`R`,这样的字符串就是 _原始_ 字符串。例如: 236 | 237 | ```python 238 | r"Newlines are indicated by \n" 239 | ``` 240 | 241 | > **关于正则表达式的使用请注意** 242 | > 243 | > 永远使用原始字符串编写正则表达式, 否则会需要大量的反斜杠, 例如反向引用可以表示为`'\\1'`或`r'\1'`. 244 | 245 | ## 变量 246 | 247 | 仅仅使用字面值会很无聊——我们需要某种方式存储可变的信息,然后使用它们。这就要引入 _变量_。变量是它的名字一样——他们的值会有变化,你可以用变量存储任何值。变量是你电脑中存储信息的内存的一部分。和字面值不一样的是,您需要某种方法来访问这些变量,因此首先需要给他们命名。 248 | 249 | ## 标识符命名 250 | 251 | 变量是标识符的一个例子。_标识符_ 是用来识别一些 _东西_ 的名字。我们遵循以下的标识符命名规则: 252 | 253 | * 标识符的第一个字符必须是字母表中的一个字母(大写或小写的ASCII或Unicode字符)或下划线(`_`)。 254 | * 标识符的其余部分可以包含字母(大写或小写的ASCII或Unicode字符)、下划线(`_`)或数字(0 - 9)。 255 | * 标识符的名称都是区分大小写的。例如,`myname`和`myName`**不**相同。注意前者中`n`小写和后者中`N`大写。 256 | * _有效_ 的标识符名称的例子有:`i`, `name_2_3`。_无效_ 的标识符名字的例子有:`2things`,`this is spaced out`,`my-name`和`>a1b2_c3`。 257 | 258 | ### 数据类型 259 | 260 | 变量可以保存不同的 _数据类型_ 的数值。基本类型是数字和字符串,我们在前面已经讨论过了。在后面的章节中,我们将看到如何使用类创建自己的类型。 261 | 262 | ### 对象 263 | 264 | 记住,Python把程序中使用的任何东西都看作 _对象_。这意味着,在Python中,我们说的“这个 _东西_”,其实就是“这个 _对象_”。 265 | 266 | > **关于面向对象编程要注意:** 267 | > Python是一门强面向对象语言,在Python中一切皆是对象,包括数字、字符串和函数。 268 | 269 | 现在我们将看到如何使用变量以及字面值。请保存下面的示例,然后运行这个程序。 270 | 271 | ## 怎样写Python程序 272 | 273 | 我们为后面的章节明确一个保存和运行一个Python程序的标准流程: 274 | 275 | ### 使用VSCode 276 | 277 | 1. 打开VSCode。 278 | 2. 新建一个文件,保存为指定的文件名。 279 | 3. 将示例代码输入到文件中。 280 | 4. 点击右上角的绿色小三角按钮运行程序。 281 | 282 | 注意:如果你需要指定命令行参数,使用菜单项`查看`->`运行`,点击`create a launch.json file`,然后在弹出的对话框里选择`Python File`,就可以创建一个`launch.json`文件。通常情况下文件看其实是这样的: 283 | 284 | ```json 285 | { 286 | // 使用 IntelliSense 了解相关属性。 287 | // 悬停以查看现有属性的描述。 288 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 289 | "version": "0.2.0", 290 | "configurations": [ 291 | { 292 | "name": "Python: 当前文件", 293 | "type": "python", 294 | "request": "launch", 295 | "program": "${file}", 296 | "console": "integratedTerminal" 297 | } 298 | ] 299 | } 300 | ``` 301 | 302 | 如果想要增加命令行参数,在`configurations`中增加`args`,如下所示: 303 | 304 | ```json 305 | { 306 | // 使用 IntelliSense 了解相关属性。 307 | // 悬停以查看现有属性的描述。 308 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 309 | "version": "0.2.0", 310 | "configurations": [ 311 | { 312 | "name": "Python: 当前文件", 313 | "type": "python", 314 | "request": "launch", 315 | "program": "${file}", 316 | "console": "integratedTerminal", 317 | "args": ["-d", "test.py"] 318 | } 319 | ] 320 | } 321 | ``` 322 | 323 | 然后运行即可 324 | 325 | ### 使用其他编辑器 326 | 327 | 1. 打开选择的编辑器。 328 | 2. 输入例子中给出的程序代码。 329 | 3. 保存为指定的文件名。 330 | 4. 在命令行使用`python program.py`运行程序。 331 | 332 | ### 例如: 使用变量和字面值 333 | 334 | 输入并运行下面的程序 335 | 336 | ```python 337 | # Filename : var.py 338 | i = 5 339 | print(i) 340 | i = i + 1 341 | print(i) 342 | 343 | s = '''This is a multi-line string. 344 | This is the second line.''' 345 | print(s) 346 | ``` 347 | 348 | 输出结果为: 349 | 350 | ``` 351 | 5 352 | 6 353 | This is a multi-line string. 354 | This is the second line. 355 | ``` 356 | 357 | **它是如何工作的** 358 | 359 | 下面介绍这个程序如何工作的。首先,我们使用赋值运算符(`=`)为变量`i`指定了字面值`5`,这一行叫做一个语句。在这个例子中,我们将名为`i`的变量与值`5`联系起来。接下来,我们使用`print`函数打印`i`的值,不出所料,打印变量的值到屏幕上。 360 | 361 | 然后,我们给存储在变量`i`中的值加`1`后,再存回到`i`中。然后,我们把它打印出来,和我们想的一样,我们得到值`6`。 362 | 363 | 同样,我们为字符串变量`s`指定了字面值字符串,然后打印它。 364 | 365 | > **静态语言程序员应注意** 366 | > 给变量赋值之后就可以使用它。不需要/不使用声明或数据类型定义。 367 | 368 | ## 逻辑行与物理行 369 | 370 | 物理行是当你写程序时 _看_ 到的一行。逻辑行是 _Python看到_ 的一个单独语句。Python默认一个 _物理行_ 对应一个 _逻辑行_。 371 | 372 | 一个逻辑行是一个语句,例如`print('Hello World')`。如果它本身只占用一行(像你在一个编辑器中看到的),那么,它也是一个物理行。 373 | 374 | 默认情况下,Python鼓励一行写一个语句的用法,这使代码更可读。 375 | 376 | 如果您想要一个物理行包括多个逻辑行,那么您必须使用一个表示逻辑行/语句结束的分号`;`显式地指明。例如: 377 | 378 | ```python 379 | i = 5 380 | print(i) 381 | ``` 382 | 383 | 与 384 | 385 | ```python 386 | i = 5; 387 | print(i); 388 | ``` 389 | 390 | 等效。同样可写成: 391 | 392 | ```python 393 | i = 5; print(i); 394 | ``` 395 | 396 | 或者是: 397 | 398 | ```python 399 | i = 5; print(i) 400 | ``` 401 | 402 | 然而,我**强烈建议**你坚持**一个物理行最多包括一个逻辑行**。这意味着你永远都不要使用分号。事实上,我**从未**使用,甚至在Python程序中从来没有见过一个分号。 403 | 404 | 上述观念在以下的情况中更显示出它的可取之处:如果你有一个很长的代码行,你可以通过使用反斜杠把它分解为多个物理行。这是被称为**显式行连接**,在这里反斜杠又被叫做续行符: 405 | 406 | ```python 407 | s = '这是一个字符串。 \ 408 | 这是字符串的继续。' 409 | print(s) 410 | ``` 411 | 412 | 输出结果为: 413 | 414 | ``` 415 | 这是一个字符串。这是字符串的继续。 416 | ``` 417 | 418 | 同样的, 419 | 420 | ```python 421 | i = \ 422 | 5 423 | ``` 424 | 425 | 与 426 | 427 | ``` 428 | i = 5 429 | ``` 430 | 431 | 相同 432 | 433 | 有一种情况不需要反斜杠就可以连接多行。这就是当逻辑行有一个左圆括号、左方括号或左花括号,但在同一行没有一个右括号结束它。这被称为**隐式行连接**。当我们在以后的章节编写程序使用列表时,看到这种情况。 434 | 435 | ### 缩进 436 | 437 | Python中的空白非常重要。实际上应该这么说,**在一行开始的空白非常重要**。这被称为 **缩进**。在逻辑行开头的前导空白(空格和制表符)用于确定逻辑行的缩进级别,它用于依次确定语句的分组。 438 | 439 | 这意味着一起的语句**必须**有相同的缩进。每一个这样的语句组被称为**块**。在后面的章节,我们将会举例说明块是何等的重要。 440 | 441 | 你应该记住的一件事是,错误的缩进可以产生错误。例如: 442 | 443 | ```python 444 | i = 5 445 | print('值是 ', i) # 错误! 注意在行的开头有一个空格 446 | print('重复,值是 ', i) 447 | ``` 448 | 449 | 当运行它时,将会发生下面的错误: 450 | 451 | ``` 452 | File "whitespace.py", line 4 453 | print('Value is ', i) # Error! Notice a single space at the start of the line 454 | ^ 455 | IndentationError: unexpected indent 456 | ``` 457 | 458 | 请注意,这里第二行的开头有一个空格。Python的错误提示告诉我们程序的语法是无效的,即程序写的不正确。这意味着,**你不能任意开始语句中的新块**(当然,除了默认的主块,您一直已经使用的)。您可以使用新块的情况,将在后面的章节详细,如`控制流`。 459 | 460 | > **如何缩进** 461 | > 462 | > 缩进只使用4个空格,这是Python官方推荐的标准。好的编辑器会为你自动这样做。确保你使用数量一致的缩进空格,否则你的程序将无法运行,或者产生未知的输出结果。 463 | 464 | > **静态语言程序员应注意** 465 | > 466 | > Python为块总是使用缩进,从来不用花括号。运行`from __future__ import braces`可以了解更多。 467 | 468 | ### 小结 469 | 470 | 现在,我们已经经历了许多细节,接下来我们会接触到更有趣的东西,如控制流语句。在这之前你一定要熟悉本章的内容。 471 | 472 | -------------------------------------------------------------------------------- /a-byte-of-python3/control_flow.md: -------------------------------------------------------------------------------- 1 | # 控制流 2 | 3 | 带目前为止,我们看到的程序都是自上而下顺序执行的。如果你想改变它的执行顺序,该怎样做呢?例如,你想让程序作出根据不同的情况采取不同的措施。例如,根据一天的时间不同,打印`早上好`或`晚上好`。 4 | 5 | 你可能已经猜到了,这要通过使用控制流语句。在Python中有三个控制流语句-- `if`, `for` 和 `while`。 6 | 7 | ## if 语句 8 | 9 | `if`语句是用来检查一个条件:**如果**条件为真,我们就运行一个代码块(你为**if代码块**),**否则**我们执行另一个代码块(称为**else代码块**)。**else子句**是可选的。 10 | 11 | 例如 (保存为 `if.py`): 12 | 13 | ```python 14 | number = 23 15 | guess = int(input('请输入一个整数: ')) 16 | 17 | if guess == number: 18 | # 新块从这里开始 19 | print('恭喜,你猜对了。') 20 | print('(但你没有获得任何奖品!)') 21 | # 新块在这里结束 22 | elif guess < number: 23 | # 另一个块 24 | print('不对,你猜的有点儿小') 25 | # 在一个块中你可以做你想做的任何事... 26 | else: 27 | print('不对,你猜的有点大') 28 | # 你猜的数比number大时才能到这里 29 | 30 | print('完成') 31 | # if语句执行完后,最后的语句总是被执行 32 | ``` 33 | 34 | 输出: 35 | 36 | ```shell 37 | C:\> python if.py 38 | 请输入一个整数: 50 39 | 不对,你猜的有点儿大 40 | 完成 41 | 42 | C:\> python if.py 43 | 请输入一个整数: 22 44 | 不对,你猜的有点儿小 45 | 完成 46 | 47 | C:\> python if.py 48 | 请输入一个整数: 23 49 | 恭喜,你猜对了。 50 | (但你没有获得任何奖品!) 51 | 完成 52 | ``` 53 | 54 | **它是如何工作的:** 55 | 56 | 在这个程序中,用户输入了一个猜测的数字,我们的程序检查它是否等于我们预先指定的数字。我们给变量`number`设置我们想要的任何整数,比如`23`。然后,我们使用`input()` 函数获取用户的输入。函数是可重用的代码块,我们在[下一章](function.md)中会了解关于它们的更多信息。 57 | 58 | 我们给内置的`input`函数提供一个字符串,该函数将其打印到屏幕上并等待用户输入。一旦我们输入一些内容并按下回车键,`input()`函数把我们的输入作为一个字符串返回。然后,我们使用`int`将这个字符串转换为整数,并将其存储在变量`guess`中。实际上,`int` 是一个类,但现在你仅需要知道可以使用它来将一个字符串转变为一个整数(假设文本中的字符串包含一个有效的整数)。 59 | 60 | 接下来,我们比较用户猜的数和我们选择的数。如果他们相等,我们打印一条成功的消息。注意,我们使用缩进级别告诉Python哪个语句属于哪个代码块。这就是为什么缩进在ython中是如此重要。我希望你坚持"一致的缩进"的规则,好吗? 61 | 62 | 注意,`if`语句在最后有一个冒号——我们告诉Python:下一行要开始一个代码块。 63 | 64 | 然后,我们检查猜的数是否小于这个数字,如果是,我们通知用户,他们猜的数必须比那个数稍高。我们这里使用的是`elif`子句,实际上将两个相关的 `if else-if else`语句组合为一个语句`if-elif-else`,这使程序更简单且减少所缩进的数量。 65 | 66 | `elif`和`else`语句也必须在逻辑行结束时有一个冒号,后跟相应的代码块(当然要通过适当的缩进) 67 | 68 | 你可以在`if`语句的if块中有另一个`if`语句——这称为`if`语句嵌套。 69 | 70 | 记住,`elif` 和`else`部分是可选的。一个最小的有效的`if`语句是: 71 | 72 | ```python 73 | if True: 74 | print('是的,它为真') 75 | ``` 76 | 77 | 在Python执行完成完整的if语句以及相关的`elif`和`else`子句,它移动到`if`包含语句的代码块中下一个语句。在本例中,它是主代码块(程序开始执行的地方),接下来的语句是 `print('完成')`。在这之后,Python将看到程序的结尾,并完成它。 78 | 79 | 尽管这是一个非常简单的程序,但是它麻雀虽小五脏俱全。Python的语法简单而且直接(对那些有C/C++背景的人是惊人的简单)。刚一开始的时候你可能还需要注意一下Python的语法要求,但经过一段时间的练习,你会感到非常舒服,有着“自然”的感觉。 80 | 81 | > **C/C++程序员需要注意** 82 | > 在Python中没有`switch`语句。您可以使用一个`if..elif..else`语句做同样的事(和在某些情况下,使用[字典](data_structures.md)去做更快) 83 | 84 | ## while语句 85 | 86 | 如果条件为真,`while`语句允许您多次执行一个代码块。`while`语句是被称为**循环语句**的一种。`while`语句可以有一个可选的`else`子句。 87 | 88 | 例如 (保存为while.py): 89 | 90 | ```python 91 | number = 23 92 | running = True 93 | 94 | while running: 95 | guess = int(input('输入一个整数 : ')) 96 | 97 | if guess == number: 98 | print('恭喜,你猜对了。') 99 | # 这使while循环停止 100 | running = False 101 | elif guess < number: 102 | print('不对,你猜的有点儿小。') 103 | else: 104 | print('不对,你猜的有点儿大。') 105 | else: 106 | print('while循环结束。') 107 | # 在这做你想做的任何事 108 | 109 | print('完成') 110 | ``` 111 | 112 | 输出: 113 | 114 | ```shell 115 | C:>\ python while.py 116 | 输入一个整数 : 50 117 | 不对,你猜的有点儿大。 118 | 输入一个整数 : 22 119 | 不对,你猜的的点儿小。 120 | 输入一个整数 : 23 121 | 恭喜,你猜对了。 122 | while循环结束。 123 | 完成 124 | ``` 125 | 126 | **它是如何工作的:** 127 | 128 | 在这个程序中,我们继续玩猜谜游戏。比上一个程序进步的一点在于,允许用户一直猜直到他猜对——每次猜测不需要向上一个程序那样重新运行。这演示了如何正确的使用`while`语句。 129 | 130 | 我们移动`input`和`if`语句到`while`循环中,在`while`循环前,设置变量`running`为`True`。首先,我们检测变量`running`是否为`True`,然后继续执行相应的**while代码块**。在这个块执行完后,再检测条件。如果这时变量`running`为真,我们就再次执行while代码块;否则,我们执行可选的else代码块,然后执行下面的语句。 131 | 132 | 当`while`循环的条件变为`False`时执行`else代码块`,这也可能发生在第一次条件检测时。如果你使用`break`语句退出循环的话,那么`else`子句就不会被执行,否则当`while`循环的条件变为`False`时就会执行。 133 | 134 | 在这里`True`和`False`被称为布尔类型,你可以认为它们分别相当于值`1`和`0`。 135 | 136 | > **C/C++程序员注意:** 137 | > 记住, while循环可以有else子句。 138 | 139 | ## `for`循环 140 | 141 | `for..in`语句是另一个循环语句,它**迭代**一个序列中的每一个对象。在后面的章节,我们将会看到更多关于[序列](data_structures.md)的细节。现在你需要知道的是:序列是一个有序的对象的集合。 142 | 143 | 例如 (保存为 for.py): 144 | 145 | ```python 146 | for i in range(1, 5): 147 | print(i) 148 | else: 149 | print('for循环结束') 150 | ``` 151 | 152 | 输出: 153 | 154 | ``` 155 | $ python for.py 156 | 1 157 | 2 158 | 3 159 | 4 160 | for循环结束 161 | ``` 162 | 163 | **它是如何工作的:** 164 | 165 | 在这个程序里,我们打印了一个数字的**序列**。我们使用内置的`range`函数生成了这个数字序列。 166 | 167 | 我们给`range`函数提供了两个数字作为参数,`range`返回一个从第一个数字到第二个数字的一个数字序列。例如, `range(1,5)`给出序列`[1, 2, 3, 4]`。默认情况下,`range` 步长值为1。如果我们为`range`函数提供第三个参数,那么这个参数就是新的步长值。例如`range(1,5,2)`得到`[1, 3]`。请记住,数字的范围小于第二个参数,即它**不**包括第二个数字。 168 | 169 | 注意,在for循环中`range()`一次只生成一个数字。如果你想立刻看到完整的数字序列,使用`list(range())`。例如, `list(range(5))` 会输出 `[0, 1, 2, 3, 4]`。list(列表)将在[数据结构](data_structures.md)中做更加详细的解释。 170 | 171 | `for`循环然后遍历这个范围,`for i in range(1,5)`相当于 `for i in [1, 2, 3, 4]`,这就像把序列中的每一个数(或对象)分配给`i`,一次一个,然后为使用这个`i`值执行该代码块。在本例中,我们只是简单的打印它的值。 172 | 173 | 记住,`else`子句是可选的。如果有`else`子句,当for循环结束时它会执行一次,除非遇到`break`语句。 174 | 175 | 记住,`for..in`循环可以作用于任何序列。在这里,我们对一个由内建的`range`函数生成的一个数字列表,但是一般来说,我们可以使用任意对象的任意序列!在后面的章节,我们将详细讨论这个问题。 176 | 177 | > **C/C++/Java/C#程序要注意** 178 | > Python的`for`循环完全不同于C/C++的`for`循环。C#程序员会注意到,在Python中`for`循环类似于C#中的`foreach`循环。Java程序员会注意到,这也类似于在Java 1.5中的`for (int i : IntArray)`。 179 | 180 | > 在C/C++中,如果你想写`for (int i = 0; i < 5; i++)`,那么在Python中你只要写`for i in range(0, 5)`。正如您可以看到的,在Python中`for`循环更简单,不易出错且让人印象深刻。 181 | 182 | ## break语句 183 | 184 | `break`语句用来**跳出**一个循环语句,即停止执行一个循环语句,即使循环条件还没有成为`False`或序列还没有被完全遍历。 185 | 186 | 很重要的一点是,如果你**跳出**`for`或`while`循环,任何相应的循环else块是**不**执行的。 187 | 188 | 例子 (保存为break.py): 189 | 190 | ```python 191 | while True: 192 | s = input('输入一些东西 : ') 193 | if s == 'quit': 194 | break 195 | print('字符串的长度是', len(s)) 196 | print('完成') 197 | ``` 198 | 199 | 输出: 200 | 201 | ``` 202 | $ python break.py 203 | 输入一些东西: Programming is fun 204 | 字符串的长度是 18 205 | 输入一些东西: When the work is done 206 | 字符串的长度是 21 207 | 输入一些东西 : if you wanna make your work also fun: 208 | 字符串的长度是 37 209 | 输入一些东西 : use Python! 210 | 字符串的长度是 11 211 | 输入一些东西 : quit 212 | 完成 213 | ``` 214 | 215 | **它是如何工作的:** 216 | 217 | 在这个程序中,我们通过循环不断地获取用户的输入,并打印输入字符串的长度。我们提供一个特殊的条件--通过检查用户输入是否是`quit`来结束程序。我们通过**跳出**循环停止程序,到达程序的结束位置。 218 | 219 | 获得字符串的长度可以使用内建的`len`函数。 220 | 221 | 记住, `break`语句同样适用于`for`循环。 222 | 223 | **Swaroop 的诗意的Python** 224 | 225 | 在这里输入一个我写的迷你诗: 226 | 227 | ``` 228 | 编程自有颜如玉 229 | 每当工作完成时 230 | 如你也想颜如玉: 231 | 使用Python! 232 | ``` 233 | 234 | ## continue语句 235 | 236 | `continue`语句是用来告诉Python跳过当前循环代码块中其余的语句,**继续**循环的下一次迭代。 237 | 238 | 例子 (保存为continue.py): 239 | 240 | ```python 241 | while True: 242 | s = input('输入一些东西: ') 243 | if s == 'quit': 244 | break 245 | if len(s) < 3: 246 | print('太小') 247 | continue 248 | print('输入的东西有足够的长度') 249 | # 在这做其它各种处理... 250 | ``` 251 | 252 | 输出: 253 | 254 | ``` 255 | $ python continue.py 256 | 输入一些东西: a 257 | 太小 258 | 输入一些东西: 12 259 | 太小 260 | 输入一些东西: abc 261 | 输入的东西有足够的长度 262 | 输入一些东西: quit 263 | ``` 264 | 265 | **它是如何工作的:** 266 | 267 | 在这个程序中,我们接受来自用户的输入,但是只有在输入的字符串至少3个字符时才处理。因此,我们使用内建的`len`函数来获得长度,如果长度小于3,通过使用`continue`句,我们跳过代码块中的其余语句。否则,在循环中的其余语句将被执行--这里可以进行其他的处理。 268 | 269 | 注意,`continue`语句同样适用于`for`循环。 270 | 271 | ### 小结 272 | 273 | 我们已经看到了如何使用三个控制流语句--`if`、`while`和`for`以及与它们相关的 `break`和`continue`语句。由于这些都是Python最常用的部分,你需要尽快和他们混得熟一点。 274 | 275 | 接下来,我们将学习如何创建和使用函数。 276 | -------------------------------------------------------------------------------- /a-byte-of-python3/dedication.md: -------------------------------------------------------------------------------- 1 | # 致敬 2 | 3 | 感谢[Kalyan Varma](http://www.kalyanvarma.net/)和其他[PESIT](http://www.pes.edu/)的管理者,他们带领我们认识了GNU/Linux和开源软件。 4 | 5 | 纪念[Atul Chitnis](http://www.nextbigwhat.com/atul-chitnis-obituary-297/),一位值得永久怀念的朋友。 6 | 7 | 感谢[互联网的缔造者](http://www.ibiblio.org/pioneers/index.html)。这本书第一次出版是在2003年,到现在仍然非常受欢迎,感谢那些充满想象力的互联网缔造者,创造者了这么好的一个知识分享平台。 -------------------------------------------------------------------------------- /a-byte-of-python3/exceptions.md: -------------------------------------------------------------------------------- 1 | # 异常 2 | 3 | 当**意外的**情况在你的程序中发生时就会产生异常。例如,当你尝试读取一个文件但它并不存在时,或者当你要删除一个正在运行的程序的时候,这类情况会通过引发**异常**来处理。 4 | 5 | 类似地,如果你的程序有一些无效的语句,Python也会**抛出**错误提示告诉你这里有一些**错误**。 6 | 7 | ## 错误 8 | 9 | 我们来看一下一个简单的`print`函数。如果我们把`print`写成了`Print`会怎样?注意字母的大小写。这是Python会**抛出**一个语法错误。 10 | 11 | ```python 12 | >>> Print("Hello World") 13 | Traceback (most recent call last): 14 | File "", line 1, in 15 | NameError: name 'Print' is not defined 16 | >>> print("Hello World") 17 | Hello World 18 | ``` 19 | 20 | 我们注意到抛出了一个`NameError`的错误,以及这个错误发生的位置。这就是当错误发生的时候**错误处理程序**所做的事情。 21 | 22 | ## 异常 23 | 24 | 我们将**尝试**读取用户的输入。我们输入下面的第一行代码并按下`Enter`执行。当你的计算机提示你输入时,在 Mac 上按下 [Ctrl-d] 或者在 Windows 上按下 [Ctrl-z] 来观察会发生什么(如果你使用的是 Windows 系统而以上两个选择都无效时,你可以尝试在命令行窗口使用 [Ctrl-c] 来产生 KeyboardInterrupt 错误)。 25 | 26 | ```python 27 | >>> s = input('请输入 --> ') 28 | Enter something --> Traceback (most recent call last): 29 | File "", line 1, in 30 | EOFError 31 | ``` 32 | 33 | Python抛出了一个名为`EOFError`的错误信息,他的是*end of file*的缩写(由`Ctrl-d`触发),这是我们的程序刚开始的时候没有预料到的。 34 | 35 | ## 异常处理 36 | 37 | 我们可以用`try..except`语句来处理异常。我们将正常执行的语句放入 try 代码块中,然后将异常处理程序放入 except 代码块中。 38 | 39 | 例如 (保存为 `exceptions_handle.py`): 40 | 41 | ```python 42 | try: 43 | text = input('请输入 --> ') 44 | except EOFError: 45 | print('为什么你按下了EOF?') 46 | except KeyboardInterrupt: 47 | print('你取消了操作') 48 | else: 49 | print('你输入了 {}'.format(text)) 50 | ``` 51 | 52 | 输出为: 53 | 54 | ```shell 55 | # 按下 Ctrl + d 56 | $ python exceptions_handle.py 57 | 请输入 --> 为什么你按下了EOF? 58 | 59 | # Press Ctrl + c 60 | $ python exceptions_handle.py 61 | 请输入 --> ^C你取消了操作 62 | 63 | $ python exceptions_handle.py 64 | 请输入 --> No exceptions 65 | 你输入了 No exceptions 66 | ``` 67 | 68 | **它是如何工作的:** 69 | 70 | 我们将所有的可能会引发异常或错误的语句写在 `try` 代码块中,然后将对应的异常处理程序写在 `except` 代码块中。每个`except`语句可以处理一个特定的异常或错误,也可以是一个异常或错误的列表(用括号表示)。如果没有提供异常或错误的名字,那么它会处理 _所有的_ 异常和错误。 71 | 72 | 请注意,每一个 `try` 语句至少应该有一个与之匹配的 `except` 语句,否则 try 语句就没有意义了。 73 | 74 | 如果你的程序发生了异常或错误,但是没有被处理,那么 Python 语言就会启动默认的异常处理程序,它会中止程序的运行,并且打印出一条异常信息。我们在之前的操作中已经见过了。 75 | 76 | 你也可以给你的 `try..except` 写上一个 `else` 代码块,当没有任何异常发生的时候就会执行 `else` 语句的内容。 77 | 78 | 在下面的例子中,我们将会学习如何获得异常对象,以便于我们能够得到关于异常额外的信息。 79 | 80 | ## 抛出异常 81 | 82 | 你可以使用 `raise` 语句 _抛出_ 一个异常。在语句中你需要提供异常或错误的名称,以及被抛出(_thrown_)的异常对象。 83 | 84 | 你抛出的异常或错误应该是一个直接或间接地从 `Exception` 派生的类。 85 | 86 | 例如:(保存为`exceptions_raise.py`): 87 | 88 | ```python 89 | class ShortInputException(Exception): 90 | '''用户自定义的异常类''' 91 | def __init__(self, length, atleast): 92 | Exception.__init__(self) 93 | self.length = length 94 | self.atleast = atleast 95 | 96 | try: 97 | text = input('Enter something --> ') 98 | if len(text) < 3: 99 | raise ShortInputException(len(text), 3) 100 | # 其他代码在这里可以正常执行 101 | except EOFError: 102 | print('Why did you do an EOF on me?') 103 | except ShortInputException as ex: 104 | print(('ShortInputException: The input was ' + 105 | '{0} long, expected at least {1}') 106 | .format(ex.length, ex.atleast)) 107 | else: 108 | print('No exception was raised.') 109 | ``` 110 | 111 | 输出为: 112 | 113 | ```shell 114 | $ python exceptions_raise.py 115 | Enter something --> a 116 | ShortInputException: The input was 1 long, expected at least 3 117 | 118 | $ python exceptions_raise.py 119 | Enter something --> abc 120 | No exception was raised. 121 | ``` 122 | 123 | **它是如何工作的:** 124 | 125 | 在这里我们创建了我们自己的异常类,新的异常类被命名为 `ShortInputException` 。它有两个字段: `length` 表示输入内容的长度, `atleast` 表示程序期望的最小长度。 126 | 127 | 在 `except` 语句中,我们通过 `as` 指定一个变量保存抛出的异常或错误的对象。这类似于函数调用中的变量和参数。在这个特定的 `except` 语句中,我们使用异常对象的 `length` 和 `atleast` 字段构造了一个异常提示信息,让用户了解为什么会抛出这个异常。 128 | 129 | ## Try ... Finally 130 | 131 | 假设你要在你的程序中读取一个文件,你怎样保证无论是否有异常抛出,文件对象都被正确的关闭呢?我们可以使用 `finally` 语句块做到这一点。 132 | 133 | 例如:(保存为 `exceptions_finally.py` ) 134 | 135 | ```python 136 | import sys 137 | import time 138 | 139 | f = None 140 | try: 141 | f = open("poem.txt") 142 | # 通常我们读取文件会采取这种形式 143 | while True: 144 | line = f.readline() 145 | if len(line) == 0: 146 | break 147 | print(line, end='') 148 | sys.stdout.flush() 149 | print("Press ctrl+c now") 150 | # 使得程序停顿一下在继续运行 151 | time.sleep(2) 152 | except IOError: 153 | print("Could not find file poem.txt") 154 | except KeyboardInterrupt: 155 | print("!! You cancelled the reading from the file.") 156 | finally: 157 | if f: 158 | f.close() 159 | print("(Cleaning up: Closed the file)") 160 | ``` 161 | 162 | 输出为: 163 | 164 | ```shell 165 | $ python exceptions_finally.py 166 | Programming is fun 167 | Press ctrl+c now 168 | ^C!! You cancelled the reading from the file. 169 | (Cleaning up: Closed the file) 170 | ``` 171 | 172 | **它是如何工作的:** 173 | 174 | 我们读取文件的内容,每读一行就让系统休息2秒,我们使用 `time.sleep` 函数让程序运行慢一点(通常情况下Python程序运行的飞快)。当程序还在运行的时候,按下 `ctrl + c` 键中止程序的运行。 175 | 176 | 我们注意到当程序退出的时候抛出了 `KeyboardInterrupt` 异常。然而,在程序退出之前,执行了 finally 代码块,并且文件对象被正确的关闭了。 177 | 178 | 请注意, Python 将变量中的 0 、 `None` 、空数组和空集合都视为 `False` 。这就是为什么我们可以在上面的代码中使用 `if f:` 。 179 | 180 | 还要注意,我们在 `print` 函数后面调用 `sys.stdout.flush()` 函数,这样可以及时输出结果。 181 | 182 | ## with语句 183 | 184 | 在 `try` 语句块中获取资源,然后再 `finally` 代码块中释放资源是一种常见做法,我们可以使用 `with` 简化一下程序的书写。 185 | 186 | 例如:(保存为`exceptions_using_with.py`) 187 | 188 | ```python 189 | with open("poem.txt") as f: 190 | for line in f: 191 | print(line, end='') 192 | ``` 193 | 194 | **它是如何工作的:** 195 | 196 | 这段程序的输出应该和之前的例子是一模一样的。唯一的区别在与我们在 `with` 语句中使用 `open` 函数打开文件,通过使用 `with open` 系统会自动关闭这个文件。 197 | 198 | 实际的处理过程是这样的,`with` 语句会获取 `open` 函数返回的对象,我们假定这个对象名称是 “thefile”。 199 | 200 | 它 _总是会_ 在进入 `with` 代码块之前调用 `thefile.__enter__` 函数,并且 _总是会_ 在语句块的最后调用 `thefile.__exit__` 函数。 201 | 202 | 这样的话我们之前在 `finally` 代码块中写的程序就会自动的在 `__exit__` 方法中被执行,这种方式可以防止我们频繁使用 `try..finally` 语句。 203 | 204 | 关于这个主题更多的讨论已经超出了本书的范畴,请参考[PEP 343](http://www.python.org/dev/peps/pep-0343/)。 205 | 206 | ## 总结 207 | 208 | 本章我们讨论了 `try..except` 和 `try..finally` 语句,我们还自定义了一个我们自己的异常类型,并且在程序中将其抛出。 209 | 210 | 下一步,我们将会浏览一下Python标准库。 211 | 212 | -------------------------------------------------- 213 | 214 | ### 继续阅读[标准库](stdlib.md) -------------------------------------------------------------------------------- /a-byte-of-python3/feedback.md: -------------------------------------------------------------------------------- 1 | # 反馈 2 | 3 | The book needs the help of its readers such as yourselves to point out any parts of the book which are not good, not comprehensible or are simply wrong. Please [write to the main author]({{ book.contactUrl }}) or the respective [translators](./translations.md#translations) with your comments and suggestions. 4 | -------------------------------------------------------------------------------- /a-byte-of-python3/first_steps.md: -------------------------------------------------------------------------------- 1 | # 第一步 2 | 3 | 现在,我们将在Python中如何运行一个经典的“Hello World”程序。通过这个例子我们将会学习如何编写、保存和运行Python程序。 4 | 5 | 使用Python运行你的程序有两种方法——使用交互式解释器或使用一个源文件。下面我们通过两个例子看一下如何使用这两种方法。 6 | 7 | ## 使用交互式解释器 8 | 9 | 在操作系统中打开终端(参考[安装](./installation.md)),然后,输入`python3`并按回车键,即可打开Python 3的交互式解释器。 10 | 11 | 启动Python 3之后,会看到`>>>`,这被称为 _Python解释器提示符_。 12 | 13 | 在Python解释器提示符下,输入 14 | 15 | ```python 16 | print("Hello World") 17 | ``` 18 | 19 | 然后按回车键。我们会看到输出`Hello World`。 20 | 21 | 下面我们使用Win11电脑举个例子。不同电脑上Python软件会有一些细节的差异,但从Python解释器提示符(即`>>>`)开始,各个操作系统显示一致。 22 | 23 | ```python 24 | C:\>python3 25 | Python 3.11.4 (tags/v3.11.4:d2340ef, Jun 7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32 26 | Type "help", "copyright", "credits" or "license" for more information. 27 | >>> print("Hello World") 28 | Hello World 29 | >>> 30 | ``` 31 | 32 | 注意,Python马上输出了代码运行的结果!刚才我们输入的是一个Python _语句_。我们使用`print`可以输出任意内容。在这里,我们提供的是字符串`Hello World`,它立刻被执行并且输出到屏幕上。 33 | 34 | ### 如何退出交互式解释器 35 | 36 | 如果你正在使用一个GNU/Linux或MaxOS的shell,您可以通过按下`[ctrl + d]`或输入`exit()`(注意:记得包含括号:`()`),然后点击回车键。 37 | 38 | 如果您使用的是Windows命令行提示符,按`[ctrl + z]`键再按回车键,即可退出交互式解释器。 39 | 40 | ## 选择一个编辑器 41 | 42 | 我们不能在每次想要运行Python程序的时候都在解释器提示符下重新输入一遍,那样太繁琐了。我们应该把Python程序保存为Python源文件,这样我们下次想要运行的时候可以随时获取它。 43 | 44 | 要创建我们的Python源文件,我们需要一个可以输入并保存它们的编辑器。优秀的程序员会选择合适的编辑器提高编写代码的效率。因此,选择一个编辑器至关重要。选择编辑器就像选购汽车一样。一个好的编辑器会让你编写Python代码如虎添翼。就像一辆好车,在旅行中可以让你更快速、更安全、更舒适地到达目的地。 45 | 46 | 选择代码编辑器的一个非常基本的需求是 _语法高亮显示_,分别以不同的颜色显示Python程序的不同部分,以便我们可以更加直观的阅读源代码并且使代码运行可视化。 47 | 48 | 如果你不知道从哪里开始,我推荐可以在Windows、Mac OS X和GNU/Linux上使用的[Visual Studio Code](https://code.visualstudio.com/)(简称VSCode)免费软件与Python插件(ext install python)。 49 | 50 | 如果您使用的是Windows,*不要使用记事本*——这是一个糟糕的选择,因为它不做语法高亮显示,而且更重要的是它不支持代码缩进——之后我们在代码范例中会看到,缩进是非常重要的。好的编辑器会自动进行代码缩进。 51 | 52 | 如果你是一名有经验的程序员,那么你一定已经使用过[Vim](http://www.vim.org/)或[Emacs](http://www.gnu.org/software/emacs/)了。毋庸置疑,这是两个功能强大的编辑器,如果你使用它们来写Python代码,会受益颇多。就我自己而言,在我的大多数项目,甚至[使用Vim写一整本书](http://www.swaroopch.com/notes/vim)。 53 | 54 | 长期来看Vim或者Emacs是非常有用的。如果你愿意花时间去学习的话,那么我强烈建议你使用它们写Python代码。然而,正如我之前提到的,初学者可以从VSCode开始学习Python,这样可以把主要精力放在Python语言上,而不是学习编辑器的使用。 55 | 56 | 再次重申,请选择一个适当的编辑器,它可以使编写Python程序更有趣也更容易。 57 | 58 | 如果你想进一步讨论关于Python编辑器选择的问题,可以参考[选择一个最好的Python代码编辑器](https://realpython.com/courses/finding-perfect-python-code-editor/) 59 | 60 | ## VSCode(原文推荐[PyCharm教育版](https://www.jetbrains.com/pycharm-edu/),考虑到这个版本功能不完整,完整功能的专业版要收费,本文推荐VSCode) 61 | 62 | [Visual Studio Code](https://code.visualstudio.com/)是一个免费的集成开发环境(IDE),你可以用它开发Python程序。 63 | 64 | 下载安装之后,在菜单中选择`查看`->`扩展`,然后输入`python extension pack`,安装下面两个扩展: 65 | 66 | * 由`Don Jayamanne`开发的`Python Extension Pack` 67 | * 由`Microsoft`开发的`Chinese (Simplified) Language Pack` 68 | 69 | ![VSCode安装Python扩展](./firststep01.png) 70 | 71 | 就完成了开发环境的配置 72 | 73 | 在VSCode中,项目就是文件夹,你可以使用资源管理器新建一个文件夹,然后在VSCode中使用菜单项`文件`->`打开文件夹...`打开这个文件夹。 74 | 75 | 打开文件夹之后,使用菜单项`文件`->`新建文件`创建一个新的文件,VSCode默认会给文件起名`Untitled-1`,选择`文件`->`保存`将文件保存在当前项目文件夹下面,命名为`helloworld.py`,就完成了第一个Python源文件的创建。 76 | 77 | 在编辑器中输入以下代码: 78 | 79 | ```python 80 | print("hello world!") 81 | ``` 82 | 83 | 点击`[Ctrl+S]`保存文件之后,点击右上角的三角图标,就可以运行`helloworld.py`程序了,可以看到`hello world!`的输出。 84 | 85 | ![VSCode选择执行环境](./firststep02.png) 86 | 87 | 上面的运行方式只可以运行单个Python源文件,不能进行调试。为了调试我们需要为Python项目创建一个`launch.json`文件,使用菜单项`查看`->`运行`,点击`create a launch.json file`,然后在弹出的对话框里选择`Python File`,就可以创建一个`launch.json`文件。有了这个文件,在右上角的三角图标右侧的下拉菜单中选择`调试`,就可以调试我们的Python程序,注意要保持helloworld.py是当前打开的文件状态。 88 | 89 | ![VSCode选择执行环境](./firststep03.png) 90 | 91 | ## Vim 92 | 93 | 1. 安装[Vim](http://www.vim.org) 94 | * Mac OS X用户应该通过[HomeBrew](https://brew.sh/index_zh-cn)安装`macvim`软件。 95 | * Windows用户应该在[Vim网站](https://www.vim.org/download.php)下载exe安装文件。 96 | * GNU/Linux用户一般情况下可以直接使用`vim`。 97 | 2. 你可以安装[jedi-vim](https://github.com/davidhalter/jedi-vim)这个插件为vim增加自动完成的功能。 98 | 3. 安装对应的`jedi`python包 : `pip install -U jedi` 99 | 100 | ## Emacs 101 | 102 | 1. 安装[Emacs 24+](https://www.gnu.org/software/emacs/). 103 | * Mac OS X用户从[https://emacsformacosx.com/](https://emacsformacosx.com/)获得emacs 104 | * Windows用户从[https://ftp.gnu.org/gnu/emacs/windows/](https://ftp.gnu.org/gnu/emacs/windows/)下载 105 | * GNU/Linux用户根据不同的发行版获得对应的emacs软件,比如Debian和Ubuntu用户可以安装`emacs24`软件包 106 | 2. 安装[ELPY](https://elpy.readthedocs.io/en/latest/) 107 | 108 | ## Using A Source File 109 | 110 | 现在让我们回归正题-编程。每当你学习一门新的编程语言时,有一个传统,你编写和运行的第一个程序是“Hello World”程序。当你运行它时,它所做的只是输出“Hello World”。正如Simon Cozens(神奇的"Beginning Perl"的作者)所说,这是“向编程神祈求帮你更好学习语言的传统咒语。” 111 | 112 | 打开你选择的编辑器,输入以下程序并将其保存为`hello.py`。 113 | 114 | 如果你使用VSCode编辑器,点击`文件`->`新建文件`,输入: 115 | 116 | ```python 117 | print('Hello World') 118 | ``` 119 | 120 | 在VSCode编辑器,选`文件`->`保存`保存文件。 121 | 122 | Python文件可以保存在哪里?你电脑中的任意文件夹都可以。如果你还不清楚,你可以创建一个新文件夹,并使用这个文件夹保存和运行你所有的Python程序: 123 | 124 | - `C:\py` 在Windows上 125 | - `/tmp/py` 在Mac OS X上 126 | - `/tmp/py` 在GNU/Linux上 127 | 128 | 使用`mkdir`命令创建一个文件夹,例如,`mkdir C:\py`。 129 | 130 | 重要提示:确保你的Python文件扩展名是`.py`,例如,`foo.py`。 131 | 132 | 运行你的Python程序: 133 | 134 | 1. 打开一个命令行窗口。 135 | 2. **进入**你刚才保存文件的目录,例如:`cd C:\py`。 136 | 3. 键入`python hello.py`运行python程序,输入如下: 137 | 138 | ```shell 139 | C:\py> python hello.py 140 | Hello World 141 | ``` 142 | 143 | ![命令行执行Python程序](./firststep04.png) 144 | 145 | 如果你得到了如上所示的输出结果,那么祝贺你!你已经成功地运行了你的第一个Python程序。您已经成功地跨越了学习编程最难的部分--开始你的第一个程序! 146 | 147 | 如果报错了,请 _准确_ 输入上面的程序,并且再次运行。注意,Python是区分大小写的,即`print`并不等于`Print`。前者是小写字母`p`和后者是大写字母`P`。同样,确保每一行的第一个字母之前没有空格或制表符——之后我们将继续阐述为[什么这很重要](./basics.md)。 148 | 149 | **它是如何工作的** 150 | 151 | Python程序是由 _语句_ 组成,在我们的第一个程序中,我们只有一个语句,在这个语句中,我们使用了`print` _函数_,它只是输出文本`Hello World`。 152 | 153 | ## 获得帮助 154 | 155 | 如果您需要快速获取任何Python函数或语句的信息,可以使用内置的`help`(帮助)功能。这是非常有用的,尤其是当使用命令行提示符时,例如,运行`help(len)`——这将显示`len`函数的帮助--用于计算对象包含了多少元素。 156 | 157 | 提示:按`q`退出帮助 158 | 159 | 类似地,您可以获取Python中几乎所有事情的帮助,可以尝试使用`help()`去学习更多关于使用`help`本身的帮助! 160 | 161 | 如果你需要获取操作符例如`return`的帮助,那么你只需要把它放到引号里,如`help('return')`,Python就可以清楚的了解我们想要获取的帮助。 162 | 163 | ## 总结 164 | 165 | 现在,你可以自由自在地编写、保存和运行Python程序了。 166 | 167 | 既然你是一名Python程序猿,在下一章我们要学习一些Python的基本概念。 168 | 169 | --- 170 | 171 | -------------------------------------------------------------------------------- /a-byte-of-python3/firststep01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/a-byte-of-python3/firststep01.png -------------------------------------------------------------------------------- /a-byte-of-python3/firststep02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/a-byte-of-python3/firststep02.png -------------------------------------------------------------------------------- /a-byte-of-python3/firststep03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/a-byte-of-python3/firststep03.png -------------------------------------------------------------------------------- /a-byte-of-python3/firststep04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/a-byte-of-python3/firststep04.png -------------------------------------------------------------------------------- /a-byte-of-python3/floss.md: -------------------------------------------------------------------------------- 1 | # 附录:免费/自由和开放源码软件 2 | 3 | > 注意:本章节是在2003年完成的,所以有些内容看起来可能有些奇怪 :-) 4 | 5 | "Free/Libre and Open Source Software", in short, [FLOSS](http://en.wikipedia.org/wiki/FLOSS) is based on the concept of a community, which itself is based on the concept of sharing, and particularly the sharing of knowledge. FLOSS are free for usage, modification and redistribution. 6 | 7 | If you have already read this book, then you are already familiar with FLOSS since you have been using *Python* all along and Python is an open source software! 8 | 9 | Here are some examples of FLOSS to give an idea of the kind of things that community sharing and building can create: 10 | 11 | [Linux](http://www.kernel.org): This is a FLOSS OS kernel used in the GNU/Linux operating system. Linux, the kernel, was started by Linus Torvalds as a student. Android is based on Linux. Any website you use these days will mostly be running on Linux. 12 | 13 | [Ubuntu](http://www.ubuntu.com): This is a community-driven distribution, sponsored by Canonical and it is the most popular GNU/Linux distribution today. It allows you to install a plethora of FLOSS available and all this in an easy-to-use and easy-to-install manner. Best of all, you can just reboot your computer and run GNU/Linux off the CD! This allows you to completely try out the new OS before installing it on your computer. However, Ubuntu is not entirely free software; it contains proprietary drivers, firmware, and applications. 14 | 15 | [LibreOffice](http://www.libreoffice.org/): This is an excellent community-driven and developed office suite with a writer, presentation, spreadsheet and drawing components among other things. It can even open and edit MS Word and MS PowerPoint files with ease. It runs on almost all platforms and is entirely free, libre and open source software. 16 | 17 | [Mozilla Firefox](http://www.mozilla.org/products/firefox): This is _the_ best web browser. It is blazingly fast and has gained critical acclaim for its sensible and impressive features. The extensions concept allows any kind of plugins to be used. 18 | 19 | [Mono](http://www.mono-project.com): This is an open source implementation of the Microsoft .NET platform. It allows .NET applications to be created and run on GNU/Linux, Windows, FreeBSD, Mac OS and many other platforms as well. 20 | 21 | [Apache web server](http://httpd.apache.org): This is the popular open source web server. In fact, it is _the_ most popular web server on the planet! It runs nearly more than half of the websites out there. Yes, that's right - Apache handles more websites than all the competition (including Microsoft IIS) combined. 22 | 23 | [VLC Player](http://www.videolan.org/vlc/): This is a video player that can play anything from DivX to MP3 to Ogg to VCDs and DVDs to ... who says open source ain't fun? ;-) 24 | 25 | This list is just intended to give you a brief idea - there are many more excellent FLOSS out there, such as the Perl language, PHP language, Drupal content management system for websites, PostgreSQL database server, TORCS racing game, KDevelop IDE, Xine - the movie player, VIM editor, Quanta+ editor, Banshee audio player, GIMP image editing program, ... This list could go on forever. 26 | 27 | To get the latest buzz in the FLOSS world, check out the following websites: 28 | 29 | - [OMG! Ubuntu!](http://www.omgubuntu.co.uk/) 30 | - [Web Upd8](http://www.webupd8.org/) 31 | - [DistroWatch](http://www.distrowatch.com) 32 | - [Planet Debian](http://planet.debian.org/) 33 | 34 | Visit the following websites for more information on FLOSS: 35 | 36 | - [GitHub Explore](http://github.com/explore) 37 | - [Code Triage](http://www.codetriage.com/) 38 | - [SourceForge](http://www.sourceforge.net) 39 | - [FreshMeat](http://www.freshmeat.net) 40 | 41 | So, go ahead and explore the vast, free and open world of FLOSS! 42 | 43 | -------------------------------------------------- 44 | 45 | ### 继续阅读[附录: 版本历史](revision_history.md) -------------------------------------------------------------------------------- /a-byte-of-python3/functions.md: -------------------------------------------------------------------------------- 1 | # 函数 2 | 3 | 函数是可重用的程序片段。它允许你给一个代码块起一个名字,使用这个名字,你可以在程序的任何地方运行任何次数。这就是所谓的函数**调用**。我们已经使用了许多内置函数, 如`len`和`range`。 4 | 5 | 在任何有实际使用价值的软件中,函数的概念都是**最**重要的基石(对于任何编程语言都一样)。所以,在这一章我们将探索函数的方方面面。 6 | 7 | 定义函数使用`def`关键字。在这个**关键字**之后是**标识**函数的名字,之后是在一对括号中附上一些变量名,最后在行的末尾是冒号。接下来是代码块--函数函数体。我们用一个简单地例子看一下怎样定义一个函数: 8 | 9 | 例子 (保存为function1.py): 10 | 11 | ```python 12 | def sayHello(): 13 | # 函数的代码块 14 | print('世界您好!') 15 | # 函数在此结束 16 | 17 | sayHello() # 调用函数 18 | sayHello() # 再次调用函数 19 | ``` 20 | 21 | 输出: 22 | 23 | ```shell 24 | C:\> python function1.py 25 | 世界您好! 26 | 世界您好! 27 | ``` 28 | 29 | **它是如何工作的:** 30 | 31 | 我们使用上述的语法,定义了一个名为`sayHello`的函数。这个函数没有参数,因此没有在圆括号中声明变量。函数的参数用来输入数据到函数中,以便我们可以给它传递不同的值,返回不同的结果。 32 | 33 | 注意,我们可以调用相同的函数两次,这意味着我们不需要再写两次同样的代码。 34 | 35 | ## 函数的参数 36 | 37 | 一个函数可以带参数--你可以在调用的时候给参数赋值,这样函数就可以使用这些参数进行处理。这些参数就像变量,所不同的是当我们调用函数时这些变量的值才会被定义,当函数运行时,它们已经有了指定的的值。 38 | 39 | 我们在定义函数的时候定义参数,写在一对括号中,之间用逗号分隔。当我们调用这个函数的时候,我们以同样的方式提供这些值。注意我们使用的术语——在函数的定义中给出的名字叫**形参**,而在函数调用时您提供的值被称为**实参**。 40 | 41 | 例子 (保存为function_param.py): 42 | 43 | ```python 44 | def print_max(a, b): 45 | if a > b: 46 | print(a, '大') 47 | elif a == b: 48 | print(a, '等于', b) 49 | else: 50 | print(b, '大') 51 | 52 | # 直接给出字面值 53 | print_max(3, 4) 54 | 55 | x = 5 56 | y = 7 57 | 58 | # 变量作为参数 59 | print_max(x, y) 60 | ``` 61 | 62 | 输出: 63 | 64 | ``` 65 | C:\> python function_param.py 66 | 4 大 67 | 7 大 68 | ``` 69 | 70 | **它如何工作的:** 71 | 72 | 在这里,我们定义了一个名为`print_max`的函数,它有`a`和`b`两个参数。我们使用简单的`if..else`语句找出比较大的数,然后打印较大的数。 73 | 74 | 我们第一次调用函数`print_max`,我们直接提供了数字作为参数。第二次调用的时候,我们使用变量作为参数。`print_max(x, y)`把实参`x`分配给形参`a`,`y`分配给`b`。在这两种情况下,`print_max`函数以同样方式工作。 75 | 76 | ## 局部变量 77 | 78 | 你在函数定义中声明的变量,与在函数外使用的其它同名变量没有任何关系,即变量名称对函数来说是**局部**的。这叫变量的**作用域**。所有变量的作用域,都是从他们在代码块中被定义开始,一直到代码块结束为止。 79 | 80 | 例子 (保存为function_local.py): 81 | 82 | ```python 83 | x = 50 84 | 85 | def func(x): 86 | print('x等于', x) 87 | x = 2 88 | print('局部变量x改为', x) 89 | 90 | func(x) 91 | print('x仍然是', x) 92 | ``` 93 | 94 | 输出: 95 | 96 | ```bash 97 | C:\> python function_local.py 98 | x等于50 99 | 局部变量x改变为2 100 | x仍然是50 101 | ``` 102 | 103 | **它是如何工作的:** 104 | 105 | 第一次我们输出的时候,我们在函数体的第一行打印变量**x**的**值**,Python使用在主程序中定义的的值,代入到实参中完成输出。 106 | 107 | 接下来,我们给`x`赋值为`2`,变量`x`是函数的局部变量,因此在函数中当我们改变`x`的值时,在主程序中定义的变量`x`不受影响。 108 | 109 | 最后在主程序中调用`print`函数,显示输出在主程序中定义的变量`x`,说明在函数中修改局部变量的值对主程序的变量没有影响。 110 | 111 | ## 使用全局声明 112 | 113 | 在函数体内部,如果你想给在主程序中(即未在任何函数或类之中)定义的变量赋值,那么你必须告诉Python,变量不是局部的,而是**全局**的。我们使用`global`语句,没有`global`语句赋值给一个在函数外定义的变量是不可能的。 114 | 115 | 如果在函数体内部没有同名的变量,您也可以不使用`global`而直接使用这些在函数外定义的变量的值。然而,我们并不鼓励这样做,因为对于程序的读者来说,他会不清楚变量是在哪里定义的。使用 `global` 语句就非常清楚,变量定义在主程序中。 116 | 117 | 例子 (保存为function_global.py): 118 | 119 | ```python 120 | x = 50 121 | 122 | def func(): 123 | global x 124 | 125 | print('x的值是', x) 126 | x = 2 127 | print('全局变量x改为', x) 128 | 129 | func() 130 | print('x的值是', x) 131 | ``` 132 | 133 | 输出: 134 | 135 | ```shell 136 | C:\> python function_global.py 137 | x的值是50 138 | 全局变量to改为2 139 | x的值是2 140 | ``` 141 | 142 | **它是如何工作的:** 143 | 144 | `global`语句用来声明`x`是全局变量,当我们在函数内给`x`赋值时,它直接改变我们在主程序中使用的`x`的值。 145 | 146 | 用一个的`global`语句可以指定多个全局变量,比如: `global x, y, z`。 147 | 148 | ## 参数默认值 149 | 150 | 对于一些函数,你可能需要一些参数是**可选**,即在用户不希望为它们提供值时使用默认值。在Python中我们可以为函数的参数指定默认的参数值。在函数定义中通过在参数名称后使用赋值运算符(`=`)后跟默认值来指定默认的参数值。 151 | 152 | 注意,默认参数值应该是一个常数。更准确的说,默认的参数值应该是不可变的——关于这一点在后面的章节中会有更详细的解释。现在,只要记住这点。 153 | 154 | 例子 (保存为 function_default.py): 155 | 156 | ```python 157 | def say(message, times = 1): 158 | print(message * times) 159 | 160 | say('你好') 161 | say('世界', 5) 162 | ``` 163 | 164 | 输出: 165 | 166 | ``` 167 | $ python3 function_default.py 168 | 你好 169 | 世界世界世界世界世界 170 | ``` 171 | 172 | **它是如何工作的:** 173 | 174 | 函数`say`是用来按照指定的次数打印一个字符串。如果我们不提供一个值,那么在默认情况下字符串只打印一次。为此,我们为参数`times`指定一个默认参数值`1`。 175 | 176 | 在第一次使用函数`say`时,我们只提供了字符串,它打印字符串一次。在第二次使用`say`时,我们提供了字符串和一个数字`5`两个参数,说明我们想要`say`字符串打印5次。 177 | 178 | > **重要提示** 179 | > 180 | > 默认参数值只能放在参数列表的最后。即在参数列表中,你不能在没有默认值的参数前有带默认参数值的参数。 181 | > 182 | > 这是因为,Python按照位置把值分配给参数。例如,`def func(a, b=5)`是有效的,而`def func(a=5, b)`是**无效**的。 183 | 184 | ## 参数关键字 185 | 186 | 如果你的函数有很多参数,在调用的时候您想给其中一部分参数赋值,那么你可以通过为参数命名来为它们赋值,这叫做**参数关键字**。我们使用名称(关键字)而不是位置(我们一直使用的)来指定函数的参数。 187 | 188 | 这有两个优势:一是,可以更自由的使用函数,因为我们不需要担心参数的顺序;二是,如果其他参数有默认值,我们可以只给我们想赋值的参数赋值。 189 | 190 | 例子 (保存为function_keyword.py): 191 | 192 | ```python 193 | def func(a, b=5, c=10): 194 | print('a为', a, ',b为', b, ',c为', c) 195 | 196 | func(3, 7) 197 | func(25, c=24) 198 | func(c=50, a=100) 199 | ``` 200 | 201 | 输出: 202 | 203 | ``` 204 | $ python function_keyword.py 205 | a为 3 ,b为 7 ,c为 10 206 | a为 25 ,b为 5 ,c为 24 207 | a为 100 ,b为 5 ,c为 50 208 | ``` 209 | 210 | **它是如何工作的:** 211 | 212 | 名为`func`的函数中有一个没有默认值的参数,后面跟两个有默认值的参数。 213 | 214 | 第一次使用`func(3, 7)`,参数`a`得到值`3`,参数`b`得到值`7`,参数`c`得到默认值`10`。 215 | 216 | 第二次使用`func(25, c=24)`, 参数`a`按照参数的位置得到值`25`,然后参数`c`按照参数名(参数关键字)得到值`24`,变量`b`得到默认值`5`。 217 | 218 | 第三次使用`func(c=50, a=100)`,我们为所有给定的值使用了参数关键字。注意,我们为参数`c`指定值是在参数`a`之前,尽管在函数定义中`a`在`c`前。 219 | 220 | 221 | ## 可变参数 222 | 223 | 有时你可能想定义一个函数,它可以获取任意数量的参数,这可以通过使用星号(保存为function_varargs.py)实现: 224 | 225 | ```python 226 | def total(a=5, *numbers, **phonebook): 227 | print('a', a) 228 | 229 | # 通过元组遍历全部的参数 230 | for single_item in numbers: 231 | print('single_item', single_item) 232 | 233 | # 通过字典遍历全部的参数 234 | for first_part, second_part in phonebook.items(): 235 | print('-----') 236 | print(first_part, second_part) 237 | 238 | 239 | print(total(10, 1, 2, 3, Jack=1123, John=2231, Inge=1560)) 240 | ``` 241 | 242 | 输出: 243 | 244 | ```shell 245 | C:\> python function_varargs.py 246 | a 10 247 | single_item 1 248 | single_item 2 249 | single_item 3 250 | Inge 1560 251 | John 2231 252 | Jack 1123 253 | None 254 | ``` 255 | 256 | **它是如何工作的:** 257 | 258 | 当我们声明一个带星号的参数,如`*param`,那么从这一点开始到结束的所有的位置参数都被收集到一个叫`param`的元组中。 259 | 260 | 同样,当我们声明一个双星参数,如`**param`,那么从这一点开始到结束的所有关键字参数都被收集到一个叫`param`的字典中。 261 | 262 | 我们将在[数据结构](data_structures.md)中探讨元组和字典。 263 | 264 | ## return语句 265 | 266 | return 语句用来从函数中**return(返回)**,也就是说跳出函数。同样,我们也可以从函数中选择性地**返回一个值**。 267 | 268 | 例子 (保存为function_return.py): 269 | 270 | ```python 271 | def maximum(x, y): 272 | if x > y: 273 | return x 274 | elif x == y: 275 | return '两个数相等' 276 | else: 277 | return y 278 | 279 | print(maximum(2, 3)) 280 | ``` 281 | 282 | 输出: 283 | 284 | ``` 285 | C:\> python function_return.py 286 | 3 287 | ``` 288 | 289 | **它是如何工作的:** 290 | 291 | 函数`maximum`返回参数中的最大值,在这个例子中我们提供给函数的参数是两个数字。它使用了简单的`if..else`语句找到比较大的值,然后**返回**那个值。 292 | 293 | 注意,没有返回值的`return`语句相当于`return None`(什么也不返回)。`None`是Python中的一个特殊类型,它代表什么也没有。例如,如果一个变量的值是`None`,它说明这个变量没有值。 294 | 295 | 除非你已经写了自己的`return`语句,否则,每个函数在末尾都默认包含一个`return None`语句。通过运行`print(someFunction())`你可以看到这一点,这里`someFunction` 没有使用`return`语句,比如: 296 | 297 | ```python 298 | def someFunction(): 299 | pass 300 | ``` 301 | 302 | 在Python中pass语句用来说明一个空的代码块。 303 | 304 | > 提示:已经有一个叫`max`的内建函数能够完成'find maximum'函数的功能 ,因此,我们还是尽可能使用内建函数。 305 | 306 | ## 文档字符串 307 | 308 | Python有一个叫**文档字符串(docstrings)**的好特性。文档字符串是你应该使用的一个重要工具,它可以帮助你给程序补充文档信息,令其容易理解。Python还有一个令人惊叹的特性,当程序运行的时候,我们可以从函数中获取文档字符串。 309 | 310 | 例子 (保存为function_docstring.py): 311 | 312 | ```python 313 | def print_max(x, y): 314 | '''打印两个数中的最大值。 315 | 316 | 两个值必须是整数。''' 317 | # 如果可能,转换为整数 318 | x = int(x) 319 | y = int(y) 320 | 321 | if x > y: 322 | print(x, '最大') 323 | else: 324 | print(y, '最大') 325 | 326 | print_max(3, 5) 327 | print(print_max.__doc__) 328 | ``` 329 | 330 | 输出: 331 | 332 | ```shell 333 | C:\> python function_docstring.py 334 | 5 最大 335 | 打印两个数中的最大值。 336 | 337 | 两个值必须是整数。 338 | ``` 339 | 340 | **它是如何工作的:** 341 | 342 | 函数的第一个逻辑行的字符串是那个函数的**文档字符串**。注意,文档字符串也适用于在后面的章节将要学习的[模块](modules.md)和[类](oop.md)。 343 | 344 | 通常情况下,文档字符串有多行,第一行紧挨着`'''`,并且以句号(。)结束。第二行是空行,从第三行开始是详细描述。**强烈建议**,为所有重要的函数写文档字符串都要遵循此贯例。 345 | 346 | 我们可以使用`print_max`函数的`__doc__`(注意,**双下划线**)属性访问print_max函数的文档字符串。只要记住,Python把**一切事物**当做对象看待,这也包括函数。我们将在[类](oop.md)这一章学习关于对象的更多知识。 347 | 348 | 如果你在Python中已经使用过`help()`,那么你已经看到如何使用文档字符串了!它所做的仅仅是获取函数的 `__doc__` 属性,并以一个整洁的方式显示给你。你可以试一下上面的函数——在你的程序中增加一行`help(print_max)`尝试一下。 349 | 350 | 自动化工具可以从你的程序中以这种方式检索文档。因此,我**强烈建议**,为你写的任何重要函数书写文档字符串。来自Python的自动化工具`pydoc`使用文档字符串的工作原理类似于`help()`。 351 | 352 | ## 小结 353 | 354 | 我们已经了解了函数的方方面面,但请注意,我们仍然没有覆盖关于函数的全部内容。不过我们已经涵盖了Python函数日常使用的大部分内容。 355 | 356 | 接下来,我们将看到如何创建及使用Python模块。 357 | -------------------------------------------------------------------------------- /a-byte-of-python3/hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/a-byte-of-python3/hello.png -------------------------------------------------------------------------------- /a-byte-of-python3/index.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | --- 3 | 4 | # 简明Python教程 5 | 6 | 简明Python教程(A Byte of Python)十一本免费的Python编程入门教程,为初学者学习Python语言提供一个由浅入深的教程。如果你对于计算机的了解仅限于它可以保存文件,那么你可以阅读本书学习Python编程。 7 | 8 | ## 基于Python 3 9 | 10 | 本书教给你怎样使用Python 3。还将通过一系列的讲解,帮助您适应更加古老但更为常见的 Python 2。 -------------------------------------------------------------------------------- /a-byte-of-python3/install-on-win10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/a-byte-of-python3/install-on-win10.jpg -------------------------------------------------------------------------------- /a-byte-of-python3/installation.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | 本书所描述的"Python 3",指的是[Python 3.6](https://www.python.org/downloads/)或更高的版本。 4 | 5 | ## 在Windows 10 或 11上安装 6 | 7 | 在`Microsoft Store`应用商店中搜索`python3`,可以看到不同版本的`Python 3`软件,选择一个高于`Python 3.6`的安装即可,参见下图: 8 | 9 | ![install-on-win10](install-on-win10.jpg) 10 | 11 | 如果你已经完成安装,可以跳过剩余内容直接阅读下一章节。 12 | 13 | ## 在Windows上安装 14 | 15 | 16 | 访问[[https://www.python.org/downloads/](https://www.python.org/downloads/)下载最新版本,在撰写本文时,最新版为3.11.4,我们以此为范例进行安装。安装过程和其它基于Windows的软件类似。 17 | 18 | 如果你的Windows版本比Vista更老,你只能下载[Python 3.4版本](https://www.python.org/downloads/windows/),因为更高版本的Python需要更高版本的Windows。 19 | 20 | 注意 21 | > 在安装过程中,一定要勾选`Add Python 3.11 to PATH` 22 | 23 | 如果想要修改安装路径,点击`Customize installation`, 再点击`Next`,键入`C:\Python311`(或者其他路径)作为新的安装路径。 24 | 25 | 如果在安装的时候没有勾选`Add Python 3.11 to PATH`,可以在安装中勾选`Add Python to environment variables`. 这和`Add Python 3.11 to PATH`效果相同。 26 | 27 | 你可以为所有用户安装Python Launcher,不安装也没关系。Launcher用来在不同的Python版本之间进行切换。 28 | 29 | 如果你的安装路径没有设置正确(勾选`Add Python 3.11 to PATH`或者`Add Python to environment variables`),参考下一个章节(`DOS提示符`)的步骤来修正。否则,请参考本章的`在Windows命令行上运行Python`。 30 | 31 | 提示: 对于有经验的程序员,如果你对Docker比较熟悉,请参考[Python in Docker](https://hub.docker.com/_/python/) 和 [Docker on Windows](https://docs.docker.com/windows/). 32 | 33 | ### DOS提示符 34 | 35 | If you want to be able to use Python from the Windows command line i.e. the DOS prompt, then you need to set the PATH variable appropriately. 36 | 如果你想要在Windows命名行(例如DOS提示符)使用Python,你需要正确设置PATH变量。 37 | 38 | 对于Windows 2000、XP、2003,点击`控制面板` -> `系统` -> `高级` -> `环境变量`。在 _系统变量_ 中点击`PATH`,选择`编辑`,然后在已有内容的最后部分添加`;C:\Python311`(请核实存在该文件夹,对于新版本Python来说,文件夹的名字可能不同)。要注意使用正确的目录名。 39 | 40 | 对于早期版本的Windows,打开`C:\AUTOEXEC.BAT`文件,添加一行`PATH=%PATH%;C:\Python311`,然后重启系统。对于Windows NT,使用AUTOEXEC.NT文件。 41 | 42 | 对于Windows Vista 43 | 44 | * 点击“开始”,选择`控制面板`。 45 | * 点击“系统”,在右侧您将看到“查看计算机基本信息”。 46 | * 左侧是一个任务列表,点击最后一项`高级系统设置`。 47 | * 显示`系统属性`对话框`高级`选项卡。点击右下角的`环境变量`按钮。 48 | * 在下方标题为`系统变量`框中,滚动`Path`,点击`编辑`按钮。 49 | * 按需修改路径。 50 | * 重启操作系统。Vista只有重启才会使系统路径变量的修改生效。 51 | 52 | 对于Windows 7/8: 53 | 54 | * 在桌面上右键单击`我的电脑`,选择`属性`;或点击`开始` -> `控制面板` -> `系统和安全` -> `系统`,点击左侧的`高级系统设置`,然后选择`高级`选项卡。点击底部的`环境变量`按钮,在下方的`系统变量`中找到`PATH`变量,选中它点击`编辑`。 55 | * 在变量值的最后,追加`;C:\Python311`(请确认是这个路径,新版的Python可能会有所不同)。 56 | * 如果这个值是`%SystemRoot%\system32`;,它将变成%`SystemRoot%\system32;C:\Python311`。 57 | * 点击`确定`完成。不需要重启,只要重新打开Windows命令行就可以。 58 | 59 | 对于Windows 10: 60 | 61 | Windows开始菜单 > `设置` > `关于` > `系统` > `高级系统设置` > `环境变量` (通常在底部) > (点击上半部分的`Path`变量然后点击`编辑`) > `新建` > (输入Python安装路径,例如`C:\Python311\`) 62 | 63 | ### 在Windows命令行上运行Python 64 | 65 | 对于Windows用户,如果正确地设置了PATH变量,你可以在命名行运行Python。 66 | 67 | 要打开Windows终端,点击开始按钮,点击`运行`。在对话框输入`cmd`,按下回车键。 68 | 69 | 然后输入`python`即可,注意不要有拼写错误。 70 | 71 | ## 在Mac OS X上安装 72 | 73 | 对于Mac OS X用户,使用[Homebrew](https://brew.sh/index_zh-cn)命令:`brew install python3`安装。 74 | 75 | To verify, open the terminal by pressing `[Command + Space]` keys (to open Spotlight search), type `Terminal` and press `[enter]` key. Now, run `python3` and ensure there are no errors. 76 | 想要验证Python 3是否已经正确安装,使用`[Command + Space]`键打开终端(打开Spotlight搜索),输入`Terminal`然后回车。之后,运行`python3`,注意不要有拼写错误。 77 | 78 | ## 在GNU/Linux上安装 79 | 80 | 对于GNU/Linux用户,使用你的Linux发行版的包管理器来安装Python 3,如果你使用的是Debian & Ubuntu: `sudo apt-get update && sudo apt-get install python3`。 81 | 82 | 想要验证Python 3是否已经正确安装,打开`Terminal`应用程序,或者按下`Alt + F2`然后输入`gnome-terminal`。如果不成功,请参考你所用Linux发行版的文档。然后再命令行运行`python3`命令,注意不要有拼写错误。 83 | 84 | 运行下面的命令,可以输出Python的版本: 85 | 86 | ```shell 87 | $ python3 -V 88 | Python 3.6.0 89 | ``` 90 | 91 | 提示:`$`是shell的提示符,根据你电脑上的操作系统设置会有所不同,这里我简单的使用`$`符号表示。 92 | 93 | 注意:输出的版本信息在你的电脑上可能会有所不同,这取决于你安装的Python版本。 94 | 95 | ## 总结 96 | 97 | 从现在起,我们假定你已经在自己的电脑上安装好了Python。 98 | 99 | 接下来,我们将开始编写我们的第一个Python程序。 -------------------------------------------------------------------------------- /a-byte-of-python3/io.md: -------------------------------------------------------------------------------- 1 | ## 输入与输出 2 | 3 | 某些情况下,你的程序必须与用户进行交互。例如,你想获取来自用户的输入,然后给用户打印返回的结果。我们可以分别使用`input()`和`print()`函数来实现。 4 | 5 | 对于输出,我们可以使用`str`类中的方法。例如,你可以使用`rjust`方法得到一个右对齐、指定宽度的字符串。可以通过`help(str)`来查看更详细的信息。 6 | 7 | 另一个常见的输入/输出类型是处理文件。创建、读取和写入文件的能力对于很多程序来说是至关重要的,我们将在本章探索这个特性。 8 | 9 | ## 用户输入 10 | 11 | 将这个程序保存为`io_input.py`: 12 | 13 | ```python 14 | def reverse(text): 15 | return text[::-1] 16 | 17 | 18 | def is_palindrome(text): 19 | return text == reverse(text) 20 | 21 | 22 | something = input('输入文本:') 23 | if (is_palindrome(something)): 24 | print("是的,这是回文") 25 | else: 26 | print("不,这不是回文") 27 | 28 | ``` 29 | 30 | 输出: 31 | 32 | ```shell 33 | C:\> python io_input.py 34 | 输入文本: 蜜蜂 35 | 不,这不是回文 36 | C:\> python io_input.py 37 | 输入文本: 人上人 38 | 是的,这是回文 39 | ``` 40 | 41 | **它是如何工作的:** 42 | 43 | 我们使用切片特性来反转文本。我们已经知道了如何通过`seq[a:b]`来得到位置`a`到位置`b`的[序列切片](./data_structures.md)。我们还可以提供第三个参数来决定切片的**步长**,切片默认的步长是`1`,因为这样会返回文本连续的部分。给一个负值的步长,即`-1`,会返回文本的反转。 44 | 45 | `input()`函数将字符串作为参数,并且展示给用户。然后它会等待用户输入并按下返回键。一旦用户输入并按下了返回键,`input()`函数会将会返回用户输入的文本。 46 | 47 | 我们得到文本并反转它。如果原始文本和反转文本是相等的,那么这个文本就是一个[回文](http://en.wiktionary.org/wiki/palindrome)。 48 | 49 | ### 家庭作业 50 | 51 | 对英文来说检查一段文字是不是回文,需要忽略标点、空格和大小写。例如:"Rise to vote, sir." 是一句回文,但目前版本的程序还不能认出它。你能够修改程序让它能认出这句回文吗? 52 | 53 | 如果你没有思路,请参考文末的提示。 54 | 55 | ## 文件 56 | 57 | 为了读写文件,你可以通过创建一个`file`类的对象,然后使用`read`、`readline`或`write`方法来打开和使用文件。对文件的读写能力取决于你打开文件时选择的模式。当你处理完文件后,你可以使用`close`方法告诉 Python 你已经使用完文件了。 58 | 59 | 例子 (保存为 io_using_file.py): 60 | 61 | ```python 62 | poem = '''\ 63 | Programming is fun 64 | When the work is done 65 | if you wanna make your work also fun: 66 | use Python! 67 | ''' 68 | 69 | # 打开文件进行 'w'riting 写操作 70 | f = open('poem.txt', 'w') 71 | # 将文本写入到文件 72 | f.write(poem) 73 | # 关闭文件 74 | f.close() 75 | 76 | # 如果没有指定文件打开方式 77 | # 默认使用 'r'ead 读模式 78 | f = open('poem.txt') 79 | while True: 80 | line = f.readline() 81 | # 零行意味着 EOF 文件结尾 82 | if len(line) == 0: 83 | break 84 | # `line` 中已经自带换行了 85 | # 因为它是从文件中读取出来的 86 | print(line, end='') 87 | # 关闭文件 88 | f.close() 89 | ``` 90 | 91 | 输出: 92 | 93 | ``` 94 | C:\> python io_using_file.py 95 | Programming is fun 96 | When the work is done 97 | if you wanna make your work also fun: 98 | use Python! 99 | ``` 100 | 101 | **它是如何工作的:** 102 | 103 | 通过`open`方法我们很容易就能创建一个新的文件对象。我们指定文件名和打开方式,通过内置的`open`函数打开文件,当文件不存在时则创建文件。文件有很多种打开模式,可以是:读模式(`'r'`),写模式(`'w'`)或追加模式(`'a'`)。我们也可以指定以什么方式进行读、写和追加,是文本模式(`'t'`)还是二进制模式(`'b'`)。还有很多打开模式的组合,你可以通过`help(open)`命令来查看详细的说明。默认情况下`open()`认为文件以文本模式打开进行读取操作。 104 | 105 | 在我们的例子里,第一次我们用`write`方法打开/创建了这个文件,并把字符串变量`poem`写入文件里,之后我们用`close`关闭了文件。 106 | 107 | 接下来我们再次打开同一个文件用于读取。我们不需要指定模式,因为默认的读取文件模式已经足够了。我们使用`readline`方法在一个循环中每次读取文件的一行。这个方法每次会返回包括换行符在内的一整行。当读到**空**字符时,就说明已经到了文件的结尾,我们就可以跳出(break)循环了。 108 | 109 | 最后,我们用`close`关闭了文件。 110 | 111 | 从`readline`的输出中我们可以得知:这个程序已经成功地把小诗写入了`poem.txt`文件,并可以从中读取出来,打印到屏幕上。 112 | 113 | ## 序列化 114 | 115 | 我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等。 116 | 117 | Python提供了一个标准模块`pickle`,你可以使用该模块将**任何**简单的Python对象存储在文件中,然后可再次取回。这个过程也被称为**持久化存储对象**。 118 | 119 | 例子 (保存为`io_pickle.py`): 120 | 121 | ```python 122 | import pickle 123 | 124 | # 这是我们将存储对象的文件名 125 | shoplistfile = 'shoplist.data' 126 | # 购物的清单 127 | shoplist = ['苹果', '芒果', '胡萝卜'] 128 | 129 | # 写入文件 130 | f = open(shoplistfile, 'wb') 131 | # 将对象存储到文件 132 | pickle.dump(shoplist, f) 133 | f.close() 134 | 135 | # 销毁 shoplist 变量 136 | del shoplist 137 | 138 | # 从存储中读回 139 | f = open(shoplistfile, 'rb') 140 | # 从文件加载对象 141 | storedlist = pickle.load(f) 142 | print(storedlist) 143 | f.close() 144 | ``` 145 | 146 | 输出: 147 | 148 | ```shell 149 | C:\> python pickling.py 150 | ['苹果', '芒果', '胡萝卜'] 151 | ``` 152 | 153 | **它是如何工作的:** 154 | 155 | 要将对象存储在文件中,必须先以二进制写入模式`open`文件,然后调用`pickle`模块的`dump`函数将对象保存到文件 file 中去,这个过程叫做**pickling**。 156 | 157 | 之后,我们可使用`pickle`模块的`load`函数来获取并返回这个对象。此过程称为**unpickling**。 158 | 159 | ## Unicode 160 | 161 | 到目前为止,当我们编写和使用字符串或者读取和写入文件时,我们只使用了简单的英文字符。 英语和非英语字符都可以用 Unicode 码表示(请参阅本节末尾的文章了解更多信息),默认情况下 Python 3 使用 Unicode 存储字符串变量(想想所有我们用单或双或三重引号包裹的文本)。 162 | 163 | > 注意:如果你使用的是Python 2,并且我们希望能够读取和编写其他非英语语言,我们需要使用`unicode`类型,所有内容都以字符`u`开头,例如:`u"hello world"`。 164 | 165 | ```python 166 | >>> "hello world" 167 | 'hello world' 168 | >>> type("hello world") 169 | 170 | >>> u"hello world" 171 | 'hello world' 172 | >>> type(u"hello world") 173 | 174 | ``` 175 | 176 | 当通过互联网发送数据时,我们需要以字节为单位发送数据,这是计算机易于理解的方式。将 Unicode 码(这是 Python 在存储字符串时使用的)转换为字节的规则称为编码。一种流行的编码方式是 UTF-8 。我们可以通过在`open`函数中使用一个简单的关键字参数来读写 UTF-8 。 177 | 178 | ```python 179 | # encoding=utf-8 180 | import io 181 | 182 | f = io.open("abc.txt", "wt", encoding="utf-8") 183 | f.write(u"Imagine non-English language here") 184 | f.close() 185 | 186 | text = io.open("abc.txt", encoding="utf-8").read() 187 | print(text) 188 | ``` 189 | 190 | **它是如何工作的:** 191 | 192 | 我们使用 `io.open` 然后在第一个 open 语句中使用 `encoding` 参数对信息进行编码,然后在解码信息时再在第二个 open 语句中使用该参数。 请注意,我们应该只在文本模式下使用 open 语句时的使用编码。 193 | 194 | 每当我们编写一个使用 Unicode 文字的程序(通过在字符串之前放置一个 `u` )就像我们上面使用的那样,我们必须确保 Python 本身被告知我们的程序使用 UTF-8,我们必须把 `# encoding=utf-8` 注释在我们程序的顶部。 195 | 196 | 想要详细了解此主题,请阅读以下内容: 197 | 198 | - ["The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets"](http://www.joelonsoftware.com/articles/Unicode.html) 199 | - [Python Unicode Howto](http://docs.python.org/3/howto/unicode.html) 200 | - [Pragmatic Unicode talk by Nat Batchelder](http://nedbatchelder.com/text/unipain.html) 201 | 202 | ## 小结 203 | 204 | 我们已经讨论了各种类型的输入 / 输出操作,包括:文件处理、pickle 模块和 Unicode。 205 | 206 | 接下来,我们将探讨索异常的概念。 207 | 208 | ### 家庭作业提示 209 | 210 | > 使用一个元组(从[这里](http://grammar.ccc.commnet.edu/grammar/marks/marks.htm)你可以找到所有标点符号的一个列表)来保存所有需要禁用的字符,然后使用成员资格测试来确定一个字符是否应该被移除,即 forbidden = (!, ?, ., ...)。 -------------------------------------------------------------------------------- /a-byte-of-python3/modules.md: -------------------------------------------------------------------------------- 1 | # 模块 2 | 3 | 您已经看到如何通过定义函数在程序中重用代码。如果你想在其它程序中重用一组函数,怎么办?你可能已经猜到了,答案就是模块。 4 | 5 | 编写模块有各种各样的方法,但是最简单的方法是创建一个以`.py`为扩展名,包含函数和变量的文件。 6 | 7 | 编写模块的另一种方式就像是编写Python解释器一样,可以使用[C 编程语言](http://docs.python.org/3/extending/index.html)编写模块。当它们被编译后,当使用标准的Python解释器时,你可以在Python代码中使用这些模块。 8 | 9 | 一个模块可以通过**imported(导入)**另一个程序而使用其功能。我们可以通过同样的方法使用Python标准库。首先 ,我们看一下如何使用标准库模块。 10 | 11 | 例子 (保存为module_using_sys.py): 12 | 13 | ```python 14 | import sys 15 | 16 | print('命令行参数是:') 17 | for i in sys.argv: 18 | print(i) 19 | 20 | print('\n\nPYTHONPATH是', sys.path, '\n') 21 | ``` 22 | 23 | 输出: 24 | 25 | ``` 26 | $ python module_using_sys.py we are arguments 27 | 命令行参数是: 28 | module_using_sys.py 29 | we 30 | are 31 | arguments 32 | 33 | 34 | PYTHONPATH是 ['d:\\KanCloud\\from-python-to-django\\a-byte-of-python3', 'C:\\Users\\boris\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 35 | # 还有很多,这里不一一列出 36 | 'C:\\Users\\boris\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages'] 37 | 38 | 39 | ``` 40 | 41 | **它是如何工作的:** 42 | 43 | 首先,我们使用`import`语句**import(导入)**`sys`(系统)模块。通常情况下,这意味着我们告诉Python,我们想使用这个模块。`sys`模块包含了与Python解释器和其运行环境(如操作系统)有关的函数。 44 | 45 | 当Python执行`import sys`语句时,它会查找`sys`模块。在这里,它是一个内建模块,因此,Python知道到去哪里找到它。 46 | 47 | 如果它还没有被编译,也就是Python的源代码,那么,Python解释器将在`sys.path`目录列表中搜索。如果找到了这个模块,那么就执行模块中的代码,对你的程序来说,这个模块就变成**有效**的。注意,初始化只有在我们**第一次**导入一个模块时完成。 48 | 49 | 我们通过点符号访问`sys`模块中的`argv`变量,就像这样`sys.argv`。它表明,`argv`这个名字是`sys`模块的一部分。这种方法的另一个优点是,如果你的程序中使用了名为`argv`的变量,那么他们不会冲突。 50 | 51 | `sys.argv`变量一个字符串**list(列表)**(我们会在[后面的章节](./data_structures.md)详细介绍)。具体来说,`sys.argv`包含**命令行参数**的列表,也就是使用命令行向你的程序传递的参数清单。 52 | 53 | 如果您正在使用IDE编写并运行这些程序,在菜单中寻找一种方法来指定命令行参数传递给你的程序。 54 | 55 | 这里,当我们执行`python module_using_sys.py we are arguments`时,我们使用 `python`命令和后面的参数运行`module_using_sys.py`模块。Python把命令行参数存储在`sys.argv`变量中供我们使用。 56 | 57 | 记住,运行脚本的名字通常是`sys.argv`列表中的第一个参数。因此,这里的`sys.argv[0]`是`'module_using_sys.py'`,`sys.argv[1]`是`'we'`,`sys.argv[2]`是`'are'`和`sys.argv[3]`是`'arguments'`。注意,Python从0而不是1开始计数。 58 | 59 | `sys.path`包含可以被导入的模块所在的目录名列表。我们注意到`sys.path`的第一个字符串就是程序的当前路径。这意味着你可以直接导入位于当前目录中的模块。否则,你必须把你的模块放在`sys.path`列表中的一个目录中。 60 | 61 | 请注意,当前目录是程序启动的目录。运行`import os; print(os.getcwd())`找到你的程序的当前目录。 62 | 63 | ## 字节编译的.pyc文件 64 | 65 | 导入整个模块是一个代价相对较高的事情,所以Python提供了一些技巧使它可以效率更高。一种方法是创建扩展名为`.pyc`的**字节编译**文件,这是Python将程序转换成的一种中间形式(我们在[简介](introduction.md)中介绍过Python是如何工作的。当你下次从一个不同程序导入模块时,这种`.pyc`文件是有很用的--它将快得多,因为导入模块一部分需要处理的工作已经完成了。同时,这些字节编译的文件是与操作系统平台无关的。 66 | 67 | 注意:这些`.pyc`文件通常在与之相应的`.py`文件的同一个目录中创建。如果Python在那个目录中没有写入权限,那么就**无法**创建`.pyc`文件。 68 | 69 | ## from ... import语句 70 | 71 | 如果你想直接导入`argv`变量到程序中(为了避免每次为它键入`sys.`),可以使用`from sys import argv`语句。 72 | 73 | > 一般来说,应该**避免**使用这个语句,尽量使用`import`语句,这样可以在程序中避免名称冲突,使你的程序更具有可读性。 74 | 75 | 例如: 76 | 77 | ```python 78 | from math import sqrt 79 | print("16的平方根是", sqrt(16)) 80 | ``` 81 | 82 | ## 模块的`__name__` 83 | 84 | 每个模块都有一个名字,在模块内部的语句可以获得它所在的模块的名字。当我们想搞清楚模块是直接运行还是被导入的时候,这种设计很方便。前面提到过,当一个模块被第一次导入时,其所包含的代码被执行。我们可以通过使用这个特性,根据模块是直接运行还是从另一个模块被导入,执行不同的操作。我们可以通过使用模块的`__name__`属性来实现上述功能。 85 | 86 | 例子 (保存为 module_using_name.py): 87 | 88 | ```python 89 | if __name__ == '__main__': 90 | print('这个程序正在被自己运行') 91 | else: 92 | print('我从别的模块被导入') 93 | ``` 94 | 95 | 输出: 96 | 97 | ```shell 98 | C:\> python module_using_name.py 99 | 这个程序是直接运行的 100 | C:\> python3 101 | >>> import using_name 102 | 我从被别的模块导入 103 | >>> 104 | ``` 105 | 106 | **它是如何工作的:** 107 | 108 | 每个Python模块都会定义自己的`__name__`,如果是`'__main__'`,这意味着模块在被用户直接运行,我们可以执行相应的操作。 109 | 110 | ## 制作属于你自己的模块 111 | 112 | 创建自己的模块是很容易的,就像你一直在这样的那样。因为每个Python程序就是一个模块。你只需要确保它有一个`.py`扩展名。下面的例子会让你明白这一点。 113 | 114 | 例子 (保存为mymodule.py): 115 | 116 | ```python 117 | def say_hi(): 118 | print('嗨,这就是我的模块。') 119 | 120 | __version__ = '0.1' 121 | ``` 122 | 123 | 上面的是**模块**的一个例子。正如您看到的,和我们通常的Python程序相比,没有什么特别的。接下来我们看一下如何在我们其它的程序中使用这个模块。 124 | 125 | 记住,该模块要么放置在我们导入它的程序同一个目录中,要么放置在`sys.path`目录列表中的一个目录中。 126 | 127 | 另一个模块(保存为mymodule_demo.py): 128 | 129 | ```python 130 | import mymodule 131 | 132 | mymodule.say_hi() 133 | print('版本', mymodule.__version__) 134 | ``` 135 | 136 | 输出: 137 | 138 | ```shell 139 | C:\> python mymodule_demo.py 140 | 嗨,这就是我的模块。 141 | 版本 0.1 142 | 143 | ``` 144 | 145 | **它是如何工作的:** 146 | 147 | 注意,我们使用相同的点符号来访问模块的成员。Python充分重用相同的符号产生了独特的'Pythonic'的感觉,这样我们不需要总是学习新的方法来做事情。 148 | 149 | 这是使用`from..import`语法的一个版本(保存为mymodule_demo2.py): 150 | 151 | ```python 152 | from mymodule import say_hi, __version__ 153 | 154 | say_hi() 155 | print('版本', __version__) 156 | ``` 157 | 158 | `mymodule_demo2.py`和`mymodule_demo.py`的输出相同。 159 | 160 | 注意,如果在导入模块中已经有一个`__version__`名字的声明,这里会有一个冲突。这也可能是因为它是常见的做法--对于每个模块使用这个名字声明它的版本号。因此,我们推荐使用`import`语句,虽然它可能让你的程序有点长。 161 | 162 | 你还可以使用: 163 | 164 | ```python 165 | from mymodule import * 166 | ``` 167 | 168 | 这将导入所有的公共名称如 say_hi,但不会导入__version__,因为它始于双下划线。 169 | 170 | > 注意:尽量不要使用`import *`这种方式,诸如`from mymodule import *`等 171 | 172 | 173 | 174 | > **Python之禅** 175 | > 176 | > Python的一个指导原则是"显式优于隐式"。运行`import this`去学习更多相关的信息。 177 | 178 | ## `dir`函数 179 | 180 | 您可以使用内置的`dir()`函数列出一个对象定义的标识符。例如,对于一个模块,包括在模块中已经定义的函数、类和变量。 181 | 182 | `dir()`函数有参数。当你给`dir()`提供一个模块名字时,它返回在那个模块中定义的名字的列表。当没有为其提供参数时, 它返回当前模块中定义的名字的列表。 183 | 184 | 例如: 185 | 186 | ```shell 187 | C:\>python 188 | 189 | >>> import sys 190 | 191 | # 获得属性列表,在这里是sys模块的属性列表 192 | >>> dir(sys) 193 | ['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_base_executable', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_framework', '_getframe', '_git', '_home', '_xoptions', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'set_coroutine_wrapper', 'setcheckinterval', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions', 'winver'] 194 | 195 | # 获得当前模块的属性列表 196 | >>> dir() 197 | ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'sys'] 198 | 199 | # 创建了一个新变量 'a' 200 | >>> a = 5 201 | 202 | >>> dir() 203 | ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'sys'] 204 | 205 | # 删除/移除一个名字 206 | >>> del a 207 | 208 | >>> dir() 209 | ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'sys'] 210 | 211 | >>> 212 | ``` 213 | 214 | **它是如何工作的:** 215 | 216 | 首先,我们在导入的`sys`模块上使用`dir`。我们能看到模块包含的巨大的属性列表。 217 | 218 | 然后,我们使用没有传递参数的`dir`函数。默认情况下,它返回当前模块的属性列表。注意,导入的模块仍然是这个列表的一部分。 219 | 220 | 为了看到`dir`在起作用,我们定义了一个新的变量`a`,并为其赋值,然后检查`dir`,我们发现列表中添加了一个同名变量。我们使用`del`语句移除当前模块的变量或属性,在`dir`函数的输出中变化再次得到体现。 221 | 222 | 关于`del`的一点注意事项--这个语句用于**删除**一个变量/属性,语句运行后(这里是`del a`),你不能再访问变量`a`--就像它从来根本没有存在过。 223 | 224 | 注意,`dir()`函数对**任何**对象都起作用。例如,运行`dir('str')`来学习`str`(string)类型的更多知识。 225 | 226 | 还有一个[`vars()`](http://docs.python.org/3/library/functions.html#vars)函数可以列出所有的属性及其值,但并不是在所有情况下都起作用。 227 | 228 | ## 包(Packages) 229 | 230 | 现在,你必须开始观察组织你的程序的层次结构。变量通常在函数内部。函数和全局变量通常在模块内部。如果你想组织模块?这就涉及到打包了。 231 | 232 | 包只是模块的文件夹,使用一个特殊的`__init__.py`文件,告诉Python这个文件夹是特殊的,因为它包含Python模块。 233 | 234 | 假设你想创建一个叫做'世界'的程序包,分装'亚洲'、'非洲'等等,分包按序包含'印度'、'马达加斯加'等等。 235 | 236 | 这是你的文件结构: 237 | 238 | ``` 239 | - <在sys.path中现有的一些文件夹>/ 240 | - world/ 241 | - __init__.py 242 | - asia/ 243 | - __init__.py 244 | - india/ 245 | - __init__.py 246 | - foo.py 247 | - africa/ 248 | - __init__.py 249 | - madagascar/ 250 | - __init__.py 251 | - bar.py 252 | ``` 253 | 254 | 包只是为了方便按照层次组织模块。在[Python标准库](./stdlib.md)中,你会看到包的许多实例。 255 | 256 | ## 小结 257 | 258 | 就像函数是可重用的一部分程序一样,模块也是可重用的程序。包是组织模块的更高一级的层次结构。Python自带的标准库是一系列包和模块的例子。 259 | 260 | 我们已经看到了如何使用这些模块和创建我们自己的模块。 261 | 262 | 接下来,我们将学习一些有趣的称为数据结构的概念。 263 | -------------------------------------------------------------------------------- /a-byte-of-python3/more.md: -------------------------------------------------------------------------------- 1 | # 更多 2 | 3 | 迄今为止我们已经学习了Python中的大多数常用知识。本章中我们会接触到更多的知识,使得我们更全面的掌握Python。 4 | 5 | ## 传递元组 6 | 7 | 你是否希望过从函数返回两个不同的值?做到这点使用元组即可。 8 | 9 | ```python 10 | >>> def get_error_details(): 11 | ... return (2, 'details') 12 | ... 13 | >>> errnum, errstr = get_error_details() 14 | >>> errnum 15 | 2 16 | >>> errstr 17 | 'details' 18 | ``` 19 | 20 | 注意,我们使用`a, b = `这个表达式把元组的两个字段分别赋给两个变量。 21 | 22 | 这也意味着在Python中最快速的交换两个变量的值得方法是: 23 | 24 | ```python 25 | >>> a = 5; b = 8 26 | >>> a, b 27 | (5, 8) 28 | >>> a, b = b, a 29 | >>> a, b 30 | (8, 5) 31 | ``` 32 | 33 | ## 特殊方法 34 | 35 | 有一些诸如__intit__和__del__的方法在类中拥有特殊的含义。 36 | 37 | 特殊方法用于模拟某些内建类型的行为。例如,你希望为你的类使用`x[key]`索引操作(就像在列表和元组中那样),那么你仅仅需要实现`__getitem__`方法就可以了。顺便提一句,Python正是这样实现`list`类的! 38 | 39 | 一些有用的特殊方法列在下表中。如果你想了解所有的特殊方法,详见[帮助文档](http://docs.python.org/3/reference/datamodel.html#special-method-names). 40 | 41 | - `__init__(self, ...)` 42 | - 在对象第一次被创建后,返回之前调用。 43 | 44 | - `__del__(self)` 45 | - 在对象被销毁前调用(我们无法预期这个函数什么时候被调用,因此尽量避免使用它)。 46 | 47 | - `__str__(self)` 48 | - 在使用`print`函数或`str()`时调用。 49 | 50 | - `__lt__(self, other)` 51 | - 在使用_小于_运算符时(<)调用。类似的其它运算符也存在对象的特殊方法(+, >等)。 52 | 53 | - `__getitem__(self, key)` 54 | - 当使用`x[key]`索引操作时调用。 55 | 56 | - `__len__(self)` 57 | - 当使用内建`len()`函数时调用,一般用于序列的对象。 58 | 59 | ## 单语句块 60 | 61 | 我们已经看到每个语句块都根据它的缩进级别将彼此区分开。不过有一个例外,如果某语句块只包含单条语句,你可以把它放到同一行,例如条件语句或循环语句。下面的例子清楚的说明了这点: 62 | 63 | ```python 64 | >>> flag = True 65 | >>> if flag: print('Yes') 66 | ... 67 | Yes 68 | ``` 69 | 70 | 注意上面的单条语句被放置到同一行而没有作为单独的块。虽然你利用这点可以让程序变的_更短_,但我强烈建议你避免使用这个快捷方式(除了错误检测),主要原因是使用适当的缩进可以更方便的添加额外的语句。 71 | 72 | ## Lambda表达式 73 | 74 | `lambda`语句用于在运行时创建新的函数对象。通常情况下`lambda`语句带有一个参数,后面跟着一个简单的表达式作为函数体,把参数代入函数得到的返回值就是新的函数的返回值。 75 | 76 | 例如 (保存为`more_lambda.py`): 77 | 78 | ```python 79 | points = [{'x': 2, 'y': 3}, 80 | {'x': 4, 'y': 1}] 81 | points.sort(key=lambda i: i['y']) 82 | print(points) 83 | ``` 84 | 85 | 输出: 86 | 87 | ``` 88 | $ python more_lambda.py 89 | [{'y': 1, 'x': 4}, {'y': 3, 'x': 2}] 90 | ``` 91 | 92 | **它是如何工作的:** 93 | 94 | 注意,`list`对象的`sort`函数有一个名为`key`的参数决定了这个列表是怎样被排序的(通常情况下为升序或者降序)。在我们的例子中,我们想要有一个自己的排序规则,我们需要写一个比较函数,而不是使用`def`定义一个只在这里使用一次的函数,因此我们使用lambda表达式创建一个新的函数。 95 | 96 | ## 列表解析 97 | 98 | 列表解析用于从一个现有的列表派生出一个新的列表。 假设你有一个数字列表,你想让其中所有大于2的元素乘以2并组成一个新的列表。类似问题正是使用列表解析的理想场合。 99 | 100 | 例子 (保存为`more_list_comprehension.py`): 101 | 102 | ```python 103 | listone = [2, 3, 4] 104 | listtwo = [2*i for i in listone if i > 2] 105 | print(listtwo) 106 | ``` 107 | 108 | 输出: 109 | 110 | ``` 111 | $ python more_list_comprehension.py 112 | [6, 8] 113 | ``` 114 | 115 | **它是如何工作的:** 116 | 117 | 当某些条件满足时(`if i > 2`)我们执行某些操作(`2 * i`),由此产生一个新列表。注意原始列表并不会被改变。 118 | 119 | 使用列表解析的好处在于,当我们使用循环遍历元素并将其存储到新列表时可以减少样板代码量。 120 | 121 | ## 函数接收元组和列表 122 | 123 | 这里有一种特殊的方法可以将函数的形参当做元组或字典,那就是分别使用`*`和`**`前缀。 124 | 当需要在函数内得到可变数量的实参时这个方法很有用。 125 | 126 | ```python 127 | >>> def powersum(power, *args): 128 | ... '''Return the sum of each argument raised to the specified power.''' 129 | ... total = 0 130 | ... for i in args: 131 | ... total += pow(i, power) 132 | ... return total 133 | ... 134 | >>> powersum(2, 3, 4) 135 | 25 136 | >>> powersum(2, 10) 137 | 100 138 | ``` 139 | 140 | 因为`args`变量带有`*`前缀,因此所有额外的实参都会被当做一个元组存入`args`中并传给函数。 141 | 如果把这里的`*`换成`**`,则所有额外的形参都会被当做一个字典的键/值对。 142 | 143 | ## assert语句 144 | 145 | `assert`用于断言一个表达式为真。例如,你需要确保正在使用的列表至少有一个元素,否则引发一个错误,这种情况很适合使用`assert`语句。 146 | 当assert语句断言失败,则引发一个`AssertError`。 147 | 148 | ```python 149 | >>> mylist = ['item'] 150 | >>> assert len(mylist) >= 1 151 | >>> mylist.pop() 152 | 'item' 153 | >>> assert len(mylist) >= 1 154 | Traceback (most recent call last): 155 | File "", line 1, in 156 | AssertionError 157 | ``` 158 | 159 | `assert`应当慎重使用。多数时候用于捕获异常,处理问题或是向用户显示错误后随即终止程序。 160 | 161 | ## 装饰器(decorator) 162 | 163 | 装饰器是包装函数的一种快捷方式。如果你有很多函数使用了同样的一段代码,使用装饰器对函数进行包装会非常方便。例如,我创建了一个`retry`装饰器,可以用在任何函数中,当触发任何异常的时候,他会让这个函数不断重复执行,每次执行之间有一个固定的间隔,最多执行5次。这种设计在需要通过网络进行远程调用的时候非常有用: 164 | 165 | ```python 166 | from time import sleep 167 | from functools import wraps 168 | import logging 169 | logging.basicConfig() 170 | log = logging.getLogger("retry") 171 | 172 | 173 | def retry(f): 174 | @wraps(f) 175 | def wrapped_f(*args, **kwargs): 176 | MAX_ATTEMPTS = 5 177 | for attempt in range(1, MAX_ATTEMPTS + 1): 178 | try: 179 | return f(*args, **kwargs) 180 | except: 181 | log.exception("Attempt %s/%s failed : %s", 182 | attempt, 183 | MAX_ATTEMPTS, 184 | (args, kwargs)) 185 | sleep(10 * attempt) 186 | log.critical("All %s attempts failed : %s", 187 | MAX_ATTEMPTS, 188 | (args, kwargs)) 189 | return wrapped_f 190 | 191 | 192 | counter = 0 193 | 194 | 195 | @retry 196 | def save_to_database(arg): 197 | print("Write to a database or make a network call or etc.") 198 | print("This will be automatically retried if exception is thrown.") 199 | global counter 200 | counter += 1 201 | # This will throw an exception in the first call 202 | # And will work fine in the second call (i.e. a retry) 203 | if counter < 2: 204 | raise ValueError(arg) 205 | 206 | 207 | if __name__ == '__main__': 208 | save_to_database("Some bad value") 209 | ``` 210 | 211 | 输出: 212 | 213 | ``` 214 | $ python more_decorator.py 215 | Write to a database or make a network call or etc. 216 | This will be automatically retried if exception is thrown. 217 | ERROR:retry:Attempt 1/5 failed : (('Some bad value',), {}) 218 | Traceback (most recent call last): 219 | File "more_decorator.py", line 14, in wrapped_f 220 | return f(*args, **kwargs) 221 | File "more_decorator.py", line 39, in save_to_database 222 | raise ValueError(arg) 223 | ValueError: Some bad value 224 | Write to a database or make a network call or etc. 225 | This will be automatically retried if exception is thrown. 226 | ``` 227 | 228 | **它是如何工作的:** 229 | 230 | 请参考: 231 | 232 | - http://www.ibm.com/developerworks/linux/library/l-cpdecor.html 233 | - http://toumorokoshi.github.io/dry-principles-through-python-decorators.html 234 | 235 | ## Python 2 与Python 3的区别 236 | 237 | 请参考: 238 | 239 | - ["Six" library](http://pythonhosted.org/six/) 240 | - [Porting to Python 3 Redux by Armin](http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/) 241 | - [Python 3 experience by PyDanny](http://pydanny.com/experiences-with-django-python3.html) 242 | - [Official Django Guide to Porting to Python 3](https://docs.djangoproject.com/en/dev/topics/python3/) 243 | - [Discussion on What are the advantages to python 3.x?](http://www.reddit.com/r/Python/comments/22ovb3/what_are_the_advantages_to_python_3x/) 244 | 245 | ## Summary 246 | 247 | 本章我们探讨了Python语言更多的特性。虽然我们仍然没有覆盖到Python语言的全部特性,但基本上已经可以应付在实践中的绝大多数情况。对于你即将创建的任何应用程序来说这就已经足够了。 248 | 249 | 接下来,我们来看看阅读完本书之后怎样继续学习Python。 250 | 251 | -------------------------------------------------- 252 | 253 | ### 继续阅读[继续学习](what_next.md) -------------------------------------------------------------------------------- /a-byte-of-python3/mymodule.py: -------------------------------------------------------------------------------- 1 | def say_hi(): 2 | print('嗨,这就是我的模块。') 3 | 4 | __version__ = '0.1' -------------------------------------------------------------------------------- /a-byte-of-python3/op_exp.md: -------------------------------------------------------------------------------- 1 | # 运算符和表达式 2 | 3 | 你编写的大多数语句(逻辑行)都包含**表达式**。表达式的一个简单例子是`2+3`。一个表达式可分解成运算符和操作对象。 4 | 5 | **运算符**的功能是做一些事,通过符号(如`+`)或特定的关键字表示。运算符需要一些数据来操作,这些数据被你作**操作数**(操作对象)。在这个例子中`2`和`3`是操作数。 6 | 7 | ## 运算符 8 | 9 | 我们将简单地看一下运算符和它的用法: 10 | 11 | 注意,您可以使用交互式解释器计算例子中给出的表达式。例如,为了测试表达式`2 + 3`,使用交互式Python解释器提示符: 12 | 13 | ```python 14 | >>> 2 + 3 15 | 5 16 | >>> 3 * 5 17 | 15 18 | >>> 19 | ``` 20 | 21 | 以下是一些运算符的简介: 22 | 23 | - `+` (加号) 24 | - 两个对象相加 25 | - `3 + 5` 得 `8`, `'a' + 'b'` 得 `'ab'`。 26 | 27 | - `-` (减号) 28 | - 给出一个数减去另一数的差;如果缺少第一个操作数,它默认为是0。 29 | - `-5.2` 得到一个负数,`50 - 24` 得 `26`. 30 | 31 | - `*` (乘法) 32 | - 给出两个数的乘积或返回重复多次的字符串。 33 | - `2 * 3` 得 `6`. `'la' * 3` 得到 `'lalala'`. 34 | 35 | - `**` (幂) 36 | - 返回x的y次幂 37 | - `3 ** 4` 得 `81` (也就是`3 * 3 * 3 * 3`) 38 | 39 | - `/` (除法) 40 | - x除以y 41 | - `13 / 3` 得`4.3333333333333333`. 42 | 43 | - `//` (整除) 44 | - 返回x除以y所得到的最大的整数商。注意如果其中一个参数是浮点数,那么结果也是浮点数。 45 | - `13 // 3` 得 `4` 46 | - `-13 // 3` 得 `-5` 47 | - `9//1.81` 得 `4.0` 48 | 49 | - `%` (取模) 50 | - 返回除法的余数 51 | - `13 % 3` 得 `1`. `-25.5 % 2.25` 得 `1.5`. 52 | 53 | - `<<` (向左移位) 54 | - 数字向左移动指定位数。(在内存中每个数字由比特或二进制数表示,例如:0和1)。 55 | - `2 << 2` 得 `8`. `2` 用二进制表示为`10`。 56 | - 左移两位得到`1000`,它表示数字`8`。 57 | 58 | - `>>` (向右移位) 59 | - 数字向右移动指定位数。 60 | - `11 >> 1` 得 `5`. 61 | - `11` 用二进制表示为`1011`,向右移动1位后得到二进制`101`,表示数字`5`。 62 | 63 | - `&` (按位与) 64 | - 数字的按位与 65 | - `5 & 3`得 `1`。 66 | 67 | - `|` (按位或) 68 | - 数字的按位或 69 | - `5 | 3`得 `7`。 70 | 71 | - `^` (按位异或) 72 | - 数字的按位异或 73 | - `5 ^ 3` 得 `6`。 74 | 75 | - `~` (按位求反) 76 | - x的位求反结果为-(x+1) 77 | - `~5` 得 `-6`。详见 [http://stackoverflow.com/a/11810203]() 78 | 79 | - `<` (小于) 80 | - 返回x是否小于y。所有的比较运算符返回`True`或`False`。注意这些名字的大小写。 81 | - `5 < 3` 返回 `False` 而 `3 < 5` 返回 `True`. 82 | - 比较运算符可以任意连接:`3 < 5 < 7` 返回 `True`. 83 | 84 | - `>` (大于) 85 | - 返回x是否大于y 86 | - `5 > 3` 返回 `True`。如果操作对象都是数字,它们首先转换为普通类型,否则,将返回`False`。 87 | 88 | - `<=` (小于等于) 89 | - 返回x是否小于等于y 90 | - `x = 3; y = 6; x <= y` 返回 `True`. 91 | 92 | - `>=` (大于等于) 93 | - 返回x是否大于等于y 94 | - `x = 4; y = 3; x >= y` 返回 `True`。 95 | 96 | - `==` (等于) 97 | - 比较操作对象是否相等 98 | - `x = 2; y = 2; x == y` 返回 `True`. 99 | - `x = 'str'; y = 'stR'; x == y` 返回 `False`. 100 | - `x = 'str'; y = 'str'; x == y` 返回 `True`. 101 | 102 | - `!=` (不等于) 103 | - 比较操作对象是否不相等 104 | - `x = 2; y = 3; x != y` 返回 `True`. 105 | 106 | - `not` (逻辑非) 107 | - 如果 x 是 `True`,它返回 `False`。如果 x 是 `False`,它返回 `True`。 108 | - `x = True; not x` 返回 False. 109 | 110 | - `and` (逻辑与) 111 | - 如果x是`False`, `x and y` 返回 `False`,否则它返回y的值。 112 | - `x = False; y = True; x and y` 返回 `False`,因为 x 为假。在这种情况下,Python将不计算y,因为它知道`and`左边表达式是 `False` ,这意味着整个表达式将为 `False` ,而不论其它值为什么。这叫做计算捷径。 113 | 114 | - `or` (逻辑或) 115 | - 如果 x 为 `True`, 它返回真,否则它返回y的值。 116 | - `x = True; y = False; x or y` 返回 `True`。计算捷径这也适用。 117 | 118 | ## 复合赋值运算符 119 | 120 | 我们经常会对一个变量进行数学运算,然后将操作的结果返回给最初的变量。这样的表达式可以使用一个更加简化的**复合赋值运算符**: 121 | 122 | ```python 123 | a = 2 124 | a = a * 3 125 | ``` 126 | 127 | 可以写成: 128 | 129 | ```python 130 | a = 2 131 | a *= 3 132 | ``` 133 | 134 | 注意:将 `var = var operation expression` 写成 `var operation= expression`。 135 | 136 | ## 运算顺序 137 | 138 | 如果你有一个表达式如 `2 + 3 * 4`, 是先做加法还是先做乘法呢?我们的高中数学告诉我们,应该先做乘法。这意味着乘法运算符比加法运算符具有更高的优先级。 139 | 140 | 下表对 Python 中运算符的优先顺序进行了总结,从最低优先级(最后绑定)到最高优先级(最先绑定)。 意思是说,在给定的表达式中,Python将按照自下而上的顺序,首先计算表格下方的表达式。 141 | 142 | 下表取自[Python参考手册](https://docs.python.org/zh-cn/3/reference/expressions.html#evaluation-order),放在这里是为了提供一个完整的文档。为了显式地指定优先级,更好的做法是使用圆括号组织运算符和操作数。这可使程序更加易读。详见下一节-改变运算顺序。 143 | 144 | - `lambda` : Lambda表达式 145 | - `if - else` : 条件表达式 146 | - `or` : 逻辑或 147 | - `and` : 逻辑与 148 | - `not x` : 逻辑非 149 | - `in, not in, is, is not, <, <=, >, >=, !=, ==` : 比较、成员检测、相等检测 150 | - `|` : 按位或 151 | - `^` : 按位异或 152 | - `&` : 按位与 153 | - `<<, >>` : 移位 154 | - `+, -` : 加和减 155 | - `*, /, //, %` : 乘法,除法,浮点除和余数 156 | - `+x, -x, ~x` : 正,负,按位非 157 | - `**` : 幂 158 | - `x[index], x[index:index], x(arguments...), x.attribute` : 索引,切片,函数调用,属性引用 159 | - `(expressions...), [expressions...], {key: value...}, {expressions...}` : 显式绑定或元组,显式列表,显式字典,显式集合 160 | 161 | 我们没有遇到的运算符将在后面的章节解释。 162 | 163 | 上表中在同一行列出的操作符具有**相同的优先级**。例如,`+`和`-`具有相同的优先级。 164 | 165 | ## 改变运算顺序 166 | 167 | 为使表达式更具可读性,我们可以使用圆括号。例如`2 + (3 * 4)` 肯定比需要知道运算符优先级的 `2 + 3 * 4` 更容易理解。与其他方面一样,应该合理使用括号不应该冗余(不要过分使用),如`(2 + (3 * 4))`。 168 | 169 | 使用括号有一个额外的优势——它帮助我们更改运算顺序。例如,如果您想要在一个表达式中先计算加法再计算乘法,那么你可以这样写 `(2 + 3) * 4`。 170 | 171 | ## 结合性 172 | 173 | 运算符通常从左到右。这意味着具有相同优先级的运算符按照从左到右的方式计算。例如`2 + 3 + 4`计算为 `(2 + 3) + 4`。 174 | 175 | ## 表达式 176 | 177 | 例子 (保存为expression.py): 178 | 179 | ```python 180 | length = 5 181 | breadth = 2 182 | 183 | area = length * breadth 184 | print('Area is', area) 185 | print('Perimeter is', 2 * (length + breadth)) 186 | ``` 187 | 188 | 输出: 189 | 190 | ``` 191 | C:\> python expression.py 192 | Area is 10 193 | Perimeter is 14 194 | ``` 195 | 196 | **它是如何工作的** 197 | 198 | 矩形的长度和宽度以存储在不同的变量中。我们使用这些变量计算矩形的面积和周长。我们把表达式`length * breadth` 的结果存储在变量`area`中,然后使用`print`函数输出结果结果。在第二种情况下,在打印函数中我们直接使用表达式`2 * (length + breadth)`的值。 199 | 200 | 同样要注意,Python**完美打印**是如何输出的。即使我们没有在`'Area is'` 和变量`area`,之间指定一个空格,Python为我们得到一个干净漂亮的输出,而且这种方式使得程序的可读性更强(因为我们不需要担心输出字符串之间的空格)。这只是让Python程序员的生活更轻松的例子之一。 201 | 202 | ### 小结 203 | 204 | 我们已经看到了如何使用运算符,操作对象和表达式——这是任何程序的基石。接下来,我们将看到在我们的程序中,如何在语句中使用它们。 205 | -------------------------------------------------------------------------------- /a-byte-of-python3/preface.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | Python可能是少数的几个既简单易学又功能强大的编程语言之一。对于初学者或者专家来说都非常适合,尤其是使用Python编程是一件非常快乐的事情。本书旨在帮助读者学习这门伟大的语言,让编程工作变的更加轻松。这就是所谓的“工欲善其事必先利其器”。 4 | 5 | ## 读者对象 6 | 7 | 本书作为Python编程语言的指南或教程,主要面向初学者,同时对有经验的程序员也有帮助。 8 | 9 | 本书目的是,如果对于计算机,你只知道如何保存文本文件,那么你可以从本书学习Python。如果之前你有编程经验,那么你同样可以从本书学习Python。 10 | 11 | 如果您之前有过编程经验,你将对Python和你喜欢的编程语言之间的区别感兴趣——这些区别在文中会高亮显示。可以预见的是,Python将很快成为你最喜爱的编程语言! 12 | 13 | ## 官方网站 14 | 15 | 本书中文版的官方网站为[简明Python教程](https://www.kancloud.cn/borisliu/byte-of-python-zh),英文版的官方网站为[https://python.swaroopch.com/](https://python.swaroopch.com/),你可以在线阅读全部的内容,下载最新的版本,或者[购买英文版纸质印刷书籍](https://swaroopch.com/buybook),也可以给我反馈。 16 | 17 | ## 分享一些发人深省的金句 18 | 19 | > 构建软件设计有两种途径:一种是足够简单以致明显没有缺陷,另一种是足够复杂以致没有明显缺陷。 20 | 21 | > -- C. A. R. Hoare 22 | 23 | > 人生的成功,专注和坚持比天才和机会更重要。 24 | 25 | > -- C. W. Wendte 26 | -------------------------------------------------------------------------------- /a-byte-of-python3/revision_history.md: -------------------------------------------------------------------------------- 1 | # 附录:版本历史 2 | 3 | 我为我编写的“Diamond”软件编写简化安装过程的安装程序时,我第一次开始使用Python。我不得不在Python还是Perl上绑定Qt库进行选择。我在网上做了一些研究,偶然发现了[Eric S. Raymond的一篇文章] (http://www.python.org/about/success/esr/), Raymond是一个著名的、值得尊敬的黑客。其中他谈道,Python是如何成为他最喜爱的编程语言的。我也发现PyQt的绑定比Perl-QT更加成熟。所以我决定选择Python。 4 | 5 | 然后,我开始搜索Python的优秀书籍。我没能找到一本!我确实找到了一些O'Reilly的书,但是它们要么太贵,要么更像是参考手册而不是教程。于是,我免强接受了Python的随机文档。但是它过于简单和小巧。它的确给出了关于Python的妙计,但是不完整。由于我有编程经验,因此我能够对付它,但它并不适合于初学者。 6 | 7 | 在我第一次使用Python六个月后,我安装了当时最新的Red Hat 9.0 Linux,开始使用KWord。我对它很兴奋,突然冒出一个想法,用它写一些关于Python的东西。我开始写了几页,但是很快就有30页之多。然后我认真地将其变成书的形式,使它更有用。经过几次重写,它已经达到了作为学习Python语言有用教程的水准。我将这本书作为我的贡献捐赠给开源社区。 8 | 9 | 本书开始于我在Python上的学习笔记,尽管为满足他人的口味,我做出了大量的努力,但直到现在我依然这么认为: 10 | 11 | 在开源的真正精神中,我收到了很多热心读者的建设性意见、批评和[反馈](./README.md#who-reads-bop),这些帮助我改进了本书。 12 | 13 | ## 本书的状态 14 | 15 | 本书需要像您这样的读者的帮助,指出任何不足、难以理解或者错误之处。请[写信给主要作者](http://www.swaroopch.com/contact/) 或者各个[译者](translations.md#translations)留下您的意见和建议。 16 | 17 | 18 | # Appendix: Revision History {#revision-history} 19 | 20 | - 4.0 21 | - 19 Jan 2016 22 | - Switched back to Python 3 23 | - Switched back to Markdown, using [GitBook](https://www.gitbook.com) and [Spacemacs](http://spacemacs.org) 24 | 25 | - 3.0 26 | - 31 Mar 2014 27 | - Rewritten for Python 2 using [AsciiDoc](http://asciidoctor.org/docs/what-is-asciidoc/) and [adoc-mode](https://github.com/sensorflo/adoc-mode/wiki). 28 | 29 | - 2.1 30 | - 03 Aug 2013 31 | - Rewritten using Markdown and [Jason Blevins' Markdown Mode](http://jblevins.org/projects/markdown-mode/) 32 | 33 | - 2.0 34 | - 20 Oct 2012 35 | - Rewritten in [Pandoc format](http://johnmacfarlane.net/pandoc/README.html), thanks to my wife who did most of the conversion from the Mediawiki format 36 | - Simplifying text, removing non-essential sections such as `nonlocal` and metaclasses 37 | 38 | - 1.90 39 | - 04 Sep 2008 and still in progress 40 | - Revival after a gap of 3.5 years! 41 | - Rewriting for Python 3.0 42 | - Rewrite using http://www.mediawiki.org[MediaWiki] (again) 43 | 44 | - 1.20 45 | - 13 Jan 2005 46 | - Complete rewrite using [Quanta+](https://en.wikipedia.org/wiki/Quanta_Plus) on [Fedora](http://fedoraproject.org/) Core 3 with lot of corrections and updates. Many new examples. Rewrote my DocBook setup from scratch. 47 | 48 | - 1.15 49 | - 28 Mar 2004 50 | - Minor revisions 51 | 52 | - 1.12 53 | - 16 Mar 2004 54 | - Additions and corrections 55 | 56 | - 1.10 57 | - 09 Mar 2004 58 | - More typo corrections, thanks to many enthusiastic and helpful readers. 59 | 60 | - 1.00 61 | - 08 Mar 2004 62 | - After tremendous feedback and suggestions from readers, I have made significant revisions to the content along with typo corrections. 63 | 64 | - 0.99 65 | - 22 Feb 2004 66 | - Added a new chapter on modules. Added details about variable number of arguments in functions. 67 | 68 | - 0.98 69 | - 16 Feb 2004 70 | - Wrote a Python script and CSS stylesheet to improve XHTML output, including a crude-yet-functional lexical analyzer for automatic VIM-like syntax highlighting of the program listings. 71 | 72 | - 0.97 73 | - 13 Feb 2004 74 | - Another completely rewritten draft, in DocBook XML (again). Book has improved a lot - it is more coherent and readable. 75 | 76 | - 0.93 77 | - 25 Jan 2004 78 | - Added IDLE talk and more Windows-specific stuff 79 | 80 | - 0.92 81 | - 05 Jan 2004 82 | - Changes to few examples. 83 | 84 | - 0.91 85 | - 30 Dec 2003 86 | - Corrected typos. Improvised many topics. 87 | 88 | - 0.90 89 | - 18 Dec 2003 90 | - Added 2 more chapters. [OpenOffice](https://en.wikipedia.org/wiki/OpenOffice) format with revisions. 91 | 92 | - 0.60 93 | - 21 Nov 2003 94 | - Fully rewritten and expanded. 95 | 96 | - 0.20 97 | - 20 Nov 2003 98 | - Corrected some typos and errors. 99 | 100 | - 0.15 101 | - 20 Nov 2003 102 | - Converted to [DocBook XML](https://en.wikipedia.org/wiki/DocBook) with XEmacs. 103 | 104 | - 0.10 105 | - 14 Nov 2003 106 | - Initial draft using [KWord](https://en.wikipedia.org/wiki/Kword). 107 | 108 | -------------------------------------------------- 109 | 110 | ### 继续阅读[附录: 翻译](translations.md) -------------------------------------------------------------------------------- /a-byte-of-python3/stdlib.md: -------------------------------------------------------------------------------- 1 | # 标准库 2 | 3 | Python标准库包括大量实用的模块,是每个 Python 标准安装的一部分。熟悉 Python 标准库是非常重要的,因为如果你了解这些库可以完成事情的范围,很多问题可以快速解决。 4 | 5 | 我们会快速浏览一下最常用的标准库模块,如果你想查看 Python 标准库的完整文档,请访问[标准库参考](http://docs.python.org/3/library/),这份文档随着 Python 安装包也安装在你的电脑上。 6 | 7 | 接下来让我们一起探索一些有用的模块。 8 | 9 | > 注意:如果你发现本章讨论的问题太过深奥,你可以略过本章。然而,我强烈建议你在熟练掌握 Python 编程技能之后再返回头过来看看本章的内容。 10 | 11 | ## `sys`模块 12 | 13 | `sys` 模块包括一些与操作系统相关的功能。我们已经知道了 `sys.argv` 可以列出命令行的参数列表。 14 | 15 | 假定我们想要检查我们使用的 Python 版本,我们可以使用 `sys` 模块完成这个工作。 16 | 17 | ```python 18 | >>> import sys 19 | >>> sys.version_info 20 | sys.version_info(major=3, minor=8, micro=5, releaselevel='final', serial=0) 21 | >>> sys.version_info.major == 3 22 | True 23 | ``` 24 | 25 | **它是如何工作的** 26 | 27 | `sys` 模块包含了一个名为 `version_info` 的元组,保存着 Python 软件的版本信息,其中第一项为主版本号, 我们可以通过读取这些内容来使用它。 28 | 29 | ## 日志模块 30 | 31 | 如果你想要输出调试信息,或者保存其他一些重要信息,以便于你可以随时调取,用来检查你的程序是否按照预期在运行。该怎么办?可以使用 `logging` 模块。 32 | 33 | 保存为 `stdlib_logging.py`: 34 | 35 | ```python 36 | import os 37 | import platform 38 | import logging 39 | 40 | if platform.platform().startswith('Windows'): 41 | logging_file = os.path.join(os.getenv('HOMEDRIVE'), 42 | os.getenv('HOMEPATH'), 43 | 'test.log') 44 | else: 45 | logging_file = os.path.join(os.getenv('HOME'), 46 | 'test.log') 47 | 48 | print("Logging to", logging_file) 49 | 50 | logging.basicConfig( 51 | level=logging.DEBUG, 52 | format='%(asctime)s : %(levelname)s : %(message)s', 53 | filename=logging_file, 54 | filemode='w', 55 | ) 56 | 57 | logging.debug("Start of the program") 58 | logging.info("Doing something") 59 | logging.warning("Dying now") 60 | 61 | ``` 62 | 63 | 输出为: 64 | 65 | ``` 66 | $ python stdlib_logging.py 67 | Logging to /Users/swa/test.log 68 | 69 | $ cat /Users/swa/test.log 70 | 2014-03-29 09:27:36,660 : DEBUG : Start of the program 71 | 2014-03-29 09:27:36,660 : INFO : Doing something 72 | 2014-03-29 09:27:36,660 : WARNING : Dying now 73 | ``` 74 | 75 | 在命令行中使用 `cat` 命令读取 'test.log' 文件的输出。如果 `cat` 命令不可用,你可以使用一个Visual Studio Code打开 `test.log` 。 76 | 77 | **它是如何工作的** 78 | 79 | 我们使用了 Python 标准库的三个模块: `os` 模块用于和操作系统进行交互, `platform` 模块用于获取运行平台(如操作系统)的信息, `logging` 模块用于记录日志。 80 | 81 | 首先,我们调用 `platform.platform()` 来检查程序运行的操作系统(更详细的信息请参考 `import platform; help(platform)` )。如果是 Windows 操作系统,我们找到要主驱动器、用户根文件夹和文件名。我们把这三个信息拼接起来,就得到了日志文件的完整路径。针对其他的操作系统,我们只需要知道用户的主文件夹以及文件名,就可以得到日志文件的完整路径。 82 | 83 | 我们使用 `os.path.join()` 函数连接三部分作为一个字符串,保存日志文件的完整路径。之所以使用函数做字符串连接而不是采用字符串 `+` 操作,是因为我们要确保这个操作可以在所有的操作系统环境下运行。注意:我们在这里使用的 `join()` 方法是 `os` 模块的一部分,它与我们在本书的其他地方使用的字符串方法 `join()` 不同。 84 | 85 | 我们可以配置 `logging` 模块把所有的信息用特定的格式写到我们指定的文件中。 86 | 87 | 最后,我们可以指定输出信息的属性,比如调试、提示、警告或者致命错误。一旦程序运行,我们可以通过查看这个文件了解我们程序的运行情况,即使程序没有向用户输出信息。 88 | 89 | ## Module of the Week 系列 90 | 91 | Python 标准库还有很多有用的模块,比如[调试](http://docs.python.org/3/library/pdb.html), 92 | [处理命令行参数](http://docs.python.org/3/library/argparse.html), [正则表达式](http://docs.python.org/3/library/re.html)等等。 93 | 94 | 进一步学习 Python 标准库的最佳方法,推荐大家阅读Doug Hellmann的[Python Module of the Week](http://pymotw.com/3/contents.html)系列,也可以在Amazon[购买](http://amzn.com/0321767349)纸质的书籍,当然你也可以直接阅读[Python官方文档](http://docs.python.org/3/)。 95 | 96 | ## 总结 97 | 98 | 我们学习了 Python 标准库的一些模块,强烈推荐大家快速浏览一下[Python标准库官方文档](http://docs.python.org/3/library/),以获得所有可以使用的模块。 99 | 100 | 接下来,我们探讨一下 Python 语言其他方面的事情,这样可以让我们这本教程更加完整。 101 | 102 | -------------------------------------------------- 103 | 104 | ### 继续阅读[更多](more.md) -------------------------------------------------------------------------------- /a-byte-of-python3/test.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | 3 | # 这是我们将存储对象的文件名 4 | shoplistfile = 'shoplist.data' 5 | # 购物的清单 6 | shoplist = ['苹果', '芒果', '胡萝卜'] 7 | 8 | # 写入文件 9 | f = open(shoplistfile, 'wb') 10 | # 将对象存储到文件 11 | pickle.dump(shoplist, f) 12 | f.close() 13 | 14 | # 销毁 shoplist 变量 15 | del shoplist 16 | 17 | # 从存储中读回 18 | f = open(shoplistfile, 'rb') 19 | # 从文件加载对象 20 | storedlist = pickle.load(f) 21 | print(storedlist) 22 | f.close() 23 | -------------------------------------------------------------------------------- /a-byte-of-python3/translation_howto.md: -------------------------------------------------------------------------------- 1 | # 参与翻译工作 2 | 3 | 1. The full source of the book is available from {{ book.sourceUrl }}. 4 | 2. Please [fork the repository](https://help.github.com/articles/fork-a-repo). 5 | 3. Then, fetch the repository to your computer. You need to know how to use [Git](http://www.git-scm.com) to do that. 6 | 4. Read the [GitBook documentation](https://help.gitbook.com), esp. the [Markdown section](https://help.gitbook.com/format/markdown.html). 7 | 5. Start editing the `.md` files to translate to your local language. 8 | 6. [Sign up on GitBook.com](https://www.gitbook.com), create a book and you can see a beautifully rendered website, with links to download PDF, EPUB, etc. 9 | 10 | -------------------------------------------------- 11 | 12 | ### 继续阅读[反馈](feedback.md) -------------------------------------------------------------------------------- /a-byte-of-python3/what_next.md: -------------------------------------------------------------------------------- 1 | # 继续学习 2 | 3 | 如果你有认真通读本书之前的内容并且实践其中包含的大量例程,那么你现在一定可以熟练使用python了。 4 | 同时你可能也编写了一些程序用于验证python特性并提高你的python技能。如果还没有这样做的话,你应该去试试。 5 | 现在的问题是接下来应该做什么? 6 | 7 | 我建议你先解决下面的问题: 8 | 9 | > 创建你自己的命令行版本的*通讯录程序*,利用它你可以浏览修改删除或搜索诸如朋友,家人,同事等联系人和他们的email地址/或电话号码等信息。这些信息必须存起来以便需要时提取。 10 | 11 | 思考下我们已经学到的各种知识,这个问题其实相当简单。 12 | 如果你感觉还是不好下手的话,这有一些提示。 13 | 14 | 创建一个表示联系人(persion)信息的类。使用字典存储联系人对象并以人物的名字作为字典键。 15 | 然后利用pickle模块把这些对象永久存储到你的硬盘中。最后通过字典的内建方法add, delete和modify分别增加删除修改联系人。 16 | 17 | 只要你有能力完成这个程序,你就可以自信的说你是一个python程序员了。那么现在马上给我[发送e-mail](http://www.swaroopch.com/contact/)好感谢我编写了如此强大的教程吧;-)当然这步是可选的但我还是希望你发过来。同时,也请考虑下[购买纸质书籍](http://www.swaroopch.com/buybook/)以支持本书的持续发展。 18 | 19 | 如果你觉得上面的程序太简单,这还有另一个: 20 | 21 | > 实现[replace命令](http://unixhelp.ed.ac.uk/CGI/man-cgi?replace)。此命令用于在给定的文件列表中的所有文件中替换指定的字符串。 22 | 23 | replace命令可以简单的执行字符串替换也可以复杂的进行模式查找(正则表达式),这取决于你的意愿。 24 | 25 | ## 继续完成新的项目 26 | 27 | 如果你发现上述程序依然很简单,那么你可以看一下这个更加复杂的项目清单,用你自己的方法完成这些程序: https://github.com/thekarangoel/Projects#numbers (同样的清单还可以参考 [Martyr2's Mega Project List](http://www.dreamincode.net/forums/topic/78802-martyr2s-mega-project-ideas-list/)). 28 | 29 | 更多的项目: 30 | 31 | - [Exercises for Programmers: 57 Challenges to Develop Your Coding Skills](https://pragprog.com/book/bhwb/exercises-for-programmers) 32 | - [Intermediate Python Projects](https://openhatch.org/wiki/Intermediate_Python_Workshop/Projects). 33 | 34 | ## 实例代码 35 | 36 | 学习程序设计最好的办法就是编写阅读大量代码: 37 | 38 | - [Python Cookbook](http://code.activestate.com/recipes/langs/python/) 对于某些种类的问题Python Cookbook提供了许多解决问题的珍贵技巧和诀窍。此网是每个python用户都必读的。 39 | - [Python Module of the Week](http://pymotw.com/2/contents.html) 是另外一个必读的网站,主要是[Python标准库](./stdlib.md#stdlib)的指南。 40 | 41 | ## 忠告 42 | 43 | - [The Hitchhiker's Guide to Python!](http://docs.python-guide.org/en/latest/) 44 | - [The Elements of Python Style](https://github.com/amontalenti/elements-of-python-style) 45 | - [Python Big Picture](http://slott-softwarearchitect.blogspot.ca/2013/06/python-big-picture-whats-roadmap.html) 46 | - ["Writing Idiomatic Python" ebook](http://www.jeffknupp.com/writing-idiomatic-python-ebook/) (paid) 47 | 48 | ## 视频 49 | 50 | - [Full Stack Web Development with Flask](https://github.com/realpython/discover-flask) 51 | - [PyVideo](http://www.pyvideo.org) 52 | 53 | ## 问题与解答 54 | 55 | - [Official Python Dos and Don'ts](http://docs.python.org/3/howto/doanddont.html) 56 | - [Official Python FAQ](http://www.python.org/doc/faq/general/) 57 | - [Norvig's list of Infrequently Asked Questions](http://norvig.com/python-iaq.html) 58 | - [Python Interview Q & A](http://dev.fyicenter.com/Interview-Questions/Python/index.html) 59 | - [StackOverflow questions tagged with python](http://stackoverflow.com/questions/tagged/python) 60 | 61 | ## 教程 62 | 63 | - [Hidden features of Python](http://stackoverflow.com/q/101268/4869) 64 | - [What's the one code snippet/python trick/etc did you wish you knew when you learned python?](http://www.reddit.com/r/Python/comments/19dir2/whats_the_one_code_snippetpython_tricketc_did_you/) 65 | - [Awaretek's comprehensive list of Python tutorials](http://www.awaretek.com/tutorials.html) 66 | 67 | ## 讨论组 68 | 69 | 如果你被某个问题难住了,也不知道找谁求助,那么[python-tutor list](http://mail.python.org/mailman/listinfo/tutor)是个提问的好地方。 70 | 71 | 提问之前需要先做做功课,你应该自己先尝试解决问题,然后[智慧的提问](http://catb.org/~esr/faqs/smart-questions.html) 72 | 73 | ## 新闻 74 | 75 | 如果你想了解python的最新动态,请关注[Official Python Planet](http://planet.python.org)。 76 | 77 | ## 安装库 78 | 79 | Python包索引[Python Package Index](http://pypi.python.org/pypi)拥有数量巨大的开源库,你可以在自己的程序中使用它们。 80 | 81 | 安装和使用这些库,你可以使用[pip](http://www.pip-installer.org/en/latest/)工具。 82 | 83 | ## 创建一个网站 84 | 85 | 你可以使用[Django](https://www.djangoproject.com/)创建你自己的网站,本文后面会教你一步一步学习使用Django Web框架)。(我自己的喜好,原文写的是Flask框架,但是这个框架动不动就好几个月都不更新,我想还是选择一个由团队持续维护的框架比较靠谱) 86 | 87 | ## 编写GUI应用程序 88 | 89 | 如果你想使用Python创建自己的GUI应用程序。那么可以使用已绑定到Python上的GUI(图形用户界面)库。 90 | 绑定允许你在自己的程序中使用这些库,而库本身是用C/C++或其它语言编写的。 91 | 92 | 使用Python你可以选择很多种GUI库: 93 | 94 | - Kivy 95 | - http://kivy.org 96 | 97 | - PyGTK 98 | - GTK+工具包的python绑定。它是GNOME的基础。GTK+含有很多奇怪的用法,不过一旦熟悉它你就能够快速创建GUI应用了。其中Glade图形界面设计器是必不可少的。GTK+的文档仍然完善中。GTK+在linux上工作的很好,但其windows实现仍未完成。 另外使用GTK+你既可以创建开源也可以创建私有软件。 入门可以阅读[PyGTK教程](http://www.pygtk.org/tutorial.html). 99 | 100 | - PyQt 101 | 102 | - 这是绑定到Python的Qt工具包,它是创建KDE的基石。 Qt非常易用,功能又很强大,尤其是仰仗于它的Qt Designer与出色的Qt文档。 如果你在创建开源软件(GPL’ed)则PyQt是免费的, 相反创建商业的私有软件的用户就要掏银子买它了。 从Qt4.5开始你同样可以用它创建非GPL软件。 作为入门可以阅读[PyQt5从入门到精通](https://www.gitbook.com/book/borisliu/pyqt5-gui-dev/details)。 103 | 104 | - wxPython 105 | 106 | - 这是绑定到python的wxWidgets工具包。 wxPython有一定的学习曲线。但是具有很强的可移植性,可以运行在linux,windows,Mac甚至是嵌入式平台之上。 wxPython拥有很多可用的IDE,其中包括GUI设计器和诸如SPE(Stani的python编辑器)(http://spe.pycs.net)和wxGlade(http://wxglade.sourceforge.net/)的开发工具。 使用wxPython你既可以创建开源软件也可以创建私有软件。入门可以阅读wxPython教程(http://zetcode.com/wxpython/) 107 | 108 | ### GUI小结 109 | 110 | 更多的选择参见[GuiProgramming wiki page at the official python website](http://www.python.org/cgi-bin/moinmoin/GuiProgramming)。 111 | 112 | 很不幸,python没有一个标准GUI工具。我建议根据你的情况选择上面的工具。考虑的第一歌因素是你是否愿意付费使用GUI工具。第二你是否希望程序只运行在windows或mac或linux还是希望都能运行。第三对于linux平台,你是一个KDE还是一个GNOME用户呢。 113 | 114 | 【译者】:对于国内用户来说,首选PyQt5平台,首先Qt工具是这几个工具中使用最广泛的,有一些知名的公司如LG、松下、ABB等都采用Qt作为GUI开发平台;其次,PyQt是这几个工具中升级更新最频繁的,选用一个长期不更新的软件包会各种奇葩的坑等着你去经历;最后,Qt和PyQt的中文的开发文档以及社区也是最完整的。还有,GPL版权(此处省去23412字)。 115 | 116 | 更详细广泛的分析,见Python Papers 第26页卷3问题1(http://archive.pythonpapers.org/ThePythonPapersVolume3Issue1.pdf) 117 | 118 | ## 各种python实现 119 | 120 | 一个程序设计语言通常包含两部分 – 语言和软件。语言指出如何编写程序。而软件用来运行我们的程序。 121 | 122 | 我们一直在用CPython运行我们的程序,之所以称为CPython是因为它是用C语言实现的并且为标准Python解释器。 123 | 124 | 另外还有其它的软件也可以运行python程序: 125 | 126 | - [Jython](http://www.jython.org) 127 | - 一个运行在Java平台的Python实现。这意味着你可以在Python语言内部使用Java库和类,反之亦然。 128 | 129 | - [IronPython](http://www.codeplex.com/Wiki/View.aspx?ProjectName=IronPython) 130 | - 一个运行在.NET平台的Python实现。即你可以在Python语言内部使用.NET库和类,反之亦然。 131 | 132 | - [PyPy](http://codespeak.net/pypy/dist/pypy/doc/home.html) 133 | - 一个用python写的python实现!这是一个研究项目,用于使之可以快而容易的改进解释器,因为解释器本身就是用动态语言编写的。(而不是类似上面的C, java或C#等静态语言) 134 | 135 | 除此之外还有CLPython(http://common-lisp.net/project/clpython/)一个Common Lisp编写的python实现。[Brython](http://brython.info/)是一个运行在JavaScript解释器之上的IronPython的接口,这可能意味着你可以使用python(替代JavaScript)编写web浏览器程序(“Ajax”)。 136 | 137 | 以上的每个实现都有自己的擅长领域。 138 | 139 | ## 函数式编程 (为更高级别用户) {#functional-programming} 140 | 141 | 当你要开始完成较大规模的应用程序的时候,你一定要学习一下函数式编程范式,这是与我们前面所学的[面向对象编程](./oop.md)相对应的另外一种编程范式: 142 | 143 | - [Functional Programming Howto by A.M. Kuchling](http://docs.python.org/3/howto/functional.html) 144 | - [Functional programming chapter in 'Dive Into Python' book](http://www.diveintopython.net/functional_programming/index.html) 145 | - [Functional Programming with Python presentation](http://ua.pycon.org/static/talks/kachayev/index.html) 146 | - [Funcy library](https://github.com/Suor/funcy) 147 | - [PyToolz library](http://toolz.readthedocs.org/en/latest/) 148 | 149 | ## Summary 150 | 151 | 现在我们已经来到本书的结尾了。不过据说,结束意味着另一个开始!你现在是一个满腔热情的Python程序员,很可能摩拳擦掌准备利用Python解决大量问题。现在你可以让计算机自动完成许多以前无法想象的事情或是编写游戏或是更多更多。既然如此!那就行动起来大干一场吧! 152 | 153 | -------------------------------------------------- 154 | 155 | ### 继续阅读[附录:免费/自由和开放源码软件](floss.md) -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "highlight" 4 | ] 5 | } -------------------------------------------------------------------------------- /byte-of-python-chinese-edition.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/byte-of-python-chinese-edition.pdf -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/cover.jpg -------------------------------------------------------------------------------- /django-step-by-step/chapter01.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (一) 2 | 3 | ## 1 开篇 4 | 5 | Django 是新近出来的 Rails 方式的 web 开发框架。在接触 Django 之前我接触过其它几种 Python 下的 web framework, 但感觉 Karrigell 是最容易上手的。不过 Django 从我个人的感觉上来看,它的功能更强大,社区也很活跃,高手众多,发展也是极为迅速。我个人很看好。但在学习的过程中感觉与 Karrigell 的开发体验差距比较大。那么我想,Karrigell 与 Django 的开发体验到底差别在哪里,为什么 Django 给我的感觉还不是那么清晰直观呢? 6 | 7 | 我想可能是因为 Django 的教程过于想把它的特色展示给大家,因此,对于初学者来说一下子接触的东西太多,反倒让大家很难理解。于是我想从最最简单的例子做起,并且记录下来,并且将其形成一个教程。 8 | 9 | ## 2 Karrigell的入门体验 10 | 11 | 不知道大家是否了解 Karrigell ,它是一个优秀的 web framework 框架,在发现它之后我写了不少关于代码分析的文章。因为它开发方式非常灵活,特别是方便,对它的印象也非常好。只不过开发 web 不是我的主业,因此实践得少。不过 Zoom.Quiet 在这方面已经有所建树,大家可以去 wiki 上学习他写的 “问卷调查生成系统”的快速体验教程 。这是一个非常详细的过程,而且还有图。 12 | 13 | 那么为什么 Karrigell 开发让人感觉到方便呢?我想来想去可能有这些原因: 14 | 15 | 现在的开发,特别是 Python 的开发,我们都喜欢而且习惯边学边做,从小做起,一边做一边看效果。因此从小入手,步步有体验,这就是 Python 开发的特点。我们不会一上来就写出非常大的东西,而是写一点,运行一下,调试一下,再写一点,运行一下,调试一下,慢慢地积少成多。这种方式非常典型,也更为大多数人习惯。而 Karrigell 则基本上就是这样的。安装完 Karrigell ,然后就可以运行了。不用写程序,写个简单的 html 页面,直接放在它的webapps目录下(这是2.2版,如果你没有修改karrigell.ini的root参数的缺省目录),在浏览器就可以看了。就这么简单。写程序也简单呀,写个hello.py,里面就是: 16 | 17 | ```python 18 | print "Hello, Karrigell!" 19 | ``` 20 | 21 | 这就是最简单的web体验。从这里入手后,你就可以一点点地开始学习其它的web知识了。积少成多。 Karrigell 真是就是这样。 22 | 23 | ## 3 Django的入门体验 24 | 25 | 但 Django 呢?如果说最简单的web体验 Hello, Django! 如何写呢?决不会象 Karrigell 那样简单,只从它提供的教程来看,你无法在安装后非常 Easy 地写出一个 Hello, Django! 的例子,因为有一系列的安装和准备工作要做。那么下面我把我所尝试写最简单的 Hello, Django! 的例子写出来。 26 | 27 | > 请注意,我测试时是在 Windows环境下进行的。 28 | 29 | ### 3.1 安装 30 | 31 | ``` 32 | C:\>pip install django 33 | ``` 34 | 35 | ### 3.2 生成项目目录 36 | 37 | 因为 Karrigell 可以直接开发,因此放在哪里都可以。而 Django 是一个框架,它有特殊的配置要求,因此一般不需要手工创建目录之类的工作, Django 提供了 django-admin.py 可以做这件事。 38 | 39 | ``` 40 | C:\>django-admin startproject newtest 41 | ``` 42 | 43 | 这样就在当前目录下创建了一个 newtest 目录,进去入可以看到这样的目录结构: 44 | 45 | ``` 46 | newtest/ 47 | manage.py 48 | newtest/ 49 | __init__.py 50 | settings.py 51 | urls.py 52 | wsgi.py 53 | ``` 54 | 55 | 这个 newtest 将是我们以后工作的目录,许多讲解都是基于这个目录的。 56 | 57 | > 最外层的newtest/目录包括了项目的全部文件,这个目录名可以随意修改。 58 | > 59 | > manage.py: 60 | > 提供简单化的 django-admin.py 命令,特别是可以自动进行 DJANGO_SETTINGS_MODULES 和 PYTHONPATH 的处理,而没有这个命令,处理上面环境变量是件麻烦的事情 61 | > 62 | > newtest/子目录是项目实际运行所依赖的Python包。目录名就是Python包名,如果需要导入子目录的模块就需要使用这个名字(例如mysite.urls)。 63 | > 64 | > newtest/__init__.py: 65 | > 表示这是一个 Python 的包 66 | > 67 | > newtest/settings.py: 68 | > 它是django的配置文件 69 | > 70 | > newtest/uls.py: 71 | > url映射处理文件, Karrigell 没有这种机制,它通过目录/文件/方法来自动对应,而 Django 的url映射是url对于某个模块方法的映射,目前不能自动完成 72 | > 73 | > newtest/wsgi.py: 74 | > 如果需要将项目部署到兼容WSGI的Web服务器上,这个文件就是入口。 75 | 76 | 虽然 django-admin.py 为我们生成了许多东西, 而且这些东西在以后的开发中你都需要熟悉,但现在我们的目标是最简单的体验,就认为我们不需要知道它们都有什么用吧。 77 | 78 | 项目创建好了,那么我们可以启动服务器吗? Django 为了开发方便,自带了一个用于开发的 web server。 79 | 80 | ### 3.3 启动 web server 81 | 82 | 别急呀,还没看见 Hello, Django! 在哪里呢。是的,我只是想看一看, Django 能否启动。 83 | 84 | ``` 85 | C:\newtest\>python manage.py runserver 86 | ``` 87 | 88 | 一旦出现: 89 | 90 | ``` 91 | Performing system checks... 92 | 93 | System check identified no issues (0 silenced). 94 | 95 | You have unapplied migrations; your app may not work properly until they are applied. 96 | Run 'python manage.py migrate' to apply them. 97 | 98 | November 25, 2016 - 15:50:53 99 | Django version 1.10, using settings 'mysite.settings' 100 | Starting development server at http://127.0.0.1:8000/ 101 | Quit the server with CONTROL-C. 102 | ``` 103 | 104 | 说明 Django 真的启来了。在浏览器中看一下,有一个祝贺页面,说明成功了。 105 | 106 | ![](./chapter01.png) 107 | 108 | ### 3.4 增加一个helloworld的app吗? 109 | 110 | 在 Django 中绝大多数应用都是以app形式存在的,但一定要加吗?其实并不需要。在 Django 中,每个app就是一个子包,真正调用时需要通过 URL Dispatch 来实现url与模块方法的映射。这是 Django 的一大特色,但也是有些麻烦的地方。不用它,你无法发布一个功能,如果在 Django 中存在一种缺省的简单映射的方式,这样我想可以大大提高 Django 的入门体验度。 111 | 112 | 因此根据 URL Dispatch 的机制,我们只要保证 Django 可以在正确的地方找到方法进行调用即可。那么我们就根本不去创建一个app了。 113 | 114 | 在 newtest子目录下创建一个文件 helloworld.py 内容为: 115 | 116 | ```python 117 | from django.http import HttpResponse 118 | 119 | def index(request): 120 | return HttpResponse("Hello, Django.") 121 | ``` 122 | 123 | ### 3.5 修改urls.py 124 | 125 | ```python 126 | from django.conf.urls import url 127 | from django.contrib import admin 128 | from . import helloworld 129 | 130 | urlpatterns = [ 131 | url(r'^admin/', admin.site.urls), 132 | url(r'^$', helloworld.index), 133 | ] 134 | ``` 135 | 136 | 好了。保存了。上面的 r'^$' 是为了匹配空串,也就是形如: http://localhost:8000/ 。如果这时 web server 已经启动了,那么直接刷新页面就行了。 137 | 138 | 现在觉得 Django 是不是简单多了,除了创建一个项目的操作,然后可能要修改两个配置文件,其它还都简单吧。 139 | 140 | ## 4 结论 141 | 142 | Django 本身的确是一种松散的框架组合,它既复杂又简单。复杂是因为如果你想使用它的自动化的、高级的功能你需要学习很多的东西,而且它的教程一上来就是以这种过于完整的例子进行展示,自然会让你觉得很麻烦。不过看了我的讲解之后,是不是觉得还是挺简单的。那么我们就先以无数据库的方式进行下去,一点点地发掘 Django 的功能特性吧。 143 | 144 | 回过头来再细想一想,之所以认为 Karrigell 简单而 Django 复杂,主要在于 Karrigell 的教程适合我们这种由浅入深,循序渐近的方式,而 Django 虽然也可以这样,但它的教程却没有做成这样,因此让我们茫然。当然到最后,在我们熟练之后我们不再会有这样的感觉,毕竟这只是入门的体验,但就是这种体验可能会吓走许多的人呢。 145 | -------------------------------------------------------------------------------- /django-step-by-step/chapter01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter01.png -------------------------------------------------------------------------------- /django-step-by-step/chapter02.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (二) 2 | 3 | ## 1 引言 4 | 5 | 随着学习,我们的例子也开始复杂了,下一步我想实现一个简单的 web 加法器。界面会是这样: 6 | 7 | ![](./chapter0201.png) 8 | 9 | 很简单。通过本节的学习我们可以学习到: 10 | 11 | 如何处理页面表格提交的数据,并且会对 URL Dispatch 作更进一步的解释。 12 | 13 | ## 2 创建 add.py 文件 14 | 15 | 我们在newtest子目录中创建一个 add.py 文件。(由于我们还没有涉及到 Django 的模型,因此象 add.py 这样的东西叫什么呢?还是称其为 View 吧。因为在 django 中,View 是用来显示的,它代替了一般的 MVC 中的 Control 的作用,因为 Django 中不是 MVC 而是 MTV (Model Template View)) 16 | 17 | ```python 18 | from django.http import HttpResponse 19 | 20 | text = """
21 | + 22 | 23 |
""" 24 | 25 | def index(request): 26 | if 'a' in request.POST: 27 | a = int(request.POST['a']) 28 | b = int(request.POST['b']) 29 | else: 30 | a = 0 31 | b = 0 32 | return HttpResponse(text % (a, b, a + b)) 33 | ``` 34 | 35 | 这里只有一个 index 方法。所有在 view 中的方法第一个参数都会由 Django 传入 request 对象,它就是请求数据对象,它是由 Django 自动生成。其中有 GET 和 POST 属性,分别保存两种不同的提交方式的数据,它们都可以象字典一样工作。 36 | 37 | 那么我的想法就是: 38 | 39 | 进入页面就是上面的效果,页面上有两个输入文本框,一个提交按钮,一个显示结果的文本框。在两个输入文本框中输入整数,然后点击提交("="号按钮),将返回相同的页面,但结果文本框中将显示两数相加的和。两个输入文本框分别定义为 a 和 b 。 40 | 41 | 这里的逻辑就是:先判断 POST 数据中是否有变量 a ,如果没有则表示是第一次进入,则 a, b 初始为 0 ,然后返回页面。如果有变量 a ,则计算结果,返回页面。 42 | 43 | 其实这里面有许多可以细说的东西,那么我把它们放在后面陈述。 44 | 45 | ## 3 修改urls.py 46 | 47 | ```python 48 | from django.conf.urls import url 49 | from django.contrib import admin 50 | from . import helloworld, add 51 | 52 | urlpatterns = [ 53 | url(r'^admin/', admin.site.urls), 54 | url(r'^$', helloworld.index), 55 | url(r'^add/$', add.index), 56 | ] 57 | ``` 58 | 59 | 增加 add 的 url 映射。 60 | 61 | ## 4 启动 server 62 | 63 | ## 5 在浏览器测试 64 | 65 | 66 | 67 | 点击提交之后,你会看到下面这个信息: 68 | 69 | ![](./chapter0202.png) 70 | 71 | 这是由于Django默认启动了防止CSRF(Cross-site request forgery:跨站请求伪造,是一种对网站的恶意利用)攻击的安全设置。本章暂不讨论这方面的内容,我们通过装饰器(decorator)关闭这个设置。 72 | 73 | 修改add.py文件: 74 | 75 | ```python 76 | from django.http import HttpResponse 77 | from django.views.decorators.csrf import csrf_exempt 78 | 79 | text = """
80 | + 81 | 82 |
""" 83 | 84 | 85 | @csrf_exempt 86 | def index(request): 87 | if 'a' in request.POST: 88 | a = int(request.POST['a']) 89 | b = int(request.POST['b']) 90 | else: 91 | a = 0 92 | b = 0 93 | return HttpResponse(text % (a, b, a + b)) 94 | ``` 95 | 96 | 你会看到和我相似的界面,然后输入整数试一试吧。 97 | 98 | ## 6 补充说明 99 | 100 | 1. 在 form 中的 method="post" 。你当然可以使用 get ,但是在 Django 的设计风格中认为,使用 POST 表示要对数据进行修改,使用 GET 则只是获取,这是一个设计风格,并且不仅仅属于 Django 。如果能够养成习惯是非常好的。 101 | 2. Django 提供了 URL Dispatch 文档,专门讲解有关 url 映射的东西。其中有一部分是关于 url 的正则表达式解析的。原本我认为象 Karrigell 中一样,定义在 form 中的变量会自动映射为方法的参数,但是我错了。方法中的参数是从 url 中通过正则表达式解析出来的,或者是在 url_conf(即 urls.py 文件)中指定的。因此它与 Karrigell 一点也不一样。因此,如果你想从 POST 或 GET 数据中得到值,那么象我一样去做好了。使用 request.POST 或 request.GET 或还有一个可以“统吃”的方法 request.REQUEST ,它们是一个字典数据,使用起来也算方便。 102 | 103 | 从这里我更想了解方法中参数的使用,当然这个例子并没有,有机会再使用吧。关于正则表达式解析参数在 blog 和 rss 中用得是非常多的。 104 | -------------------------------------------------------------------------------- /django-step-by-step/chapter0201.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0201.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0202.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0202.png -------------------------------------------------------------------------------- /django-step-by-step/chapter03.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (三) 2 | 3 | ## 1 引言 4 | 5 | 本教程只想从浅到深地将大家带入到 Django 的世界,因此都是以简单的例子出发,而且这些例子都是为了说明问题,本身并没有什么实际的意义。因此许多高级话题都无法涉及,需要大家自行看文档,做试验。 6 | 7 | 从上一例我们看到,表格的生成是直接在 index() 函数中返回的 HTML 代码,这种混合方式对于大型开发非常不好,下面我们就学习模板的使用。 Django 自带有模板系统,但你可以不使用它,只要在 return 前使用自已喜欢的模板系统进行处理,然后返回即可。但 Django 自带的模板系统有很多特点,我不做过多的说明。我只是想使用它。 8 | 9 | 现在我的问题就是: 10 | 11 | >我有一个通讯录数据,我想使用一个表格来显示。 12 | 13 | 为了方便,我们不需要使用数据库,因此我把它存在 view 文件中。 14 | 15 | ## 2 创建 list.py 16 | 17 | ```python 18 | from django.shortcuts import render_to_response 19 | 20 | address = [ 21 | {'name':'张三', 'address':'地址一'}, 22 | {'name':'李四', 'address':'地址二'} 23 | ] 24 | 25 | def index(request): 26 | return render_to_response('list.html', {'address': address}) 27 | ``` 28 | 29 | 这里使用了一个新方法是 render_to_response ,它可以直接调用模板并返回生成好的文本,直接返回它即可。它接收两个参数,第一个是模板的名字。 30 | 31 | 第二个参数是一个字典,这里只有一个 Key ,名字是 address ,它的值是一个字典的列表。只要注意模板所接收的就是这样的字典和包含字典的列表就行了。 32 | 33 | ## 3 在 newtest 中创建 templates 目录 34 | 35 | 用来存放模板文件 36 | 37 | ## 4 修改 settings.py 38 | 39 | 修改`INSTALLED_APPS`的内容,增加`'newtest',`,最后的设置为: 40 | 41 | ``` 42 | INSTALLED_APPS = [ 43 | 'django.contrib.admin', 44 | 'django.contrib.auth', 45 | 'django.contrib.contenttypes', 46 | 'django.contrib.sessions', 47 | 'django.contrib.messages', 48 | 'django.contrib.staticfiles', 49 | 'newtest', 50 | ] 51 | ``` 52 | 53 | Django会自动搜索newtest/templates目录下的模板文件。 54 | 55 | ## 5 创建 templates/list.html 56 | 57 | ``` 58 |

通讯录

59 | 60 | 61 | {% for user in address %} 62 | 63 | 64 | 65 | 66 | {% endfor %} 67 |
姓名地址
{{ user.name }}{{ user.address }}
68 | ``` 69 | 70 | 很简单,就是生成了一个两列的表格。在 Django 模板中 `{{}}` 表示引用一个变量, `{%%}` 表示代码调用。在变量引用中, Django 还支持对变量属性的访问,同时它还有一定的策略,详细的建议查看 The Django template language 文档。这里我也使用了汉字,因此它也需要使用 utf-8 编码。 71 | 72 | 这里使用 for .. in 的模板 Tag 处理。因此 address 需要是一个集合。在我们的 View 代码中, address 为一个 list 值。每个 list 又是一个字典。因此 `{{ user.name }}` 和 `{{ user.address }}` 就是将这个字典中的元素取出来。 73 | 74 | 75 | ## 6 修改 urls.py 76 | 77 | ```python 78 | from django.conf.urls import url 79 | from django.contrib import admin 80 | from . import helloworld, add, list 81 | 82 | urlpatterns = [ 83 | url(r'^admin/', admin.site.urls), 84 | url(r'^$', helloworld.index), 85 | url(r'^add/$', add.index), 86 | url(r'^list/$', list.index), 87 | ] 88 | ``` 89 | 90 | 增加了 list 的 url 映射。 91 | 92 | ## 7 启动 server 93 | 94 | 效果如这样: 95 | 96 | ![](./chapter03.png) -------------------------------------------------------------------------------- /django-step-by-step/chapter03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter03.png -------------------------------------------------------------------------------- /django-step-by-step/chapter04.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (四) 2 | 3 | ## 1 引言 4 | 5 | 经过前几节的学习,我想大家应该比较熟悉 Django 的大致开发流程: 6 | 7 | * 增加 view 方法 8 | * 增加模板 9 | * 修改 urls.py 10 | 11 | 就是这样。剩下的就是挖掘 Django 提供的其它的能力。在我们还没有进入模型(model)之前还是再看一看外围的东西,再更进一步体验 Django 吧。 12 | 13 | 在 Django 中我看到了一个生成 csv 格式的文档(Outputting CSV dynamically),非常好,它没有数据库,正好用来做演示。 14 | 15 | 更进一步,现在我的需求就是提供 excel 格式文件的下载。 16 | 17 | 我们会在原来 list(表格) 例子基础上进行演示,步骤就是上面的流程。 18 | 19 | ## 2 修改 templates/list.html 20 | 21 | 在文件最后增加: 22 | 23 | ```html 24 |

Excel格式下载

25 | ``` 26 | 27 | 它将显示为一个链接,它所指向的链接将用来生成 Excel 文件。 28 | 29 | ## 3 在newtest下增加 xls_test.py 30 | 31 | ```python 32 | from django.http import HttpResponse 33 | from django.template import loader, Context 34 | 35 | address = [ 36 | ('张三', '地址一'), 37 | ('李四', '地址二') 38 | ] 39 | 40 | def output(request, filename): 41 | response = HttpResponse(content_type='application/ms-excel') 42 | response['Content-Disposition'] = 'attachment; filename=%s.xls' % filename 43 | 44 | t = loader.get_template('xls.html') 45 | c = Context({ 46 | 'data': address, 47 | }) 48 | response.write(t.render(c)) 49 | return response 50 | ``` 51 | 52 | 这里使用的东西多了一些。这里没有 `render_to_response` 了,而是演示了一个完整的从头进行模板解析的处理过程。为什么需要这样,因为我们需要修改 `response` 对象的值,而 `render_to_response` 封装了它使得我们无法修改。从这里我们也可以看到,在调用一个方法时, Django 会传入一个 `request` 对象,在返回时,你需要将内容写入 `response` ,必要时修改它的某些属性。更详细的建议你参考 django 所带的 request_response 文档,里面详细描述了两个对象的内容,并且还可以在交互环境下进行测试,学习非常方便。 53 | 54 | 这里 `address` 不再是字典的列表,而是 tuple 的列表。让人高兴的是, Django 的模板除了可以处理字典,还可以处理序列,而且可以处理序列中的元素。一会在模板定义中我们会看到。 55 | 56 | 这里 `output()` 是我们希望 Django 调用的方法,不再是 `index()` 了。(不能老是一样的呀。)而且它与前面的 `index()` 不同,它带了一个参数。这里主要是想演示 url 的参数解析。因此你要注意,这个参数一定是放在 url 上的。它表示输出文件名。 57 | 58 | ```python 59 | response = HttpResponse(mimetype='application/ms-excel') 60 | response['Content-Disposition'] = 'attachment; filename=%s.xls' % filename 61 | ``` 62 | 63 | 这两行是用来处理输出类型和附件的,以前我也没有用过,这回也学到了。它表明返回的是一个Excel格式的文件。 64 | 65 | ```python 66 | t = loader.get_template('xls.html') 67 | c = { 68 | 'data': address, 69 | } 70 | response.write(t.render(c)) 71 | ``` 72 | 73 | 这几行就是最原始的模板使用方法。先通过 `loader` 来找到需要的模板,然后生成一个 template 对象,再生成一个 `Context` 对象,它就是一个字典集。然后 `t.render(c)` 这个用来对模板和提供的变量进行合并处理,生成最终的结果。最后调用 `response.write()` 将内容写入。 74 | 75 | ## 4 增加 templates/xls.html 76 | 77 | ```html 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | {% for row in data %} 86 | 87 | 88 | 89 | 90 | {% endfor %} 91 | 92 | 93 | ``` 94 | 95 | 使用了一个 for 循环。这里 `data` 与上面的 `Context` 的 `data` 相对应。因为 `data` 是一个列表,它的每行是一个 tuple ,因此 `row.0`, `row.1` 就是取 tuple 的第一个和第二个元素。`|` 是一个过滤符,它表示将前一个的处理结果作为输入传入下一个处理。因此 Django 的模板很强大,使用起来也非常直观和方便。 `addslashes` 是 Django 模板内置的过滤 Tag ,它用来将结果中的特殊字符加上反斜线。 96 | 97 | 同时我们注意到,每个 `{{}}` 前后都有一个双引号,这样就保证每个字符串使用双引号引起来。然后在第一个与第二个元素之间还使用了逗号分隔。最后 `endfor` 在下一行,表示上面每行模板后有一个回车。 98 | 99 | Django 还允许你自定义 Tag ,在 The Django template language: For Python programmers 文档中有描述,其实是很简单的。 100 | 101 | ## 5 修改 urls.py 102 | 103 | ```python 104 | from django.conf.urls import url 105 | from django.contrib import admin 106 | from . import helloworld, add, list, xls_test 107 | 108 | urlpatterns = [ 109 | url(r'^admin/', admin.site.urls), 110 | url(r'^$', helloworld.index), 111 | url(r'^add/$', add.index), 112 | url(r'^list/$', list.index), 113 | url(r'^xls/(?P\w+)/$', xls_test.output), 114 | ] 115 | ``` 116 | 117 | 增加了 xls 的 url 映射。 118 | 119 | 上面的正则表达式有些复杂了,因为有参数的处理在里面。 `(?P\w+)` 这是一个将解析结果起名为 `filename` 的正则表达式,它完全符合 Python 正则表达式的用法。在最新的 Django 中,还可以简化一下: `(\w+)` 。但这样需要你的参数是按顺序传入的,在一个方法有多个参数时一定要注意顺序。 120 | 121 | 还记得吗?我们的链接是写成 `/xls/address/` ,因此上面实际上会变成对 `xls_test.output(filename='address')` 的调用。 122 | 123 | ## 6 启动 server 124 | 125 | 看一下结果吧。点击链接,浏览器会提示你保存文件的。 126 | 127 | 很简单吧。但这里面的内容其实也不少,而且许多地方都有很大的扩展空间。 128 | -------------------------------------------------------------------------------- /django-step-by-step/chapter05.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (五) 2 | 3 | ## 1 引言 4 | 5 | 其实本教程以展示基本概念为已任,对于一些高级的话题我也在不停地学习中,希望能有所展示。让我们一起学习吧。 6 | 7 | 在了解了基本的 Django 开发的过程及 Django 的一些基本特性之后,越来越多的东西在等着我们。现在我们就学习一下 session 吧。 session 可以翻译为“会话”,做过web的可能都知道。它就是为了实现页面间的数据交换而产生的东西,一般有一个 session_id ,它会保存在浏览器的 cookie 中,因此如果你的浏览器禁止了 cookie ,下面的试验是做不了的。 8 | 9 | 在 Django 中的 session 也非常简单,它就存在于 `request` 对象的 `session` 属性中。你可以把它看成一个字典就可以了。 10 | 11 | 下面我们做一个非常简单的功能:首先当用户进入某个页面,这个页面会显示一个登录页面,上面有一个文本框用来输入用户名,还有一个提交按钮用来提交数据。当用户输入用户名,然后点提交,则显示显示用户已经登录,并且打印出用户的姓名来,同时还提供一个“注销”按钮。然后如果用户再次进入这个页面,则显示同登录成功后的页面。如果点击注销则重新进入未登录的页面。 12 | 13 | ## 2 在newtest下创建 login.py 14 | 15 | ```python 16 | from django.http import HttpResponseRedirect 17 | from django.shortcuts import render_to_response 18 | from django.views.decorators.csrf import csrf_exempt 19 | 20 | @csrf_exempt 21 | def login(request): 22 | username = request.POST.get('username', None) 23 | if username: 24 | request.session['username'] = username 25 | username = request.session.get('username', None) 26 | if username: 27 | return render_to_response('login.html', {'username':username}) 28 | else: 29 | return render_to_response('login.html') 30 | 31 | @csrf_exempt 32 | def logout(request): 33 | try: 34 | del request.session['username'] 35 | except KeyError: 36 | pass 37 | return HttpResponseRedirect("/login/") 38 | ``` 39 | 40 | 有些复杂了吗?没关系,让我解释一下。这里有两个方法: `login()` 和 `logout()` 。 `login()` 用来提供初始页面、处理提供数据和判断用户是否登录。而 `logout()` 只是用来从 session 中删除用户名,同时将页面重定向到 login 画面。这里我仍然使用了模板,并且根据传入不同的字典来控制模板的生成。是的,因为 Django 的模块支持条件判断,所以可以做到。 41 | 42 | 在 `login()` 中的判断逻辑是: 43 | 44 | * 先从 POST 中取 `username` (这样 `username` 需要由模板的 form 来提供),如果存在则加入到 session 中去。加入 session 很简单,就是一个字典的 Key 赋值。 45 | * 然后再从 session 中取 `username` ,有两种可能:一种是上一步实现的。还有一种可能是直接从以前的 session 中取出来的,它不是新产生的。而这里并没有细分这两种情况。因此这个判断其实对应两种页面请求的处理:一种是提交了用户姓名,而另一种则是处理完用户提交姓名之后,用户再次进入的情况。而用户再次进入时,由于我们在前面已经将他的名字保存在 session 里面了,因此可以直接取出来。如果 session 中存在,则表示用户已经登录过,则输出 `login.html` 模板,同时传入了 `username` 字典值。而如果 session 中不存在,说明用户从来没有登录过,则输出 `login.html` 模板,这次不带值。 46 | 47 | 因此对于同一个 `login.htm` 模板传入的不同值,后面我们会看到模板是如何区分的。 48 | 49 | 在 `logout()` 中很简单。先试着删除 session ,然后重定向页面到 login 页面。这里使用了 `HttpResponseRedirect` 方法,它是从以前我们看到的 `HttpResponse` 派生来的子类。更多的派生子类和关于 response 的内容要参考 Request and response objects 文档。 50 | 51 | ## 3 创建 templates/login.html 52 | 53 | ```html 54 | {% if not username %} 55 |
56 | 用户名:
57 | 58 | 59 | {% else %} 60 | 你已经登录了!{{ username }}
61 |
62 | 63 | 64 | {% endif %} 65 | ``` 66 | 67 | 整个是一个 `if` 语句。在 Django 模板中的 `if` 可以象 Python 一样使用,如使用 `not` , `and` , `or` 。象 `if not username` 表示什么呢?它表示如果 username 不存在,或为空,或是假值等等。而此时我们利用了 `username` 不存在这种判断。 68 | 69 | 上面的逻辑表示,如果 `username` 不存在,则显示一个表单,显示用户名输入文本框。如果存在,则显示已经登录信息,同时显示用户名和注销按钮。而这个注销铵钮对应于 `logout()` 方法。 70 | 71 | ## 4 修改 urls.py 72 | 73 | ```python 74 | from django.conf.urls import url 75 | from django.contrib import admin 76 | from . import helloworld, add, list, xls_test, login 77 | 78 | urlpatterns = [ 79 | url(r'^admin/', admin.site.urls), 80 | url(r'^$', helloworld.index), 81 | url(r'^add/$', add.index), 82 | url(r'^list/$', list.index), 83 | url(r'^xls/(?P\w+)/$', xls_test.output), 84 | url(r'^login/$', login.login), 85 | url(r'^logout/$', login.logout), 86 | ] 87 | ``` 88 | 89 | 增加了 login 和 logout 两个url映射。 90 | 91 | ## 5 启动 server 运行 92 | 93 | 但我要说,你一定会报错。而且我的也在报错。为什么,因为从这一刻起,我们就要进入有数据库的环境了。因为在 django 中 session 是存放在数据库中的。所以在这里要进行数据库的初始化了。 94 | 95 | ## 6 查看 settings.py 96 | 97 | 我们在创建newtest工程的时候,Django已经为我们默认生成了数据库处理的配置: 98 | 99 | ```python 100 | DATABASES = { 101 | 'default': { 102 | 'ENGINE': 'django.db.backends.sqlite3', 103 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 104 | } 105 | } 106 | ``` 107 | 108 | 这里使用的是 sqlite3 。在使用数据库时,你同时需要自已去安装相应的数据库处理模块。 109 | 110 | ## 7 初始化数据库 111 | 112 | 配置不需要做什么修改,下一步我们进行数据库的初始化工作,包括建库、建表等: 113 | 114 | ```shell 115 | python manage.py migrate 116 | ``` 117 | 118 | ## 8 启动 server 119 | 120 | 这次再进入试吧 121 | 122 | (http://localhost:8000/login/) 123 | 124 | 从此我们要进入数据库的世界了,当然目前还没有用到,而 Django 提供的许多自动化的高级功能都是需要数据库支持的。 125 | -------------------------------------------------------------------------------- /django-step-by-step/chapter06.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (六) 2 | 3 | ## 1 引言 4 | 5 | 以后的例子可能会越来越复杂,没办法因为我们用的东西越来越复杂,同时我们的能力也在增长。 6 | 7 | 下面我们按照 [TurboGears](http://www.turbogears.org) 的 [Wiki in 20 Minutes](http://turbogears.readthedocs.io/en/latest/turbogears/wiki20.html) 的例子仿照一个,我们要用 [Django](https://www.djangoproject.com/) 来做 wiki。我不会按 TurboGears 的操作去做,只是实现一个我认为的最简单的 wiki。 8 | 9 | 现在我的要求是: 10 | 11 | >做一个简单的wiki,要可以修改当前页面,即在页面下面提供一个编辑的按钮。然后还要识别页面中的两个开头大写的单词为页面切换点,可以进入一个已经生成好的页面,或提示创建一个新页面。 12 | 13 | 下面我们将开始创建 Django 中的 app 了。 14 | 15 | 先说一下。如果你看过官方版的教程,它就是讲述了一个 Poll 的 app 的生成过程。那么一个 app 就是一个功能的集合,它有自已的 model ,view 和相应的模板,还可以带自已的 urls.py 。那么它也是一个独立的目录,这样一个 app 就可以独立地进行安装,你可以把它安装到其它的 Django 服务器中去。因此采用 app 的组织形式非常有意义。而且 `adango-admin.py` 也提供了一个针对 app 的命令,一会我们就会看到。而且 Django 提供一些自动功能也完全是针对于 app 这种结构的。Model, Template, View 就合成了 MTV 这几个字母。 Model 是用来针对数据库,同时它可以用来自动生成管理界面, View 在前面我们一直都用它,用来处理请求和响应的相当于MVC框架中的 Controller 的作用, Template 用来生成界面。 16 | 17 | ## 2 创建 wiki app 18 | 19 | ```Shell 20 | python manage.py startapp wiki 21 | ``` 22 | 23 | 24 | 这样在 wiki 子目录下有以下文件: 25 | 26 | * __init__.py 27 | 表示 wiki 目录是一个包。 28 | 29 | * views.py 30 | 用来放它的 view 的代码。 31 | 32 | * models.py 33 | 用来放 model 代码。 34 | 35 | * apps.py 36 | 用来放配置代码 37 | 38 | * admin.py 39 | 用来配置当前的wiki如何使用Django Admin功能 40 | 41 | * tests.py 42 | 用来放测试代码 43 | 44 | * migrations目录 45 | 用来放每一次数据库变化后需要对数据库做的变化 46 | 47 | ## 3 编辑 wiki/models.py 48 | 49 | ```Python 50 | from django.db import models 51 | 52 | # Create your models here. 53 | class Wiki(models.Model): 54 | pagename = models.CharField(max_length=20, unique=True) 55 | content = models.TextField() 56 | ``` 57 | 58 | 每个 model 其实在 Django 中就是一个表,你将用它来保存数据。在实际的应用中,一般都要与数据库打交道,如果你不想用数据库,那么原因可能就是操作数据库麻烦,创建数据库环境也麻烦。但通过 Django 的 model 处理,它是一种 ORM (Object Relation Mapping, 对象与关系的映射),可以屏蔽掉底层数据库的细节,同时提供以对象的形式来处理数据。非常方便。而且 Django 的 model 层支持多种数据库,如果你改变数据库也不是什么问题,这也为以后的数据库迁移带来好处。总之,好处多多,大家多多体会吧。 59 | 60 | Wiki 是 model 的名字,它需要从 models.Model 派生而来。它定义了两个字段,一个是字段是 pagename , 用来保存 wiki 页面的名字,它有两个参数,一个是最大长度(不过从这点上不如 [SQLAlchemy](http://www.sqlalchemy.org/) 方便, SQLAlchemy并不需要长度,它会根据有无长度自动转为 TEXT 类型),目前 CharField 需要这个参数;另一个是 unique 表示这个字段不能有重复值。还有一个字段是 content ,用来保存 wiki 页面的内容,它是一个 TextField 类型,它不需要最大长度。 61 | 62 | 现在不太了解 model 没有关系,关键是看整个生成过程。 63 | 64 | 一旦你定义好了 model ,在运行时, Django 会自动地为这个 model 增加许多数据操作的方法。关于 model 和 数据库操作API的详细内容参见 [Model reference](https://docs.djangoproject.com/en/1.10/ref/models/) 和 [Database API reference](https://docs.djangoproject.com/en/1.10/ref/databases/) 的文档。 65 | 66 | ## 4 修改 settings.py, 安装 app 67 | 68 | 虽然我们的其它工作没有做完,但我还是想先安装一下 app 吧。每个一 app 都需要安装一下。安装一般有两步: 69 | 70 | ### 4.1 修改settings.py 71 | 72 | ```Python 73 | INSTALLED_APPS = [ 74 | 'django.contrib.admin', 75 | 'django.contrib.auth', 76 | 'django.contrib.contenttypes', 77 | 'django.contrib.sessions', 78 | 'django.contrib.messages', 79 | 'django.contrib.staticfiles', 80 | 'newtest', 81 | 'wiki.apps.WikiConfig', 82 | ] 83 | ``` 84 | 85 | 这个在文件的最后,django开头的是缺省定义的。给出指定 wiki 包的引用名来。这一步是为了以后方便地导入所必须的。因为我们的目录都是包的形式,因此这里就是与目录相对应的。 86 | 87 | ### 4.2 执行(在newtest目录下) 88 | 89 | ```Shell 90 | python manage.py makemigrations 91 | python manage.py migrate 92 | ``` 93 | 94 | 如果没有报错就是成功了。这一步 Django 将根据 model 的信息在数据库中创建相应的表。表就是这样创建出来的。 95 | 96 | ## 5 在命令行下加入首页(FrontPage) 97 | 98 | 我们假设首页的名字为 FrontPage ,并且我们将在命令行下增加它,让我们熟悉一下命令行的使用 99 | 100 | 进入 newtest 目录,然后: 101 | 102 | ```Shell 103 | python manage.py shell 104 | ``` 105 | 106 | 进入 python 107 | 108 | ```Python 109 | >>> from wiki.models import Wiki 110 | >>> page = Wiki(pagename='FrontPage', content='Welcome to Easy Wiki') 111 | >>> page.save() 112 | >>> Wiki.objects.all() 113 | [] 114 | >>> p = Wiki.objects.all()[0] 115 | >>> p.pagename 116 | 'FrontPage' 117 | >>> p.content 118 | 'Welcome to Easy Wiki' 119 | ``` 120 | 121 | 在 Django 中,对于数据库的记录有两种操纵方式,一种是集合方式,一种是对象方式。集合方式相当于表级操作,可以使用 model.objects 来处理。 objects 对象有一些集合方式的操作,如 all() 会返回全部记录, filter() 会根据条件返回部分记录。而象插入新记录则需要使用记录方式来操作,些时要直接使用 model 类。 122 | 123 | ## 6 修改 wiki/views.py 124 | 125 | ```Python 126 | from .models import Wiki 127 | from django.template import loader, Context 128 | from django.http import HttpResponse, HttpResponseRedirect 129 | from django.shortcuts import render_to_response 130 | from django.views.decorators.csrf import csrf_exempt 131 | 132 | def index(request, pagename=""): 133 | """显示正常页面,对页面的文字做特殊的链接处理""" 134 | if pagename: 135 | #查找是否已经存在页面 136 | # pages = Wiki.objects.get_list(pagename__exact=pagename) 137 | pages = Wiki.objects.filter(pagename=pagename) 138 | if pages: 139 | #存在则调用页面模板进行显示 140 | return process('wiki/page.html', pages[0]) 141 | else: 142 | #不存在则进入编辑画面 143 | return render_to_response('wiki/edit.html', {'pagename':pagename}) 144 | 145 | else: 146 | # page = Wiki.objects.get_object(pagename__exact='FrontPage') 147 | page = Wiki.objects.get(pagename='FrontPage') 148 | return process('wiki/page.html', page) 149 | 150 | @csrf_exempt 151 | def edit(request, pagename): 152 | """显示编辑存在页面""" 153 | # page = Wiki.objects.get_object(pagename__exact=pagename) 154 | page = Wiki.objects.get(pagename=pagename) 155 | return render_to_response('wiki/edit.html', {'pagename':pagename, 'content':page.content}) 156 | 157 | @csrf_exempt 158 | def save(request, pagename): 159 | """保存页面内容,老页面进行内容替换,新页面生成新记录""" 160 | content = request.POST['content'] 161 | # pages = Wiki.objects.get_list(pagename__exact=pagename) 162 | pages = Wiki.objects.filter(pagename=pagename) 163 | if pages: 164 | pages[0].content = content 165 | pages[0].save() 166 | else: 167 | page = Wiki(pagename=pagename, content=content) 168 | page.save() 169 | return HttpResponseRedirect("/%s" % pagename) 170 | 171 | import re 172 | 173 | r = re.compile(r'\b(([A-Z]+[a-z]+){2,})\b') 174 | def process(template, page): 175 | """处理页面链接,并且将回车符转为
""" 176 | t = loader.get_template(template) 177 | content = r.sub(r'\1', page.content) 178 | content = re.sub(r'[\n\r]+', '
', content) 179 | c = {'pagename':page.pagename, 'content':content} 180 | return HttpResponse(t.render(c)) 181 | ``` 182 | 183 | 代码有些长,有些地方已经有说明和注释了。简单说一下: 184 | 185 | * `index()` 用来显示一个 wiki 页面。它需要一个参数就是页面的名称。如果在数据库中找得到,则调用 `process()` 方法(`process()` 方法是一个自定义方法,主要用来对页面的文本进行处理,比如查找是否有满足 wiki 命名规则的单词,如果有则替换成链接。再有就是将回车转为 `
` )。如果没有找到,则直接调用编辑模板显示一个编程页面。当然,这个页面的内容是空的。只是它的页面名字就是 `pagename` 。如果 `pagename` 为空,则进入 `FrontPage` 页面。 `Wiki.objects` 对象有 `filter()` 方法和 `get()` 方法,一个返回一个结果集,一个返回指定的对象。这里为什么使用 `filter()` 呢,因为一旦指定文件不存在,它并不是返回一个 `None` 对象,而是抛出异常,而我没有使用异常的处理方式。通过 `filter()` 如果存在则结果中应有一个元素,如果不存在则应该是一个 `[]` 。这样就知道是否有返回了。 186 | 187 | **Note** 188 | 189 | >filter() 中使用的参数与一般的 db-api 是一样的,但如果是比较相等,可以为: pagename__exact=pagename 也可以简化为 pagename=pagename 。 190 | 191 | **Note** 192 | 193 | >在 Django 中,一些字段的比较操作比较特殊,它是在字段名后加 __ 然后是比较条件。这样看上去就是一个字符串。具体的参见 The Database API 。 194 | 195 | **Note** 196 | 197 | >回车转换的工作其实可以在模板中使用 filter 来完成。 198 | 199 | * 在上一章我们将所有的模板都放在了`newtest/templates`目录下,从本章开始,为了区分方便,我们会针对每一个app创建`templates/app`的子目录,将模板文件(edit.html)放在app目录下统一管理。由于Django针对TEMPLATES的默认的设置有`'APP_DIRS': True`,会自动到每一个app的`templates`目录下寻找模板文件。 200 | 201 | 因为我们在设计 model 时已经设置了 pagename 必须是唯一的,因此一旦 filter() 有返回值,那它只能有一个元素,而 pages[0] 就是我们想要的对象。 202 | 203 | * page = wikis.get(pagename='FrontPage') 204 | 205 | 是表示取出 pagename 为 FrontPage 的页面。你可能要说,为什么没有异常保护,是的,这也就是为什么我们要在前面先要插条记录在里面的原因。这样就不会出错了。再加上我要做的 wiki 不提供删除功能,因此不用担心会出现异常。 206 | 207 | * `edit()` 用来显示一个编辑页面,它直接取出一个页面对象,然后调用 wiki/edit.html 模板进行显示。也许你还是要问,为什么不考虑异常,因为这里不会出现。为什么?因为 edit() 只用在已经存在的页面上,它将用于存在页面的修改。而对于不存在的页面是在 index() 中直接调用模板来处理,并没有直接使用这个 edit() 来处理。也许你认为这样可能不好,但由于在 edit() 要重新检索数据库,而在 index() 已经检索过一次了,没有必要再次检索,因此象我这样处理也没什么不好,效率可能要高一些。当然这只是个人意见。 208 | 209 | * save() 用来在编辑页面时用来保存内容的。它先检查页面是否在数据库中存在,如果不存在则创建一个新的对象,并且保存。注意,在 Django 中,对对象处理之后只有调用它的 save() 方法才可以真正保存到数据库中去。如果页面已经存在,则更新页面的内容。处理之后再重定向到 index() 去显示这个页面。 210 | 211 | ## 7 在 wiki 中创建 templates 子目录 212 | 213 | ## 8 编辑 wiki/templates/wiki/page.html 214 | 215 | ```html 216 |

{{ pagename }}

217 |

{{ content }}

218 |
219 |

220 |
221 | 222 |

223 | ``` 224 | 225 | 它用来显示页面,同时提供一个“编辑”按钮。当点击这个按钮时将调用 view 中的 edit() 方法。 226 | 227 | ## 9 编辑 wiki/templates/wiki/edit.html 228 | 229 | ```html 230 |

编辑:{{ pagename }}

231 |
232 |
233 | 234 | 235 | ``` 236 | 237 | 它用来显示一个编辑页面,同时提供“保存”按钮。点击了保存按钮之后,会调用 view 中的 save() 方法。 238 | 239 | ## 10 修改 urls.py 240 | 241 | ```Python 242 | from django.conf.urls import include, url 243 | from django.contrib import admin 244 | from . import helloworld, add, list, xls_test, login 245 | 246 | urlpatterns = [ 247 | url(r'^admin/', admin.site.urls), 248 | url(r'^$', helloworld.index), 249 | url(r'^add/$', add.index), 250 | url(r'^list/$', list.index), 251 | url(r'^xls/(?P\w+)/$', xls_test.output), 252 | url(r'^login/$', login.login), 253 | url(r'^logout/$', login.logout), 254 | url(r'^wiki/', include('wiki.urls')), 255 | ] 256 | ``` 257 | 258 | 在wiki目录下增加一个urls.py的文件,然后编辑内容增加了 wiki 等4个 url 映射。 259 | 260 | ```Python 261 | from django.conf.urls import url 262 | 263 | from . import views 264 | 265 | urlpatterns = [ 266 | url(r'^$', views.index), 267 | url(r'^(?P\w+)/$', views.index), 268 | url(r'^(?P\w+)/edit/$', views.edit), 269 | url(r'^(?P\w+)/save/$', views.save), 270 | ] 271 | ``` 272 | 273 | 这里要好好讲一讲 URL 的设计(个人所见)。 274 | 275 | 一般一个 wiki ,我们访问它的一个页面可能为:wiki/pagename。因此我设计对 index() 方法的调用的 url 为: 276 | 277 | ```Python 278 | r'^wiki/(?P\w+)/$' 279 | ``` 280 | 281 | 也就是把 wiki/后面的解析出来作为 `pagename` 参数。但这样就带来一个问题,如果我想实现 `wiki/edit.html` 表示修改, `pagename` 作为一个参数通过 POST 来提交好象就不行了。因为上面的解析规则会“吃”掉这种情况。因此我采用 [Zope](http://www.zope.org) 的表示方法:把对象的方法放在对象的后面。我可以把 `pagename` 看成为一个对象, `edit` , `save` 是它的方法,放在它的后面,也简单,也清晰。当然如果我们加强上面的正则表达式,也可以解析出 `wiki/edit.html` 的情况,但那就是你设计的问题了。这里就是我的设计。 282 | 283 | 因此 wiki/pagename 就是显示一个页面,wiki/pagename/edit 就是编辑这个页面, wiki/pagename/save 就是保存页面。而 `pagename` 解析出来后就是分别与 `index()` , `edit()` , `save()` 的 `pagename` 参数相对应。 284 | 285 | 下面你可以运行了。 286 | 287 | 11 启动 server 288 | 进入 (http://localhost:8000/wiki) 289 | 290 | 首先进入这个页面: 291 | 292 | ![](./chapter0601.png) 293 | 294 | 然后你点编辑,则进入FrontPage的编辑界面: 295 | 296 | ![](./chapter0602.png) 297 | 298 | 然后我们加上一个 TestPage ,它符合 wiki 的名字要求,两个首字母大写的单词连在一起。然后点击保存。 299 | 300 | ![](./chapter0603.png) 301 | 302 | 看见了吧。页面上的 TestPage 有了链接。点击它将进入: 303 | 304 | ![](./chapter0604.png) 305 | 306 | 这是 TestPage 的编辑页面。让我们输入中文,然后输入 FrontPage 。然后保存。 307 | 308 | ![](./chapter0605.png) 309 | 310 | 好了,剩下的你来玩吧。点击 FrontPage 将回到首页。 311 | -------------------------------------------------------------------------------- /django-step-by-step/chapter0601.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0601.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0602.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0602.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0603.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0603.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0604.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0604.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0605.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0605.png -------------------------------------------------------------------------------- /django-step-by-step/chapter07.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (七) 2 | 3 | ## 1 引言 4 | 5 | 敢问路在何方,路在脚本。如果你坚持下来,一定会有收获的。 6 | 7 | 直到目前我们已经学了: 8 | 9 | * settings.py的设置 10 | * url dispatcher 11 | * 模板 12 | * session 13 | * app 14 | * model 15 | 16 | 其实在某些方面,使用 Django 还可以更加方便。而且我们还有许多东西没有学,一点点跟着我学吧。 17 | 18 | 我有一个通讯录,它是保存在 Excel 文件中的,我不想每次到目录下去打开它,我希望用 Django 做一个web上的简单应用,如何做呢? 19 | 20 | ## 2 创建 address app 21 | 22 | ```shell 23 | python manage.py startapp address 24 | ``` 25 | 26 | 这样就创建好了 address 相关的目录了。 27 | 28 | ## 3 修改 address/models.py 29 | 30 | ```python 31 | from django.db import models 32 | 33 | # Create your models here. 34 | 35 | class Address(models.Model): 36 | name = models.CharField('姓名', max_length=6, unique=True) 37 | gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), 38 | max_length=1) 39 | telphone = models.CharField('电话', max_length=20) 40 | mobile = models.CharField('手机', max_length=11) 41 | ``` 42 | 43 | 这回 model 复杂多了。在上面你可以看到我定义了四个字段: name , gender , telpnone , mobile 。其中 gender 表示性别,它可以从一个 tuple 数据中进行选取。并且在后面的 radio_admin=True 表示在 admin 的管理界面中将使用 radio 按钮来处理。 44 | 45 | **Note** 46 | 47 | >Django 提供了许多的字段类型,有些字段类型从数据的取值范围来讲没有什么区别,但之所以有这种区别,是因为:Django 的数据类型不仅仅用于创建数据库,进行 ORM 处理,还用于 admin 的处理。一方面将用来对应不同的 UI 控件,另一方面提供对不同的数据类型将进行不同的数据校验的功能。 48 | 49 | 在 Django 中每个字段都可以有一个提示文本,它是第一个参数,如果没有则会使用字段名。因此我定义的每个字段为了方便都有一个对应的汉字提示文本。 50 | 51 | 因为本节主要是讲 admin 的使用。admin 是 Django 提供的一个核心 app(既然是 app 就需要安装,一会就看到了),它可以根据你的 model 来自动生成管理界面。我为什么要用它,因为有了这个管理界面,对于通讯录的增加、删除、修改的处理界面完全可以通过 admin 来自动生成,我不用自已写。不相信吗?我们就会看到了。 52 | 53 | 那么 admin 到底可以带来些什么好处呢?它的功能很强大,不仅界面漂亮,还能对数据提供操作记录,提供搜索。特别是它是在用户权限控制之下,你都可以不用考虑安全的东西了。并且它本身就是一个非常好的学习的东西,特别是界面自动生成方面,学习它的代码可以用在我们自已的定制之中。当然,你许你用不上 admin ,它的确有一定的适应范围,不过对于大部分工作来说它可能足够了。对于那些交互性强的功能,你可能要自已实现许多东西,对于管理集中,主要以发布为主的东西,使用它可以节省你大量的时间。至于怎么使用,你要自已去权衡。但这一点对于快速实现一个 web 应用,作用非常大,这是 Django 中的一个亮点。 54 | 55 | ## 4 修改 settings.py 56 | 57 | ```python 58 | INSTALLED_APPS = ( 59 | 'django.contrib.admin', 60 | 'django.contrib.auth', 61 | 'django.contrib.contenttypes', 62 | 'django.contrib.sessions', 63 | 'django.contrib.messages', 64 | 'django.contrib.staticfiles', 65 | 'newtest', 66 | 'wiki.apps.WikiConfig', 67 | 'address.apps.AddressConfig', 68 | ) 69 | ``` 70 | 71 | 这里我们加入了Address的app ,我们注意到系统默认就已经添加了admin的应用,就是django.contrib.admin 。 admin 也是一个应用,需要加入INSTALLED_APPS才可以使用,这些与标准的app的安装没有什么不同。 72 | 73 | ## 5 安装 address app 74 | 75 | ```shell 76 | python manage.py makemigrations 77 | python manage.py migrate 78 | ``` 79 | 80 | 这样将在数据库中创建 address 相关的表。 81 | 82 | ## 6 增加超级用户 83 | 84 | 进入 (http://localhost:8000/admin) 85 | 86 | ![](./chapter0701.png) 87 | 88 | 进入看一看吧。咦,要用户。对,admin 功能是有用户权限管理的,因此一个 admin 替你完成了大量的工作:用户的管理和信息的增加、删除、修改这类功能类似,开发繁琐的东西。那么我们目前还没有一个用户,因此可以在命令下创建一个超级用户,有了这个用户,以后就可以直接在 admin 界面中去管理了。 89 | 90 | ```shell 91 | python manage.py createsuperuser 92 | ``` 93 | 94 | 它会让你输入用户名,邮件地址和口令。 95 | 96 | 这回再进去看一下吧。 97 | 98 | ![](./chapter0702.png) 99 | 100 | 上面已经有一些东西了,其中就有用户管理。但如何通过 admin 增加通讯录呢?别急,我们需要编辑一下address/admin.py,告诉admin应用我们的Address对象可以被admin管理。 101 | 102 | **Note** 103 | 104 | >因此是否启用 admin 管理取决于你。只要在 address/admin.py 中增加 admin 相关的部分,我们的应用才可以在 admin 中被管理。 105 | 106 | ## 7 修改 address/admin.py 107 | 108 | ```python 109 | from django.contrib import admin 110 | 111 | # Register your models here. 112 | 113 | from .models import Address 114 | 115 | admin.site.register(Address) 116 | ``` 117 | 118 | 有了这个东西,你就可以在 admin 中看到 adress 这个 app 了。再到浏览器中看一下是什么样子了。 119 | 120 | ![](./chapter0703.png) 121 | 122 | 看见了吧。上面有增加和删除的按钮,先让我们点击一下增加吧。 123 | 124 | ![](./chapter0704.png) 125 | 126 | 这个自动生成的界面是不是很不错。增加一条保存起来了。不过我发现当我输入 `limodou` 时,只能输入 `limodo` 好象 `u` 输不进去。为什么?因为我把姓名按汉字算最多6个就够了,一旦我使用英文的名字可能就不够。因此这是一个问题,一会要改掉。 127 | 128 | ![](./chapter0705.png) 129 | 130 | 怎么新增的记录叫
这样看上去很别扭。为什么会这样,因为没有定义特殊的方法。下面就让我们定义一下。 131 | 132 | ## 8 修改 address/models.py 133 | 134 | ```python 135 | from django.db import models 136 | 137 | # Create your models here. 138 | 139 | class Address(models.Model): 140 | name = models.CharField('姓名', max_length=6, unique=True) 141 | gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), 142 | max_length=1) 143 | telphone = models.CharField('电话', max_length=20) 144 | mobile = models.CharField('手机', max_length=11) 145 | 146 | def __str__(self): 147 | return self.name 148 | ``` 149 | 150 | 改好了,再刷新下页面。这次看见了吗?增加了一个 __str__ 方法。这个方法将在显示 Address 实例的时候起作用。我们就使用某个联系人的姓名就行了。 151 | 152 | ![](./chapter0706.png) 153 | 154 | 你记得吗?Model 是与数据库中的表对应的,为什么我们改了 model 代码,不需要重新对数据库进行处理呢?因为只要不涉及到表结构的调整是不用对表进行特殊处理的。不过,我们马上要修改表结构了。 155 | 156 | ## 9 修改 address/models.py 157 | 158 | 姓名留短了真是不方便,另外我突然发现需要再增加一个房间字段。 159 | 160 | ```python 161 | from django.db import models 162 | 163 | # Create your models here. 164 | 165 | class Address(models.Model): 166 | name = models.CharField('姓名', max_length=20, unique=True) 167 | gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), 168 | max_length=1) 169 | telphone = models.CharField('电话', max_length=20) 170 | mobile = models.CharField('手机', max_length=11) 171 | room = models.CharField('房间', max_length=10, default='') 172 | 173 | def __str__(self): 174 | return self.name 175 | ``` 176 | 177 | 这回表结构要改变了,怎么做呢? 178 | 179 | ## 10 修改表结构 180 | 181 | 我们可以使用Django提供的数据迁移功能,在不影响原有数据的情况下,更新表结构。主要包括下面的命令: 182 | 183 | * migrate,负责应用迁移,以及取消应用并列出其状态。 184 | * makemigrations, 负责基于你的模型修改创建一个新的迁移 185 | * sqlmigrate, 展示迁移的sql语句 186 | 187 | 大家一定已经注意到了,我们增加的字段`room`多了一个默认值空字符串,这是为了在增加一个新的字段中之后,可以使用这个默认值填充已有的记录。下面我们创建一个新的迁移 188 | 189 | ```shell 190 | python manage.py makemigrations 191 | ``` 192 | 193 | 然后我们就在address/migrations目录下得到了一个新的`0002_auto_XXXXXXXX_XXXX.py`的文件,这个文件由Django自动生成,用于迁移数据库使用,我们不用管它,使用下面的命令执行迁移: 194 | 195 | ```shell 196 | python manage.py migrate 197 | ``` 198 | 199 | 现在数据库中已经是新的表结构了,我们可以对数据继续进行操作。 200 | 201 | ## 11 进入 admin 202 | 203 | 我们可以再次进入 admin 了,增加,删除,修改数据了。 204 | 205 | 用了一会,也许你会希望:能不能有汉化版本的界面呢?答案是肯定的,而且已做好了。 206 | 207 | ## 12 修改 settings.py 208 | 209 | 在`MIDDLEWARE`部分,增加`django.middleware.locale.LocaleMiddleware`,代码如下: 210 | 211 | ```python 212 | MIDDLEWARE = [ 213 | 'django.middleware.security.SecurityMiddleware', 214 | 'django.contrib.sessions.middleware.SessionMiddleware', 215 | 'django.middleware.common.CommonMiddleware', 216 | 'django.middleware.csrf.CsrfViewMiddleware', 217 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 218 | 'django.contrib.messages.middleware.MessageMiddleware', 219 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 220 | 'django.middleware.locale.LocaleMiddleware', 221 | ] 222 | ``` 223 | 224 | 刷新下界面,是不是变成汉字了。 225 | 226 | 国际化支持在 Django 中做得是非常的出色,程序可以国际化,模板可以国际化,甚至js都可以国际化。这一点其它的类似框架都还做不到。而国际化的支持更是 RoR 的一个弱项,甚至在 [Snakes and Rubies](http://snakesandrubies.com/event) 的会议上,RoR 的作者都不想支持国际化。但 Django 却做得非常出色,目前已经有二十多种语言译文。 227 | 228 | 在增加,删除,修改都做完了,其实还剩下什么呢?显示和查询。那么实现它则需要写 view 和使用模板了。这个其实也没什么,最简单的,从数据库里查询出所有的数据,然后调用模板,通过循环一条条地显示。不错是简单。但是在做之前,先让我们想一想,这种处理是不是最常见的处理方法呢?也许我们换成其它的应用也是相似的处理。如果很多这样的处理,是不是我们需要每次都做一遍呢?有没有通用的方便的方法。答案是:有! Django 已经为我们想到了,这就是 [Generic views](https://docs.djangoproject.com/en/1.11/topics/class-based-views/generic-display/) 所做的。它把最常见的显示列表,显示详细信息,增加,修改,删除对象这些处理都已经做好了一个通用的方法,一旦有类似的处理,可以直接使用,不用再重新开发了。但在配置上有特殊的要求。具体的可以看 Generic views 文档。 229 | 230 | 从这里我有一点想法,我认为 view 这个名称特别容易让人产生误解,为什么呢?因为 view 可以译为视图,给人一种与展示有关的什么东西。但实际上 Django 中的 view 相当于一个 Controller 的作用,它是用来收集数据,调用模板,真正的显示是在模板中处理的。因此我倒认为使用 Controller 可能更合适,这样就称为 MTC 了。呵呵,只是个人想法。 231 | 232 | 另外, Generic views 产生的意义在于 Django 的哲学理含 DRY (Don't repeat yourself, 不要自已重复),目的是重用,减少重复劳动。还有其它的哲学理含参见 [Design philosophies](https://docs.djangoproject.com/en/1.11/misc/design-philosophies/) 文档。 233 | 234 | 因此可以知道 view 可以极大地简化, Django 在这点上认为:每个应用的显示都可能是不同的,因此这件事需要用户来处理。但如果有最简单的封装,对于开发人员在测试时会更方便,但目前没有,因此模板我们还是要准备,而且还有特殊的要求,一会就看到了。 235 | 236 | 对于目前我这个简单的应用来说,我只需要一个简单的列表显示功能即可,好在联系人的信息并不多可以在一行显示下。因此我要使用 `django.views.generic` 模块来处理。 237 | 238 | ## 13 增加 address/urls.py 239 | 240 | 对,我们为 address 应用增加了自已的 urls.py。 241 | 242 | ```python 243 | from django.conf.urls import url 244 | 245 | from . import views 246 | 247 | urlpatterns = [ 248 | url(r'^$', views.IndexView.as_view(), name='index'), 249 | ] 250 | ``` 251 | 252 | 我们使用`as_view`这个generic view的方法显示默认的列表界面,可以大大的简化views.py的编码工作,现在我们的views.py代码如下: 253 | 254 | ```python 255 | from django.views import generic 256 | 257 | from .models import Address 258 | 259 | class IndexView(generic.ListView): 260 | model = Address 261 | template_name = 'address_list.html' 262 | ``` 263 | 264 | 我们只需要从`generic.ListView`继承,并创建一个基于类的View,命名为`IndexView`,然后为这个类设置两个成员变量,一个为`model = Address`,指定我们的generic view需要显示哪一个模型的数据;再设置`template_name = 'address_list.html'`,指定显示的模板。 265 | 266 | 前面已经谈到:使用 generic view 只是减少了 view 的代码量,但对于模板仍然是必不可少的。因此要创建符合 generic view 要求的模板。主要是模板存放的位置和模板文件的名字。 267 | 268 | 缺省需要的模板文件名为: `app_label/model_name_list.html` ,在这个模板中可以使用 `object_list`变量访问模型的列表 。 269 | 270 | ## 14 在 address 中创建 templates 子目录 271 | 272 | ## 15 创建 address/templates/address/list.html 273 | 274 | ```html 275 |

通讯录

276 |
277 |
{{ row.0|addslashes}}{{ row.1|addslashes}}
278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | {% for person in address_list %} 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | {% endfor %} 294 |
姓名性别电话手机房间
{{ person.name }}{{ person.gender }}{{ person.telphone }}{{ person.mobile }}{{ person.room }}
295 | ``` 296 | 297 | ## 16 修改 urls.py 298 | 299 | 将我们的应用的 urls.py include 进去。 300 | 301 | ```python 302 | from django.conf.urls import include, url 303 | from django.contrib import admin 304 | from . import helloworld, add, list, xls_test, login 305 | 306 | urlpatterns = [ 307 | url(r'^admin/', admin.site.urls), 308 | url(r'^$', helloworld.index), 309 | url(r'^add/$', add.index), 310 | url(r'^list/$', list.index), 311 | url(r'^xls/(?P\w+)/$', xls_test.output), 312 | url(r'^login/$', login.login), 313 | url(r'^logout/$', login.logout), 314 | url(r'^wiki/', include('wiki.urls')), 315 | url(r'^address/', include('address.urls')), 316 | ] 317 | ``` 318 | 319 | 可以看到 `r'^address/'` 没有使用 `$` ,因为它只匹配前部分,后面的留给 address 中的 `urls.py` 来处理。 320 | 321 | ## 17 启动 server 看效果 322 | 323 | ![](./chapter0707.png) -------------------------------------------------------------------------------- /django-step-by-step/chapter0701.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0701.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0702.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0702.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0703.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0703.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0704.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0704.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0705.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0705.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0706.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0706.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0707.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0707.png -------------------------------------------------------------------------------- /django-step-by-step/chapter08.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (八) 2 | 3 | ## 1 引言 4 | 5 | 上一讲的确很长,但如果看代码你会发现,代码主要在 model 的调整中,`urls.py` 的工作不多,而连一行 view 的代码都没有写。是不是非常方便呢! 6 | 7 | 那么让我们来继续完善这个通讯录吧。 8 | 9 | 现在我想完成的是: 10 | 11 | * 增加批量导入和导出功能 12 | 13 | 为什么要批量导入呢?因为一般情况下,我一定是已经有了一个通讯录文件(象以前我说过的Excel文件),那么现在需要转到 web 上来,难道要我一条条全部手工录入吗?能不能上传文件,自动插入到数据库中去呢?那么就让我们实现一个文件上传的处理吧。 14 | 15 | 为了简化,我采用csv格式文本文件(这个文件在svn中有一个例子 `data.csv` ,不然就自行生成好了)。 16 | 17 | ``` 18 | abc,M,11,11,11, 19 | bcd,M,11,11,11, 20 | ass,M,11,11,11, 21 | dfsdf,F,11,11,11, 22 | sfas,F,11,11,11, 23 | ... 24 | ``` 25 | 26 | ## 2 修改 address/templates/address/list.html 27 | 28 | ```html 29 |

通讯录

30 |
31 |
32 | 上传通讯录文件:
33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {% for person in address_list %} 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {% endfor %} 52 |
姓名性别电话手机房间
{{ person.name }}{{ person.gender }}{{ person.telphone }}{{ person.mobile }}{{ person.room }}
53 | ``` 54 | 55 | ## 3 修改 address/views.py 56 | 57 | ```python 58 | from django.http import HttpResponseRedirect 59 | from django.shortcuts import render_to_response 60 | from django.views.decorators.csrf import csrf_exempt 61 | 62 | @csrf_exempt 63 | def upload(request): 64 | file_obj = request.FILES.get('file', None) 65 | if file_obj: 66 | import csv 67 | from io import StringIO 68 | try: 69 | csvfile = StringIO(file_obj.read().decode()) 70 | reader = csv.reader(csvfile) 71 | except: 72 | return render_to_response('address/error.html', 73 | {'message':'你需要上传一个csv格式的文件!'}) 74 | for row in reader: 75 | objs = Address.objects.filter(name=row[0]) 76 | if not objs: 77 | obj = Address(name=row[0], gender=row[1], 78 | telphone=row[2], mobile=row[3], room=row[4]) 79 | else: 80 | obj = objs[0] 81 | obj.gender = row[1] 82 | obj.telphone = row[2] 83 | obj.mobile = row[3] 84 | obj.room = row[4] 85 | obj.save() 86 | 87 | return HttpResponseRedirect('/address/') 88 | else: 89 | return render_to_response('address/error.html', 90 | {'message':'你需要上传一个文件!'}) 91 | ``` 92 | 93 | 这里有一个 `upload()` 方法,它将使用 csv 模块来处理上传的 csv 文件。首先查找姓名是否存在于数据库中,如果不存在则创建新记录。如果存在则进行替换。如果没有指定文件直接上传,则报告一个错误。如果解析 csv 文件出错,则也报告一个错误。 94 | 95 | 报造错误使用了一个名为 error 的模板,我们马上要创建。 96 | 97 | ## 4 创建 address/templates/address/error.html 98 | 99 | ```html 100 |

出错

101 |

{{ message }}

102 |
103 |

返回

104 | ``` 105 | 106 | 很简单。 107 | 108 | ## 5 修改 address/urls.py 109 | 110 | ```python 111 | from django.conf.urls import url 112 | 113 | from . import views 114 | 115 | urlpatterns = [ 116 | url(r'^$', views.IndexView.as_view(), name='index'), 117 | url(r'^upload/$', views.upload), 118 | ] 119 | ``` 120 | 121 | 增加一个 upload 的 url 映射。 122 | 123 | ## 6 启动 server 测试 124 | 125 | 这样导入功能就做完了。那导出呢?很简单了,参考 csv 的例子去做就可以了。不过,并不全是这样,仍然有要修改的地方,比如 csv.html 模板,它因为写死了处理几个元素,因此需要改成一个循环处理。 126 | 127 | ## 7 修改 address/templates/address/csv.html 128 | 129 | ```python 130 | {% for row in data %}{% for i in row %}"{{ i|addslashes }}",{% endfor %} 131 | {% endfor %} 132 | ``` 133 | 134 | 将原来固定个数的输出改为循环处理。 135 | 136 | ## 8 修改 address/templates/address/list.html 137 | 138 | 增加一个生成导出的 csv 文件的链接 139 | 140 | ```python 141 |

通讯录

142 |
143 |
144 | 上传通讯录文件:
145 | 146 |
147 |
148 |

导出为csv格式文件

149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | {% for person in object_list %} 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | {% endfor %} 166 |
姓名性别电话手机房间
{{ person.name }}{{ person.gender }}{{ person.telphone }}{{ person.mobile }}{{ person.room }}
167 | ``` 168 | 169 | ## 9 修改 apps/address/views.py 170 | 171 | ```python 172 | from django.http import HttpResponse 173 | from django.template import loader, Context 174 | 175 | def output(request): 176 | response = HttpResponse(content_type='text/csv') 177 | response['Content-Disposition'] = 'attachment; filename=%s' % 'address.csv' 178 | t = loader.get_template('address/csv.html') 179 | objs = Address.objects.all() 180 | d = [] 181 | for o in objs: 182 | d.append((o.name, o.gender, o.telphone, o.mobile, o.room)) 183 | c = {'data': d,} 184 | response.write(t.render(c)) 185 | return response 186 | ``` 187 | 188 | 在开始处增加了对 `HttpResponse`, `loader`, `Context` 的导入。然后增加了用于输出处理的 `output()` 方法。 189 | 190 | ## 10 修改 address/urls.py 191 | 192 | ```python 193 | from django.conf.urls import url 194 | 195 | from . import views 196 | 197 | urlpatterns = [ 198 | url(r'^$', views.IndexView.as_view(), name='index'), 199 | url(r'^upload/$', views.upload), 200 | url(r'^output/$', views.output), 201 | ] 202 | ``` 203 | 204 | 增加了对 output 方法的 url 映射。 205 | 206 | ## 11 启动 server 测试 -------------------------------------------------------------------------------- /django-step-by-step/chapter09.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (九) 2 | 3 | ## 1 引言 4 | 5 | 不知道大家有没有对这个通讯录感到厌烦了,希望没有,因为还有一些东西没有讲完呢。 6 | 7 | 最让我感觉不满意的就是通讯录的显示了,的确很难看,希望可以美化一下。那么主要从这几方面: 8 | 9 | * 对姓名进行排序 10 | * 生成分页结果 11 | * 增加css和一些图片 12 | 13 | ## 2 修改 address/models.py 实现排序 14 | 15 | 可以在 model 中增加一个叫 `Meta` 的内类,然后通过对其设置类属性可以用来控制 model 的模型属性。如我们想实现表的排序,可以在 `Meta` 中增加一个 `ordering = ['name']` 的属性即可。它表示按 `name` 进行排序。它可以有多个字段。如果在字段前加'-'表示倒序。修改完毕在浏览器中看一下效果就知道了。models.py的代码如下: 16 | 17 | ```python 18 | from django.db import models 19 | 20 | # Create your models here. 21 | 22 | class Address(models.Model): 23 | name = models.CharField('姓名', max_length=20, unique=True) 24 | gender = models.CharField('性别', choices=(('M', '男'), ('F', '女')), 25 | max_length=1) 26 | telphone = models.CharField('电话', max_length=20) 27 | mobile = models.CharField('手机', max_length=11) 28 | room = models.CharField('房间', max_length=10, default='') 29 | 30 | def __str__(self): 31 | return self.name 32 | 33 | class Meta: 34 | ordering = ["name"] 35 | ``` 36 | 37 | ## 3 修改 templates/address/address/list.html 实现分页显示 38 | 39 | ```html 40 | 41 | 42 | 43 | 44 |

通讯录

45 |
46 |
47 | {% if is_paginated %} 48 | 49 | 50 | 58 | 59 |
51 | {% if page_obj.has_previous %} 52 | 上一页 53 | {% endif %} 54 | {% if page_obj.has_next %} 55 | 下一页 56 | {% endif %} 57 |
60 | {% endif %} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {% for person in address_list %} 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | {% endfor %} 78 |
姓名性别电话手机房间
{{ person.name }}{{ person.gender }}{{ person.telphone }}{{ person.mobile }}{{ person.room }}
79 |
80 | 81 | 82 | 90 | 95 | 96 |
83 |
84 | 文件导入: 85 | 86 |
87 | 88 |
89 |
91 |

92 | 导出为csv文件 93 |

94 |
97 | 98 | 99 | ``` 100 | 101 | 这时我仍然使用的是 generic view 来处理。但对布局作了简单的调整,将导入和导出的内容移到下面去了。同时增加了对分页的支持: 102 | 103 | ```html 104 | {% if page_obj.has_previous %} 105 | 上一页 106 | {% endif %} 107 | {% if page_obj.has_next %} 108 | 下一页 109 | {% endif %} 110 | ``` 111 | 112 | 在使用 generic view的 `object_list` 时,它会根据 URL Dispatch 中是否设置了 `paginate_by` 这个参数来决定是否使用分页机制。一会我们会看到在 `urls.py` 的这个参数。一旦设置了这个参数,则 `object_list` 会使用 Django 提供的一个分页处理器来实现分页。它会自动产生分页所用到的许多的变量,这里我们使用了 `has_previous`, `previous`, `has_next`, `next` 这四个变量,还有其它一些变量可以使用。具体的参见 [Generic views](https://docs.djangoproject.com/en/1.11/topics/class-based-views/generic-display/) 文档。 113 | 114 | 这里是根据是否有前一页和下一页来分别生成相应的链接。对于分页的链接,需要在url中增加一个 Query 关键字 `page` 。因此我的模板中会使用 `page={{ previous }}` 和 `page={{ next }}` 分别指向前一页和下一页的页码。 115 | 116 | ## 4 修改address/views.py 117 | 118 | ```python 119 | class IndexView(generic.ListView): 120 | model = Address 121 | template_name = 'address/list.html' 122 | paginate_by = 2 123 | ``` 124 | 125 | 我们为`IndexView`类增加一个成员变量:`paginate_by`,指定每一页显示2条记录。 126 | 127 | 128 | ## 5 启动 server 测试 129 | 130 | 显示效果为 131 | 132 | ![](./chapter0901.png) 133 | 134 | 下面让我们为它添加一些CSS和图片,让它变得好看一些。 135 | 136 | 首先要说明一下,我们一直处于开发和测试阶段,因此我们一直使用的都是 Django 自带的 server(其实我个人感觉这个 server 的速度也挺快的),但最终我们的目的是把它部署到 Apache 上去。现在我们打算增加 CSS 和添加一些图片, Django 提供了这个能力,这就是对静态文件的支持,但是它只是建议在开发过程中使用。真正到了实际环境下,还是让专门的 web server 如 Apache 来做这些事情。只要改一下链接设置就好了。更详细的说明要参见 [Managing static files](https://docs.djangoproject.com/en/1.11/howto/static-files/) 的文档。同时在 Django 中为了不让你依赖这个功能,特别在文档的开始有强烈的声明:使用这个方法是低效和不安全的。同时当 `DEBUG` 设置(在 `settings.py` 中有这个选项, `True` 表示处于调试期,会有一些特殊的功能)为 `False` 时,这个功能就自动无效了,除非你修改代码让它生效。 137 | 138 | ## 6 修改 urls.py 139 | 140 | ```python 141 | from django.conf import settings 142 | from django.conf.urls.static import static 143 | 144 | urlpatterns = [ 145 | # ... 其他的URL Pattern ... 146 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 147 | ``` 148 | 149 | 我们使用static函数,它需要两个参数。第一个参数是通过URL访问静态文件时的相对路径,在`settings.py`文件中默认设置为`STATIC_URL = '/static/'`,也就是通过`http://yourhost/static/`访问静态文件;第二个参数是静态文件在服务器上存放的路径,`STATIC_ROOT` 就是我将用来存放 CSS 和图片的地方,这里我使用了一个 `STATIC_PATH` ,它从哪里来呢?它是我自已在 `settings.py` 中定义的。在前面有一个导入语句: 150 | 151 | ```python 152 | from django.conf import settings 153 | ``` 154 | 155 | 从这里可以看到是如何使用 settings.py 的,我们完全可以自已定义新的东西,并让它在整个项目中生效。 156 | 157 | ## 7 修改 settings.py 158 | 159 | 在最后增加: 160 | 161 | ```python 162 | STATIC_ROOT = os.path.join(BASE_DIR, "collect_static/") 163 | 164 | STATICFILES_DIRS = [ 165 | os.path.join(BASE_DIR, "static"), 166 | ] 167 | ``` 168 | 169 | STATIC_ROOT这个字段的的目录路径是用来为部署而收集静态文件的地方。 170 | 更具体的说呢,当我们执行`python manage.py collectstatic`命令的时候,系统会帮我们把所有的静态文件都收集到该目录下。 171 | STATICFILES_DIRS默认是一个空列表,那么这个设置定义了staticfiles app将会遍历的一个附加的位置信息。该值应该设置为一个字符串的列表形式,每个元素都是附加文件目录的绝对路径。 172 | > 注意:这些路径都应该使用unix风格的斜杠,即便是在windows平台上("C:/Users/user/mysite/extra_static_content") 173 | 174 | 那么我需要在 `newtest` 目录下创建一个 `static`和`collect_static` 的目录。 175 | 176 | ## 8 创建 newtest/static 目录 177 | 178 | 这样根据上面 `urls.py` 的设置,我们以后将通过 `/static/XXX` 来使用某些静态文件。 179 | 180 | 为了美化,我想需要一个 CSS 文件来定义一些样式,同时我还想提供一个 Django Powered 的图片。 [在这里有官方提供的图标](http://www.djangoproject.com/community/badges/) 。 于是我下了一个放在了 `static` 目录下。同时 CSS 怎么办,自已重头写,太麻烦,反正只是一个测试。于是我下载了 Django 站点用的 css 叫 `base.css` 也放在了 `static` 下面。下面就是对模板的改造。 181 | 182 | > 在 SVN 中我放了一个 css 和 gif 图片大家可以使用,不然可能看不出效果。 183 | 184 | 为了通用化,我新增了一个 `base.html` 它是一个框架,而以前的 `address/list.html` 是它的一个子模板。这样我们就可以了解如何使用模板间的嵌套了。 185 | 186 | ## 9 创建 newtest/templates/base.html 187 | 188 | ```html 189 | 191 | 192 | 193 | 194 | 195 | 196 | Address 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 |
212 | {% block content %}content{% endblock %} 213 |
214 | 220 | 221 | 222 | ``` 223 | 224 | 有些代码也是从 Django 的网页中拷贝来的。特别要注意的是: 225 | 226 | ```python 227 | {% block content %}content{% endblock %} 228 | ``` 229 | 230 | 这样就是定了一个可以扩展的模块变量块,我们将在 `address/list.html` 中扩展它。同时对 CSS 和 Django-Powered 的图片引用的代码是: 231 | 232 | ```html 233 | 234 | 235 | ``` 236 | 237 | 前面都是从 `static` 开始的。这样就将使用我们前面在 `urls.py` 中的设置了。 238 | 239 | ## 10 修改 templates/address/address/list.html 240 | 241 | ```html 242 | {% extends "base.html" %} 243 | {% block content %} 244 | 249 | 252 |
253 |
254 | {% if is_paginated %} 255 | 256 | 257 | 262 | 263 |
{% if page_obj.has_previous %} 258 | 上一页 259 | {% endif %} {% if page_obj.has_next %} 260 | 下一页 261 | {% endif %}
264 | {% endif %} 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | {% for person in address_list %} 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | {% endfor %} 282 |
姓名性别电话手机房间
{{ person.name }}{{ person.gender }}{{ person.telphone }}{{ person.mobile }}{{ person.room }}
283 |
284 | 285 | 286 | 294 | 299 | 300 |
287 |
288 | 文件导入: 289 | 290 |
291 | 292 |
293 |
295 |

296 | 导出为csv文件 297 |

298 |
301 | {% endblock %} 302 | ``` 303 | 304 | 基本上没有太大的变化,主要是增加了一些 `div` 标签,同时最开始使用: 305 | 306 | ```html 307 | {% extends "base" %} 308 | ``` 309 | 310 | 表示是对 base 的扩展,然后是相应的块的定义: 311 | 312 | ```html 313 | {% block content %} 314 | ... 315 | {% endblock %} 316 | ``` 317 | 318 | 注意,所有扩展的东西一定要写在块语句的里面,一旦写到了外面,那样就不起作用了。 319 | 320 | Django 的模板可以不止一次的扩展,但这里没有演示。 321 | 322 | ## 11 启动 server 测试 323 | 324 | 现在你看到的页面是不是象我这样? 325 | 326 | ![](./chapter0902.png) 327 | 328 | ## 12 重要的版权问题 329 | 330 | 版权是一个可能我们大多数人都不重视的问题,但在实际生产中,这是一个必须重视的问题。许多东西象CSS,图片,甚至可能是一种布局,设计都有可能有版权,在使用这些东西的时候一定要注意相关的说明。不要给自已造成麻烦。如果你不清楚,建议你去找清楚的人,或与所有者联系。特别是对于开源,版权更是一个很重要的东西,因为这是保护我们的武器,希望每个人都重视。特别是对于正式发布的东西,一定要将版权问题交待清楚。 -------------------------------------------------------------------------------- /django-step-by-step/chapter0901.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0901.png -------------------------------------------------------------------------------- /django-step-by-step/chapter0902.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter0902.png -------------------------------------------------------------------------------- /django-step-by-step/chapter10.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十) 2 | 3 | ## 1 引言 4 | 5 | 现在我们看一看所展示出来的页面,你满意吗?还有可以改进的地方。比如性别,它显示出来的直接是数据库的值,而不是对应的“男”,“女”,怎么办。还有表格显示也不是很好看。没说的,改! 6 | 7 | 最初我想使用 CustomManipulator (Manipulator 是 Django 中用来自动生成元素对应的 HTML 代码的对象,你可以定制它),但使用 Manipulator 的话,你不能再使用 generic view 了,需要自已去实现 generic view 的某些代码,当然可以 copy and paste,但我目前不想那样做。于是我想到可以扩展 Django 的模板,自定义一个 filter 来实现它。(具体扩展的文档参见 [The Django template language: For Python programmers](https://docs.djangoproject.com/en/2.0/ref/templates/api/) ,你不仅可以扩展filter,还可以扩展 Tag ,还可以设置模板变量,还可以进行块处理等复杂的操作,自已去看吧。) 8 | 9 | ## 2 创建 address/templatetags 目录 10 | 11 | 注意,这个目录要在某个应用的下面,同时它应与 models, views.py在同一层目录下。 12 | 13 | ## 3 创建 address/templatetags/__init__.py 文件 14 | 15 | 文件为空即可。 16 | 17 | ## 4 创建自定义模板文件 address/templatetags/change_gender.py 18 | 19 | 文件名为你想要装入到模板中的名字。如文件起名为 `change_gender.py` ,那么你将可以在模板中使用: 20 | 21 | {% raw %} 22 | ```Python 23 | {% load change_gender %} 24 | ``` 25 | {% endraw %} 26 | 27 | 来导入。 28 | 29 | ## 5 编辑 change_gender.py 30 | 31 | ```Python 32 | from django import template 33 | 34 | register = template.Library() 35 | 36 | @register.filter(name='change_gender') 37 | def change_gender(value): 38 | if value == 'M': 39 | return '男' 40 | else: 41 | return '女' 42 | ``` 43 | 44 | 先是导入 template 模块,然后生成一个 register 的对象,我将用来它注册我所定义的 filter 。我实现的 filter 将命名为 "change_gender" ,它没有参数(一个filter可以接受一个参数,或没有参数)。当 value 为 M 时返回 男 ,当 value 为 F 时返回 女 。然后调用 register 的 filter 来注册它。这里有两种写法,一种是使用 Python 2.4才支持的 decorator (此行注释掉了),另一种是使用标准的写法。在使用 decorator 时,如果 filter 方法有多个参数的话,需要指明 name 参数,否则可以直接写为: 45 | 46 | ```python 47 | @register.filter 48 | ``` 49 | 50 | 它自动将函数名认为是filter的名字。 51 | 52 | ## 6 修改 templates/address/list.html 53 | 54 | {% raw %} 55 | ```HTML 56 | {% extends "base.html" %} 57 | {% block content %} 58 | {% load change_gender %} 59 | 66 | 69 |
70 |
71 | 72 | 73 | 78 |
{% if has_previous %} 74 | 上一页 75 | {% endif %} {% if has_next %} 76 | 下一页 77 | {% endif %}
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | {% for person in object_list %} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | {% endfor %} 96 |
姓名性别电话手机房间
{{ person.name }}{{ person.gender|change_gender }}{{ person.telphone }}{{ person.mobile }}{{ person.room }}
97 | 98 | 99 | 105 | 106 | 107 |
100 |
101 | 文件导入:
102 | 103 |
104 |

导出为csv文件

108 |
109 | {% endblock %} 110 | ``` 111 | {% endraw %} 112 | 113 | 改动了以下几个地方: 114 | 115 | 1. 增加了 {% raw %}`{% load change_gender %}`{% endraw %}来导入自定义的 filter 。 116 | 117 | 2. 增加了几个样式,象 `mytr1`, `mytr2` 等。 118 | 119 | 3. 显示结果的 table 改为: 120 | 121 | ```html 122 | 123 | ``` 124 | 125 | 4. 表头改为: 126 | 127 | ```html 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | ``` 136 | 137 | 增加了样式处理 138 | 139 | 5. 数据显示的 tr 标签改为: 140 | 141 | {% raw %} 142 | ```html 143 | 144 | ``` 145 | {% endraw %} 146 | 147 | 使用了 cycle Tag 来处理表格行的样式切换。注意: cycle 处理的是字符串。 148 | 149 | 6. 修改 `{% raw %}{{ person.gender }}{% endraw %}` 为 `{% raw %}{{ person.gender|change_gender }}{% endraw %}` 150 | 151 | ## 7 启动 server 进行测试 152 | 153 | > 注意,一定要重启。象 templatetags 之类是在导入时处理的,因此如果 server 已经启动再添加的话是不起作用的。其它象增加 app, 修改 settings.py 都是要重启,而修改 urls.py , view, model代码,模板什么的可以不用重启,在必要时 Django 的测试 web server 会自动重启。如果你使用 Apache 的话,估计绝大多数情况下要重启,可能只有修改模板不用吧。不过也仍然可以设置 Apache 以便让每次请求过来时重新装入 Python 模块。 154 | 155 | 如果一切成功,你会看到 M, F 都改过来了。这里如果你感兴趣还可以改成小图标来表示,点缀一下。 156 | 157 | 效果画面为: 158 | 159 | ![](./chapter1001.png) -------------------------------------------------------------------------------- /django-step-by-step/chapter1001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter1001.png -------------------------------------------------------------------------------- /django-step-by-step/chapter11.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十一) 2 | 3 | ## 1 引言 4 | 5 | 让我们再仔细看一下这个通讯录,我们知道,如果想增加新的记录,一种方法是通过 admin 界面,这个已经由 [Django](https://www.djangoproject.com/) 自动为我们做好了。我们还可以批量导入,这个是我们实现的。但是这里有风险,为什么?如果什么人都可以导入这可是件不好的事,那么怎么办: **加权限控制** 。 6 | 7 | Django 自带了一个权限控制系统,那么我们就用它。因此先让我们简单地了解一下 Django 中的权限。同时我希望只有特殊权限的人才可以做这件事情,在前几讲中我们一直使用超级用户,但这并不是个好的习惯。因此让我们先创建个个人用户吧。 8 | 9 | ## 2 添加一个个人用户 10 | 11 | 使用 admin 用户进入管理界面 [http://localhost:8000/admin]() 12 | 13 | 在 Auth 下有用户一项,点击添加按钮进入添加界面,还挺复杂的。在这里提示是黑体的字段是必输项,其实只有两项是需要我们输的:用户名和密码。用户名好办,密码有一些复杂度的要求: 14 | 15 | ![](./chapter1101.png) 16 | 17 | - 你的密码不能与其他个人信息太相似。 18 | - 你的密码必须包含至少 8 个字符。 19 | - 你的密码不能是大家都爱用的常见密码。 20 | - 你的密码不能全部为数字。 21 | 22 | 知道了密码的要求之后,那么我们只要填入用户名,密码就行了。 23 | 24 | ![](./chapter1102.png) 25 | 26 | > 注意,职员状态检查框如果不打勾,则你的用户也无法使用,因为他不能登录。也许你担心,如果打勾了,那不是他就能做好多事了吗?其实不然。在 Django 中,创建一个 app 之后都有一些基本的权限会自动生成,而这些除了超级用户,它们是不会自动赋给某个用户的。因此如果管理员不给某个用户关于 app 的使用权限,那么这个用户根本没有办法操纵这些 app ,甚至连看都看不到(大家自已试一下就知道了)。这样他能够做的只是登录,但这也许就够了,有时我们需要的就是一个用户的合法身份,而不是一定要他能做些什么。 27 | 28 | `request` 对象提供一个 `user` 对象,你可以根据它来判断当前用户的身份,所属的组,所拥有的权限。我们可以在 view 代码中进行用户身份的检查。 29 | 30 | 现在我的想法是:限制特殊用户来做这件事。首先我可以在 `settings.py` 中设定这个用户名,然后在 view 中检查当前用户是否是 `settings.py` 中设定的用户。 31 | 32 | ## 3 修改 newtest/settings.py 33 | 34 | 在最后增加: 35 | 36 | ```python 37 | UPLOAD_USER = 'limodou' 38 | ``` 39 | 40 | > 这里请把 limodou 改成你想要的名字。要注意,在后面的测试中你需要按这里指定的名字创建一个用户。 41 | 42 | ## 4 修改 address/views.py 43 | 44 | ```python 45 | #... 46 | from django.conf import settings 47 | 48 | @csrf_exempt 49 | def upload(request): 50 | if request.user.username != settings.UPLOAD_USER: 51 | return render_to_response('address/error.html', 52 | {'message':'你需要使用 %s 来登录!' % settings.UPLOAD_USER}) 53 | #... 54 | ``` 55 | 56 | 我们从 django.conf 导出了 settings ,然后在 `upload()` 中判断当前用户名是否是等于 `settings.UPLOAD_USER` 这个用户名,如果不是则提示出错信息。否则继续处理。 57 | 58 | 好象一切都挺简单,但这里还有一个大问题:能不能自动导向一个用户注册的页面去呢?上面的处理是需要用户进入 admin 管理界面进行注册后,再进行操作。如果没有注册就上传文件,则只会报错。这里我希望实现:如果用户没有注册过,自动显示一个注册页面。如何做呢? 59 | 60 | 文档中提出了一个方法: 61 | 62 | ```python 63 | from django.contrib.auth.decorators import login_required 64 | 65 | @login_required 66 | def my_view(request): 67 | ... 68 | ``` 69 | 70 | 这个方法我试过了,但失败了。主要的原因是:如果你还没有注册,它会自动导向 /accounts/login/ ,而这个URL目前是不存在的。在我分析了 login.py 代码之后,我认为它只是一个框架,并不存在 Django 已经提供好的模板可以直接使用,如果要使用它是不是需要我自已去建一个可以用的模板?没办法,我分析了 admin 的代码之后,最终找到了一种替代的方法: 71 | 72 | ```python 73 | from django.contrib.admin.views.decorators import staff_member_required 74 | 75 | @staff_member_required 76 | def upload(request): 77 | ``` 78 | 79 | admin 已经提供了这样的一个方法: `staff_member_required` 。它允许我使用 admin 的登录画面。 80 | 81 | 一旦把上面的代码补充完整,代码是这样的: 82 | 83 | ```python 84 | from .models import Address 85 | 86 | from django.http import HttpResponseRedirect 87 | from django.shortcuts import render_to_response 88 | from django.views.decorators.csrf import csrf_exempt 89 | from django.conf import settings 90 | from django.contrib.admin.views.decorators import staff_member_required 91 | 92 | @staff_member_required 93 | @csrf_exempt 94 | def upload(request): 95 | if request.user.username != settings.UPLOAD_USER: 96 | return render_to_response('address/error.html', 97 | {'message':'你需要使用 %s 来登录!' % settings.UPLOAD_USER}) 98 | file_obj = request.FILES.get('file', None) 99 | if file_obj: 100 | import csv 101 | from io import StringIO 102 | try: 103 | csvfile = StringIO(file_obj.read().decode()) 104 | reader = csv.reader(csvfile) 105 | except: 106 | return render_to_response('address/error.html', 107 | {'message':'你需要上传一个csv格式的文件!'}) 108 | for row in reader: 109 | objs = Address.objects.filter(name=row[0]) 110 | if not objs: 111 | obj = Address(name=row[0], gender=row[1], 112 | telphone=row[2], mobile=row[3], room=row[4]) 113 | else: 114 | obj = objs[0] 115 | obj.gender = row[1] 116 | obj.telphone = row[2] 117 | obj.mobile = row[3] 118 | obj.room = row[4] 119 | obj.save() 120 | 121 | return HttpResponseRedirect('/address/') 122 | else: 123 | return render_to_response('address/error.html', 124 | {'message':'你需要上传一个文件!'}) 125 | 126 | from django.http import HttpResponse 127 | from django.template import loader, Context 128 | 129 | def output(request): 130 | response = HttpResponse(content_type='text/csv') 131 | response['Content-Disposition'] = 'attachment; filename=%s' % 'address.csv' 132 | t = loader.get_template('address/csv.html') 133 | objs = Address.objects.all() 134 | d = [] 135 | for o in objs: 136 | d.append((o.name, o.gender, o.telphone, o.mobile, o.room)) 137 | c = {'data': d,} 138 | response.write(t.render(c)) 139 | return response 140 | ``` 141 | 142 | 基本没有变化,主要是开始的一些地方增加了用户权限的处理。 143 | 144 | ## 5 启动 server 测试 145 | 146 | 在点击上传之后,如果没有注册会进入登录画面。如果已经注册,但用户名不对,则提示一个出错信息。不过,一旦注册出错,没有提供自动重新登录的功能,因此你需要进入 admin 管理地址,然后注销当前用户,再重新上传或先用正确的用户登录。因为是个简单的 app ,没必要做得那么完善。同时还存在的一个问题是,如果你没有注册过,那么点击上传按钮后,将进入登录画面,但如果成功,你上传的文件将失效,需要重新再上传。那么解决这个问题的一个好方法就是:不要直接显示上传的东西,而是先提供一个链接或按钮,认证通过后,再提供上传的页面,这样可能更好一些。 147 | 148 | 在 [User authentication in Django](https://docs.djangoproject.com/en/2.0/topics/auth/) 文档中还有许多的内容,如权限,在模板中如何使用与认证相关的变量,用户消息等内容。 149 | -------------------------------------------------------------------------------- /django-step-by-step/chapter1101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter1101.png -------------------------------------------------------------------------------- /django-step-by-step/chapter1102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter1102.png -------------------------------------------------------------------------------- /django-step-by-step/chapter12.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十二) 2 | 3 | ## 1 引言 4 | 5 | 如果通讯录中的记录很多,我希望有一种搜索的方法,下面就让我们加一个搜索功能吧。当然,这个搜索功能是很简单的。在 [Django](https://www.djangoproject.com) 邮件列表中看到 WorldOnline(好象是它)有一个搜索的框架,可以定义哪些模块的哪些字段要参加搜索。这样在处理时会自动将相应的信息加入到搜索数据库中进行预处理。现在这个框架并没有开放源码,而且它底层使用的搜索的东西并不是 Django 本身的。这里我只是对姓名字段进行查找。 6 | 7 | ## 2 修改 templates/address/list.html 8 | 9 | ```html 10 | [...] 11 |
12 |
13 |
姓名性别电话手机房间
14 | 20 |
15 |
16 | 搜索姓名: 17 | 18 |
19 |
21 | 22 | 23 |
{% if has_previous %} 24 | [...] 25 | ``` 26 | 27 | 在显示分页的代码上面增加了搜索的处理。 28 | 29 | 从上面可以看到,条件输入处我增加了一个 `searchvalue` 的变量,希望在提交一个搜索后,显示页面的同时显示当前显示时使用的条件。 30 | 31 | 由于搜索结果页面也是一个列表页面,我们希望能够用[第九讲](./chapter09.md)介绍过的`generic view`来显示结果,因为列表页面的处理非常简单: 32 | 33 | ```python 34 | class IndexView(generic.ListView): 35 | model = Address 36 | template_name = 'address/list.html' 37 | paginate_by = 2 38 | ``` 39 | 40 | 但是这里存在一个困难:如何把搜索条件,搜索字符串与generic view 相关联呢?通过 `urls.py` 我想是不行的,因为它只从 url 解析,而且对于 QUERY_STRING 是不进行解析的(QUERY_STRING 是指: `http://example.com/add/?name=test` 中 `?` 后面的东西,也就是 `name=test` )。对于搜索条件,我会使用一个 form 来处理, `method` 会设为 `GET` ,因此生成的 url 中,查询条件正如这个例子,如: `http://localhost:8000/address/search/?search=limodou` 。这样无法变成上面所要用到的参数。 41 | 42 | 这里我们需要对generic view进行一下扩充,我们需要实现`get_queryset`和`get_context_data`这两个方法。分别用来指定结果集和模板渲染的参数,我们先来看看新的view方法怎么写: 43 | 44 | ## 3 修改 address/views.py 45 | 46 | ```python 47 | class SearchView(generic.ListView): 48 | 49 | template_name = 'address/list.html' 50 | paginate_by = 2 51 | 52 | def get_queryset(self): 53 | if self.request.GET.get('search'): 54 | self.name = self.request.GET['search'] 55 | return Address.objects.filter(name = self.name) 56 | else: 57 | self.name = None 58 | return Address.objects.all() 59 | 60 | def get_context_data(self, **kwargs): 61 | context = super().get_context_data(**kwargs) 62 | if self.name: 63 | context['searchvalue'] = self.name 64 | return context 65 | ``` 66 | 67 | 我们使用`get_queryset`方法代替了`model = Address`,这样可以更加灵活的定义返回的结果集。 68 | 69 | 我们使用`get_context_data`指定了可以传入到模板中的上下文字典。 70 | 71 | `self.request.GET['search']` 从 GET 中得到数据,是一个方便的用法。它将得到提交的查询姓名条件,如果有这个参数,那么我们使用`filter`函数对结果进行过滤。如果没有提交,则显示全部数据。 72 | 73 | 74 | ## 4 修改 address/urls.py 75 | 76 | ```python 77 | urlpatterns = [ 78 | url(r'^admin/', admin.site.urls), 79 | url(r'^$', helloworld.index), 80 | url(r'^add/$', add.index), 81 | url(r'^list/$', list.index), 82 | url(r'^xls/(?P\w+)/$', xls_test.output), 83 | url(r'^login/$', login.login), 84 | url(r'^logout/$', login.logout), 85 | url(r'^wiki/', include('wiki.urls')), 86 | url(r'^address/', include('address.urls')), 87 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 88 | ``` 89 | 90 | 增加了一个 search 的 url 链接映射。 91 | 92 | ## 5 启动 server 测试 93 | 94 | 感觉这个通讯录也差不多了,现在让我们将其部署到 Apache 上去跑一跑吧。 95 | 96 | 但部署到 apache 时才知道,问题很多啊。主要问题如下: 97 | 98 | - CentOS 7服务器默认自带的Python版本太低 99 | 100 | CentOS 7自带的Python版本为Python2.7,我们希望能够使用最新的Python 3.6 101 | 102 | - 相对路径的问题 103 | 104 | 许多使用相对路径的地方都不对了。必须使用绝对路径。不过这一点对于部署来说的确有些麻烦,好在要改动的地方不多,主要在 settings.py 中。如数据库名字(sqlite3),模板的位置。 105 | 106 | 其它的就是要注意的地方了。 107 | 108 | ## 6 部署到 Apache 上的体验 109 | 110 | 只能说是体验了,因为我不是 Apache 的专家,也不是 mod_wsgi 的专家,因此下面的内容只能算是我个人的配置记录,希望对大家有所帮助。 111 | 112 | ### 6.1 服务器安装Python 3.6 113 | 114 | 下面的操作我们都假定环境是CentOS 7的环境,您可以在阿里云、腾讯云等公有云服务商购买ECS服务器,会自动给你安装好相应的操作系统,最后给你一个root的用户名和密码。 115 | 116 | 使用你自己熟悉的SSH环境,用root用户登录即可,首先安装Python 3.6,执行下面的命令。 117 | 118 | ```bash 119 | yum install -y python36 120 | curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py 121 | python36 get-pip.py 122 | pip install virtualenv 123 | ``` 124 | 125 | 这样的话你安装的pip会默认使用Python3.6,我们顺手安装好了virtualenv环境。操作系统的Python环境不要安装太多的库文件,都放到自己应用的venv环境中,创建一个虚拟的环境。 126 | 127 | ### 6.2 安装 mod_wsgi 模块 128 | 129 | mod_wsgi的安装有很多种方法,这里介绍的是官方推荐的办法,使用pip安装,首先需要安装http的开发包,然后使用pip安装mod_wsgi到系统的lib库中,执行下面的命令。 130 | 131 | ```bash 132 | yum install -y http-devel python36-devel 133 | pip install mod_wsgi 134 | ``` 135 | 136 | 然后我们需要将mod_wsgi安装到apache服务器module中去。 137 | 138 | ```bash 139 | cd /etc/httpd/modules 140 | ln -s /usr/lib64/python3.6/site-packages/mod_wsgi/server/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so mod_wsgi.so 141 | ``` 142 | 143 | 我们通过在`/etc/httpd/modules`下面创建符号链接的方式,让apache在启动的时候自动加载mod_wsgi.so。 144 | 145 | 然后我们需要在`/etc/httpd/conf.modules.d`中创建一个文件,加载mod_wsgi.so,使用`vi /etc/httpd/conf.modules.d/10-wsgi.conf`命令创建配置文件,然后录入下面的内容: 146 | 147 | ``` 148 | LoadModule wsgi_module modules/mod_wsgi.so 149 | ``` 150 | 151 | 之后使用`systemctl restart httpd`重启apache服务即可。 152 | 153 | ### 6.2 创建配置文件 154 | 155 | 假定我们的django工程在/var/www/proc/newtest,那么我们应该创建`/etc/httpd/conf.d/wsgi.conf` 156 | ```bash 157 | WSGIScriptAlias /newtest /var/www/proc/newtest/newtest/wsgi.py process-group=newtest 158 | WSGIPythonHome /var/www/proc/newtest/venv 159 | WSGIPythonPath /var/www/proc/newtest 160 | 161 | 162 | 163 | Require all granted 164 | 165 | 166 | 167 | #Deamon模式设置 168 | WSGIDaemonProcess newtest python-home=/var/www/proc/newtest/venv python-path=/var/www/proc/newtest 169 | WSGIProcessGroup newtest 170 | 171 | #静态文件 172 | Alias /newtest/robots.txt /var/www/proc/newtest/static/robots.txt 173 | Alias /newtest/favicon.ico /var/www/proc/newtest/static/favicon.ico 174 | 175 | Alias /newtest/media/ /var/www/proc/newtest/media/ 176 | Alias /newtest/static/ /var/www/proc/newtest/static/ 177 | 178 | 179 | Require all granted 180 | 181 | 182 | 183 | Require all granted 184 | 185 | ``` 186 | 187 | `WSGIPythonHome` 是Python运行环境的绝对路径,这里指向我们virtualenv的目录 188 | 189 | 这里我还设了两个别名,用来指向 `media` 和 `static` 目录。在 `media` 和 `static` 的 `Location` 中设置不进行脚本的解析。 190 | 191 | > 上面的 media 路径是指向 Django 在 Python 上的安装目录。你完全可以将其拷贝出来,这样可能要方便得多。另外在 linux 下使用 ln 也相当的方便。 192 | 193 | ### 6.3 测试 194 | 195 | [http://localhost:8888/address]() 196 | 197 | 更详细的内容请参见 mod_wsgi 文档。关于 admin 的 media 和 template 好象并不需要配置,大家有什么结果可以告诉我。 198 | 199 | 同时如果你不想每次重启 Apache 来进行测试,可以将: 200 | 201 | ``` 202 | MaxRequestsPerChild 0 203 | ``` 204 | 205 | 改为: 206 | 207 | ``` 208 | MaxRequestsPerChild 1 209 | ``` 210 | 211 | ## 7 后话 212 | 213 | 上面的步骤是直接把开发的东西发布到了 Apache 中去,但实际中开发与运行可能环境根本不一样,最主要可能就是数据库方面的变化,如果model变化,则有可能要编写数据切换程序。许多实际的问题都需要仔细地考虑。 214 | -------------------------------------------------------------------------------- /django-step-by-step/chapter13.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十三) 2 | 3 | ## 1 引言 4 | 5 | 经过一段时间的学习,我想大家对于 [Django](https://www.djangoproject.com/) 的一些基础的东西已经有所了解,但 Django 本身的内容不仅仅如此,它还在发展中,还有许多的专题是我还没有向大家介绍的。因此,随着我和大家一同地学习,我会继续向大家介绍一些更高级的话题。 6 | 7 | 随着对于web的了解越来越多,我对于 web 上的开发也越来越有兴趣。的确,在实际的工作中我也发现,现在越来越强调团队的管理,许多事情单纯搞一两个人是很困难的,因此如何提高团队工作的一致性和方便性越来越重要,比如:在我所在的项目组,有一些统计信息需要每个人提供,然后进行汇总。目前还是采用手工的方式,这种方式的确简单,但不能自动地进行管理,也不利于以后的归档处理。因此我很希望做成 web 的应用,让每个人可以自由创建项目,提交数据。但就是这样的一个简单的工作,也不是非常简单的事情。如何快速对 Django 加深了解,如何提高开发效率,如何更有效地利用 web 是我更关心的,而不仅仅是做出一个可用的应用来。这包括一系列的 NewEdit 的扩展,及其关知识的积累。 8 | 9 | 特别让我感兴趣,并且可以极大的提高用户体验的一种 web 技术就是 [Ajax](https://zh.wikipedia.org/wiki/AJAX) 了。它是什么?它是一种技术的总称,包括了 Html, CSS, XML, Javascript 等与 web 相关技术的合集,在我以前的 Blog 也有一些涉及,但那时关注的焦点不在 web 上。现在有机会和时间好好地了解了一下,特别是在 Django 中已经做为实现的目标正在逐步地开展起来,只不过目前还没有可用的东西呈现出来。 10 | 11 | [Ajax](https://zh.wikipedia.org/wiki/AJAX)技术实际上就是利用了浏览器提供的XMLHttpRequest函数(XHR),在不重新加载网页的情况下,可以异步从后台读取数据改变网页内容的一项技术。AJAX即Asynchronous JavaScript and XML(异步JavaScript和XML) 12 | 13 | 随着近些年前端技术的不断发展,JavaScript也在不断进化。现在我们使用前端库和React、Angular、Vue等框架构建了动态的网站。AJAX的概念也经历了重大变化,因为现代异步JavaScript调用涉及检索JSON而不是XML。有很多库允许你从客户端应用程序对服务器进行异步调用。有些进入到浏览器标准,有些则有很大的用户基础,因为它们不但灵活而且易于使用。有些支持promises,有些则使用回调。 14 | 15 | Vue2.0之后,尤雨溪推荐大家用[axios](https://github.com/axios/axios)替换JQuery ajax,让[axios](https://github.com/axios/axios)进入了很多人的目光中。[axios](https://github.com/axios/axios)本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。 16 | 17 | 下面就让我以 [axios](https://github.com/axios/axios) 为基础来向大家介绍一下如何在 Django 中使用它,使用一些简单的 Ajax 技术。 18 | 19 | 首先让我们关心一下 Ajax 与 Django 的关系。其实 Ajax 本身包含许多的内容,它有浏览器端的显示技术,有与后台通讯的处理,因此与 Django 有关系的其实只有与后台交互那块东西。这样,更多的关于前端显示的技术,如:显示特效,这些都属于 CSS, Javascript的内容,而这些与 [Python](https://www.python.org/) 本身的关系也不大,因此你还需要掌握这些东西才可以做得更好。也许有机会会有专题和学习和介绍这些方面的东西。 20 | 21 | 下面的试验主要关注的是前端与后端的交互,也就是如何实现浏览器与 Django 交互,体验不进行页面的刷新(这是Ajax最大的好处,一切都好象在本地进行一样)。 22 | 23 | 就目前来说, Ajax 与后台交互都是通过浏览器提供的 `XMLHttpRequest` 对象来实现的。这个对象支持同步和异步的调用,但由于 Javascript 本身没有多线程这个东西,因此为了不阻塞浏览器,一般都采用异步方式来调用,而这也是一般的 Ajax 框架提供了默认方式。就目前来说,交互数据也有多种格式,比如:XML, Json , 纯文本/Html。 XML 不用说了,但一般采用 http 协议的 web server 是无法直接支持,因此需要进行转换。同时在浏览器你要同时进行XML的解析,不是非常方便。 Json 是一种数据表示格式,它非常象 Python 的数据类型。而且它只有数据,没有任何的格式,因此数据传输量非常小。再加上处理起来也很方便,在传输上可以直接转换为文本,然后再转换成不同语言的数据结构即可。对于 Python 是非常方便。再有就是文本/Html方式,一种是自定义格式,通过转化为文本进行处理,另一种就是直接使用 html 标记。前一种需要自行做扩展,后一种则是最方便。下面我们将先使用 html 方式,然后再使用 Json 来进行试验。 24 | 25 | 我设计了一个非常简单的例子:提供一个输入框,用户输入文本,然后点提交,直接在下面显示后台返回的结果。因为我不是 Javascript , CSS 的专家,可能有不对的地方。 26 | 27 | ## 2 创建 Ajax 应用 28 | 29 | ```bash 30 | python manage.py startapp ajax 31 | ``` 32 | 33 | ## 3 修改 ajax/views.py 34 | 35 | ```Python 36 | # Create your views here. 37 | from django.http import HttpResponse 38 | 39 | def input(request): 40 | input = request.REQUEST["input"] 41 | return HttpResponse('

You input is "%s"

' % input) 42 | ``` 43 | 44 | 从这里可以看出,我需要一个 input 字段,然后返回一个HTML的片段。 45 | 46 | ## 4 创建 templates/ajax 目录 47 | 48 | ## 5 创建 templates/ajax/ajax.html 49 | 50 | ```HTML 51 | 53 | 54 | 55 | Ajax Test 56 | 57 | 58 | 59 | 60 | 61 |

62 | Ajax 演示 63 |

64 |
65 |
66 | 输入: 67 | 68 |
69 |
70 |
71 | 72 | 73 | ``` 74 | 75 | 这个模板将作为初始页面,它用来处理向后台发起请求。在这里它没有需要特殊处理的模板变量,只需要显示即可。但在这里的确有许多要说明的东西。 76 | 77 | 这是一个标准的 html 的页面,在 head 标签中,它将引入三个 js 文件: jquery.js 、 axios.js 和 ajax_test.js 。我们采用[jsdelivr](https://www.jsdelivr.com)所提供的CDN服务加载jquery.js和axios.js,这样有两个好处,一来可以保持我们始终采用最新的版本,二来也可以分担我们服务器的流量压力。从 url 上可以看出,我将会ajax_test.js放在 static 下,这个地址就是 static 目录。 78 | 79 | 在 html 文件中有一个 form ,它的 id 是 form ,我将用它来查找 form 对象。它有一个文本输入框,还有一个按钮,但这个按钮并不是 submit 按钮。这里有许多与标准的 form 不一样的地方,没有 action, 没有 method ,而且没有 submit 按钮。为什么要这样,为了简单。以前写 HTML,CSS, Javascript 和事件之类的处理,我们一般可能会写在一起,但这样的确很乱。我们在这里尝试使用代码分离技术,而这也是目前可能流行的做法。我们在独立的 Javascript 中编写代码,在装载页面时动态地查找相应的元素,然后设置元素的一些属性,如 style ,事件代码等。而在 Html 文档中,你看到的元素中一般就只有 id , class 等内容。这样的好处可以使得处理为以后重用及优化带来方便,同时可以通过编程的方式实现批量的处理,而且也使得 Html 页面更简单和清晰。因为我要使用 Ajax 去动态提交信息,不需要真正的 form 的提交机制,我只是需要用到 form 元素中的数据而已,因此象 action, method 等内容都没有用。 id 是必须的,我需要根据它找到我想要处理的元素对象。 80 | 81 | > 不过分离的作法是你的文件将增多,也可能不如放在一个文件中便于部署吧。这是一个仁者见仁,智者见智的作法。 82 | 83 | `
` 它是用来显示结果的层。 84 | 85 | 整个处理过程就是: 86 | 87 | 在装载 html 页面时,会对按钮进行初始化处理,即增加一个 `onclick` 的事件处理,它将完成 Ajax 的请求及结果返回后的处理。然后用户在页面显示出来后,可以输入文本,点击按钮后,将调用 `onclick` 方法,然后提交信息到 Django ,由 Django 返回信息,再由 Ajax 的 deferred 对象(后面会介绍)调用显示处理。 88 | 89 | ## 6 创建 static/ajax_test.js 90 | 91 | ```Javascript 92 | function submit(){ 93 | axios.get('/ajax/input/', { 94 | params: { 95 | input: $("#name").val() 96 | } 97 | }) 98 | .then(function (response) { 99 | // handle success 100 | console.log(response); 101 | $("#output").html(response.data); 102 | $("#output").show(); 103 | }) 104 | .catch(function (error) { 105 | // handle error 106 | console.log(error); 107 | alert(error); 108 | }); 109 | } 110 | 111 | function init() { 112 | $("#submit").click(function(){ 113 | submit(); 114 | }); 115 | $("#output").hide(); 116 | } 117 | 118 | $(function(){ 119 | init(); 120 | }); 121 | ``` 122 | 123 | 这里有许多是 [jQuery](https://jquery.com/)和[axios](https://github.com/axios/axios) 的方法。 124 | 125 | 首先让我们看 `$(function(){});` 它表示将 `init()` 函数加到 `window.onload` 的响应事件对列中。浏览器在装载完一个页面后,会自动调用 `onload` 事件处理。因此在这里是进行初始化的最好的地方。 126 | 127 | `init()` 方法一方面完成对 id 名为 `submit` 的按钮 `onclick` 处理函数的绑定工作,另一个是将 id 为 `output` 的元素隐藏。其实不隐藏也无所谓,因为它本来就是空的,因此你也看不到东西。不过如果有其它的东西这样的处理却也不错。 128 | 129 | `$()` 是 jQuery 提供的一个 `getElement()` 函数别名,它将根据元素的 id 来得到某个对象。 130 | 131 | `hide()` 是隐藏某个元素。想要显示某个元素可以使用 `show()` 。 132 | 133 | 最重要的工作都在 `submit()` 这个函数中。它通过调用 axios 提供的 `get()` 函数提交一个 Ajax 请求到后台。第一个参数是请求的 url ,第二个如果有的话,应该是 Query String ,即一个 url 的 ? 后面的东西。 134 | 135 | 在执行了 `get()` 之后,结果可能当时并没有返回回来,因为这是一个异步调用。因此为了在结果回来之后做后续的处理,我还需要挂接两个异步函数,一个用来处理成功的情况,一个是用来处理失败的情况。 `then()`和`catch()` 就是做这件事的。 136 | 137 | `then()` 在 HTTP GET请求正确返回后会被调用。 `response` 是一个对象,它有一个 `data` 属性可以使用。这里因为 Django 返回的是 Html 片段,因此我只是简单地将 `output` 对象(用于显示的 div 层)的内容进行了设置。然后调用 `show()` 来将层显示出来。 138 | 139 | `catch()` 则只是调用 `alert()` 显示出错而已。 140 | 141 | 这里有许多 Javascript 、jQuery、axios 的东西,如果大家不了解则需要补补课了。 142 | 143 | ## 7 创建 ajax/urls.py 144 | 145 | 增加两行: 146 | 147 | ```Python 148 | from django.conf.urls import url 149 | from django.views.generic import TemplateView 150 | from . import views 151 | 152 | urlpatterns = [ 153 | url(r'^$', TemplateView.as_view(template_name="ajax/ajax.html")), 154 | url(r'^input/$', views.input), 155 | ] 156 | ``` 157 | 158 | 前一个使用了 generic view 所提供的 `TemplateView.as_View` 方法可以直接显示一个模板。后一个则指向了 `views.input()` 方法,它用于在前一个页面点击按钮后与后台交互的处理。 159 | 160 | ## 8 安装 ajax 应用 161 | 162 | 修改 `settings.py` 163 | 164 | ```Python 165 | INSTALLED_APPS = ( 166 | 'django.contrib.admin', 167 | 'django.contrib.auth', 168 | 'django.contrib.contenttypes', 169 | 'django.contrib.sessions', 170 | 'django.contrib.messages', 171 | 'django.contrib.staticfiles', 172 | 'debug_toolbar', 173 | 'newtest', 174 | 'wiki.apps.WikiConfig', 175 | 'address.apps.AddressConfig', 176 | 'ajax.apps.AjaxConfig', 177 | ) 178 | ``` 179 | 180 | ## 9 启动 server 测试 181 | 182 | 这样你在文本框中输入内容,点击提交后就会立即在文本框的下面看到结果,而页面没有刷新,这就是 Ajax 就直接的做用。 183 | -------------------------------------------------------------------------------- /django-step-by-step/chapter14.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十四) 2 | 3 | ## 1 引言 4 | 5 | [Ajax](https://zh.wikipedia.org/wiki/AJAX) 因为大量地使用了 Javascript ,而调试 Javascript 的确不是件容易的事,在这方面只有不停地测试,还要靠耐心。而且 Ajax 本身可能还有一些安全方面的东西需要考虑,但这些话题需要你自已去学习了。 6 | 7 | 在试验了简单的 Html 返回片段之后,让我们再体验一下 Json 的应用吧。为了使用 Json ,我下载了 [simplejson](https://simplejson.readthedocs.io/en/latest/) 模块。我下载的是 1.1 版本。还可以使用 easy_install 来安装。 8 | 9 | 如何使用 simplejson 在它自带的文档有示例很简单,下面我们就用它来试验 Json 的例子。 10 | 11 | 我将在上一例的基础之上,增加一个按钮,这个按钮点击后,会发送一个请求(不带 Json 信息),然后 [Django](https://www.djangoproject.com/) 会返回一个 Json 格式的表格数据,分为头和体两部分。然后前端动态生成一个表格显示在 `output` 层中。 12 | 13 | ## 2 修改 ajax/views.py 14 | 15 | ```Python 16 | #coding=utf-8 17 | # Create your views here. 18 | from django.http import HttpResponse 19 | 20 | def input(request): 21 | input = request.REQUEST["input"] 22 | return HttpResponse('

You input is "%s"

' % input) 23 | 24 | def json(request): 25 | a = {'head':('Name', 'Telphone'), 'body':[(u'张三', '1111'), (u'李四', '2222')]} 26 | import simplejson 27 | return HttpResponse(simplejson.dumps(a)) 28 | ``` 29 | 30 | > 由于使用了汉字,前面的 coding=utf-8 一定要加上。 31 | 32 | `json()` 是新加的方法。 a 是一个字典,它会被封装为 Json 的格式。这里还使用了汉字,但使用了 unicode 的表示。我发现 simplejson 在处理非 ascii 码时会自动转为 unicode ,但不正确,因此我直接使用了unicode。因此我希望浏览器可以根据这个数据生成表格。 33 | 34 | ## 3 修改 templates/ajax/ajax.html 35 | 36 | ```HTML 37 | 39 | 40 | 41 | Ajax Test 42 | 43 | 44 | 45 | 46 |

47 | Ajax 演示 48 |

49 |
50 |
51 | 输入: 52 | 53 | 54 |
55 |
56 |
57 | 58 | 59 | ``` 60 | 61 | 这里只是增加了一个按钮, id 是 `json` 。它将用来触发 Ajax 请求。 62 | 63 | ## 4 修改 media/ajax_test.js 64 | 65 | ```Javascript 66 | function callJson(){ 67 | var d = loadJSONDoc('/ajax/json/'); 68 | d.addCallbacks(onSuccessJson, onFail); 69 | } 70 | row_display = function (row) { 71 | return TR(null, map(partial(TD, null), row)); 72 | } 73 | onSuccessJson = function (data){ 74 | var output = $("output"); 75 | table = TABLE({border:"1"}, THEAD(null, row_display(data.head)), 76 | TBODY(null, map(row_display, data.body))); 77 | replaceChildNodes(output, table); 78 | showElement(output); 79 | } 80 | function init() { 81 | var btn = $("submit"); 82 | btn.onclick = submit; 83 | var output = $("output"); 84 | hideElement(output); 85 | var btn = $("json"); 86 | btn.onclick = callJson; 87 | } 88 | ``` 89 | 90 | 在最后一行 `addLoadEvent(init);` 前加入上面的内容。对于 id 为 `json` 的按钮的事件绑定方式与上一例相同,都是在 `init()` 中进行的。在 `callJson()` 中进行实际的 Json 调用,这次使用了 [MochiKit](https://mochi.github.io/mochikit/) 提供的 `loadJSONDoc()` 函数,它将执行一个 url 请求,同时将返回结果自动转化为 Json 对象。一旦成功,将调用 `onSuccessJson()` 函数。在这里将动态生成一个表格,并显示出来。 91 | 92 | 表格的显示使用了 MochiKit 的 DOM 中的示例的方法。 `row_display()` 是用来生成一行的。`TBODY` 中使用map来处理数组数据。在 MochiKit 中有许多象 [Python](https://www.python.org/) 内置方法的函数,因为它的许多概念就是学的 Python 。 `replaceChildNodes()` 是用来将生成的结果替换掉 `output` 元素的内容。 93 | 94 | ## 5 修改 urls.py 95 | 96 | ```Python 97 | (r'^ajax/json/$', 'newtest.ajax.views.json'), 98 | ``` 99 | 100 | 增加上面一行。这样就增加了一个 Json 的 url 映射。 101 | 102 | ## 6 启动 server 进行测试 103 | 104 | 这里两个演示共用了 output 层作为显示的对象,你可以同时试一试两个例子的效果。 105 | 106 | 不过这里有一个问题:只有返回时使用了 Json 。的确是,这样是最简单处理的情况。因为 Json 可以包装为字符串,这样不用在底层进行特殊处理。如果请求也是 Json 的,需要设计一种调用规则,同时很有可能要实现 MiddleWare 来支持。在 Django 中的确有人已经做过类似的工作。不过我目前没有研究得那么深,因此只要可以处理返回为 Json 的情况已经足够了。而且 Django 也正在进行 Ajax 的支持工作,不过可能是以 [dojo](http://dojotoolkit.org/) 为基础的,让我们拭目以待吧。 107 | -------------------------------------------------------------------------------- /django-step-by-step/chapter15.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十五) 2 | 3 | ## 1 引言 4 | 5 | 在 [Ajax](https://zh.wikipedia.org/wiki/AJAX) 的试验中,你会看到有一些是用英文写的。下面就让我们学习如何将应用改为支持 i18n 处理的吧。在本讲中我会讲述我实现的过程,同时对一些问题进行讨论。 [Django](https://www.djangoproject.com/) 中 i18n 的实现过程: 6 | 7 | ### 1.1 在程序和模板中定义翻译字符串 8 | 9 | 在程序中就是使用 `_()` 将要翻译的字符串包括起来。这里有几种做法,一种是什么都不导入,这样就使用缺省的方式,另一种是导入 Django 提供的翻译函数。特别是 Django 提供了 Lazy 翻译函数,特别可以用在动态语言的切换。在模板中分几种情况: 10 | 11 | 可以使用 `{% trans %}` 标签。它用来翻译一句话,但不能在它中间使用模板变量。 12 | 如果是大段的文本,或要处理模板变量,可以使用 `{% blocktrans %}{% endblocktrans %}` 来处理。 13 | Django 还支持简单的 Javascript 的 i18n 的处理,但有兴趣自已去看吧。 14 | 15 | ### 1.2 生成 po 文件 16 | 17 | 定义好翻译串之后使用 `bin/make-messages.py` 来生成 po 文件。 18 | 19 | Django 支持多层次的处理。比如在整个 Django 的源码项目,在某一个工程,在某一个应用。在不同层次去实现 i18n 时,需要在不同的层次的根目录去执行 `make-messages.py` 。那么可以将 `make-messages.py` 拷贝到相应的目录去执行,特别是在你的工程或应用中。在执行 `make-messasges.py` 时,需要你预先创建 `conf/locale` 或 `locale` 目录,而 `make-messasges.py` 是不会自动为你创建的。那么 `conf/locale` 多用在源码中,象 Django 的源码就是放在 `conf/locale` 中的。 **但在运行时,对于自已的项目和应用却是从 ``locale`` 中来找的 。**因此还是建议你创建 `locale` 来存放 po 文件。 20 | 21 | 第一次执行时: 22 | 23 | ```bash 24 | make-messages.py -l zh_CN 25 | ``` 26 | 27 | 这时会生成 `locale/zh_CN/LC_MESSAGES/django.po` 和 `django.pot` 两个文件。 28 | 29 | 然后你就可以开始翻译了。翻译完成之后,首先要执行类目->设置,将缺省的参数修改一下。主要是:项目名称及版本,团队,团队专用电子邮件,字符集(一般为 utf-8)。这些如果不改, poEdit 在保存时会报错。使用 poEdit 的一个好处是,在保存时它会自动将 po 编译成 mo 文件。 30 | 31 | 以后再更新时: 32 | 33 | ```bash 34 | make-messasges.py -a 35 | ``` 36 | 37 | 如果已经有多个语言文件,那么执行时会同时更新这些 po 文件。 38 | 39 | ## 1.3 配置 40 | 41 | Django 有一系列的策略来实现 i18n 的功能。基本上分为静态和动态。 42 | 43 | 静态是指在 `settings.py` 中设置 `LANGUAGE_CODE` 为你想要的语言。那么这里要注意,中文的语言编码是 `zh-cn` ,但 `locale` 目录下却是 `zh_CN` 。这是为什么:其实一个是 language(zh-cn) ,一个是 locale(zh_CN) ,在 Django 的 `utils.translation.py` 中有专门的方法可以进行转换。因此在 Django 的程序中使用的是 language 的形式,在目录中却是使用 locale 的形式。一旦设为静态,则它表示是全局性质的,在所有其它的策略失效后将使用这种策略。 44 | 45 | 而动态是指在运行中对于不同的用户,不同的浏览器的支持的语言可以有不同的语言翻译文件被使用。这种方式需要在 `settings.py` 中安装 `django.middleware.locale.LocaleMiddleware` 到 `MIDDLEWARE_CLASSES` 中去。同时如果你想在实现应用中的翻译文件被使用,也要采用这种方式。 46 | 47 | 在一个请求发送到 Django 之后,如果安装了 `LocaleMiddleware` ,它会采用下面的策略: 48 | 49 | * 在当前用户的 session 中查找 `django_language` 键字。 50 | * 如果没有找到则在 cookie 中查找叫 `django_language` 的值。 51 | * 如果没有找到,则查看 `Accept-Language` HTTP 头。这个头是由浏览器发送给服务器的。 52 | * 如果没有找到,则使用全局的 `LANGUAGE_CODE` 设置。 53 | 54 | 如果你使用 FireFox 可以在 Tools->Options->Advanced->Eidt Languages 设置你所接受的语言,并且将 `zh-cn` 放在最前面。 55 | 56 | 上面讲述得还是有些粗,建议你好好阅读 i18n 的文档。 57 | 58 | > 国际化处理的文档请参阅: [Internationalization](https://docs.djangoproject.com/en/2.0/topics/i18n/) 文档 59 | 60 | 下面开始我们的试验。 61 | 62 | ## 2 修改 ajax/views.py 63 | 64 | ```Python 65 | #coding=utf-8 66 | # Create your views here. 67 | from django.http import HttpResponse 68 | 69 | def input(request): 70 | input = request.REQUEST["input"] 71 | return HttpResponse(_('

You input is "%s"

') % input) 72 | 73 | def json(request): 74 | a = {'head':(unicode(_('Name'), 'utf-8'), unicode(_('Telphone'), 'utf-8')), 75 | 'body':[(u'张三', '1111'), (u'李四', '2222')]} 76 | import simplejson 77 | return HttpResponse(simplejson.dumps(a)) 78 | ``` 79 | 80 | 这里对所有英文都使用 `_()` 进行了封装。但对于 Json 方法,这里我使用 `unicode(_('Name'), 'utf-8')` 进行了转换。 81 | 82 | ## 3 修改 settings.py 83 | 84 | 增加 `LocaleMiddleware` 85 | 86 | ```Python 87 | MIDDLEWARE_CLASSES = ( 88 | 'django.contrib.sessions.middleware.SessionMiddleware', 89 | 'django.middleware.locale.LocaleMiddleware', 90 | 'django.middleware.common.CommonMiddleware', 91 | 'django.middleware.doc.XViewMiddleware', 92 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 93 | ) 94 | ``` 95 | 96 | 这里在文档中对于 `LocaleMiddleware` 的顺序有要求,要求排在 `SessionMiddleware` 之后,但在其它的 Middleware 之前。 97 | 98 | > 话虽如此,但我感觉目前顺序影响不大,也许只是个人感觉吧。 99 | 100 | ## 4 创建 ajax/locale 目录 101 | 102 | ## 5 拷贝 make-messasges.py 到 ajax 目录下 103 | 104 | ## 6 执行 make-messasges.py 105 | 106 | ```bash 107 | cd ajax 108 | make-message.py -l zh_CN 109 | ``` 110 | 111 | ## 7 使用 poEdit 翻译 django.po 文件 112 | 113 | 按上面说的先更新 pot 文件,然后修改缺省的参数,再保存。 114 | 115 | > 如果你没有 poEdit,或不在 Windows 平台下,那么只好自已去想办法了。同时这里 make-message.py 还需要 Windows 下的 xgettext 工具。可以在 [http://code.djangoproject.com/wiki/Localization]() 找到说明。 116 | 117 | 这里我没有演示模板的处理。因为 Ajax 所用到的模板没有放在 `ajax` 目录下,而是放在 `templates` 目录下。因此,如果想支持 i18n 的话,目录的布置是一个问题。所以不再试验了。 118 | 119 | ## 8 启动 server 测试 120 | 121 | 是不是都是中文了呢?如果不是,看一看是否浏览器没有设置成接受 `zh-cn` 。 122 | -------------------------------------------------------------------------------- /django-step-by-step/chapter16.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十六) 2 | 3 | ## 1 引言 4 | 5 | [Django](https://www.djangoproject.com/) 中的模板系统可以被自由扩展,如自定义 filter, 自定义 Tag 等。其中 filter 用于对变量的处理。而 Tag 则功能强大,几乎可以做任何事情。我认为 Tag 的好处有非常多,比如: 6 | 7 | * 可以简单化代码的生成。一个 Tag 相当于一个代码片段,把重复的东西做成 Tag 可以避免许多重复的工作。 8 | * 可以用来组合不同的应用。将一个应用的展示处理成 Tag 的方式,这样就可以在一个模板中组合不同的应用展示 Tag,而且修改模板也相对容易。 9 | 10 | 如果要自定义 Tag ,那么要了解 Tag 的处理过程。在 Django 中, Tag 的处理分为两步。 11 | 12 | 1. 编译。即把 Tag 编译为一系列的 `django.template.Node` 结点。 13 | 2. 渲染(Render)。即对每个 Node 调用它们的 `render()` 方法,然后将输出结果拼接起来。 14 | 15 | 因此自定义一个 Tag ,你需要针对这两步处理来做工作。 16 | 17 | 在 [The Django template language: For Python programmers](https://docs.djangoproject.com/en/2.0/ref/templates/api/) 文档中讲解了一些例子。大家可以看一下。 18 | 19 | 那么下面,我将实现一个显示日历的自定义 Tag 。 20 | 21 | ## 2 下载 HTMLCalendar 模块并安装 22 | 23 | 不想全部自已做,因此找了一个现成的模块。去 [HTMLCalender](https://sourceforge.net/projects/py-templates/) 的主页下载这个模块。 24 | 25 | 然后解压到一个目录下,执行安装: 26 | 27 | ```bash 28 | python setup.py install 29 | ``` 30 | 31 | ## 3 下载 HTMLTemplate 模块并安装 32 | 33 | 然后解压到一个目录下,执行安装: 34 | 35 | ```bash 36 | python setup.py install 37 | ``` 38 | 39 | 因为上面的 HTMLCalender 需要它才可以运行。去 [HTMLCalender](https://sourceforge.net/projects/py-templates/) 主页下载这个模块。 40 | 41 | ## 4 创建 my_alendar 应用 42 | 43 | ```bash 44 | manage.py startapp my_calendar 45 | ``` 46 | 47 | 这里起名为 my_calendar 。因为如果起名为 calendar 会与系统的 calendar 模块重名。 48 | 49 | ## 5 创建 my_calendar/templatetags 目录 50 | 51 | ```bash 52 | cd my_calendar 53 | md templatetags 54 | ``` 55 | 56 | > 在 Windows 下是 md, 在 Linux 下是 mkdir 。 57 | 58 | ## 6 创建 my_calendar/templatetags/__init__.py 文件 59 | 60 | 空文件即可 61 | 62 | ## 7 创建 my_calendar/templatetags/my_calendar.py 文件 63 | 64 | ```Python 65 | from django import template 66 | 67 | register = template.Library() 68 | 69 | class CalendarNode(template.Node): 70 | def __init__(self): 71 | pass 72 | 73 | def render(self, context): 74 | return "Calendar" 75 | 76 | def do_calendar(parser, token): 77 | return CalendarNode() 78 | 79 | register.tag('calendar', do_calendar) 80 | ``` 81 | 82 | 上面的代码只是一个空架子。不过让我们仔细地解释一下: 83 | 84 | * `register` 与自定义 filter 一样,它将用来注册一个 Tag 的名字到系统中去。 85 | 86 | * `CalendarNode` 它是 `template.Node` 的一个子类。每个 Tag 都需要从 `Node` 派生。这个类可以只有 `render()` 方法,用来返回处理后的文本。 `__init__()` 可能是有用的,先预留。 87 | 88 | * `render()` 方法接受一个 `context` 参数。这个参数就是在执行模板的渲染时由 View 传入的。不过更复杂的例子是你可以修改 `context` ,这样达到注入新变量的目的。不过本例没有演示。 89 | 90 | * `do_calendar()` 是一个由模板处理引擎在发现一个 Tag 的名字之后,将进行调用的方法。那么我们的 Tag 可能在模板中写为 {% raw %}`{% calendar %}`{% endraw %} 。这个方法将在下面通过注册过程与一个名字相对应,这里我们想使用 `calendar` 。 91 | 92 | 它接受两个参数: 93 | 94 | - `parser` 这是模板处理引擎对象,我们没有用到。 95 | 96 | - `token` 表示 Tag 的原始文本。如果在模板中我们定义 Tag 为 {% raw %}`{% calendar 2006 1 %}`{% endraw %}, 那么 `token` 就为 `calendar 2006 1` 。因此你需要对它进一步地处理。 97 | 98 | 它将返回一个 Node 的实例,在本例中就是 `CalendarNode` 实例。 99 | 100 | * `register.tag('calendar', do_calendar)` 用来注册 Tag 名字和对应的处理方法。 101 | 102 | 尽管我们没有对 calendar 所带的参数进行处理,但它仍然可以显示。要知道我们还没有使用 HTMLCalender 模块呢。 103 | 104 | ## 8 创建 templates/my_calendar 目录 105 | 106 | ## 9 创建 templates/my_calendar/calendar.html 文件 107 | 108 | {% raw %} 109 | ```Python 110 | {% load my_calendar %} 111 | {% calendar 2006 1 %} 112 | ``` 113 | {% endraw %} 114 | 115 | ## 10 修改 usls.py 116 | 117 | 增加下面的 url 配置: 118 | 119 | ```Python 120 | (r'^calendar/$', 'django.views.generic.simple.direct_to_template', 121 | {'template': 'my_calendar/calendar'}), 122 | ``` 123 | 124 | ## 11 修改 settings.py 安装 my_calendar 应用 125 | 126 | ```Python 127 | INSTALLED_APPS = ( 128 | 'django.contrib.auth', 129 | 'django.contrib.contenttypes', 130 | 'django.contrib.sessions', 131 | 'django.contrib.sites', 132 | 'newtest.wiki', 133 | 'newtest.address', 134 | 'newtest.ajax', 135 | 'newtest.my_calendar', 136 | 'django.contrib.admin', 137 | ) 138 | ``` 139 | 140 | 增加了 my_calendar 应用。 141 | 142 | ## 12 启动 server 测试 143 | 144 | 页面上应该显示出 Calendar 的文本。我们在模板中定义的参数没有被用到。因为我们没有真正调用 HTMLCalender 输出,因此上面只是说明框架是可用的。 145 | 146 | 下面让我们加入参数的处理。 147 | 148 | ## 13 修改 my_calendar/templatetags/my_calendar.py 149 | 150 | ```Python 151 | from django import template 152 | import HTMLCalendar 153 | 154 | register = template.Library() 155 | 156 | class CalendarNode(template.Node): 157 | def __init__(self, year, mon): 158 | self.year = int(year) 159 | self.mon = int(mon) 160 | 161 | def render(self, context): 162 | return HTMLCalendar.MonthCal().render(self.year, self.mon) 163 | 164 | def do_calendar(parser, token): 165 | try: 166 | tag_name, arg = token.contents.split(None, 1) 167 | except ValueError: 168 | #if no args then using current date 169 | import datetime 170 | today = datetime.date.today() 171 | year, mon = today.year, today.mon 172 | else: 173 | try: 174 | year, mon = arg.split(None, 1) 175 | except ValueError: 176 | raise template.TemplateSyntaxError, "%r tag requires year and mon arguments" % tag_name 177 | 178 | return CalendarNode(year, mon) 179 | 180 | register.tag('calendar', do_calendar) 181 | ``` 182 | 183 | 主要改动如下: 184 | 185 | 1. 增加了 `import HTMLCalendar` 的导入。 186 | 187 | 2. 修改了 `CalendarNode` 的 `__init__()` 方法,增加了两个参数。 188 | 189 | 3. 修改了 `CalendarNode` 的 `render()` 方法。改成输出一个 Calendar 的表格。 190 | 191 | 4. 修改了 `do_calendar()` 函数,增加了参数的处理。如果没有输入参数则使用当前的年、月值。否则使用指定的年、月参数。如果解析有误,则引发异常。 192 | 193 | > 不过在调试的过程中,的确有一些错误。象开始时我命名为 calendar 目录,结果造成与系统的 calendar 模块重名。然后不得已进行了改名。为什么发现要导入 HTMLTemplate 呢?因为在处理时 HTMLCalender 抛出了异常。但成功后我已经把这些调试语句去掉了。而且发现这些错误 Django 报告得有些简单,你可能不清楚倒底是什么错。因此最好的方法:一是在命令行下导入试一下,看一看有没有导入的错误。另外就是使用 try..except 然后使用 traceback 模块打印异常信息。 194 | 195 | ## 14 启动 server 测试 196 | 197 | 你会看到: 198 | 199 | ![](./chapter1601.png) 200 | 201 | 也许感到不好看,没关系,可以通过 CSS 进行美化。当然,这样可能还是不让人满意,比如:不是 i18n 方式的,因此看不到中文。不过这已经不是我们的重点了。掌握了自定义 Tag 的方法就可以自行进行改造了。 202 | 203 | 同时 HTMLCalender 模块本身可以传入一些链接,这样就可以在日历上点击了。这里不再试验了。有兴趣的可以自已做一下。 204 | -------------------------------------------------------------------------------- /django-step-by-step/chapter1601.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/django-step-by-step/chapter1601.png -------------------------------------------------------------------------------- /django-step-by-step/chapter17.md: -------------------------------------------------------------------------------- 1 | # Django Step by Step (十七) 2 | 3 | ## 1 引言 4 | 5 | 经过前面许多讲之后,我想大家应该对 [Django](https://www.djangoproject.com/) 的基本开发概念和过程已经有所了解。那么是时候讲一些关于设计方面的东西。首先要声明,目前 Django 基本上还没有什么设计的教程,而我也只能写一些个人体会。 6 | 7 | 那么这篇教程的体会就是:View, Template and Templatetag 8 | 9 | ## 2 View, Temaplte 和 Tag 之间的关系 10 | 11 | View 在 Django 中是用来处理请求的,一个 url 请求上来后经过 Django 的处理首先找到这个 url pattern 对应的 View 模块的某个方法。因此 View 是处理请求的起点,同时,在 View 中的方法需要返回,因此它还是一个请求的终点。因此象 Template 和 Tag 只不过是处理中的某些环节。 View 可处理的范围远大于 Template 而 Tag 则只能用在 Template 中。因此从使用范围上说:View > Template > Tag。 12 | 13 | Template 是用来输出内容的,目前在 Django 中你可以用它输出文本之类的东西。但象图片之类的非文本的东西,则只能通过 View 来实现,再有如果想在输出时加入一些特殊的 HttpHeader 的控制也只能在 View 中实现。当然,在大多数情况下我们只处理动态的文本生成,其它许多东西都是静态的。象图片之类的可以通过链接来引用。 14 | 15 | Tag 是在 Template 中被使用的。它的作用很多,如控制模板逻辑,还可以输出内容并做转换等。Tag 可以自定义,因此你可以在 Tag 中做几乎你想做的有关内容输出的任何事,如从数据库中取出内容,然后加工,输出,许多事情。 16 | 17 | 在 Django 中提供了一种方便的方法,可以直接将 url 与模板相对应起来。但并不是说你不需要 View 的参与,而是这个 View 的功能是预先写好的,它的作用很简单,就是在 View 方法中直接渲染一个模板并输出。因此说,看上去好象是直接对应,但实际上还是有 View 的处理。比如: 18 | 19 | ```Python 20 | (r'^ajax/$', 'django.views.generic.simple.direct_to_template', 21 | {'template': 'ajax/ajax.html'}), 22 | ``` 23 | 24 | 这是在讲 Ajax 的一个 url 的配置,其中使用了 django.views.generic.simple.direct_to_template 这个做好的 View 方法。 25 | 26 | ## 3 如何设计 27 | 28 | 从上面的分析我们可以看出, View, Template, Tag 功能不尽相同,但的确有部分功能的重叠,特别是在文本信息的输出。 29 | 30 | 如何比较好的选择使用什么来输出呢? 31 | 32 | 可以从几下以方面考虑: 33 | 34 | 1. 输出内容 35 | 36 | HTML或文本内容,可以考虑使用 View + Template + Tag ,其它的考虑使用 View 37 | 38 | 2. 输出范围 39 | 40 | 如果是多数据源,比如一个首页,可能包含许多不同的内容,如个人信息统计,Blog展示,日历,相关的链接,分类等,这些信息类型不同,如何比较好的处理呢?可以以 View 为主,即数据在 View 中提供,在模板中考虑输出和布局。但有一个问题,重用不方便。因此采用 Tag 可能更适合。因此对于单一或简单数据源可以只采用 View 和 Template 来实现,而对于多数据源可以采用使用 Temaplate 控制布局,Tag 用来输出单数据源信息。 41 | 42 | 同时对于多数据源的信息还可以考虑使用 Ajax 技术动态的将信息结合在一起。但使用 Ajax 则需要动态与后台交互,将单数据源的信息组织在一起,这样每个来源都是一个 View 的处理。不过这个有些复杂,这里我们不去考虑它。 43 | 44 | 因此当你设计结构时,首先考虑实现的内容,是文本的,则可以考虑使用 View, Template 和 Tag。 45 | 46 | 然后再看是否有重用的需要,有的话,将可重用的部分使用 Tag 来实现,而 View 和 Template 作布局和控制。 47 | 48 | ## 4 结论 49 | 50 | 这里我想到一个问题:我一直想使用 Admin 作为我的数据管理的界面。但经过上面的分析,Admin 目前大多数情况下只处理单一数据表,有些包含关系的,比如一对一,多对一,多对多的可以在一个编辑页面中同时处理多个表的记录,但它还是有可能无法满足复杂的多数据源的表现和编辑问题。因此 Admin 应该可以认为是一个缺省的数据库管理界面,而不完全是一个用户管理界面。因此大多数情况下,你仍然需要自定义管理界面,而不能完全依靠 Admin 。除非你的应用简单,同时对于管理界面的要求不高。 51 | 52 | 解决了这个问题,于是我们不必太留恋 Admin 的功能,我相信会有一些好的解决方案来满足我们的要求,或者就是我们自已来创建这样的项目。 53 | -------------------------------------------------------------------------------- /django-step-by-step/index.md: -------------------------------------------------------------------------------- 1 | # Django Step By Step 2 | -------------------------------------------------- 3 | 4 | ## 目录 5 | 6 | ### [第一讲 对于django望而生畏的人,有兴趣看一看,程序如何从简单到复杂](chapter01.md) 7 | ### [第二讲 生成一个web form用来做加法的简单例子](chapter02.md) 8 | ### [第三讲 使用Template的简单例子](chapter03.md) 9 | ### [第四讲 生成csv格式文件](chapter04.md) 10 | ### [第五讲 session的示例,开始进入数据库的世界](chapter05.md) 11 | ### [第六讲 一个wiki的例子](chapter06.md) 12 | ### [第七讲 一个通讯录的例子](chapter07.md) 13 | ### [第八讲 为通讯录增加文件导入和导出功能](chapter08.md) 14 | ### [第九讲 通讯录的美化,使用嵌套模板,静态文件,分页处理等](chapter09.md) 15 | ### [第十讲 扩展django的模板,自定义filter,进一步美化](chapter10.md) 16 | ### [第十一讲 用户管理和使用authentication来限制用户的行为](chapter11.md) 17 | ### [第十二讲 搜索功能的实现和Apache上的部署体验](chapter12.md) 18 | ### [第十三讲 简单的Ajax的实现(一),MochiKit的一些使用](chapter13.md) 19 | ### [第十四讲 简单的Ajax的实现(二),使用SimpleJson来交换数据](chapter14.md) 20 | ### [第十五讲 i18n 的一个简单实现](chapter15.md) 21 | ### [第十六讲 自定义 Calendar Tag](chapter16.md) 22 | ### [第十七讲 View, Template, Tag之间的关系](chapter17.md) 23 | 24 | -------------------------------------------------- 25 | 26 | ### 继续阅读[第一讲](chapter01.md) -------------------------------------------------------------------------------- /django-tips/python_rules.md: -------------------------------------------------------------------------------- 1 | # Python开发规范 -------------------------------------------------------------------------------- /hello.py: -------------------------------------------------------------------------------- 1 | i = 5 2 | print(i) 3 | i = i + 5 4 | print(i) -------------------------------------------------------------------------------- /introduction/env.md: -------------------------------------------------------------------------------- 1 | # 搭建Python开发环境 2 | --- 3 | 4 | ## 选择哪个版本 5 | 6 | 当前Python最高版本是3.12,Django最高版本是5.0。 7 | 8 | Python和Django的版本号,都遵循[语义化版本规范](https://semver.org/lang/zh-CN/),也就是`主版本号.次版本号.修订号` 9 | 10 | 本文选择的Python版本是3.9,Django版本是4.2。 11 | 12 | ## 安装Python 3.9 13 | 14 | 对于 Python 的初学者,建议从 Microsoft Store 安装 Python。 15 | 16 | 转到“开始” 菜单(左下方 Windows 图标),输入“Microsoft Store”,选择用于打开应用商店的链接。 17 | 18 | 应用商店打开后,从右上方菜单中选择“搜索”,然后输入“Python”。 在“应用”下,从结果中选择要使用的 Python 3.9 版本。 确定要安装的版本后,请选择“获取”。 19 | 20 | Python 完成下载和安装过程后,使用“开始”菜单(左下方 Windows 图标)打开 Windows PowerShell。 PowerShell 打开后,输入 `python --version` 以确认已在计算机上安装了 Python3。 21 | 22 | ```shell 23 | PS C:\> python --version 24 | Python 3.9.13 25 | PS C:\> 26 | ``` 27 | Python 的 Microsoft Store 安装包括 pip(标准包管理器)。 通过 pip 可以安装和管理不属于 Python 标准库的其他包。 若要确认还可使用 pip 安装和管理包,请输入 `pip --version`。 28 | 29 | ```shell 30 | PS C:\> pip --version 31 | pip 23.2.1 from C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2032.0_x64__qbz5n2kfra8p0\Lib\site-packages\pip (python 3.9) 32 | PS C:\> 33 | ``` 34 | 35 | 在PowerShell中输入`python`,然后就进入到Python交互模式,它的提示符是`>>>` 36 | 37 | 如果你看到下面的提示,那么说明你已经安装成功了。 38 | 39 | ![](./installpython.png) 40 | 41 | Python环境搭好之后我们写下第一行代码,输出一个"hello world!": 42 | 43 | ```python 44 | print('hello world!') 45 | ``` 46 | 47 | 你会看到在终端输出了`hello world!`的字符串 48 | 49 | ![](./hello-world.png) 50 | 51 | 恭喜你,完成安装! 52 | 53 | ## 安装virtualenv 54 | 55 | Python程序在运行过程中可能会使用到很多第三方的包(package)。 56 | 57 | 每个Python程序使用的第三方包都不太一样,那么我们需要为每一个Python程序创建一个独立的Python运行环境。 58 | 59 | virtualenv就是用来为每一个Python程序创建一套“隔离”的Python运行环境。virtualenv可以建立多个独立的虚拟环境,各个环境中拥有自己的python解释器和各自的package包,互不影响。Python 3.11已经自带了virtualenv。 60 | 61 | 62 | 用`python -m venv .venv`就可以创建一个名为`.venv`的虚拟环境了,进入这个虚拟环境后,再使用pip install安装其它的package就只会安装到这个虚拟环境里,不会影响其它虚拟环境或系统环境。 63 | 64 | 接下来我们要用这个工具创建我们自己的开发环境。 65 | 66 | ## 安装Django 4.2 67 | 68 | 为了能够使用Django的命令行,我们把Django安装到Windows系统的环境中,在命令行模式中输入: 69 | 70 | ```shell 71 | C:\>pip instal django -i https://pypi.tuna.tsinghua.edu.cn/simple 72 | ``` 73 | 就可以完成安装。其中`-i https://pypi.tuna.tsinghua.edu.cn/simple`是安装源,可以替换成你喜欢的其他源,比如豆瓣源:`-i https://pypi.doubanio.com/simple` 74 | 75 | 76 | ## 创建第一个Django项目 77 | 78 | 为了能够统一地管理Django项目的代码等信息,我们将代码和virtualenv环境都放在同一个目录中,这样无论这个目录拷贝到哪里,都可以直接加载环境之后开始运行,首先输入下面的命令创建第一个django项目: 79 | 80 | ```shell 81 | C:\>django-admin startproject helloworld 82 | C:\>cd helloworld 83 | ``` 84 | 85 | 然后我们在helloworld目录下面创建一个venv的目录,作为这个Django项目的虚拟环境,之后我们在这个虚拟环境中安装Django 86 | 87 | ```shell 88 | C:\helloworld\>python -m venv .venv 89 | C:\helloworld\>.\.venv\scripts\activate 90 | (.venv) C:\helloworld\>pip install django -i https://pypi.tuna.tsinghua.edu.cn/simple 91 | ``` 92 | 93 | 这个时候你应该可以看到提示符前面增加了“(.venv)”的字样,如下所示: 94 | 95 | ![](./virtualenv.png) 96 | 97 | 这个命令行模式就已经处于`.venv`这个虚拟的Python环境下面了,如果你再输入`python`命令的时候,就会使用这个虚拟环境下面的Python。请注意,下面的教程我们都是在这个环境下面运行的。 98 | 99 | 下面让我们运行一下我们的第一个Django项目。如果你处于Python交互模式,使用`exit()`命令可以退出到命令行模式。在命令行模式下面输入: 100 | 101 | ```shell 102 | (.venv) C:\helloworld\>python manage.py runserver 103 | ``` 104 | 105 | 然后我们使用浏览器打开这个地址[http://localhost:8000/](http://localhost:8000/)就可以看到一个欢迎页面了。 106 | 107 | ![](./first_django.png) 108 | 109 | 在命令行下面使用`Ctrl+C`可以退出这个Django项目。 110 | 111 | ## 安装Visual Studio Code作为Python IDE 112 | 113 | 访问下载Visual Studio Code(简称VS Code)客户端,然后安装。打开后会看到如下的界面。 114 | ![](./vscode1.png) 115 | 116 | 点击箭头所指的`扩展`按钮,输入`@popular`,就会显示最流行的扩展清单,选择Python扩展进行安装。 117 | 118 | ![](./home-screenshot-win.png) 119 | 120 | 由于我们的virtualenv目录会放到Python项目的venv子目录下,所以我们要对Python扩展进行一点设置。在VS Code菜单中选择“文件”->“首选项”->“设置”,就会打开用户设置的文件`settings.json`,将`Python: Default Interpreter Path`修改为我们的Python环境路径: 121 | 122 | ``` 123 | ${workspaceRoot}/.venv/scripts/python.exe 124 | ``` 125 | 126 | ## 使用VS Code打开helloworld 127 | 128 | 在helloworld目录下输入: 129 | 130 | ``` 131 | (.venv) C:\helloworld\>code . 132 | ``` 133 | 134 | 即可使用VS Code打开helloworld项目。VS Code没有项目描述文件,一个目录就是一个项目,后面的例子我们都用这个开发工具完成。 135 | 136 | ## 完结! 137 | 138 | -------------------------------------------------- 139 | 140 | ### 继续阅读[简明Python教程](../a-byte-of-python3/index.md) -------------------------------------------------------------------------------- /introduction/first_django.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/introduction/first_django.png -------------------------------------------------------------------------------- /introduction/hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/introduction/hello-world.png -------------------------------------------------------------------------------- /introduction/home-screenshot-win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/introduction/home-screenshot-win.png -------------------------------------------------------------------------------- /introduction/installpython.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/introduction/installpython.png -------------------------------------------------------------------------------- /introduction/using_python.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/introduction/using_python.jpg -------------------------------------------------------------------------------- /introduction/virtualenv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/introduction/virtualenv.png -------------------------------------------------------------------------------- /introduction/vscode1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/introduction/vscode1.png -------------------------------------------------------------------------------- /封面.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/borisliu/from-python-to-django/8c89714cf54d30f92d33cd973128921ac0017c03/封面.png --------------------------------------------------------------------------------