python基础
#
是注释。通过空格进行缩进,当一行语句以 :
结尾时,缩进的语句视为一段代码块。按约定俗成的规范,使用4个空格进行缩进。最后代码就类似这样:
- # output name
- a = 100
- if a > 10:
- print("a>10")
- else:
- print("a<=10")
1. 数据类型和变量
1.1. 数据类型
整数
:例如:1, -10, 0等。十六进制表示整数方式为:以 0x
为前缀,后面为0-9, a-f
,例如:0x11af
浮点数
:例如:0.12, -0.1。对于很大的浮点数就用科学计数法表示,把10用e表示,例如:1.23 * 10的9次方表示为1.23e9
或 12.3e8
整数和浮点数在计算器存储方式不同,整数运算永远是精确的,但是浮点数可能会出现四舍五入的误差。
1.2. 字符串
字符串
:以 单引号''
或 双引号""
括起来的文本,例如:'abc'
, "abc"
。
\
是转义符,例如:'I\'m ok'
中\
转义字符串内部中的'
,输出结果为 I'm ok
。此外 \n
表示换行符,\t
表示制表符。
为了简化,还可以通过 r''
表示''
内部的字符串不转义。通过'''...'''
来表示很多行,示例:
- >>> print('\转义: ', '\\\t\\')
- \转义: \ >>> print('不转义:', r'\\\t\\')
- 不转义: \\\t\>>> print('多行: ', '''line1
- ... line2
- ... line3''')
- 多行: line1
- line2
- line3
1.3. 布尔值
布尔值只有 True
和 False
两种,可以通过 and
, or
, not
分别进行与、或、非运算。例如:
- >>> True and True
- True
- >>> True or Flase
- True
- >>> not 5 > 1
- False
1.4. 空值
空值使用 None
表示,类似null
的含义。
1.5. 变量
变量可以是任意数据类型,变量名必须是 大小写、数字、_
组成,不能以数字开头。变量通过 =
赋值,例如:a = 123
将变量a赋值为整数123。
python是动态语言
,变量的类型是不固定的。而例如Java
则是静态语言
,变量在定义时必须指定类型,例如:int a = 123;
,赋值时候如果类型不匹配,
则编译时会报错。与静态语言相比,动态语言更灵活。
1.6. 常量
常量就是不能变的变量。一般习惯上将常量大写。
python中除法有 /
和 //
。/
计算结果是浮点数,//
计算结果则为整数。通过 %
来取两个整数相除的余数。例如:
- >>> 10 / 3
- 3.3333333333333335
- >>> 10 // 3
- 3
- >>> 10 % 3
- 1
2. 字符串和编码
2.1. 字符串
python中的字符串类型为str
,在内存中以Unicode表示,一个字符对应若干字节。如果在网络上传输或保存到磁盘,就需要把str
转化为字节bytes
。bytes
类型数据用带b
前缀的单引号或双引号表示:x = b'abc'
,注意 abc
是str
类型,而b'abc'
则是bytes
类型。
str
通过encode()
可以将编码为指定的bytes
,例如:
- >>> '中文'.encode('utf-8')
- b'\xe4\xb8\xad\xe6\x96\x87'
- >>> 'abc'.encode('ascii')
- b'abc'
- >>> '中文'.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
的编码范围,故报错。
bytes
可以通过decode()
方法解码为str
,例如:
- >>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
- '中文'
- >>> b'abc'.decode('ascii')
- 'abc'
通过len()
可以计算str
的字符数或bytes
的字节数。例如:
- >>> len('中文')
- 2
- >>> len('中文'.encode('utf-8'))
- 6
操作字符串时,我们经常会将str和bytes互相转换,为了避免乱码的问题,一般推荐使用utf-8
编码方式。
2.2. 格式化
python中的字符串格式化方式和C语言类似,都是通过%
运算符实现。例如:
- >>> "Hi, %s, you have %d" % ('peter', 100)
- 'Hi, peter, you have 100'
有几个占位符,后面就有几个变量,顺序要对应好。常见的占位符有:
- %s: 字符串
- %d: 整数
- %f: 浮点数,%.2f 表示保留2位小数
另一种格式化字符串的方法是使用字符串的format()
方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}...
,例如:
- >>> "Hi, {0}, you have {1:.2f}".format('peter', 3.1415926)
- 'Hi, peter, you have 3.14'
3. 基本数据结构
3.1 list
list是内置的数据结构:列表,表示有序的数据集合。例如:books = ['a', 'b', 'c']
,books就是一个list。使用的一些方法如下:
- >>> books = ['a', 'b', 'c']
- >>> books
- ['a', 'b', 'c']
- >>> len(books) # 计算list元素的个数
- 3
- >>> books[0] # 通过索引访问list中的元素,索引下标从0开始
- 'a'
- >>> books[4] # 超出list的范围会报IndexError错误,最后一个下标是 len-1
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- IndexError: list index out of range
- >>> books[-1] # 下标-1代表最后一个元素,-2是倒数第二个元素,以此类推
- 'c'
- >>> books[-2]
- 'b'
- >>> books.append(True) # 往list队尾添加元素
- >>> books.insert(1, ['abc', 'def']) # 往list指定下标位置(1)添加元素
- >>> books # list内的元素可以是不同的类型
- ['a', ['abc', 'def'], 'b', 'c', True]
- >>> books.pop() # 从队尾删除元素
- True
- >>> books.pop(1) # 从list指定下标位置(index=1)删除元素
- ['abc', 'def']
- >>> books;
- ['a', 'b', 'c']
- >>> books[1] = 'abc' # 可以类似数组,直接替换指定下标位置的元素
- >>> list = list(range(5)) # 通过range()函数生成0-4的整数序列,再通过list()函数转换为list
- >>> list
- [0, 1, 2, 3, 4]
3.2 tuple
tuple是另一种有序数组,但和list不同的是tuple一经初始化就不能再修改,不能使用append()
,pop()
等修改方法。可以和list类似使用books[0]
,books[-1]
正常访问元素。不可变使得代码更安全。使用方法如下:
- >>> books = (1,2,['a','b']) # 通过(... , ...)定义tuple
- >>> books
- (1, 2, ['a', 'b'])
- >>> books[2][1]
- 'b'
3.3 dict
dict全称为dictionary,是python的内置字典。使用 key-value 键值对存储,一个key只对应一个value。类似 java 中的 map,使用了哈希表的数据结构,有极快的查找速度。使用方法如下:
- >>> dict = {'a':100, 'b':200, 'c':300}
- >>> dict['c'] # 根据key获取value
- 300
- >>> dict['Adam'] = 400 # 通过key放入value数据
- >>> 'Adam' in dict # 判断key是否在字典中
- True
- >>> 'adam' in dict
- False
- >>> dict.get('Adam') # key如果不存在,则会返回None
- 400
- >>> dict.pop('a') # 删除一个key-value对
- 100
注意:dict的key是不可变的
3.4 set
set和dict类似,也是一组key的集合,但不存储value。在set中,key不能重复。使用方法如下:
- >>> set = set([1,2,3]) # 新建set,以list作为输入集合
- >>> set.add(1) # 往set中添加元素,但set中元素不能重复
- >>> set.remove(1) # 从set中移除元素
- >>> s1 = set([1,2,3])
- >>> s1
- {1, 2, 3}
- >>> s2 = set([2,3])
- >>> s1 & s2 # 求s1和s2两个set的交集
- {2, 3}
- >>> s1 | s2 # 求s1和s2两个set的并集
- {1, 2, 3}
4. 条件和循环
4.1. 条件判断
条件判读通过if
, elif
, else
完成,完成形式如下:
- if <条件判断1>:
- <执行1>
- elif <条件判断2>:
- <执行2>
- elif <条件判断3>:
- <执行3>
- else:
- <执行4>
如果一个if
判断为True,则会忽略下面的判断语句
4.2. 循环
循环方式有两种,一种是for...in
循环,依次将list或tuple中的元素迭代出来,计算1-100的和:
- sum = 0
- for x in range(101):
- sum += x
- print(sum)
另一种方式是while
循环,只要条件满足while后语句,就一直循环。计算1-100的和:
- sum = 0
- x = 1
- while x <= 100:
- sum += x
- x += 1
- print(sum)
可以通过break
提前退出while循环,contince
提前结束当前循环,进行下次循环。这两个语句通常需要配合if
使用
三、 函数
1. 调用函数
如果想调用一个函数,需要知道这个函数的名称和参数。如abs()
求绝对值的函数,只要一个参数,可以通过help(abs)
查看该函数的帮助信息。
- >>> abs(-1)
- 1
- >>> abs(1,2)
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- TypeError: abs() takes exactly one argument (2 given)
- >>> abs('abc')
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- TypeError: bad operand type for abs(): 'str'
如果传参的个数或类型不正确,会报TypeError
错误,并提示错误信息。
函数名就是指向函数对象的引用,可以将函数名赋值给一个变量,相当于给函数起了个"别名":
- >>> a = abs # 将变量a指向函数abs()
- >>> a(-1) # 通过a调用abs()函数
- 1
2. 定义函数
定义函数使用def
,函数的返回值使用return
,例如:
- def my_abs(x):
- if not isinstance(x, (int, float)): # 检查 x 的数据类型
- raise TypeError("type error") # 抛出TypeError异常
- if x >= 0:
- x;
- else:
- return -x;
-
- print(my_abs(-1))
如果没有return
值,则会返回None
。
pass
作为占位符,表示什么都不会做。如果没想好怎么写函数中的代码,可以先用pass让代码运行起来。而缺少了pass,代码会报错。
函数可以返回多个返回值,但其实是返回的单一值:tuple
。但写起来方便,可以使用多个变量来接受一个tuple。
- def test(x):
- return 1,2
-
- x, y = test('a')
- print(test('a'))
- 输出:
- (1, 2)
3. 函数的参数
函数除了必选参数外,还提供了默认参数,可变参数和关键字参数,使得定义的函数能处理复杂的参数,简化开发者的调用。
1)默认参数:先定义power函数,计算x的n次方:
- def enroll(name, gender, age=6, city='Beijing'):
- # 第3、4个参数设置默认值
- print('name:', name)
- print('gender:', gender)
- print('age:', age)
- print('city:', city)
- return
-
- print(enroll('a', 'F')) # 等同于调用enroll('a','F',6,'Beijing')
- print(enroll('a', 'F', city='BJ')) # 当参数不按定义的顺序传递时,需要把参数名写上,此时age还使用默认值
设置默认参数有几点需要注意:
- 必选参数在前,默认参数在后。否则无法判断参数的值该是哪个
- 变化大的参数放前面,变化小的参数放后面。变化小的可以设置为默认参数,好处就是降低了调用函数的难度。
- 默认参数必须指向不可变对象
2)可变参数,传入函数的参数个数是可变的,可以是0, 1...个。可变参数是在参数前面加上了*
,例如:
- def cacl(*number): # number接收的是一个tuple
- for x in number:
- print(x)
-
- cacl()
- cacl(1,2,3) # 函数可以传任意值
- nums = [1,2,3]
- cacl(*nums) # nums前加一个*,将list或tuple的值作为可变参数传递
3)关键字参数:允许传递0或任意个含参数名的参数,这些关键字参数在函数内部组装成一个dict。关键字参数可以扩展函数的功能。例如:
- def person(name, age, **kw):
- print('name', name, 'age', age, 'others', kw)
-
- # 可传入任意个关键字参数
- person('peter', 10, gender='M', job='Engineer')
-
- # 可先组装dict,然后通过**dict将所有key-value用关键字参数传入函数的**kw,kw获得dict的一份拷贝,对kw的改动不会影响外面的dict
- dict = {'gender':'M', 'job':'Engineer'}
- person('peter', 10, **dict)
4)命名关键字参数:
函数调用者可以传入任意不受限制的关键字参数,在函数内部可以通过kw检查到底传递了哪些。例如:
- def person(name, age, **kw):
- if 'city' in kw: # 检查是否有city和job参数
- print(kw['city'])
- if 'job' in kw:
- print(kw['job'])
- print(name, age, kw)
如果要限制关键字参数的名字,可以使用命名关键字参数,和关键字参数**kw
不同,命名关键字参数需要一个特殊的分隔符*
,*
后面的参数被视为命名关键字参数。例如:
- def person(name, age, *, city, job): # 只接收city和job作为关键字参数
- print(name, age, city, job)
-
- # 命名关键字参数必须传入参数名,如果没有传入参数名,则调用会报错
- person('name', 18, city='city', job='job')
4. 递归函数
函数内部可以调用其他函数,如果一个函数在内部调用本身,则这个函数是递归函数。例如:计算n的阶乘用递归方式写出来就是
- def func(n):
- if n == 1:
- return 1
- return n * func(n-1)
四、高级特性
利用python的一些高级特性,可以用1行代码实现很多功能,从而帮助我们提升了开发效率。
1. 切片
python提供了切片(Slice)操作符,用于取list或tuple的部分元素。
- >>> L = ['a','b','c','d','e']
- >>> L[0:3] # 取前3个元素,从索引0到索引3(但不包括索引3)的元素
- ['a', 'b', 'c']
- >>> L[:3] # 如果第一个索引是0,可以省略
- ['a', 'b', 'c']
- >>> L[-2:-1] # 倒数切片,倒数第2个元素,倒数第一个元素索引是-1
- ['d']
- >>> L[-2:] # 倒数前2个元素
- ['d', 'e']
- >>> L[0:5:2] # 取前5个元素,每2个取一次
- ['a', 'c', 'e']
- >>> L[::2] # 取所有元素,每2个取一次
- ['a', 'c', 'e']
tuple是不可变的一种list,也支持切片操作,操作结果仍是tuple
- >>> (1,2,3,4,5)[:3]
- (1, 2, 3)
字符串"abcde"也可看做是一个list,每个元素就是一个字符,也可以使用切片操作,其结果仍旧是字符串
2. 迭代
如果给定一个list或tuple,可以使用for循环来遍历,这种遍历称为迭代(Iteration)。python中的迭代是通过for...in
来完成,不仅可迭代list/tuple。还可迭代其他对象。
- # 迭代list
- >>> l = list(range(10))
- >>> for item in l:
- ... print(item)
-
- # 迭代dict,由于dict的存储不是像list那样顺序存储,所有迭代结果可能不是按顺序的
- >>> d = {'a':1, 'b':2}
- >>> for key in d: # 默认是迭代的key
- ... print(key)
- ...
- b
- a
- >>> for value in d.values(): # 迭代value
- ... print(value)
- ...
- 2
- 1
- >>> for k,v in d.items(): # 迭代key和value
- ... print(k, v)
- ...
- b 2
- a 1
-
- # 迭代字符串
- >>> for ch in 'abc':
- ... print(ch)
- ...
- a
- b
- c
当使用for
时,只要作用与一个迭代对象,就可以正常运行,我们不需要关注迭代对象是list还是其他数据类型。可以通过collections
模块的Iterable
类型判断一个对象是否是可迭代对象:
- >>> from collections import Iterable
- >>> isinstance('abc', Iterable) # str类型可迭代
- True
- >>> isinstance(123, Iterable) # 整数不可迭代
- False
- >>> dict={'a':1}
- >>> isinstance(dict, Iterable) # dict类型可迭代
- True
python内置的enumerate
函数可以将list变成索引-元素对。这样可以在for
中迭代索引和对象本身:
- >>> l = ['a','b','c','d']
- >>> for i,value in enumerate(l):
- ... print(i, value)
- ...
- 0 a
- 1 b
- 2 c
- 3 d
3. 列表生成式
python内置了简单而强大的生成list的方式,通过使用列表生成式可以写出非常简洁的代码。
- # 生成 2-10 的list
- >>> L=list(range(2,11))
- >>> L
- [2, 3, 4, 5, 6, 7, 8, 9, 10]
此外,还可以在for...in...
后面加上if
进行判断。例如:生成3-10中偶数的平方的list:
- >>> L=[x * x for x in range(3,11) if x % 2 ==0]
- >>> L
- [16, 36, 64, 100]
等价于
- >>> L=[]
- >>> for x in range(3,11):
- ... if x % 2 == 0:
- ... L.append(x * x)
- ...
- >>> L
- [16, 36, 64, 100]
列表生成式也可以使用两个变量来生成list:
- >>> dict={'a':'1', 'b':'2'}
- >>> [k+'='+v for k,v in dict.items()]
- ['b=2', 'a=1']
4. 生成器
通过列表生成器,可以直接创建一个列表。但是,假如创建一个包含100万个元素的列表,需要占用大量的内存空间,而我们只需要前几个元素,那么大多数元素占用的内存空间就浪费了。
对于这种情况,python提供了生成器(generator)
,能一边循环一边计算,从而不需要创建完整的list,节省了内存空间。
第一种创建generator
生成器的方式:将[]
改为()
- >>> L = [x * x for x in range(1,10)] # 创建list
- >>> L # 打印list的每一个元素
- [1, 4, 9, 16, 25, 36, 49, 64, 81]
- >>> g = (x * x for x in range(1,10)) # 创建generator
- >>> g
- <generator object <genexpr> at 0x1024e0bf8>
- >>> next(g) # 用next()函数获取generator下一个返回值
- 1
- >>> next(g)
- 4
next()会计算g的下一个元素,当没有元素时,会抛出StopIteration
异常。generator是可迭代对象,可以使用for
循环来代替next()
- >>> g = (x * x for x in range(1,5))
- >>> for n in g:
- ... print(n)
- ...
- 1
- 4
- 9
- 16
第二种创建generator
生成器的方式:函数定义中使用yield
,将函数变成generator
下面是生成菲波那切数列的函数,除第一个和第二个数之外,后面每个数等于前两个数的和:
- def fib(max):
- n, a, b = 0, 0, 1
- while n < max:
- print(b)
- a, b = b, a + b
- n = n + 1
- return 'done'
-
- fib(6) # 输出菲波那切数列前6个数
这种逻辑非常类似generator,可以从第一个元素开始,推算后面任意的元素。将print(b)
改为yield b
,该函数就就变成了generator。generator和普通函数执行流程不一样:
- 普通函数是顺序执行,遇到
return
或最后一行函数语句就会返回;
- 而变为
generator
的函数,每次调用next()
的时候执行,遇到yield
中断并返回值,再次调用next()
时从上次yield
语句处继续执行。
- >>> def fib(max):
- ... n, a, b = 0, 0, 1
- ... while n < max:
- ... yield b
- ... a, b = b, a + b
- ... n = n + 1
- ... return 'done'
- ...
- >>> g = fib(6) # 生成generator对象
- >>> g
- <generator object fib at 0x1024e0bf8>
- >>> next(g) # 调用next()函数不断获取后一个返回值
- 1
- >>> next(g)
- 1
- >>> next(g)
- 2
- >>> next(g)
- 3
- >>> next(g)
- 5
- >>> next(g)
- 8
- >>> next(g) # 此时 n = max, 没有yield可以执行了,所有调用就报错了
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- StopIteration: done
-
- >>> for n in fib(6): # 也可以使用for循环进行generator的迭代
- ... print(n)
5. 迭代器
直接作用于for
循环的数据类型有以下几种:
- 集合数据类型:
list
,tuple
,dict
,set
,str
等
generator
:包括生成器和带yield
的生成器函数
这些可作用for循环的对象统称为可迭代对象:Iterable
。可通过isinstance()
判断一个对象是否是Iterable:
- >>> isinstance([],Iterable)
- True
- >>> isinstance((x for x in range(10)),Iterable)
- True
- >>> isinstance(123,Iterable)
- False
生成器不仅可作用于for循环,还可被next()
不断调用返回下一个值,直至抛出StopIteration
,这类对象成为迭代器:Iterator
,也可使用isinstance
进行判断:
- >>> from collections import Iterator
- >>> isinstance([],Iterator)
- False
- >>> isinstance((x for x in range(10)), Iterator)
- True
迭代器Iterator表示的是一个数据流,可将数据流看做是一个有序队列,但不能提前知道队列的长度,只能通过next()函数实时按需计算下一个元素的值。
五、函数式编程
1. 高阶函数
函数名是指向函数的变量,对于函数abs()
,可将abs
函数名看做变量,它指向可以计算绝对值的函数。另外,函数本身可以赋值给其他变量:
- >>> abs(-10) # 调用求绝对值的函数
- 10
- >>> abs # abs是函数本身
- <built-in function abs>
- >>> f=abs # 将函数本身赋值给变量
- >>> f
- <built-in function abs>
- >>> f(-10) # 通过变量来调用函数。直接调用abs()和调用变量f()相同
- 10
既然变量可以指向函数,函数的参数能接受变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称为高阶函数
。函数式编程就是指这种高度抽象的编程范式。例如:
- def add(x, y, f):
- print(f(x) + f(y))
-
- add(-5, 6, abs) # f=abs 作为参数传入, 最后计算abs(-5) + abs(6)
-
- 输出:11
1.1 map/reduce
map()
函数接收两个参数,第一个是函数,第二个是Iterable
(可迭代对象)。map将函数作用与序列的每一个元素,并作为结果返回Iterator
(迭代器)。举例如下:
- # 第一个参数str是str()函数,将list中的每个数字转化为str
- >>> result = map(str, [1,2,3,4,5])
- >>> isinstance(result, Iterator)
- True
- >>> list(result) # 结果是Iterator惰性序列,通过list()函数将其转化为list
- ['1', '2', '3', '4', '5']
reduce()
函数也接收两个参数,第一个是函数,第二个是Iterable
。reduce将函数作用在序列上,将结果继续和序列的下一个元素做累积计算。效果就是:
- reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
举例如下:将[1,3,5,7,9]转化为13579
- >>> from functools import reduce
- >>> def fn(x, y):
- ... return x * 10 + y
- ...
- >>> reduce(fn, [1,3,5,7,9])
- 13579
map()
可以与reduce()
配合起来使用:实现将str转化为int,只需要几行代码
- >>> DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
- # str也是一个Iterable
- >>> reduce(lambda x, y:x*10 + y, map(lambda x:DIGITS[x], '13579'))
- 13579
1.2 filter
filter()
也接收一个函数和一个Iterable
序列,将函数依次作用与序列中的每个元素,然后保留返回True的元素,丢弃返回False的元素,最终返回Iterator
序列。通过help()
了解filter的说明:
- >>> help(filter)
-
- Help on class filter in module builtins:
-
- class filter(object)
- | filter(function or None, iterable) --> filter object
- |
- | Return an iterator yielding those items of iterable for which function(item)
- | is true. If function is None, return the items that are true.
filter()
主要是实现筛选功能。例如:筛选list中的奇数
- >>> def is_odd(n):
- ... return n % 2 == 1
- ...
- # filter返回Iterator序列,需要用list()来获取所有结果
- >>> list(filter(is_odd, [1,2,3,4,5]))
- [1, 3, 5]
1.3 sorted
sorted()
函数可以对Iterable
序列进行排序。默认是从小到大的升序,也可指定reverse=True
改为从大到小。它还可以接受一个函数key来自定义排序方法:该函数会作用与序列的每一个元素,然后根据key函数返回的结果进行排序。
- # 对list进行排序
- >>> sorted([1, -5, 3])
- [-5, 1, 3]
- # 接收key函数实现自定义排序,按绝对值大小排序
- >>> sorted([1, -5, 3], key=abs)
- [1, 3, -5]
- # 反向排序
- >>> sorted([1, -5, 3], key=abs, reverse=True)
- [-5, 3, 1]
2. 返回函数
高阶函数除了能将函数作为参数,还可将函数作为返回值。例如:
- >>> def lazy_sum(*args):
- ... def sum():
- ... s = 0
- ... for n in args:
- ... s = s + n
- ... return s
- ... return sum # 返回求和函数
- ...
- >>> f=lazy_sum(1,2,3,4,5) # 调用lazy_sum时,返回求和函数而不是求和结果
- >>> f
- <function lazy_sum.<locals>.sum at 0x101c09ae8>
- >>> f() # 调用f()时,才会计算求和结果
- 15
闭包
:在上面的例子中,lazy_sum函数中定义了函数sum,内部sum函数可以引用外部函数lazy_sum的参数和局部变量(args)。当lazy_sum返回sum时,相关参数和变量(args)都保存在返回的sum函数中,这种成为闭包。返回闭包时候需要注意一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
3. 匿名函数
我们在传入函数时,有时候不需要显式的定义函数,直接传入匿名函数更方便。关键字lambda
表示匿名函数,例如:lambda x: x * x
表示计算x的平方的函数,冒号前面的x表示函数参数,lambda只需要一个表达式,不需要写return
,返回值就是表达式的值。
- # map第一个参数为传入的函数
- >>> list(map(lambda x : x * x, [1,2,3]))
- [1, 4, 9]
4. 装饰器
装饰器
:在代码运行期间动态的增加函数功能的方式称为装饰器。例如:有一个now()函数,希望在函数调用的前后打印日志,而又不修改函数的定义,从而增加now()函数的功能。
- # 定义能打印日志的装饰器,接收一个函数作为参数,并返回一个函数
- import functools
-
- def log(func):
- # 将原始函数now的__name__等属性复制到wapper中,否则部分依赖函数签名的代码执行时会出错
- @functools.wraps(func)
- def wapper(*args, **kw):
- print("call %s" % func.__name__)
- return func(*args, **kw)
- return wapper
-
- # 使用@语法,将装饰器置于函数的定义处
- @log
- def now():
- print("2018-01-01")
-
- now() # 调用now函数,不仅运行now本身,也会在now之前打印一行日志
-
- ---
- call now
- 2018-01-01
将@log放置在now函数定义处,相当于执行了now = log(now)
,log()是一个装饰器,返回一个函数,所以现在now指向了新的函数wapper()(即在log内定义的函数)。wapper函数的参数定义是*args, **kw
,因此wapper可以接收任何参数。
5. 偏函数
int()
能将字符串转化为整数,默认是按10进制进行转化转换,还提供了额外的base参数来指定做N进制的转换。
- >>> int('123')
- 123
- >>> int('123', base=8) # 按8进制进行转换
- 83
可以通过functional.partial
帮我们创建一个偏函数
,创建一个新的函数int2(),将int()函数的base参数设置默认值,从而使用int2()函数转化2进制会更方便一些。注意:新的int2函数中base=2是默认值,也可以设置为其他值:
- >>> import functools
- >>> int2=functools.partial(int, base=2)
- >>> int2('100101')
- 37
- >>> int2('100101',base=10)
- 100101
六、模块
一个.py文件称为一个模块(module)
,使用模块提升了代码的可维护性,编写完一个模块,可以在其他模块引用。使用模块还能避免函数和变量名冲突,相同名字的函数和变量可以在不同的模块中。如果为了避免模块名冲突,python又按目录来组织模块的方法,称为包(package)
。
- mycompany
- ├─ __init__.py
- ├─ abc.py
- └─ xyz.py
以上目录存放例子,mycompany是一个顶层包名,abc.py的模块名就是mycompany.abc
。每个目下会有一个__init__.py
文件,这个文件是必须的,否则python会将它看做是一个普通的目录,而不是一个包,init.py本身就是一个模块,模块名为mycompany。
自己创建模块时需要注意不要和系统自带的模块名冲突,例如,系统自带了sys模块,自己的模块就不能命名为sys.py。
使用python本身内置的模块,只需要import 模块名
就可以了。例如:import sys
,就导入了sys模块,变量sys就指向该模块,利用sys变量,就可以访问sys模块的所有功能。
七、面向对象编程
面向对象编程
,简称OOP(Object Oriented Programming),是一种程序设计思想,将对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。python中,所有数据类型都可以视为对象,也可以自定义对象,自定义的对象数据类型就是类(Class)的概念。面向对象的三大特点:封装、继承、多态。
1. 类和实例
类(Class)是抽象的模板,而实例(Instance)是根据类创建出来的一个个具体的"对象",每个对象拥有相同的方法,而各自的数据不同。
- # class关键字定义类,后面object表示Student是从object继承的,
- class Student(object):
-
- # 通过定义特殊的__init__方法,在创建类时,将必须绑定的属性强制填写进去
- # __init__第一个参数是self,表示实例本身,在内部将name和score属性绑定到self上
- def __init__(self, name, score):
- self.name = name
- self.score = score
-
- def print(self):
- print("%s %s" % (self.name, self.score))
-
- # 创建Student实例,必须传入与__init__方法匹配的参数
- std = Student("name", 90)
- # Student实例将name和score数据封装起来,通过调用实例的方法,操作对象的内部数据
- std.print()
2. 访问限制
上例中,仍可以通过std.name
来访问和修改实例的name属性。如果要将name和score内部属性设置为私有变量(private),可以在属性的名称前加两个下划线__
,只有内部可以访问,外部不可以访问。如下:
- class Student(object):
- def __init__(self, name, score):
- self.__name = name
- self.__score = score
- def print_score(self):
- print("%s, %s" % (self.__name, self.__score))
变量名类似__xxx__
,前后都有两个下划线的是特殊变量,特殊变量可以直接访问,不是private的。
3. 继承与多态
定义一个新的class可以继承
已有的class,从而获得父类的全部功能。例如:
- class Animal(object):
- def run(self):
- print('animal run')
-
- # Cat类继承自Animal类
- class Cat(Animal):
- # 将父类中的run方法覆写掉
- def run(self):
- print('cat run')
Cat类也可以覆写run方法,从而代码调用时,会调用子类的run方法。这样,我们会获得了多态
。
- # 该函数接受Animal类型的变量
- >>> def run_twice(animal):
- ... animal.run()
- ... animal.run()
- ...
- >>> run_twice(Animal()) # 当传入Animal对象时
- animal run
- animal run
- >>> run_twice(Cat()) # 当传入Cat对象时
- cat run
- cat run
对于传入的不同的Animal子类,run_twice()函数不需要做任何修改,只需要接受Animal类型就可以了。因为Cat等子类也算是Animal类型,然后可以按照Animal的类型进行操作,可以放心的调用run()方法,而具体的run()方法是作用在Animal、Cat对象上,由运行时该对象的确切类型决定。这就是多态的好处:
- 对扩展开放:允许新增Animal的子类
- 对修改封闭:不需要修改依赖Animal类型的run_twice()函数
静态语言 vs 动态语言:
- 对于静态语言(例如Java):如果需要传入Animal类型,则传入的对象必须是Animal类型或它的子类,否则将无法调用run()
- 对于动态语言(例如python):则不一定需要传入Animal类型,只需要保证传入的对象有一个run()方法就行。
这就是动态语言的鸭子类型
:不像静态语言那样要求严格的继承体系,一个对象"看起来像鸭子,走起路来像鸭子",那它就可以看做是鸭子。
4. 获取对象信息
可以使用type()
来判断对象的类型:
- # 判断基本类型
- >>> type(123)
- <class 'int'>
- >>> type('123')
- <class 'str'>
-
- # 如果一个变量指向一个类或函数
- >>> type(Cat())
- <class '__main__.Cat'>
- >>> type(abs)
- <class 'builtin_function_or_method'>
type
返回Class类型,可以比较两个type是否相同:
- >>> type(123)==type(456)
- True
- >>> type('abc')==str
- True
- >>> type('abc')==type(123)
- False
对类的继承关系,type不太好判断,可以使用instance()
函数,告诉我们一个对象是否是该类型,或该类型的子类。例如:Cat类继承了Animal:
- >>> a = Animal()
- >>> c = Cat()
- >>> isinstance(a, Animal)
- True
- >>> isinstance(c, Animal)
- True
- >>> isinstance(a, Cat)
- False
-
- # 判断基本类型
- >>> isinstance(123, int)
- True
- >>> isinstance('123', int)
- False
可以使用dir()
获得一个对象的所有属性和方法,配合getattr()
,setattr()
,hasattr()
,操作一个对象的状态:
- >>> a = Animal()
- >>> dir(a)
- ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'run']
- >>> hasattr(a, 'x') # 是否有'x'属性
- False
- >>> setattr(a, 'x', 10) # 设置属性'x'
- >>> getattr(a, 'x') # 获取属性'x'
- 10
-
- >>> fn = getattr(a, 'run') # 获取a对象的run方法,赋值到变量fn
- >>> fn() # fn()与a.run()是一样的
- animal run
5. 实例属性和类属性
python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性时通过实例变量或self:
- >>> a = Animal()
- >>> a.score = 123
如果直接在Animal类本身绑定一个属性,可在class中定义,这种属性时类属性
:
- >>> class Animal(object):
- ... name = "name"
- ...
- >>> a = Animal()
- >>> a.name # 实例a没有name属性,会查找class的name属性
- 'name'
- >>> Animal.name # 打印类的name属性
- 'name'
- >>> a.name='name_object' # 给实例a绑定name属性
- >>> a.name # 实例属性优先级比类属性高,所有屏蔽了类的name属性
- 'name_object'
实例属性
归各个实例所有,互不干扰。类属性
属于类所有,所有实例共享一个类属性。不要对实例属性和类属性使用相同的名字,否则将发生难以发现的错误
八、面向对象高级编程
1. 使用slots
在程序运行时可以动态给class绑定属性,但如果想限制实例的属性,例如只允许给Student类添加name或age属性,可以在提供定义class时,设置一个特殊的__slots__
变量:
- >>> class Student(object):
- ... __slots__=('name','age') # 通过tuple设置允许绑定的属性明恒
- ...
- >>> a = Student()
- >>> a.name="name" # 绑定属性name
- >>> a.other=123 # 绑定属性other,不在__slots__定义中,出现错误
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- AttributeError: 'Student' object has no attribute 'other'
other不在__slots__
定义中,所以不能绑定other属性,得到AttributeError
错误。__slots__
定义的属性仅对当前实例起作用,对继承的子类不起作用。
2. 使用@property
在java中,如果定义了一个属性,一般会实现这个属性的getter和setter方法。在python中,可以通过@property
装饰器将方法变成属性调用,这样既能检查参数,又能用属性这样简单的方式来访问变量。
- >>> class Student(object):
- ... @property
- ... def score(self):
- ... return self._score
- ...
- ... @score.setter
- ... def score(self, value):
- ... if not isinstance(value,int):
- ... raise ValueError('score must be integer')
- ... if value > 100 or value < 0:
- ... raise ValueError('score must between 0-100')
- ... self._score=value
-
- >>> s = Student()
- >>> s.score=-1
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- File "<stdin>", line 10, in score
- ValueError: score must between 0-100
- >>> s.score=10 # 实际转化为s.set_score(10)
- >>> s.score # 实际转化为s.get_score()
- 10
@property
将getter方法变成属性,此时@property本身又创建了@score.setter装饰器,负责将setter方法变为属性赋值,于是可以对score进行属性操作。如果只定义了getter方法,没有定义setter方法,那么就是只读属性。通过@property
可以让调用者写出简洁的代码,同时保证了参数的校验。
3. 多重继承
设计类的继承关系时,通常主线都是单一继承下来的,如果需要加入额外的功能,通过多重继承就可以实现,这种设计称为MixIn
,例如:让Dog除了继承Animal,同时继承Runnable,为了更好的看出继承关系,一般将Runnable改为RunnableMixIn:
- class Dog(Animal, RunnableMixIn):
- pass
设计类的时候,我们优先考虑多重继承来组合多个MixIn的功能,可快速构造所需的子类。
4. 定制类
python中有许多特殊用途的函数可以帮助我们定制类。
__str__()
:打印对象时,自定义返回的字符串
- >>> class Student(object):
- ... def __init__(self,name):
- ... self.name = name
- ... def __str__(self):
- ... return 'student object (name:%s)' % self.name
- ...
- >>> print(Student('apple'))
- student object (name:apple)
__iter__
:如果一个类想用于for...in
,类似list或tuple,就需要实现__iter__()
方法,该对象返回一个迭代对象,然后for循环会不断调用迭代对象的__next__()
方法拿到下一个值,直至遇到StopIteration时退出循环。例如,for循环迭代菲波那切数列:
- >>> class Fib(object):
- ... def __init__(self):
- ... self.a, self.b = 0, 1 # 初始化计数器 a,b
-
- ... def __iter__(self):
- ... return self # 返回迭代对象自己
-
- ... def __next__(self):
- ... self.a, self.b = self.b, self.a + self.b # 计算下一个值
- ... if self.a > 100: # 退出循环的条件
- ... raise StopIteration()
- ... return self.a # 返回下一个值
- ...
- >>> for n in Fib():
- ... print(n)
__getitem__
:上面Fib()可像list一样用于for循环,如果想像list那样按下标取元素,需要实现__getitem__()
方法。与之对应的是__setitem__()
方法,把对象视作list来对集合赋值,__delitem__()
,用于删除某个元素。
__getattr_
:当调用类的方法或属性时,如果不存在会报错。没有找到属性的情况下,会调用__getattr__
,该方法可把一个类的所有属性和方法全都动态化处理。例如:
- >>> class Student(object):
- ... def __getattr__(self, attr):
- ... if attr=='age':
- ... return 18
- ... if attr=='name':
- ... return lambda:'name'
- ...
- >>> stu.name()
- 'name'
- >>> stu.age
- 18
5. 使用枚举类
通过Enum
类能实现枚举功能。
- >>> from enum import Enum
- >>> Sex=Enum('Sex', ('Male', 'Female')) # 获得Sex类型的枚举类
-
- >>> for name, member in Sex.__members__.items(): # Sex所有成员
- ... print(name, '->', member, '->', member.value)
- ...
- # value属性从1开始自动赋值给枚举成员
- Male -> Sex.Male -> 1
- Female -> Sex.Female -> 2
如果要更精确的控制枚举类型,可以从Enum
中派生自定义类:
- >>> from enum import Enum, unique
- >>>
- >>> @unique # unique装饰器检查没有重复值
- ... class Sex(Enum):
- ... Male=10 # 自定义设置value
- ... Female=20
-
- # 使用枚举类型
- >>> print(Sex.Male)
- Sex.Male
- >>> print(Sex.Male.value)
- 10
- >>> print(Sex(10)) # 根据value值获取枚举常量
- Sex.Male
九、错误处理
一般语言的错误处理机制类似:try...except...finally...
,大致实现如下:
- try:
- 运行代码...
- except ValueError as e:
- # try中代码如果出错,会跳转至except语句块处理对应的异常
- finally:
- # 执行完except后,最终会到finally语句块(不出现错误,也会执行finally)
错误也是一个class,所有错误类型基本继承自BaseException
,在使用except时,也会将错误的子类一起捕获。如果错误没有被捕获,那么会按调用栈一层层往上抛,最终被python解释器捕获并打印错误日志,然后程序退出,所以我们捕获错误时,可以将错误堆栈打印出来,这样便于分析错误原因,同时也能让程序继续下去。
我们编写函数时,也可以通过raise
主动抛出错误:
- def foo(s):
- n = int(s)
- if n==0:
- raise ValueError('invalid value: %s' % s)
- return 10 / n
十、IO编程
IO即指Input/Output,IO中,Stream流是一个重要的概念,可理解为一个一个水管,数据就是水管里的水,只能单向流动
- Input Stream 就是数据从外面(磁盘、网络)流进内存
- Output Stream 就是数据从内存流到外面
1. 文件读写
读写文件就是请求OS打开一个文件对象(成为文件描述符),然后通过OS提供的接口从文件对象中读写数据。
读文件:
- >>> try:
- # 以读文件方式打开文件对象,如果文件不存在,会抛出IOError错误
- ... f = open('/Users/butterfly/test.txt', 'r')
- ... print(f.read()) # 一次读取文件的全部内容到内存中
- ... finally:
- ... if f:
- # 最终,要关闭文件,文件使用后必须关闭,因为文件对象会占用OS的资源,并且OS同一时间打开的文件数量是有限制的
- ... f.close()
使用try...finally
比较繁琐,可使用with
简化,会自动调用close方法:
- >>> with open('/Users/zhangqi/test.txt', 'r') as f:
- ... print(f.read())
读文件时,可以通过read(size)
指定每次最多读取size个字节内容。readline()
每次读取一行内容。读取二进制文件时,可以指定rb
模式打开文件。open()方法可以通过encoding
参数指定读取文件的编码方式,errors
指定读取出现错误时如何处理:
- >>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
写文件:
- >>> with open('/Users/zhangqi/test.txt', 'w') as f:
- ... f.write('hello world')
写文件与读文件唯一区别在于,传入标识符w
或者wb
表示写文本文件或二进制文件。当写文件时,OS往往不会立即把数据写入磁盘,而是缓存在内存中,在空闲时候再慢慢写入,只有调用close()
时,OS才保证将没写入的数据写入到磁盘。所以需要with
来保证最后会close。
此外,可以给open()
方法传入encoding
参数将字符串转换为指定编码,w
模式写文件会覆盖已有文件,如果想追加内容到文件末尾,可以传入a
以追加(append)模式写入。
2. StirngIO和BytesIO
StringIO可实现在内存中读写str。写str到StringIO:
- >>> from io import StringIO
- >>> f = StringIO()
- >>> f.write('hello\nworld\n') # 像文件一样写入
- 12
- >>> print(f.getvalue()) # 获取写入后的str
- hello
- world
操作二进制数据,就需要使用BytesIO:
- >>> from io import BytesIO
- >>> f = BytesIO()
- >>> f.write('中文'.encode('utf-8')) # 写入经过utf-8编码的bytes到BytesIO
- 6
- >>> print(f.getvalue())
- b'\xe4\xb8\xad\xe6\x96\x87'
3.操作文件和目录
os
模块封装了操作系统的目录和文件操作,在内部调用了操作系统提供的接口函数。下面是一些常用方法:
- # 环境变量
- >>> import os
- >>> os.environ # 查看操作系统中定义的环境变量
- >>> os.environ.get('PATH') # 获取某个环境变量中的值
-
- # 目录操作
- >>> os.path.abspath('.') # 查看当前目录的绝对路径
- >>> os.mkdir('/Users/michael/testdir') # 创建一个新目录
- >>> os.rmdir('/Users/michael/testdir') # 删除一个目录
- # 将路径拆分为两部分,后一部分是最后级别的目录或文件名
- >>> os.path.split('/Users/michael/testdir/file.txt')
- ('/Users/michael/testdir', 'file.txt')
- # 通过splitext可直接获得文件的扩展名
- >>> os.path.splitext('/path/to/file.txt')
- ('/path/to/file', '.txt')
-
- # 文件操作
- >>> os.rename('test.txt', 'test.py') # 对文件重命名
- >>> os.remove('test.py') # 删除文件
-
- # 可方便的列举当前目录下的.py文件
- >>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']