初学Python笔记

参考:廖雪峰Python教程

1、Python简介

Python是解释型语言,是用来编写应用程序的高级编程语言。Python提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容。除了内置的库外,Python还有大量的第三方库,因此开发中许多功能不必从零编写,直接使用现成的即可。但它也有一些缺点,如:

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

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

2、安装python

2.1 Python解释器

Python是跨平台的,它可以运行在Windows、Mac和各种Linux/Unix系统上。

当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。Python的解释器很多,但使用最广泛的还是CPython.

2.2 Python交互模式

打开linux终端即进入命令行模式,敲入命令python3即进入Python交互模式,它的提示符是>>>

在Python交互模式下输入exit()并回车,就退出了Python交互模式,并回到命令行模式。

在命令行模式下,也可以执行python hello.py运行一个.py文件。执行一个.py文件只能在命令行模式执行。

2.3 文本编辑器

3、Python基础

Python的语法比较简单,采用缩进方式,写出来的代码就像下面的样子:

# print absolute value of an integer:
a = 100
if a >= 0:
    print(a)
else:
    print(-a)

#开头的语句是注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块

3.1 数据类型和变量

整数

浮点数

字符串:

单引号'或双引号"括起来的任意文本。如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容。

布尔值:

在Python中,可以直接用TrueFalse表示布尔值(请注意大小写)。

空值:

是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。

变量:

Python中等号=是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量。这种变量本身类型不固定的语言称之为动态语言。与之对应的是静态语言,静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错,例如Java是静态语言。                                                                                                           

最后,理解变量在计算机内存中的表示也非常重要。当我们写:a='ABC'时,Python解释器干了两件事情:                           

(1)在内存中创建了一个'ABC'的字符串;                                                                                                                                       

(2)在内存中创建了一个名为a的变量,并把它指向'ABC'。                                                                                                            

也可以把一个变量a赋值给另一个变量b,这个操作实际上是把变量b指向变量a所指向的数据。

常量:

在Python中,通常用全部大写的变量名表示常量。但事实上它仍然是一个变量,Python根本没有任何机制保证变量不会被改变,所以用全部大写的变量名表示常量只是一个习惯上的用法。

最后解释一下整数的除法为什么也是精确的。在Python中,有两种除法,一种除法是/,计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数。还有有一种除法是//,称为地板除,两个整数的除法仍然是整数。同时注意:Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java对32位整数的范围限制在-2147483648-2147483647Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)

3.2 字符串和编码

3.2.1 字符编码

最早只有127个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为ASCII编码。但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。

Unicode标准把所有语言都统一到一套编码里,这样就不会再有乱码问题了。但最常用的是用两个字节表示一个字符。ASCII编码和Unicode编码的区别:ASCII编码是1个字节,而Unicode编码通常是2个字节。新的问题又出现了:如果统一成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再传输到浏览器。

3.2.2 Python的字符串

在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言。

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。Python对bytes类型的数据用b前缀的单引号或双引号表示:x=b'ABC'。要注意区分'ABC'b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。

在操作字符串时经常遇到strbytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对strbytes进行转换。

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:

>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
  File "", line 1, in 
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:

>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

要计算str包含多少个字符,可以用len()函数:

>>> len('ABC')
3
>>> len('中文')
2

len()函数计算的是str的字符数,如果换成byteslen()函数就计算字节数:

>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6

3.2.3 格式化

在Python中,采用的格式化方式和C语言是一致的,%运算符就是用来格式化字符串的。在字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略。如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串。举例如下:

>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
>>> 'Age: %s. Gender: %s' % (25, True)
'Age: 25. Gender: True'

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}{1}……,不过这种方式写起来比%要麻烦得多:

>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

3.3 使用list和tuple

list和tuple是Python内置的数据类型:有序集合,一个可变,一个不可变。根据需要来选择使用它们。

3.3.1 list

list是一种有序的集合,可以随时添加和删除其中的元素。

>>> classmates = ['Michael', 'Bob', 'Tracy']

可以用len()函数可以获得list元素的个数。

索引来访问list中每一个位置的元素,记得索引是从0开始的。如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素。以此类推,可以获取倒数第2个、倒数第3个等等。

list是一个可变的有序表,所以,可以往list中追加元素到末尾:list.append()。也可以把元素插入到指定的位置:list.insert()。要删除list末尾的元素,用pop()方法:list.pop(),要删除指定位置的元素,用pop(i)方法,其中i是索引位置。要把某个元素替换成别的元素,可以直接赋值给对应的索引位置。

list里面的元素的数据类型也可以不同,比如:

>>> L = ['python', 123, True, ['asp', 'php']]

 3.3.2 tuple

另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改。它没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,但不能赋值成另外的元素。tuple定义是用(),而list定义是用[]

>>> classmates = ('Michael', 'Bob', 'Tracy')

不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。

tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来。如果要定义一个空的tuple,可以写成():t=()。但是,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:t=(1,)。

最后来看一个“可变的”tuple:tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

3.4 条件判断

在Python程序中,用if、elif、else语句实现:

age = 3
if age >= 18:
    print('your age is', age)
    print('adult')
elif age >= 6:
    print('your age is', age)
    print('teenager')
else:
    print('your age is', age)
    print('kid')

根据Python的缩进规则,缩进的语句视为代码块。如果if语句判断是True,就把缩进的两行print语句执行了。 另外不要忘了冒号:。

3.5 循环

