函数基础
创建函数
def 语句
- 函数用def语句创建,语法如下:
def function_name(arguments):
"function_documentation_string"
function_body_suite
- 标题行由def关键字,函数的名字,以及参数的集合(如果有的话)组成
- def子句的剩余部分包括了一个虽然可选但是强烈推荐的文档字串,和必需的函数体
前向引用
- 函数不允许在函数未声明之前对其进行引用或者调用
def foo():
print('in foo')
bar()
foo() #报错,因为bar没有定义
----------------------------------------------------
def foo():
print('in foo')
bar()
def bar():
print('in bar’)
foo() #正常执行,虽然bar的定义在foo定义后面
调用函数
函数操作符
- 使用一对圆括号()调用函数,如果没有圆括号,只是对函数的引用
- 任何输入的参数都必须放置在括号中
>>> def foo():
... print('Hello world!')
...
>>> foo()
Hello world!
>>> foo
<function foo at 0x7f18ce311b18>
关键字参数
直接给定一个参数名称为位置参数,给定了key=val形式的参数称作关键字参数
- 关键字参数的概念仅仅针对函数的调用
- 这种理念是让调用者通过函数调用中的参数名字来区分参数
- 这样规范允许参数缺失或者不按顺序
>>> def get_info(name, age):
... print('%s is %s years old.' % (name, age))
>>> get_info('bob', 20) # OK
>>> get_info(20, 'bob') # 语法正确,语义不对
>>> get_info(age=20, 'bob') # Error,关键字参数必须在后
>>> get_info(20, name='bob') # Error,name得到了多个值
>>> get_info('bob', age=20) # OK
>>> get_info(age=20, name='bob') # OK
>>> get_info('bob') # Error,参数不够
>>> get_info('bob', 20, 100) # Error,参数太多了
参数组
参数个数不确定的函数
- python允许程序员执行一个没有显式定义参数的函数
- 相应的方法是通过一个把元组(非关键字参数)或字典(关键字参数)作为参数组传递给函数
# 参数名前加*号,表示用元组接收参数
>>> def func1(*args):
... print(args)
...
>>> func1()
()
>>> func1('hao')
('hao',)
>>> func1('hao', 123)
('hao', 123)
# 参数名前加**号,表示用字典接收参数
>>> def func2(**kwargs):
... print(kwargs)
...
>>> func2()
{}
>>> func2(name='bob', age=20)
{'name': 'bob', 'age': 20}
# 传参时加上*号或**号表示把序列或字典拆开
>>> def add(a, b):
... return a + b
...
>>> nums = [10, 20]
>>> add(nums) # 报错,把nums传给a,b没有得到数据
>>> add(*nums) # 将nums拆开,得到10和20,分别赋值给a和b
30
简单的加减法数学游戏
1. 随机生成两个100以内的数字
2. 随机选择加法或是减法
3. 总是使用大的数字减去小的数字
4. 如果用户答错三次,程序给出正确答案
from random import randint, choice
def exam():
nums = [randint(1, 100) for i in range(2)] # 生成两个数字的列表
nums.sort(reverse=True) # 降序排列
op = choice('+-') # 随机选加减法
# 计算出正确答案
if op == '+':
result = nums[0] + nums[1]
else:
result = nums[0] - nums[1]
# 让用户作答
prompt = '%s %s %s = ' % (nums[0], op, nums[1])
counter = 0
while counter < 3:
try:
answer = int(input(prompt))
except: # 可以捕获所有异常
print()
continue
# 判断对错
if answer == result:
print('你真棒!!!')
break
print('不对哟')
counter += 1
else:
print('The Anser is: %s%s' % (prompt, result))
def main():
while True:
exam()
try:
yn = input('Continue(y/n)? ').strip()[0] # 去除空白字符后取第一个字符
except IndexError:
yn = 'y'
except (KeyboardInterrupt, EOFError):
yn = 'n'
if yn in 'nN':
print('\nBye-bye')
break
if __name__ == '__main__':
main()
匿名函数
lambda
- python允许用lambda关键字创造匿名函数
- 匿名是因为不需要以标准的def方式来声明
- 一个完整的lambda“语句”代表了一个表达式,这个表达式的定义体必须和声明放在同一行
>>> def add(x, y):
... return x + y
...
>>> myadd = lambda x, y: x + y
>>> myadd(10, 20)
30
>>> add(20, 30)
50
filter()函数
用于过滤数据。filter(func, seq),将seq中的每一项作为func函数的参数进行过滤,如果func的返回值是True就留下来,否则过滤掉
- filter(func, seq):调用一个布尔函数func来迭代遍历每个序列中的元素;返回一个使func返回值为true的元素的序列
- 如果布尔函数比较简单,直接使用lambda匿名函数就显得非常方便了
>>> from random import randint
>>> nums = [randint(1, 100) for i in range(10)]
>>> nums
[89, 67, 56, 63, 66, 23, 54, 40, 69, 6]
>>> def func1(x):
... return True if x % 2 == 0 else False
>>> list(filter(func1, nums)) # 将奇数过滤掉
[56, 66, 54, 40, 6]
>>> list(filter(lambda x: True if x % 2 == 0 else False, nums))
[56, 66, 54, 40, 6]
map()函数
用于加工数据。map(func, seq),将seq中的每一项作为func的参数,func将数据加工处理后返回。
- map(func, seq):调用一个函数func来迭代遍历每个序列中的元素;返回一个经过func处理过的元素序列
- 如果布尔函数比较简单,直接使用lambda匿名函数就显得非常方便了
>>> def func2(x):
... return x * 2
>>> list(map(func2, nums))
[178, 134, 112, 126, 132, 46, 108, 80, 138, 12]
>>> list(map(lambda x: x * 2, nums))
[178, 134, 112, 126, 132, 46, 108, 80, 138, 12]
变量作用域
全局变量
在函数外面的变量是全局变量,它从定义开始到程序结束一直可见可用
- 标识符的作用域是定义为其声明在程序里的可应用范围,也就是变量的可见性
- 在一个模块中最高级别的变量有全局作用域
- 全局变量的一个特征是除非被删除掉,否则它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的
>>> x = 10
>>> def func1():
... print(x)
...
>>> func1()
10
局部变量
函数内部的变量是局部变量,只能在函数内部使用
- 局部变量只时暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动
- 当一个函数调用出现时,其局部变量就进入声明它们的作用域。在那一刻,一个新的局部变量名为那个对象创建了
- 一旦函数完成,框架被释放,变量将会离开作用域
>>> def func2():
... y = 100
... print(y)
...
>>> func2()
100
>>> print(y) # NameError,局部变量不能在全局使用
如果局部与全局有相同名称的变量,那么函数运行时,局部变量的名称将会把全局变量名称遮盖住
>>> x = 10
>>> def func3():
... x = 'hello world'
... print(x)
...
>>> func3()
hello world
>>> print(x)
10
global语句
如果需要在局部改变全局变量,需要使用global关键字
>>> x = 10
>>> def func4():
... global x
... x = 'hello world'
... print(x)
...
>>> func4()
hello world
>>> print(x)
hello world
程序在运行时,将会按这样的顺序查找名称:局部、全局、内建。
函数式编程
偏函数
偏函数是指通过fuctools.partial进行改造现有函数,生成新函数。
- 偏函数的概念是将函数式编程的概念和默认参数以及可变参数结合在一起
- 一个带有多个参数的函数,如果其中某些参数基本上固定的,那么就可以通过偏函数为这些参数赋默认值
# int()函数默认可以将字符类型的数字转成10进制整数
>>> int('1010')
1010 # 一千零一十
>>> int('1010', base=2) # 通过base=2说明1010是2进制数
10 # 输出为10进制数
# 改造int函数,把base=2固定下来,生成名为int2的新函数
>>> from functools import partial
>>> int2 = partial(int, base=2)
>>> int2('1010')
10
# 改造函数,将参数固定下来
>>> def add(a, b, c, d, e):
... return a + b + c + d + e
...
>>> add(10, 20, 30, 40, 1) # 每次调用函数,前4项的值都是一样的
101
>>> add(10, 20, 30, 40, 2)
102
>>> myadd = partial(add, 10, 20, 30, 40) # 改造add函数,固定前4个参数值
>>> myadd(1)
101
>>> myadd(2)
102
递归函数:了解性内容
如果一个函数的内部又包括了对自身的调用就是递归函数。一般来说,递归可以用循环替代。
5!=5x4x3x2x1 # 5的阶乘
5!=5x4!
5!=5x4x3!
5!=5x4x3x2!
5!=5x4x3x2x1!
快速排序:
- 假设第一个数是中间值,赋值给middle
- 遍历剩余的数字,比middle小的放到smaller列表,比middle大的放到larger列表
- 把smaller、middle和larger拼接起来
- smaller和larger继续使用相同的方法进行排序
- 如果列表只有一项或是空的就不用再排了,直接将列表返回
生成器
- 从句法上讲,生成器是一个带yield语句的函数
- 一个函数或者子程序只返回一次,但一个生成器能暂停执行并返回一个中间的结果
- yield 语句返回一个值给调用者并暂停执行
- 当生成器的next()方法被调用的时候,它会准确地从离开地方继续
- 与迭代器相似,生成器以另外的方式来运作
- 当到达一个真正的返回或者函数结束没有更多的值返回,StopIteration异常就会被抛出
# 与列表解析语法一样,只是把[]换成()
>>> ('192.168.1.%s' % i for i in range(1, 255))
<generator object <genexpr> at 0x7ffa5e95e780>
>>> ips = ('192.168.1.%s' % i for i in range(1, 255))
>>> for ip in ips:
... print(ip)
- 函数的形式
与普通的函数有所区别。一般来说,函数通过return返回一个值;生成器函数可以通过yield关键字返回很多中间结果。
>>> def mygen():
... yield 100
... a = 10 + 20
... yield a
... yield 300
>>> mg = mygen()
>>> for n in mg:
... print(n)
...
100
30
300
模块
什么是模块
- 模块支持从逻辑上组织python代码
- 当代码量变得相当大的时候, 最好把代码分成一些有组织的代码段
- 代码片段相互间有一定的联系,可能是一个包含数据成员和方法的类,也可能是一组相关但彼此独立的操作函数
- 这些代码段是共享的,所以python允许“调入”一个模块,允许使用其他模块的属性来利用之前的工作成果,实现代码重用
模块文件
- 说模块是按照逻辑来组织python代码的方法,文件是物理层上组织模块的方法
- 一个文件被看作是一个独立模块,一个模块也可以被看作是一个文件
- 模块的文件名就是模块的名字加上扩展名 . py
导入模块
搜索路径
- 模块的导入需要一个叫做“路径搜索”的过程
- python在文件系统“预定义区域”中查找要调用的模块
- 搜索路径在sys.path中定义
导入模块时,Python将会到sys.path定义的路径下查找模块,如果查到则导入,否则报错。
>>> import sys
>>> sys.path # 空串表示当前路径
['', '/usr/local/lib/python36.zip', '/usr/local/lib/python3.6', '/usr/local/lib/python3.6/lib-dynload', '/usr/local/lib/python3.6/site-packages']
模块导入方法
• 使用import导入模块
• 可以在一行导入多个模块,但是可读性会下降
• 可以只导入模块的某些属性
• 导入模块时,可以为模块取别名
>>> import pickle as p
当我们自己写的文件需要像标准模块一样,能在任意位置导入,可以
- 方法一:将自己写的模块文件放到site-packages中
- 方法二:定义环境变量PYTHONPATH=/path/to/your/modules
[[email protected] day02]# pwd
/var/ftp/nsd2019/nsd1902/python02/day02
[[email protected] day02]# ls qsort.py
qsort.py
[[email protected] day02]# export \ PYTHONPATH=/var/ftp/nsd2019/nsd1902/python02/day02
[[email protected] day02]# cd /tmp/
[[email protected] tmp]# python3
>>> import qsort # 成功导入
导入和加载
• 当导入模块时,模块的顶层代码会被执行
• 一个模块不管被导入(import)多少次,只会被加载(load)一次
[[email protected] ~]# cat foo.py
hi = 'hello'
print(hi)
[[email protected] ~]# python3
>>> import foo
Hello #第一次导入,执行print语句
>>> import foo #再次导入,print语句不再执行
内置模块
hashlib模块
• hashlib用来替换md5和sha模块,并使他们的API一致,专门提供hash算法
• 包括md5、sha1、sha224、sha256、sha384、sha512,使用非常简单、方便
通过hashlib计算md5:
>>> import hashlib
>>> m = hashlib.md5(b'123456')
>>> m.hexdigest()
'e10adc3949ba59abbe56e057f20f883e'
# 计算文件的md5值
>>> with open('/etc/passwd', 'rb') as fobj:
... data = fobj.read()
...
>>> m = hashlib.md5(data)
>>> m.hexdigest()
'decb544ed171583bb1d7722500910d9e'
# 多次更新,计算数据的md5值
>>> m1 = hashlib.md5()
>>> m1.update(b'12')
>>> m1.update(b'34')
>>> m1.update(b'56')
>>> m1.hexdigest()
'e10adc3949ba59abbe56e057f20f883e'