对于纯新手来说,学习python 3.x 有哪些书籍有什么书值得一看看

Python3基础学习笔记(精品)_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
Python3基础学习笔记(精品)
&&Python 3.4基础学习笔记,系统全面的介绍Python的基本语法和高级特性,从基本数据类型、运算符、流程控制、函数、文件IO到模块、异常处理和面向对象,并且附上了很多经过测试的代码帮助读者去理解。适合于Python初学者快速入门,相信如果你看完这个学习笔记,你会对Python有一个整体的了解。
阅读已结束,下载文档到电脑
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,方便使用
还剩50页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢Python简介
Python是著名的“龟叔”Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言。
现在,全世界差不多有600多种编程语言,但流行的编程语言也就那么20来种。如果你听说过TIOBE排行榜,你就能知道编程语言的大致流行程度。这是最近10年最常用的10种编程语言的变化图:
总的来说,这几种编程语言各有千秋。C语言是可以用来编写操作系统的贴近硬件的语言,所以,C语言适合开发那些追求运行速度、充分发挥硬件性能的程序。而Python是用来编写应用程序的高级编程语言。
当你用一种语言开始作真正的软件开发时,你除了编写代码外,还需要很多基本的已经写好的现成的东西,来帮助你加快开发进度。比如说,要编写一个电子邮件客户端,如果先从最底层开始编写网络协议相关的代码,那估计一年半载也开发不出来。高级编程语言通常都会提供一个比较完善的基础代码库,让你能直接调用,比如,针对电子邮件协议的SMTP库,针对桌面环境的GUI库,在这些已有的代码库的基础上开发,一个电子邮件客户端几天就能开发出来。
Python就为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容,被形象地称作“内置电池(batteries included)”。用Python开发,许多功能不必从零编写,直接使用现成的即可。
除了内置的库外,Python还有大量的第三方库,也就是别人开发的,供你直接使用的东西。当然,如果你开发的代码通过很好的封装,也可以作为第三方库给别人使用。
许多大型网站就是用Python开发的,例如YouTube、,还有国内的。很多大公司,包括Google、Yahoo等,甚至(美国航空航天局)都大量地使用Python。
龟叔给Python的定位是“优雅”、“明确”、“简单”,所以Python程序看上去总是简单易懂,初学者学Python,不但入门容易,而且将来深入下去,可以编写那些非常非常复杂的程序。
总的来说,Python的哲学就是简单优雅,尽量写容易看明白的代码,尽量写少的代码。如果一个资深程序员向你炫耀他写的晦涩难懂、动不动就几万行的代码,你可以尽情地嘲笑他。
那Python适合开发哪些类型的应用呢?
首选是网络应用,包括网站、后台服务等等;
其次是许多日常需要的小工具,包括系统管理员需要的脚本任务等等;
另外就是把其他语言开发的程序再包装起来,方便使用。
最后说说Python的缺点。
任何编程语言都有缺点,Python也不例外。优点说过了,那Python有哪些缺点呢?
第一个缺点就是运行速度慢,和C程序相比非常慢,因为Python是解释型语言,你的代码在执行时会一行一行地翻译成CPU能理解的机器码,这个翻译过程非常耗时,所以很慢。而C程序是运行前直接编译成CPU能执行的机器码,所以非常快。
但是大量的应用程序不需要这么快的运行速度,因为用户根本感觉不出来。例如开发一个下载MP3的网络应用程序,C程序的运行时间需要0.001秒,而Python程序的运行时间需要0.1秒,慢了100倍,但由于网络更慢,需要等待1秒,你想,用户能感觉到1.001秒和1.1秒的区别吗?这就好比F1赛车和普通的出租车在北京三环路上行驶的道理一样,虽然F1赛车理论时速高达400公里,但由于三环路堵车的时速只有20公里,因此,作为乘客,你感觉的时速永远是20公里。
第二个缺点就是代码不能加密。如果要发布你的Python程序,实际上就是发布源代码,这一点跟C语言不同,C语言不用发布源代码,只需要把编译后的机器码(也就是你在Windows上常见的xxx.exe文件)发布出去。要从机器码反推出C代码是不可能的,所以,凡是编译型的语言,都没有这个问题,而解释型的语言,则必须把源码发布出去。
这个缺点仅限于你要编写的软件需要卖给别人挣钱的时候。好消息是目前的互联网时代,靠卖软件授权的商业模式越来越少了,靠网站和移动应用卖服务的模式越来越多了,后一种模式不需要把源码给别人。
再说了,现在如火如荼的开源运动和互联网自由开放的精神是一致的,互联网上有无数非常优秀的像Linux一样的开源代码,我们千万不要高估自己写的代码真的有非常大的“商业价值”。那些大公司的代码不愿意开放的更重要的原因是代码写得太烂了,一旦开源,就没人敢用他们的产品了。
当然,Python还有其他若干小缺点,请自行忽略,就不一一列举了。
安装Python
因为Python是跨平台的,它可以运行在Windows、Mac和各种Linux/Unix系统上。在Windows上写Python程序,放到Linux上也是能够运行的。
要开始学习Python编程,首先就得把Python安装到你的电脑里。安装后,你会得到Python解释器(就是负责运行Python程序的),一个命令行交互环境,还有一个简单的集成开发环境。
安装Python 3.5
目前,Python有两个版本,一个是2.x版,一个是3.x版,这两个版本是不兼容的。由于3.x版越来越普及,我们的教程将以最新的Python 3.5版本为基础。请确保你的电脑上安装的Python版本是最新的3.5.x,这样,你才能无痛学习这个教程。
在Mac上安装Python
如果你正在使用Mac,系统是OS X 10.8~10.10,那么系统自带的Python版本是2.7。要安装最新的Python 3.5,有两个方法:
方法一:从Python官网下载Python 3.5的(网速慢的同学请移步),双击运行并安装;
方法二:如果安装了Homebrew,直接通过命令brew install python3安装即可。
在Linux上安装Python
如果你正在使用Linux,那我可以假定你有Linux系统管理经验,自行安装Python 3应该没有问题,否则,请换回Windows系统。
对于大量的目前仍在使用Windows的同学,如果短期内没有打算换Mac,就可以继续阅读以下内容。
在Windows上安装Python
首先,根据你的Windows版本(64位还是32位)从Python的官方网站下载Python 3.5对应的或(网速慢的同学请移步),然后,运行下载的EXE安装包:
特别要注意勾上Add Python 3.5 to PATH,然后点“Install Now”即可完成安装。
默认会安装到C:\Python35目录下,然后打开命令提示符窗口,敲入python后,会出现两种情况:
看到上面的画面,就说明Python安装成功!
你看到提示符&&&就表示我们已经在Python交互式环境中了,可以输入任何Python代码,回车后会立刻得到执行结果。现在,输入exit()并回车,就可以退出Python交互式环境(直接关掉命令行窗口也可以)。
情况二:得到一个错误:
‘python’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
这是因为Windows会根据一个Path的环境变量设定的路径去查找python.exe,如果没找到,就会报错。如果在安装时漏掉了勾选Add Python 3.5 to PATH,那就要手动把python.exe所在的路径添加到Path中。
如果你不知道怎么修改环境变量,建议把Python安装程序重新运行一遍,务必记得勾上Add Python 3.5 to PATH。
学会如何把Python安装到计算机中,并且熟练打开和退出Python交互式环境。
在Windows上运行Python时,请先启动命令行,然后运行python。
在Mac和Linux上运行Python时,请打开终端,然后运行python3。
Python解释器
当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。
由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种Python解释器。
当我们从下载并安装好Python 3.5后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。
CPython是使用最广的Python解释器。教程的所有代码也都在CPython下执行。
IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。
CPython用&&&作为提示符,而IPython用In [序号]:作为提示符。
PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。
绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解。
Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。
IronPython
IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。
Python的解释器很多,但使用最广泛的还是CPython。如果要和Java或.Net平台交互,最好的办法不是用Jython或IronPython,而是通过网络调用来交互,确保各程序之间的独立性。
本教程的所有代码只确保在CPython 3.5版本下运行。请务必在本地安装CPython(也就是从Python官方网站下载的安装程序)。
第一个Python程序
现在,了解了如何启动和退出Python的交互式环境,我们就可以正式开始编写Python代码了。
在写代码之前,请千万不要用“复制”-“粘贴”把代码从页面粘贴到你自己的电脑上。写程序也讲究一个感觉,你需要一个字母一个字母地把代码自己敲进去,在敲代码的过程中,初学者经常会敲错代码,所以,你需要仔细地检查、对照,才能以最快的速度掌握如何写程序。
在交互式环境的提示符&&&下,直接输入代码,按回车,就可以立刻得到代码执行结果。现在,试试输入100+200,看看计算结果是不是300:
&&& 100+200
很简单吧,任何有效的数学计算都可以算出来。
如果要让Python打印出指定的文字,可以用print()函数,然后把希望打印的文字用单引号或者双引号括起来,但不能混用单引号和双引号:
&&& print('hello, world')
hello, world
这种用单引号或者双引号括起来的文本在程序中叫字符串,今后我们还会经常遇到。
最后,用exit()退出Python,我们的第一个Python程序完成!唯一的缺憾是没有保存下来,下次运行时还要再输入一遍代码。
在Python交互式命令行下,可以直接输入代码,然后执行,并立刻得到结果。
使用文本编辑器
在Python的交互式命令行写程序,好处是一下就能得到结果,坏处是没法保存,下次还想运行的时候,还得再敲一遍。
所以,实际开发的时候,我们总是使用一个文本编辑器来写代码,写完了,保存为一个文件,这样,程序就可以反复运行了。
现在,我们就把上次的'hello, world'程序用文本编辑器写出来,保存下来。
那么问题来了:文本编辑器到底哪家强?
推荐两款文本编辑器:
一个是,免费使用,但是不付费会弹出提示框:
一个是,免费使用,有中文界面:
请注意,用哪个都行,但是绝对不能用Word和Windows自带的记事本。Word保存的不是纯文本文件,而记事本会自作聪明地在文件开始的地方加上几个特殊字符(UTF-8 BOM),结果会导致程序运行出现莫名其妙的错误。
安装好文本编辑器后,输入以下代码:
print('hello, world')
注意print前面不要有任何空格。然后,选择一个目录,例如C:\work,把文件保存为hello.py,就可以打开命令行窗口,把当前目录切换到hello.py所在目录,就可以运行这个程序了:
C:\work&python hello.py
hello, world
也可以保存为别的名字,比如first.py,但是必须要以.py结尾,其他的都不行。此外,文件名只能是英文字母、数字和下划线的组合。
如果当前目录下没有hello.py这个文件,运行python hello.py就会报错:
C:\Users\IEUser&python hello.py
python: can't open file 'hello.py': [Errno 2] No such file or directory
报错的意思就是,无法打开hello.py这个文件,因为文件不存在。这个时候,就要检查一下当前目录下是否有这个文件了。如果hello.py存放在另外一个目录下,要首先用cd命令切换当前目录:
命令行模式和Python交互模式
请注意区分命令行模式和Python交互模式。
看到类似C:\&是在Windows提供的命令行模式:
在命令行模式下,可以执行python进入Python交互式环境,也可以执行python hello.py运行一个.py文件。
看到&&&是在Python交互式环境下:
在Python交互式环境下,只能输入Python代码并立刻执行。
此外,在命令行模式运行.py文件和在Python交互式环境下直接运行Python代码有所不同。Python交互式环境会把每一行Python代码的结果自动打印出来,但是,直接运行Python代码却不会。
例如,在Python交互式环境下,输入:
&&& 100 + 200 + 300
直接可以看到结果600。
但是,写一个calc.py的文件,内容如下:
100 + 200 + 300
然后在命令行模式下执行:
C:\work&python calc.py
发现什么输出都没有。
这是正常的。想要输出结果,必须自己用print()打印出来。把calc.py改造一下:
print(100 + 200 + 300)
再执行,就可以看到结果:
C:\work&python calc.py
直接运行py文件
还有同学问,能不能像.exe文件那样直接运行.py文件呢?在Windows上是不行的,但是,在Mac和Linux上是可以的,方法是在.py文件的第一行加上一个特殊的注释:
#!/usr/bin/env python3
print('hello, world')
然后,通过命令给hello.py以执行权限:
$ chmod a+x hello.py
就可以直接运行hello.py了,比如在Mac下运行:
用文本编辑器写Python程序,然后保存为后缀为.py的文件,就可以用Python直接运行这个程序了。
Python的交互模式和直接运行.py文件有什么区别呢?
直接输入python进入交互模式,相当于启动了Python解释器,但是等待你一行一行地输入源代码,每输入一行就执行一行。
直接运行.py文件相当于启动了Python解释器,然后一次性把.py文件的源代码给执行了,你是没有机会以交互的方式输入源代码的。
用Python开发程序,完全可以一边在文本编辑器里写代码,一边开一个交互式命令窗口,在写代码的过程中,把部分代码粘到命令行去验证,事半功倍!前提是得有个27'的超大显示器!
Python代码运行助手
Python代码运行助手可以让你在线输入Python代码,然后通过本机运行的一个Python脚本来执行代码。原理如下:
在网页输入代码:
点击Run按钮,代码被发送到本机正在运行的Python代码运行助手;
Python代码运行助手将代码保存为临时文件,然后调用Python解释器执行代码;
网页显示代码执行结果:
点击右键,目标另存为:
备用下载地址:
在存放learning.py的目录下运行命令:
C:\Users\michael\Downloads& python learning.py
如果看到Ready for Python code on port 39093...表示运行成功,不要关闭命令行窗口,最小化放到后台运行即可:
需要支持HTML5的浏览器:
IE &= 9FirefoxChromeSarafi
# 测试代码:
print('Hello, world')
输入和输出
用print()在括号中加上字符串,就可以向屏幕上输出指定的文字。比如输出'hello, world',用代码实现如下:
&&& print('hello, world')
print()函数也可以接受多个字符串,用逗号“,”隔开,就可以连成一串输出:
&&& print('The quick brown fox', 'jumps over', 'the lazy dog')
The quick brown fox jumps over the lazy dog
print()会依次打印每个字符串,遇到逗号“,”会输出一个空格,因此,输出的字符串是这样拼起来的:
print()也可以打印整数,或者计算结果:
&&& print(300)
&&& print(100 + 200)
因此,我们可以把计算100 + 200的结果打印得更漂亮一点:
&&& print('100 + 200 =', 100 + 200)
100 + 200 = 300
注意,对于100 + 200,Python解释器自动计算出结果300,但是,'100 + 200 ='是字符串而非数学公式,Python把它视为字符串,请自行解释上述打印结果。
现在,你已经可以用print()输出你想要的结果了。但是,如果要让用户从电脑输入一些字符怎么办?Python提供了一个input(),可以让用户输入字符串,并存放到一个变量里。比如输入用户的名字:
&&& name = input()
当你输入name = input()并按下回车后,Python交互式命令行就在等待你的输入了。这时,你可以输入任意字符,然后按回车后完成输入。
输入完成后,不会有任何提示,Python交互式命令行又回到&&&状态了。那我们刚才输入的内容到哪去了?答案是存放到name变量里了。可以直接输入name查看变量内容:
什么是变量?请回忆初中数学所学的代数基础知识:
设正方形的边长为a,则正方形的面积为a x a。把边长a看做一个变量,我们就可以根据a的值计算正方形的面积,比如:
若a=2,则面积为a x a = 2 x 2 = 4;
若a=3.5,则面积为a x a = 3.5 x 3.5 = 12.25。
在计算机程序中,变量不仅可以为整数或浮点数,还可以是字符串,因此,name作为一个变量就是一个字符串。
要打印出name变量的内容,除了直接写name然后按回车外,还可以用print()函数:
&&& print(name)
有了输入和输出,我们就可以把上次打印'hello, world'的程序改成有点意义的程序了:
name = input()
print('hello,', name)
运行上面的程序,第一行代码会让用户输入任意字符作为自己的名字,然后存入name变量中;第二行代码会根据用户的名字向用户说hello,比如输入Michael:
C:\Workspace& python hello.py
hello, Michael
但是程序运行的时候,没有任何提示信息告诉用户:“嘿,赶紧输入你的名字”,这样显得很不友好。幸好,input()可以让你显示一个字符串来提示用户,于是我们把代码改成:
name = input('please enter your name: ')
print('hello,', name)
再次运行这个程序,你会发现,程序一运行,会首先打印出please enter your name:,这样,用户就可以根据提示,输入名字后,得到hello, xxx的输出:
C:\Workspace& python hello.py
please enter your name: Michael
hello, Michael
每次运行该程序,根据用户输入的不同,输出结果也会不同。
在命令行下,输入和输出就是这么简单。
任何计算机程序都是为了执行一个特定的任务,有了输入,用户才能告诉计算机程序所需的信息,有了输出,程序运行后才能告诉用户任务的结果。
输入是Input,输出是Output,因此,我们把输入输出统称为Input/Output,或者简写为IO。
input()和print()是在命令行下面最基本的输入和输出,但是,用户也可以通过其他更高级的图形界面完成输入和输出,比如,在网页上的一个文本框输入自己的名字,点击“确定”后在网页上看到输出信息。
请利用print()输出1024 * 768 = xxx:
# -*- coding: utf-8 -*-
print(???)
Python基础
Python是一种计算机编程语言。计算机编程语言和我们日常使用的自然语言有所不同,最大的区别就是,自然语言在不同的语境下有不同的理解,而计算机要根据编程语言执行任务,就必须保证编程语言写出的程序决不能有歧义,所以,任何一种编程语言都有自己的一套语法,编译器或者解释器就是负责把符合语法的程序代码转换成CPU能够执行的机器码,然后执行。Python也不例外。
Python的语法比较简单,采用缩进方式,写出来的代码就像下面的样子:
# print absolute value of an integer:
if a &= 0:
以#开头的语句是注释,注释是给人看的,可以是任意内容,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。
缩进有利有弊。好处是强迫你写出格式化的代码,但没有规定缩进是几个空格还是Tab。按照约定俗成的管理,应该始终坚持使用4个空格的缩进。
缩进的另一个好处是强迫你写出缩进较少的代码,你会倾向于把一段很长的代码拆分成若干函数,从而得到缩进较少的代码。
缩进的坏处就是“复制-粘贴”功能失效了,这是最坑爹的地方。当你重构代码时,粘贴过去的代码必须重新检查缩进是否正确。此外,IDE很难像格式化Java代码那样格式化Python代码。
最后,请务必注意,Python程序是大小写敏感的,如果写错了大小写,程序会报错。
Python使用缩进来组织代码块,请务必遵守约定俗成的习惯,坚持使用4个空格的缩进。
在文本编辑器中,需要设置把Tab自动转换为4个空格,确保不混用Tab和空格。
数据类型和变量
计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值。但是,计算机能处理的远不止数值,还可以处理文本、图形、音频、视频、网页等各种各样的数据,不同的数据,需要定义不同的数据类型。在Python中,能够直接处理的数据类型有以下几种:
Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等。
计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用0x前缀和0-9,a-f表示,例如:0xff00,0xa5b4c3d2,等等。
浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,比如,1.23x109和12.3x108是完全相等的。浮点数可以用数学写法,如1.23,3.14,-9.01,等等。但是对于很大或很小的浮点数,就必须用科学计数法表示,把10用e替代,1.23x109就是1.23e9,或者12.3e8,0.000012可以写成1.2e-5,等等。
整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。
字符串是以单引号'或双引号&括起来的任意文本,比如'abc',&xyz&等等。请注意,''或&&本身只是一种表示方式,不是字符串的一部分,因此,字符串'abc'只有a,b,c这3个字符。如果'本身也是一个字符,那就可以用&&括起来,比如&I'm
OK&包含的字符是I,',m,空格,O,K这6个字符。
如果字符串内部既包含'又包含&怎么办?可以用转义字符\来标识,比如:
'I\'m \&OK\&!'
表示的字符串内容是:
转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\,可以在Python的交互式命令行用print()打印字符串看看:
&&& print('I\'m ok.')
&&& print('I\'m learning\nPython.')
I'm learning
&&& print('\\\n\\')
如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r''表示''内部的字符串默认不转义,可以自己试试:
&&& print('\\\t\\')
&&& print(r'\\\t\\')
如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容,可以自己试试:
&&& print('''line1
... line3''')
上面是在交互式命令行内输入,注意在输入多行内容时,提示符由&&&变为...,提示你可以接着上一行输入。如果写成程序,就是:
print('''line1
多行字符串'''...'''还可以在前面加上r使用,请自行测试。
布尔值
布尔值和布尔代数的表示完全一致,一个布尔值只有True、False两种值,要么是True,要么是False,在Python中,可以直接用True、False表示布尔值(请注意大小写),也可以通过布尔运算计算出来:
布尔值可以用and、or和not运算。
and运算是与运算,只有所有都为True,and运算结果才是True:
&&& True and True
&&& True and False
&&& False and False
&&& 5 & 3 and 3 & 1
or运算是或运算,只要其中有一个为True,or运算结果就是True:
&&& True or True
&&& True or False
&&& False or False
&&& 5 & 3 or 1 & 3
not运算是非运算,它是一个单目运算符,把True变成False,False变成True:
&&& not True
&&& not False
&&& not 1 & 2
布尔值经常用在条件判断中,比如:
if age &= 18:
print('adult')
print('teenager')
空值
空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
此外,Python还提供了列表、字典等多种数据类型,还允许创建自定义数据类型,我们后面会继续讲到。
变量的概念基本上和初中代数的方程变量是一致的,只是在计算机程序中,变量不仅可以是数字,还可以是任意数据类型。
变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和_的组合,且不能用数字开头,比如:
变量a是一个整数。
t_007 = 'T007'
变量t_007是一个字符串。
Answer = True
变量Answer是一个布尔值True。
在Python中,等号=是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,例如:
a = 123 # a是整数
a = 'ABC' # a变为字符串
这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。例如Java是静态语言,赋值语句如下(// 表示注释):
int a = 123; // a是整数类型变量
a = &ABC&; // 错误:不能把字符串赋给整型变量
和静态语言相比,动态语言更灵活,就是这个原因。
请不要把赋值语句的等号等同于数学的等号。比如下面的代码:
x = x + 2
如果从数学上理解x = x + 2那无论如何是不成立的,在程序中,赋值语句先计算右侧的表达式x + 2,得到结果12,再赋给变量x。由于x之前的值是10,重新赋值后,x的值变成12。
最后,理解变量在计算机内存中的表示也非常重要。当我们写:
时,Python解释器干了两件事情:
在内存中创建了一个'ABC'的字符串;
在内存中创建了一个名为a的变量,并把它指向'ABC'。
也可以把一个变量a赋值给另一个变量b,这个操作实际上是把变量b指向变量a所指向的数据,例如下面的代码:
最后一行打印出变量b的内容到底是'ABC'呢还是'XYZ'?如果从数学意义上理解,就会错误地得出b和a相同,也应该是'XYZ',但实际上b的值是'ABC',让我们一行一行地执行代码,就可以看到到底发生了什么事:
执行a = 'ABC',解释器创建了字符串'ABC'和变量a,并把a指向'ABC':
执行b = a,解释器创建了变量b,并把b指向a指向的字符串'ABC':
执行a = 'XYZ',解释器创建了字符串'XYZ',并把a的指向改为'XYZ',但b并没有更改:
所以,最后打印变量b的结果自然是'ABC'了。
所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量:
但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI的值,也没人能拦住你。
最后解释一下整数的除法为什么也是精确的。在Python中,有两种除法,一种除法是/:
&&& 10 / 3
/除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
还有一种除法是//,称为地板除,两个整数的除法仍然是整数:
&&& 10 // 3
你没有看错,整数的地板除//永远是整数,即使除不尽。要做精确的除法,使用/就可以。
因为//除法只取结果的整数部分,所以Python还提供一个余数运算,可以得到两个整数相除的余数:
&&& 10 % 3
无论整数做//除法还是取余数,结果永远是整数,所以,整数运算结果永远是精确的。
请打印出以下变量的值:
f = 456.789
s1 = 'Hello, world'
s2 = 'Hello, \'Adam\''
s3 = r'Hello, &Bart&'
s4 = r'''Hello,
Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来。
注意:Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java对32位整数的范围限制在--。
Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。
字符串和编码
我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题。
因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制=十进制255),如果要表示更大的整数,就必须用更多的字节。比如两个字节可以表示的最大整数是65535,4个字节可以表示的最大整数是。
由于计算机是美国人发明的,因此,最早只有127个字母被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122。
但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。
你可以想得到的是,全世界有上百种语言,日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。
因此,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
Unicode标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。
现在,捋一捋ASCII编码和Unicode编码的区别:ASCII编码是1个字节,而Unicode编码通常是2个字节。
字母A用ASCII编码是十进制的65,二进制的;
字符0用ASCII编码是十进制的48,二进制的,注意字符'0'和整数0是不同的;
汉字中已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01101。
你可以猜测,如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00001。
新的问题又出现了:如果统一成Unicode编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。
所以,本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间:
从上面的表格还可以发现,UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
搞清楚了ASCII、Unicode和UTF-8的关系,我们就可以总结一下现在计算机系统通用的字符编码工作方式:
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:
浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:
所以你看到很多网页的源码上会有类似&meta charset=&UTF-8& /&的信息,表示该网页正是用的UTF-8编码。
Python的字符串
搞清楚了令人头疼的字符编码问题后,我们再来研究Python的字符串。
在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言,例如:
&&& print('包含中文的str')
包含中文的str
对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
&&& ord('A')
&&& ord('中')
&&& chr(66)
&&& chr(25991)
如果知道字符的整数编码,还可以用十六进制这么写str:
&&& '\u4e2d\u6587'
两种写法完全是等价的。
由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。
Python对bytes类型的数据用带b前缀的单引号或双引号表示:
x = b'ABC'
要注意区分'ABC'和b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。
以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:
&&& 'ABC'.encode('ascii')
&&& '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
&&& '中文'.encode('ascii')
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。
在bytes中,无法显示为ASCII字符的字节,用\x##显示。
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:
&&& b'ABC'.decode('ascii')
&&& b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
要计算str包含多少个字符,可以用len()函数:
&&& len('ABC')
&&& len('中文')
len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数:
&&& len(b'ABC')
&&& len(b'\xe4\xb8\xad\xe6\x96\x87')
&&& len('中文'.encode('utf-8'))
可见,1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。
在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码:
如果.py文件本身使用UTF-8编码,并且也申明了# -*- coding: utf-8 -*-,打开命令提示符测试就可以正常显示中文:
格式化
最后一个常见的问题是如何输出格式化的字符串。我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'之类的字符串,而xxx的内容都是根据变量变化的,所以,需要一种简便的格式化字符串的方式。
在Python中,采用的格式化方式和C语言是一致的,用%实现,举例如下:
&&& 'Hello, %s' % 'world'
'Hello, world'
&&& 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
你可能猜到了,%运算符就是用来格式化字符串的。在字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略。
常见的占位符有:
十六进制整数
其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数:
&&& '%2d-%02d' % (3, 1)
&&& '%.2f' % 3.1415926
如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串:
&&& 'Age: %s. Gender: %s' % (25, True)
'Age: 25. Gender: True'
有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%来表示一个%:
&&& 'growth rate: %d %%' % 7
'growth rate: 7 %'
小明的成绩从去年的72分提升到了今年的85分,请计算小明成绩提升的百分点,并用字符串格式化显示出'xx.x%',只保留小数点后1位:
# -*- coding: utf-8 -*-
print('???' % r)
Python 3的字符串使用Unicode,直接支持多语言。
str和bytes互相转换时,需要指定编码。最常用的编码是UTF-8。Python当然也支持其他编码方式,比如把Unicode编码成GB2312:
&&& '中文'.encode('gb2312')
'\xd6\xd0\xce\xc4'
但这种方式纯属自找麻烦,如果没有特殊业务要求,请牢记仅使用UTF-8编码。
格式化字符串的时候,可以用Python的交互式命令行测试,方便快捷。
使用list和tuple
Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。
比如,列出班里所有同学的名字,就可以用一个list表示:
&&& classmates = ['Michael', 'Bob', 'Tracy']
&&& classmates
['Michael', 'Bob', 'Tracy']
变量classmates就是一个list。用len()函数可以获得list元素的个数:
&&& len(classmates)
用索引来访问list中每一个位置的元素,记得索引是从0开始的:
&&& classmates[0]
&&& classmates[1]
&&& classmates[2]
&&& classmates[3]
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
IndexError: list index out of range
当索引超出了范围时,Python会报一个IndexError错误,所以,要确保索引不要越界,记得最后一个元素的索引是len(classmates) - 1。
如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:
&&& classmates[-1]
以此类推,可以获取倒数第2个、倒数第3个:
&&& classmates[-2]
&&& classmates[-3]
&&& classmates[-4]
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
IndexError: list index out of range
当然,倒数第4个就越界了。
list是一个可变的有序表,所以,可以往list中追加元素到末尾:
&&& classmates.append('Adam')
&&& classmates
['Michael', 'Bob', 'Tracy', 'Adam']
也可以把元素插入到指定的位置,比如索引号为1的位置:
&&& classmates.insert(1, 'Jack')
&&& classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
要删除list末尾的元素,用pop()方法:
&&& classmates.pop()
&&& classmates
['Michael', 'Jack', 'Bob', 'Tracy']
要删除指定位置的元素,用pop(i)方法,其中i是索引位置:
&&& classmates.pop(1)
&&& classmates
['Michael', 'Bob', 'Tracy']
要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:
&&& classmates[1] = 'Sarah'
&&& classmates
['Michael', 'Sarah', 'Tracy']
list里面的元素的数据类型也可以不同,比如:
&&& L = ['Apple', 123, True]
list元素也可以是另一个list,比如:
&&& s = ['python', 'java', ['asp', 'php'], 'scheme']
&&& len(s)
要注意s只有4个元素,其中s[2]又是一个list,如果拆开写就更容易理解了:
&&& p = ['asp', 'php']
&&& s = ['python', 'java', p, 'scheme']
要拿到'php'可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。
如果一个list中一个元素也没有,就是一个空的list,它的长度为0:
&&& L = []
&&& len(L)
另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,比如同样是列出同学的名字:
&&& classmates = ('Michael', 'Bob', 'Tracy')
现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。
不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,比如:
&&& t = (1, 2)
如果要定义一个空的tuple,可以写成():
&&& t = ()
但是,要定义一个只有1个元素的tuple,如果你这么定义:
&&& t = (1)
定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。
所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:
&&& t = (1,)
Python在显示只有1个元素的tuple时,也会加一个逗号,,以免你误解成数学计算意义上的括号。
最后来看一个“可变的”tuple:
&&& t = ('a', 'b', ['A', 'B'])
&&& t[2][0] = 'X'
&&& t[2][1] = 'Y'
('a', 'b', ['X', 'Y'])
这个tuple定义的时候有3个元素,分别是'a','b'和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?
别急,我们先看看定义的时候tuple包含的3个元素:
当我们把list的元素'A'和'B'修改为'X'和'Y'后,tuple变为:
表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!
理解了“指向不变”后,要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变。
请用索引取出下面list的指定元素:
# -*- coding: utf-8 -*-
['Apple', 'Google', 'Microsoft'],
['Java', 'Python', 'Ruby', 'PHP'],
['Adam', 'Bart', 'Lisa']
# 打印Apple:
# 打印Python:
# 打印Lisa:
list和tuple是Python内置的有序集合,一个可变,一个不可变。根据需要来选择使用它们。
计算机之所以能做很多自动化的任务,因为它可以自己做条件判断。
比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:
if age &= 18:
print('your age is', age)
print('adult')
根据Python的缩进规则,如果if语句判断是True,就把缩进的两行print语句执行了,否则,什么也不做。
也可以给if添加一个else语句,意思是,如果if判断是False,不要执行if的内容,去把else执行了:
if age &= 18:
print('your age is', age)
print('adult')
print('your age is', age)
print('teenager')
注意不要少写了冒号:。
当然上面的判断是很粗略的,完全可以用elif做更细致的判断:
if age &= 18:
print('adult')
elif age &= 6:
print('teenager')
print('kid')
elif是else if的缩写,完全可以有多个elif,所以if语句的完整形式就是:
if &条件判断1&:
elif &条件判断2&:
elif &条件判断3&:
if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elif和else,所以,请测试并解释为什么下面的程序打印的是teenager:
if age &= 6:
print('teenager')
elif age &= 18:
print('adult')
print('kid')
if判断条件还可以简写,比如写:
print('True')
只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False。
再议 input
最后看一个有问题的条件判断。很多同学会用input()读取用户的输入,这样可以自己输入,程序运行得更有意思:
birth = input('birth: ')
if birth & 2000:
print('00前')
print('00后')
输入1982,结果报错:
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: unorderable types: str() & int()
这是因为input()返回的数据类型是str,str不能直接和整数比较,必须先把str转换成整数。Python提供了int()函数来完成这件事情:
s = input('birth: ')
birth = int(s)
if birth & 2000:
print('00前')
print('00后')
再次运行,就可以得到正确地结果。但是,如果输入abc呢?又会得到一个错误信息:
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
ValueError: invalid literal for int() with base 10: 'abc'
原来int()函数发现一个字符串并不是合法的数字时就会报错,程序就退出了。
如何检查并捕获程序运行期的错误呢?后面的错误和调试会讲到。
小明身高1.75,体重80.5kg。请根据BMI公式(体重除以身高的平方)帮小明计算他的BMI指数,并根据BMI指数:
低于18.5:过轻18.5-25:正常25-28:过重28-32:肥胖高于32:严重肥胖
用if-elif判断并打印结果:
# -*- coding: utf-8 -*-
height = 1.75
weight = 80.5
条件判断可以让计算机自己做选择,Python的if...elif...else很灵活。
要计算1+2+3,我们可以直接写表达式:
&&& 1 + 2 + 3
要计算1+2+3+...+10,勉强也能写出来。
但是,要计算1+2+3+...+10000,直接写表达式就不可能了。
为了让计算机能计算成千上万次的重复运算,我们就需要循环语句。
Python的循环有两种,一种是for...in循环,依次把list或tuple中的每个元素迭代出来,看例子:
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
执行这段代码,会依次打印names的每一个元素:
所以for x in ...循环就是把每个元素代入变量x,然后执行缩进块的语句。
再比如我们想计算1-10的整数之和,可以用一个sum变量做累加:
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
sum = sum + x
print(sum)
如果要计算1-100的整数之和,从1写到100有点困难,幸好Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从0开始小于5的整数:
&&& list(range(5))
[0, 1, 2, 3, 4]
range(101)就可以生成0-100的整数序列,计算如下:
for x in range(101):
sum = sum + x
print(sum)
请自行运行上述代码,看看结果是不是当年高斯同学心算出的5050。
第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:
while n & 0:
sum = sum + n
print(sum)
在循环内部变量n不断自减,直到变为-1时,不再满足while条件,循环退出。
请利用循环依次对list中的每个名字打印出Hello, xxx!:
# -*- coding: utf-8 -*-
L = ['Bart', 'Lisa', 'Adam']
循环是让计算机做重复任务的有效的方法,有些时候,如果代码写得有问题,会让程序陷入“死循环”,也就是永远循环下去。这时可以用Ctrl+C退出程序,或者强制结束Python进程。
请试写一个死循环程序。
使用dict和set
Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,如果用list实现,需要两个list:
names = ['Michael', 'Bob', 'Tracy']
scores = [95, 75, 85]
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,list越长,耗时越长。
如果用dict实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用Python写一个dict如下:
&&& d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
&&& d['Michael']
为什么dict查找速度这么快?因为dict的实现原理和查字典是一样的。假设字典包含了1万个汉字,我们要查某一个字,一个办法是把字典从第一页往后翻,直到找到我们想要的字为止,这种方法就是在list中查找元素的方法,list越大,查找越慢。
第二种方法是先在字典的索引表里(比如部首表)查这个字对应的页码,然后直接翻到该页,找到这个字。无论找哪个字,这种查找速度都非常快,不会随着字典大小的增加而变慢。
dict就是第二种实现方式,给定一个名字,比如'Michael',dict在内部就可以直接计算出Michael对应的存放成绩的“页码”,也就是95这个数字存放的内存地址,直接取出来,所以速度非常快。
你可以猜到,这种key-value存储方式,在放进去的时候,必须根据key算出value的存放位置,这样,取的时候才能根据key直接拿到value。
把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:
&&& d['Adam'] = 67
&&& d['Adam']
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
&&& d['Jack'] = 90
&&& d['Jack']
&&& d['Jack'] = 88
&&& d['Jack']
如果key不存在,dict就会报错:
&&& d['Thomas']
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
KeyError: 'Thomas'
要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:
&&& 'Thomas' in d
二是通过dict提供的get方法,如果key不存在,可以返回None,或者自己指定的value:
&&& d.get('Thomas')
&&& d.get('Thomas', -1)
注意:返回None的时候Python的交互式命令行不显示结果。
要删除一个key,用pop(key)方法,对应的value也会从dict中删除:
&&& d.pop('Bob')
{'Michael': 95, 'Tracy': 85}
请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。
和list比较,dict有以下几个特点:
查找和插入的速度极快,不会随着key的增加而增加;需要占用大量的内存,内存浪费多。
而list相反:
查找和插入的时间随着元素的增加而增加;占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
dict可以用在需要高速查找的很多地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的第一条就是dict的key必须是不可变对象。
这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。
要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:
&&& key = [1, 2, 3]
&&& d[key] = 'a list'
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: unhashable type: 'list'
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合:
&&& s = set([1, 2, 3])
注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。。
重复元素在set中自动被过滤:
&&& s = set([1, 1, 2, 2, 3, 3])
通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果:
&&& s.add(4)
{1, 2, 3, 4}
&&& s.add(4)
{1, 2, 3, 4}
通过remove(key)方法可以删除元素:
&&& s.remove(4)
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:
&&& s1 = set([1, 2, 3])
&&& s2 = set([2, 3, 4])
&&& s1 & s2
&&& s1 | s2
{1, 2, 3, 4}
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。试试把list放入set,看看是否会报错。
再议不可变对象
上面我们讲了,str是不变对象,而list是可变对象。
对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:
&&& a = ['c', 'b', 'a']
&&& a.sort()
['a', 'b', 'c']
而对于不可变对象,比如str,对str进行操作呢:
&&& a = 'abc'
&&& a.replace('a', 'A')
虽然字符串有个replace()方法,也确实变出了'Abc',但变量a最后仍是'abc',应该怎么理解呢?
我们先把代码改成下面这样:
&&& a = 'abc'
&&& b = a.replace('a', 'A')
要始终牢记的是,a是变量,而'abc'才是字符串对象!有些时候,我们经常说,对象a的内容是'abc',但其实是指,a本身是一个变量,它指向的对象的内容才是'abc':
当我们调用a.replace('a', 'A')时,实际上调用方法replace是作用在字符串对象'abc'上的,而这个方法虽然名字叫replace,但却没有改变字符串'abc'的内容。相反,replace方法创建了一个新字符串'Abc'并返回,如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串'abc',但变量b却指向新字符串'Abc'了:
所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
使用key-value存储结构的dict在Python中非常有用,选择不可变对象作为key很重要,最常用的key是字符串。
tuple虽然是不变对象,但试试把(1, 2, 3)和(1, [2, 3])放入dict或set中,并解释结果。
我们知道圆的面积计算公式为:
当我们知道半径r的值时,就可以根据公式计算出面积。假设我们需要计算3个不同大小的圆的面积:
r1 = 12.34
s1 = 3.14 * r1 * r1
s2 = 3.14 * r2 * r2
s3 = 3.14 * r3 * r3
当代码出现有规律的重复的时候,你就需要当心了,每次写3.14 * x * x不仅很麻烦,而且,如果要把3.14改成3.的时候,得全部替换。
有了函数,我们就不再每次写s = 3.14 * x * x,而是写成更有意义的函数调用s = area_of_circle(x),而函数area_of_circle本身只需要写一次,就可以多次调用。
基本上所有的高级语言都支持函数,Python也不例外。Python不但能非常灵活地定义函数,而且本身内置了很多有用的函数,可以直接调用。
抽象是数学中非常常见的概念。举个例子:
计算数列的和,比如:1 + 2 + 3 + ... + 100,写起来十分不方便,于是数学家发明了求和符号∑,可以把1 + 2 + 3 + ... + 100记作:
这种抽象记法非常强大,因为我们看到 ∑ 就可以理解成求和,而不是还原成低级的加法运算。
而且,这种抽象记法是可扩展的,比如:
∑(n2+1)
还原成加法运算就变成了:
(1 x 1 + 1) + (2 x 2 + 1) + (3 x 3 + 1) + ... + (100 x 100 + 1)
可见,借助抽象,我们才能不关心底层的具体计算过程,而直接在更高的层次上思考问题。
写计算机程序也是一样,函数就是最基本的一种代码抽象的方式。
Python内置了很多有用的函数,我们可以直接调用。
要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数abs,只有一个参数。可以直接从Python的官方网站查看文档:
也可以在交互式命令行通过help(abs)查看abs函数的帮助信息。
调用abs函数:
&&& abs(100)
&&& abs(-20)
&&& abs(12.34)
调用函数的时候,如果传入的参数数量不对,会报TypeError的错误,并且Python会明确地告诉你:abs()有且仅有1个参数,但给出了两个:
&&& abs(1, 2)
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: abs() takes exactly one argument (2 given)
如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError的错误,并且给出错误信息:str是错误的参数类型:
&&& abs('a')
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: bad operand type for abs(): 'str'
而max函数max()可以接收任意多个参数,并返回最大的那个:
&&& max(1, 2)
&&& max(2, 3, 1, -5)
数据类型转换
Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数:
&&& int('123')
&&& int(12.34)
&&& float('12.34')
&&& str(1.23)
&&& str(100)
&&& bool(1)
&&& bool('')
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
&&& a = abs # 变量a指向abs函数
&&& a(-1) # 所以也可以通过a调用abs函数
请利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串:
# -*- coding: utf-8 -*-
print(???)
调用Python的函数,需要根据函数定义,传入正确的参数。如果函数调用出错,一定要学会看错误信息,所以英文很重要!
在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。
我们以自定义一个求绝对值的my_abs函数为例:
def my_abs(x):
if x &= 0:
请自行测试并调用my_abs看看返回结果是否正确。
请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。
如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。
return None可以简写为return。
在Python交互环境中定义函数时,注意Python会出现...的提示。函数定义结束后需要按两次回车重新回到&&&提示符下:
如果你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名):
import的用法在后续一节中会详细介绍。
如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。
pass还可以用在其他语句里,比如:
if age &= 18:
缺少了pass,代码运行就会有语法错误。
调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError:
&&& my_abs(1, 2)
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: my_abs() takes 1 positional argument but 2 were given
但是如果参数类型不对,Python解释器就无法帮我们检查。试试my_abs和内置函数abs的差别:
&&& my_abs('A')
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
File &&stdin&&, line 2, in my_abs
TypeError: unorderable types: str() &= int()
&&& abs('A')
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: bad operand type for abs(): 'str'
当传入了不恰当的参数时,内置函数abs会检查出参数错误,而我们定义的my_abs没有参数检查,会导致if语句出错,出错信息和abs不一样。所以,这个函数定义不够完善。
让我们修改一下my_abs的定义,对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现:
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x &= 0:
添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个错误:
&&& my_abs('A')
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
File &&stdin&&, line 3, in my_abs
TypeError: bad operand type
错误和异常处理将在后续讲到。
返回多个值
函数可以返回多个值吗?答案是肯定的。
比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
import math语句表示导入math包,并允许后续代码引用math包里的sin、cos等函数。
然后,我们就可以同时获得返回值:
&&& x, y = move(100, 100, 60, math.pi / 6)
&&& print(x, y)
151.32 70.0
但其实这只是一种假象,Python函数返回的仍然是单一值:
&&& r = move(100, 100, 60, math.pi / 6)
&&& print(r)
(151.32, 70.0)
原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
定义函数时,需要确定函数名和参数个数;
如果有必要,可以先对参数的数据类型做检查;
函数体内部可以用return随时返回函数结果;
函数执行完毕也没有return语句时,自动return None。
函数可以同时返回多个值,但其实就是一个tuple。
请定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程:
ax2&+ bx + c = 0
的两个解。
提示:计算平方根可以调用math.sqrt()函数:
&&& import math
&&& math.sqrt(2)
# -*- coding: utf-8 -*-
import math
def quadratic(a, b, c):
print(quadratic(2, 3, 1)) # =& (-0.5, -1.0)
print(quadratic(1, 3, -4)) # =& (1.0, -4.0)
函数的参数
定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了。对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。
Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。
我们先写一个计算x2的函数:
def power(x):
return x * x
对于power(x)函数,参数x就是一个位置参数。
当我们调用power函数时,必须传入有且仅有的一个参数x:
&&& power(5)
&&& power(15)
现在,如果我们要计算x3怎么办?可以再定义一个power3函数,但是如果要计算x4、x5……怎么办?我们不可能定义无限多个函数。
你也许想到了,可以把power(x)修改为power(x, n),用来计算xn,说干就干:
def power(x, n):
while n & 0:
对于这个修改后的power(x, n)函数,可以计算任意n次方:
&&& power(5, 2)
&&& power(5, 3)
修改后的power(x, n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。
新的power(x, n)函数定义没有问题,但是,旧的调用代码失败了,原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用:
&&& power(5)
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: power() missing 1 required positional argument: 'n'
Python的错误信息很明确:调用函数power()缺少了一个位置参数n。
这个时候,默认参数就排上用场了。由于我们经常计算x2,所以,完全可以把第二个参数n的默认值设定为2:
def power(x, n=2):
while n & 0:
这样,当我们调用power(5)时,相当于调用power(5, 2):
&&& power(5)
&&& power(5, 2)
而对于n & 2的其他情况,就必须明确地传入n,比如power(5, 3)。
从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数时,有几点要注意:
一是必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面);
二是如何设置默认参数。
当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
使用默认参数有什么好处?最大的好处是能降低调用函数的难度。
举个例子,我们写个一年级小学生注册的函数,需要传入name和gender两个参数:
def enroll(name, gender):
print('name:', name)
print('gender:', gender)
这样,调用enroll()函数只需要传入两个参数:
&&& enroll('Sarah', 'F')
name: Sarah
如果要继续传入年龄、城市等信息怎么办?这样会使得调用函数的复杂度大大增加。
我们可以把年龄和城市设为默认参数:
def enroll(name, gender, age=6, city='Beijing'):
print('name:', name)
print('gender:', gender)
print('age:', age)
print('city:', city)
这样,大多数学生注册时不需要提供年龄和城市,只提供必须的两个参数:
&&& enroll('Sarah', 'F')
name: Sarah
city: Beijing
只有与默认参数不符的学生才需要提供额外的信息:
enroll('Bob', 'M', 7)
enroll('Adam', 'M', city='Tianjin')
可见,默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。
有多个默认参数时,调用的时候,既可以按顺序提供默认参数,比如调用enroll('Bob', 'M', 7),意思是,除了name,gender这两个参数外,最后1个参数应用在参数age上,city参数由于没有提供,仍然使用默认值。
也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin'),意思是,city参数用传进去的值,其他默认参数继续使用默认值。
默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:
先定义一个函数,传入一个list,添加一个END再返回:
def add_end(L=[]):
L.append('END')
当你正常调用时,结果似乎不错:
&&& add_end([1, 2, 3])
[1, 2, 3, 'END']
&&& add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
当你使用默认参数调用时,一开始结果也是对的:
&&& add_end()
但是,再次调用add_end()时,结果就不对了:
&&& add_end()
['END', 'END']
&&& add_end()
['END', 'END', 'END']
很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。
原因解释如下:
Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
所以,定义默认参数要牢记一点:默认参数必须指向不变对象!
要修改上面的例子,我们可以用None这个不变对象来实现:
def add_end(L=None):
if L is None:
L.append('END')
现在,无论调用多少次,都不会有问题:
&&& add_end()
&&& add_end()
为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
我们以数学题为例子,给定一组数字a,b,c……,请计算a2&+ b2&+ c2&+ ……。
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:
def calc(numbers):
for n in numbers:
sum = sum + n * n
return sum
但是调用的时候,需要先组装出一个list或tuple:
&&& calc([1, 2, 3])
&&& calc((1, 3, 5, 7))
如果利用可变参数,调用函数的方式可以简化成这样:
&&& calc(1, 2, 3)
&&& calc(1, 3, 5, 7)
所以,我们把函数的参数改为可变参数:
def calc(*numbers):
for n in numbers:
sum = sum + n * n
return sum
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:
&&& calc(1, 2)
&&& calc()
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
&&& nums = [1, 2, 3]
&&& calc(nums[0], nums[1], nums[2])
这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:
&&& nums = [1, 2, 3]
&&& calc(*nums)
*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:
&&& person('Michael', 30)
name: Michael age: 30 other: {}
也可以传入任意个数的关键字参数:
&&& person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
&&& person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
&&& extra = {'city': 'Beijing', 'job': 'Engineer'}
&&& person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
当然,上面复杂的调用可以用简化的写法:
&&& extra = {'city': 'Beijing', 'job': 'Engineer'}
&&& person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
仍以person()函数为例,我们希望检查是否有city和job参数:
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
if 'job' in kw:
# 有job参数
print('name:', name, 'age:', age, 'other:', kw)
但是调用者仍可以传入不受限制的关键字参数:
&&& person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job):
print(name, age, city, job)
和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
调用方式如下:
&&& person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
&&& person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
TypeError: person() takes 2 positional arguments but 4 were given
由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
由于命名关键字参数city具有默认值,调用时,可不传入city参数:
&&& person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
使用命名关键字参数时,要特别注意,*不是参数,而是特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:
def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用,除了可变参数无法和命名关键字参数混合。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数/命名关键字参数和关键字参数。
比如定义一个函数,包含上述若干种参数:
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
&&& f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
&&& f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
&&& f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
&&& f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
&&& f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
最神奇的是通过一个tuple和dict,你也可以调用上述函数:
&&& args = (1, 2, 3, 4)
&&& kw = {'d': 99, 'x': '#'}
&&& f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
&&& args = (1, 2, 3)
&&& kw = {'d': 88, 'x': '#'}
&&& f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。
Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数不要忘了写分隔符*,否则定义的将是位置参数。
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。
于是,fact(n)用递归的方式写出来就是:
def fact(n):
return n * fact(n - 1)
上面就是一个递归函数。可以试试:
&&& fact(1)
&&& fact(5)
&&& fact(100)
如果我们计算fact(5),可以根据函数定义看到计算过程如下:
===& fact(5)
===& 5 * fact(4)
===& 5 * (4 * fact(3))
===& 5 * (4 * (3 * fact(2)))
===& 5 * (4 * (3 * (2 * fact(1))))
===& 5 * (4 * (3 * (2 * 1)))
===& 5 * (4 * (3 * 2))
===& 5 * (4 * 6)
===& 5 * 24
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000):
&&& fact(1000)
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
File &&stdin&&, line 4, in fact
File &&stdin&&, line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
fact(5)对应的fact_iter(5, 1)的调用如下:
===& fact_iter(5, 1)
===& fact_iter(4, 5)
===& fact_iter(3, 20)
===& fact_iter(2, 60)
===& fact_iter(1, 120)
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
的移动可以用递归函数非常简单地实现。
请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法,例如:
def move(n, a, b, c):
# 期待输出:
move(3, 'A', 'B', 'C')
掌握了Python的数据类型、语句和函数,基本上就可以编写出很多有用的程序了。
比如构造一个1, 3, 5, 7, ..., 99的列表,可以通过循环实现:
while n &= 99:
L.append(n)
n = n + 2
取list的前一半的元素,也可以通过循环实现。
但是在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。
基于这一思想,我们来介绍Python中非常有用的高级特性,1行代码能实现的功能,决不写5行代码。请始终牢记,代码越少,开发效率越高。
取一个list或tuple的部分元素是非常常见的操作。比如,一个list如下:
&&& L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
取前3个元素,应该怎么做?
&&& [L[0], L[1], L[2]]
['Michael', 'Sarah', 'Tracy']
之所以是笨办法是因为扩展一下,取前N个元素就没辙了。
取前N个元素,也就是索引为0-(N-1)的元素,可以用循环:
&&& r = []
&&& for i in range(n):
r.append(L[i])
['Michael', 'Sarah', 'Tracy']
对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。
对应上面的问题,取前3个元素,用一行代码就可以完成切片:
&&& L[0:3]
['Michael', 'Sarah', 'Tracy']
L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
如果第一个索引是0,还可以省略:
['Michael', 'Sarah', 'Tracy']
也可以从索引1开始,取出2个元素出来:
&&& L[1:3]
['Sarah', 'Tracy']
类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片,试试:
&&& L[-2:]
['Bob', 'Jack']
&&& L[-2:-1]
记住倒数第一个元素的索引是-1。
切片操作十分有用。我们先创建一个0-99的数列:
&&& L = list(range(100))
[0, 1, 2, 3, ..., 99]
可以通过切片轻松取出某一段数列。比如前10个数:
&&& L[:10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
后10个数:
&&& L[-10:]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
前11-20个数:
&&& L[10:20]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
前10个数,每两个取一个:
&&& L[:10:2]
[0, 2, 4, 6, 8]
所有数,每5个取一个:
&&& L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
甚至什么都不写,只写[:]就可以原样复制一个list:
[0, 1, 2, 3, ..., 99]
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:
&&& (0, 1, 2, 3, 4, 5)[:3]
字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
&&& 'ABCDEFG'[:3]
&&& 'ABCDEFG'[::2]
在很多编程语言中,针对字符串提供了很多各种截取函数(例如,substring),其实目的就是对字符串切片。Python没有针对字符串的截取函数,只需要切片一个操作就可以完成,非常简单。
有了切片操作,很多地方循环就不再需要了。Python的切片非常灵活,一行代码就可以实现很多行循环才能完成的操作。
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
在Python中,迭代是通过for ... in来完成的,而很多语言比如C或者Java,迭代list是通过下标完成的,比如Java代码:
for (i=0; i&list. i++) {
n = list[i];
可以看出,Python的for循环抽象程度要高于Java的for循环,因为Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。
list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:
&&& d = {'a': 1, 'b': 2, 'c': 3}
&&& for key in d:
print(key)
因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
默认情况下,dict迭代的是key。如果要迭代value,}

我要回帖

更多关于 连胜三人 值得一看 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信