Python中级使用
函数
调用函数
- 参数可以多个
>>> max(1, 2) 2 >>> max(2, 3, 1, -5) 3- 参数类型需要一致
>>> abs('a') TypeError: bad operand type for abs(): 'str'- 函数调用
>>> abs(100) 100 >>> abs(-20) 20数据类型转换
>>> 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
定义函数
要使用
def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回def my_abs(x): if x >= 0: return x else: return -x print(my_abs(-99))
空函数:定义一个什么事也不做的空函数,可以用
pass语句def nop(): passpass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来
返回多个值
需要从一个点移动到另一个点,给出坐标、位移和角度,就计算出新的坐标:
import math def move(x, y, step, angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx, ny
函数参数
普通形式
def power(x, n): s = 1 while n > 0: n = n - 1 s = s * x return s
默认参数:降低调用函数的难度
def power(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s >>> power(5) 25一是必选参数在前,默认参数在后,否则Python的解释器会报错
二是如何设置默认参数
- 多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数
调用
- 当不按顺序提供部分默认参数时,需要把参数名写上。比如调用
enroll('Adam', 'M', city='Tianjin') - 按顺序提供默认参数,比如调用
enroll('Bob', 'M', 7),意思是,除了name,gender这两个参数外,最后1个参数应用在参数age上,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的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了特别注意
定义默认参数要牢记一点:默认参数必须指向不变对象!
def add_end(L=None): if L is None: L = [] L.append('END') return L>>> add_end() ['END'] >>> add_end() ['END']可变参数:允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum在参数前面加了一个
*号。在函数内部,参数numbers接收到的是一个tuple>>> calc(1, 2) 5 >>> calc() 0已经有一个list或者tuple,要调用一个可变参数
>>> nums = [1, 2, 3] >>> calc(*nums) 14*nums表示把nums这个list的所有元素作为可变参数传进去
关键字参数
允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw)>>> person('Michael', 30) name: Michael age: 30 other: {} >>> person('Adam', 45, gender='M', job='Engineer') name: Adam age: 45 other: {'gender': 'M', '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
命名关键字参数
限制关键字参数的名字,就可以用命名关键字参数
def person(name, age, *, city, job): print(name, age, city, job)命名关键字参数需要一个特殊分隔符
*,*后面的参数被视为命名关键字参数。调用方式如下:
>>> person('Jack', 24, city='Beijing', job='Engineer') Jack 24 Beijing Engineer函数定义中有一可变参数,后面的命名关键字参数就不需要一个特殊分隔符
*:def person(name, age, *args, city, job): print(name, age, args, city, job)命名关键字参数必传入参数名,和位置参数不同。没有传入参数名,调用报错:
>>> person('Jack', 24, 'Beijing', 'Engineer') TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job): print(name, age, city, job)由于命名关键字参数
city具有默认值,调用时,可不传入city参数:>>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer
参数组合
- 顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
递归函数:一个函数在内部调用自身本身
def fact(n): if n==1: return 1 return n * fact(n - 1)
高级特性:减少代码量
切片
用于
list及其变种,进行连续指定索引的操作[前索引:后索引:间隔]取指定索引范围的操作
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']>>> L[0:3] ['Michael', 'Sarah', 'Tracy']L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3>>> L[:3] ['Michael', 'Sarah', 'Tracy']第一个索引是
0,还可以省略>>> L[-2:] ['Bob', 'Jack'] >>> L[-2:-1] ['Bob']同样支持倒数切片,倒数第一个元素的索引是
-1>>> L[:10:2] [0, 2, 4, 6, 8]前10个数,每两个取一个
>>> (0, 1, 2, 3, 4, 5)[:3] (0, 1, 2)tuple也可以用切片操作,只是操作的结果仍是tuple
>>> 'ABCDEFG'[:3] 'ABC' >>> 'ABCDEFG'[::2] 'ACEG'字符串
'xxx'也可以看成是一种list,每个元素就是一个字符
迭代
通过
for ... in循环遍历比如
dict就可以迭代:>>> d = {'a': 1, 'b': 2, 'c': 3} >>> for key in d: ... print(key) ... a c b字符串也是可迭代对象,可以作用于
for循环:>>> for ch in 'ABC': ... print(ch) ... A B C
判断是否可迭代对象呢?方法是通过
collections.abc模块的Iterable类型判断:>>> from collections.abc import Iterable >>> isinstance('abc', Iterable) # str是否可迭代 True >>> isinstance([1,2,3], Iterable) # list是否可迭代 True >>> isinstance(123, Iterable) # 整数是否可迭代 FalsePython内置的
enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身>>> for i, value in enumerate(['A', 'B', 'C']): ... print(i, value) ... 0 A 1 B 2 Cfor循环里,同时引用了两个变量,比如下面的代码:>>> for x, y in [(1, 1), (2, 4), (3, 9)]: ... print(x, y) ... 1 1 2 4 3 9
列表生成式
Python内置的非常简单却强大的可以用来创建list的生成式
列表生成式则可以用一行语句代替循环生成上面的list:
>>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]列表生成式也可以使用两个变量来生成list:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> [k + '=' + v for k, v in d.items()] ['y=B', 'x=A', 'z=C']使用两层循环,可以生成全排列:
>>> [m + n for m in 'ABC' for n in 'XYZ'] ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
>>> [x for x in range(1, 11) if x % 2 == 0] [2, 4, 6, 8, 10]在一个列表生成式中,
for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else>>> [x if x % 2 == 0 else -x for x in range(1, 11)] [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
生成器
列表生成式,创建一个列表。受到内存限制,列表容量是有限。创建包含100万个元素列表,占用很大的存储,如果仅仅访问前面几个元素,绝大多数元素占用空间浪费
Python中,这种一边循环一边计算的机制,称为生成器:generator;generator保存的是算法
创建一个generator
只要把一个列表生成式的
[]改成(),就创建了一个generator第一种方法>>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x * x for x in range(10)) >>> next(g) 0 >>> next(g) 1 >>> next(g) 4 或者 >>> for n in g: ... print(n) ... 0 1 4 9 16 25 36 49 64 81把
fib函数变成generator函数,只需要把print(b)改为yield b就可以第二种以斐波那契数列为例
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n = n + 1 return 'done'改为
def fib(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 return 'done'理解
普通函数是顺序执行,遇到
return语句或者最后一行函数语句就返回。generator函数,在每次调用
next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
请务必注意:调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator
eg:先错误后正确
def odd(): print('step 1') yield 1 print('step 2') yield(3) print('step 3') yield(5)>>> next(odd()) step 1 1 >>> next(odd()) step 1 1 >>> next(odd()) step 1 1>>> g = odd() >>> next(g) step 1 1 >>> next(g) step 2 3 >>> next(g) step 3 5
迭代器
可以直接作用于
for循环的数据类型有以下几种:一类是集合数据类型,如
list、tuple、dict、set、str等;一类是
generator,包括生成器和带yield的generator function。这些可以直接作用于
for循环的对象统称为可迭代对象isinstance()判断一个对象是否是**Iterable**对象:
>>> from collections.abc import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance('abc', Iterable) True >>> isinstance((x for x in range(10)), Iterable) True >>> isinstance(100, Iterable) False使用
isinstance()判断一个对象是否是**Iterator**对象:>>> from collections.abc import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>> isinstance('abc', Iterator) False可作用于
next()函数的对象都是Iterator类型把
list、dict、str等Iterable变成Iterator可以使用iter()函数:>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True
总结
- 集合数据类型如
list、dict、str等是Iterable但不是Iterator
- 集合数据类型如
函数式编程
特点:允许把函数本身作为参数传入另一个函数,还允许返回一个函数
高阶函数
特点:变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数
变量指向函数
>>> f = abs >>> f(-10) 10变量
f已经指向abs函数本身。调用abs()函数和调用变量f()完全相同
函数名也是变量
函数名就是指向函数的变量
对
abs()函数,可以把函数名abs看成变量,指向可以计算绝对值的函数>>> abs = 10 >>> abs(-10) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable实际代码不是这样写,只为说明
高阶函数总结举例
def add(x, y, f): return f(x) + f(y) print(add(-5, 6, abs))
map()和reduce()函数map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回>>> def f(x): ... return x * x ... >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81]reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)>>> from functools import reduce >>> def add(x, y): ... return x + y ... >>> reduce(add, [1, 3, 5, 7, 9]) 25
返回函数
特点:把函数作为结果值返回
eg:如果不需要立刻求和,可以不返回求和的结果,而是返回求和的函数
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum我们调用
lazy_sum()时,返回的并不是求和结果,而是求和函数:>>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function lazy_sum.<locals>.sum at 0x101c6ed90>调用函数
f时,才真正计算求和的结果:>>> f() 25调用
lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:>>> f1 = lazy_sum(1, 3, 5, 7, 9) >>> f2 = lazy_sum(1, 3, 5, 7, 9) >>> f1==f2 False
闭包
- 定义:返回的函数在其定义内部引用了局部变量
args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用 - 注意:返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量
- 定义:返回的函数在其定义内部引用了局部变量
匿名函数
定义:传入函数时,有些时候,不需要显式地定义函数
使用:
关键字
lambda表示匿名函数,冒号前面的x表示函数参数>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) [1, 4, 9, 16, 25, 36, 49, 64, 81]匿名函数
lambda x: x * x实际上就是:def f(x): return x * x限制:只能有一个表达式,不用写
return,返回值是该表达式的结果也是一个函数对象,可以把匿名函数赋值给一个变量,再利用变量来调用该函数
>>> f = lambda x: x * x >>> f <function <lambda> at 0x101c6ef28> >>> f(5) 25可以把匿名函数作为返回值返回
def build(x, y): return lambda: x * x + y * y
装饰器Decorator
定义:这种在代码运行期间动态增加功能的方式
函数是对象,函数对象可被赋值给变量,所以,变量能调用该函数
>>> def now(): ... print('2024-6-1') ... >>> f = now >>> f() 2024-6-1函数对象有
__name__属性(注意:前后各两个下划线),可拿到函数的名字:>>> now.__name__ 'now' >>> f.__name__ 'now'decorator是返回函数的高阶函数。所以,要定义一个能打印日志的decorator
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper上面的
log,它是一个decorator,接受一个函数作为参数,并返回一个函数@log def now(): print('2024-6-1')调用
now()函数,不仅运行now()函数本身,还运行now()函数前打印一行日志:>>> now() call now(): 2024-6-1
偏函数
定义:
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2举例:
>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单最后,创建偏函数时,实际可以接收函数对象、**
*args和**kw**这3个参数int2 = functools.partial(int, base=2)实际上固定了int()函数的关键字参数
base,也就是:int2('10010')相当于:
kw = { 'base': 2 } int('10010', **kw)当传入:
max2 = functools.partial(max, 10)实际上会把
10作为*args的一部分自动加到左边,也就是:max2(5, 6, 7)相当于:
args = (10, 5, 6, 7) max(*args)结果为
10
模块
在python中,一个.py文件就称之为一个模块(Module)
一个
abc.py的文件就是一个名字叫abc的模块每一个包目录下面都有一个
__init__.py的文件,这个文件必须存在,否则,Python把这个目录当普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompanymycompany ├─ __init__.py ├─ abc.py └─ xyz.py
使用模块
编写一个
hello的模块#!/usr/bin/env python3 # -*- coding: utf-8 -*- ' a test module ' __author__ = 'Michael Liao' import sys def test(): args = sys.argv if len(args)==1: print('Hello, world!') elif len(args)==2: print('Hello, %s!' % args[1]) else: print('Too many arguments!') if __name__=='__main__': test()第1行和第2行是标准注释
- 第1行注释可以让这个
hello.py文件直接在Unix/Linux/Mac上运行 - 第2行注释表示.py文件本身使用标准UTF-8编码
- 第1行注释可以让这个
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用
__author__变量把作者写进去以上六行就是Python模块的标准文件模板,当然也可以全部删掉不写
使用
sys模块的第一步,就是导入该模块:import syssys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:运行
python3 hello.py获得的sys.argv就是['hello.py'];运行
python3 hello.py Michael获得sys.argv就是['hello.py', 'Michael']if __name__=='__main__': test()当在命令行运行
hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码$ python3 hello.py Hello, world! $ python hello.py Michael Hello, Michael!$ python3 Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 23 2015, 02:52:03) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import hello >>>导入时,没有打印
Hello, word!,因为没有执行test()函数。调用
hello.test()时,才能打印出Hello, word!:>>> hello.test() Hello, world!
作用域
- 一个模块中,可能会定义很多函数和变量,但有的函数和变量希望给别人使用,有的函数和变量希望仅仅在模块内部使用
- 正常的函数和变量名是公开(public),可以被直接引用,比如:
abc,x123,PI等 - 类似
__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,自己的变量一般不要用这种变量名 - 类似
_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等- private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量
- 外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public
安装第三方模块
pip install 你想要安装的第三方库