Python的循环有两种,一种是for...in循环,依次把list或tuple中的每个元素迭代出来。for x in ...循环就是把每个元素代入变量x,然后执行缩进块的语句。 看例子:

names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)

第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。例如:

sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

3.6 dict和set

3.6.1 dict

Python内置了字典:dict,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度

和list比较,dict有以下几个特点:

  1. 查找和插入的速度极快,不会随着key的增加而变慢;
  2. 需要占用大量的内存,内存浪费多。

而list相反:

  1. 查找和插入的时间随着元素的增加而增加;
  2. 占用空间小,浪费内存很少。

所以,dict是用空间来换取时间的一种方法

在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的第一条就是dict的key必须是不可变对象。这是因为dict根据key来计算value的存储位置,即哈希算法(Hash)。

假设要根据同学的名字查找对应的成绩,用dict实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用Python写一个dict如下:dict初始化使用大括号{},内部为key : value

>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

为什么dict查找速度这么快?因为dict的实现原理和查字典是一样的。假设字典包含了1万个汉字,我们要查某一个字,一个办法是把字典从第一页往后翻,直到找到我们想要的字为止,这种方法就是在list中查找元素的方法,list越大,查找越慢。第二种方法是先在字典的索引表里(比如部首表)查这个字对应的页码,然后直接翻到该页,找到这个字。无论找哪个字,这种查找速度都非常快,不会随着字典大小的增加而变慢。dict使用的就是第二种方法。

把数据放入dict的方法,除了初始化时指定外,还可以通过key放入。此外由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉。

>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88

如何判断key是否存在有两种办法,一是通过in判断:

>>> 'Thomas' in d
False

二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:

>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1

注意:返回None的时候Python的交互环境不显示结果。

要删除一个key,用pop(key)方法,对应的value也会从dict中删除。

3.6.2 set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。

set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。

要创建一个set,需要提供一个list作为输入集合

>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果。通过remove(key)方法可以删除元素。

set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

3.6.3 再议不可变对象

对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:

>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']

而对于不可变对象,比如str,对str进行操作呢: 

>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

要始终牢记的是,a是变量,而'abc'才是字符串对象!有些时候,我们经常说,对象a的内容是'abc',但其实是指,a本身是一个变量,它指向的对象的内容才是'abc'。

当我们调用a.replace('a', 'A')时,实际上调用方法replace是作用在字符串对象'abc'上的,而这个方法虽然名字叫replace,但却没有改变字符串'abc'的内容。相反,replace方法创建了一个新字符串'Abc'并返回,如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串'abc',但变量b却指向新字符串'Abc'了。

所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的

4 函数

4.1 调用函数

Python内置了很多有用的函数,我们可以直接调用。比如求绝对值的函数abs,只有一个参数。可以直接从Python的官方网站查看文档

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

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

4.1.1 数据类型转换

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

>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False

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

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

4.2 调用函数

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

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

def my_abs(x):
    if not isinstance(x, (int, float)):      # 参数检查
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

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

在Python交互环境中定义函数时,注意Python会出现...的提示。函数定义结束后需要按两次回车重新回到>>>提示符下。

假设你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名)

4.2.1 空函数

如果想定义一个什么事也不做的空函数,可以用pass语句。pass语句什么都不做,可以用来作为占位符。

4.2.2 返回多个值

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

4.3 函数的参数

Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

4.3.1 位置参数

写一个计算x的n次方的函数:

def power(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

power(x, n)函数有两个参数:xn,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数xn

4.3.2 默认参数

定义默认参数要牢记一点:默认参数必须指向不变对象!

由于我们经常计算x2,所以,完全可以把第二个参数n的默认值设定为2:

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

默认参数可以简化函数的调用。设置默认参数时,有几点要注意:

一是必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面);

二是如何设置默认参数。

有多个默认参数时,调用的时候,既可以按顺序提供默认参数,比如调用enroll('Bob', 'M', 7),意思是,除了namegender这两个参数外,最后1个参数应用在参数age上,city参数由于没有提供,仍然使用默认值。

也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin'),意思是,city参数用传进去的值,其他默认参数继续使用默认值。

默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:先定义一个函数,传入一个list,添加一个END再返回:

def add_end(L=[]):
    L.append('END')
    return L

当你使用默认参数调用时,一开始结果也是对的:

>>> add_end()
['END']

但是,再次调用add_end()时,结果就不对了:

>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

原因解释如下:

Python函数在定义的时候,默认参数L的值就被计算出来了,即[]因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。即默认值[]是可变的list数据结构。

要修改上面的例子,我们可以用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

定义默认参数要牢记一点:默认参数必须指向不变对象!

4.3.3 可变参数

在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。

我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数。

如果已经有一个list或者tuple,要调用一个可变参数怎么办?Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

4.3.4 关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)

函数person除了必选参数nameage外,还接受关键字参数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函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

4.3.5 命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。这种方式定义的函数如下:

def person(name, age, *, city, job):
    print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。 命名关键字参数也可以有缺省值,从而简化调用。

4.3.6 参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

4.4 递归函数

递归函数的优点是定义简单,逻辑清晰。使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,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)

 5 高级特性

5.1 切片

取一个list或tuple的部分元素是非常常见的操作。对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。

比如,如何取出下述list的前三个元素:

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

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引012,正好是3个元素。如果第一个索引是0,还可以简写为L[:3]。

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

>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']

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

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

5.2 迭代

 

 

 

 

 

 

 

 

你可能感兴趣的:(Python)