文章目录
函数的基本概念
写一段代码实现某个小功能, 将代码集中在一起,做一个命名,下次直接根据命名名称使用这个代码块,这就是函数
作用
- 方便代码重用
- 分解任务,简化程序逻辑
- 使代码更加模块化
函数分类
- 内建函数: python中已经存在的函数
- 三方函数: 其他人写的函数,我们可以直接调用
- 自定义函数:自己根据需求编写的函数
函数的定义和调用
定义函数的语法:
def 函数名 ([参数列表]) :
'''文档字符串'''
函数体/若干语句
简述:
- 使用def来定义函数,然后就是一个空格和函数名称,python执行def时,会创建一个函数对象,并绑到函数名变量上
- 括号内的参数有多个时用逗号隔开,调用时参数必须和参数列表一一对应、
- 如果函数需要返回数据,可以使用return语句返回值,不包含return语句则返回None
调用函数:
先定义函数,也就是先使用def创建函数对象,当然内置函数对象是会自动创建的
创建完函数对象,可以直接使用函数名称进行调用
参数
形参和实参
形参分为: 位置参数,默认参数,可变参数,关键字参数
形参是在定义函数时给出的变量,实参是调用函数时传递的真实数据
位置参数
形参和实参必须保持一致,最好按位置传参,如果位置不对应需要指定说明
def register(name, age):
print(name, age)
# 正确传参方法
register('zhangsan', '18')
register(age='18', name='zhangsan')
# 错误传参方法
register('18', 'zhangsan')
默认参数
形参和实参可以不一致,如果不想使用默认的参数,在调用函数的时候可以指定
调用函数的时候,如果默认参数的值没有传入,则直接使用默认值
def register(name, age=18):
print(name, age)
register('zhangsan')
register('zhangsan', age=20)
可变参数
形参参数名前面加*,可变参数可允许传入0个或者任意个参数,这些可变参数在函数调用时,自动组装成一个元组
def register(*name):
print(name)
register('zhangsan', 'lisi', 'wangwu')
关键字参数
**kwargs代表关键字参数,可以传入任意多个key-value,是一个字典
def register(name, age, **kwargs):
print(name, age, kwargs)
register('zhangsan', 18, a=1, b=2)
c = dict(a=1, b=2)
register('zhangsan', 18, **c)
变量的作用域
局部变量
函数内使用的变量,只能在函数内使用
- 调用函数时,函数作用域创建,函数执行完毕,作用域销毁
- 每次调用函数都会创建一个新的函数作用域
- 函数作用域内可以访问到全局作用域的变量,但是函数外无法访问函数内的变量
- 函数作用域内访问变量或者函数,会从内到外逐层寻找,一直到全局作用域
- 函数作用域内也有声明提前的特性,对变量和函数都适用,此时函数作用域相当
全局变量
函数外定义的变量,可以在不同函数内使用
- 在页面打开时创建,页面关闭时销毁
- 编写在script标签中的变量和函数,作用域作为全局,在页面的任意位置都可以访问到
- 在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用
- 全局作用域中声明的变量和函数会作为window对象的属性和方法保存
全局变量和局部变量效率测试
局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候
在特别强调效率的地方或者循环次数较多的地方,可以通过将全局变量转为局部变量提高运行速度
import math
import time
def test1():
start = time.time()
for i in range(10000000):
math.sqrt(30)
end = time.time()
print("耗时{0}".format((end - start)))
def test2():
b = math.sqrt
start = time.time()
for i in range(10000000):
b(30)
end = time.time()
print("耗时{0}".format((end - start)))
test1()
test2()
----------------------------------
耗时0.847449541091919
耗时0.6533384323120117
深拷贝和浅拷贝
浅拷贝,指的是重新分配一块内存,创建一个新的对象,但里面的元素是原对象中各个子对象的引用
浅拷贝的方法
- 通过数据类型本身的构造器,例如 list set dict
- 对于可变序列,可以使用切片操作完成浅拷贝
- 使用copy.copy()函数完成,适用于任何数据类型
特征
- 构造器完成的浅拷贝,因为实际上是新分配了一块内存,所以和原来的变量内存不一样,直接==判断为false,但是两个变量的元素值是一样的
- 元组使用切片操作或者tuple()不会创建一份浅拷贝,因为它开辟新的内存存储的是原对象的引用,而没有创建新的对象来存储原对象的子对象的引用,所以不是浅拷贝。相反它会返回一个指向相同元组的引用。对字符串使用 str() 或者切片操作符 ‘:’,原理和 元组相同
深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联
以 copy.deepcopy() 来实现对象的深度拷贝
递归函数
什么是递归
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数
- 递归函数必须有一个明确的结束条件
- 每进入更深一层的递归时,问题规模相对于上一次递归都应减少
- 相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)
- 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
看个例子,比如我们用循环来实现叠加:
def sum1(n):
sum = 0
for i in range(1,n + 1):
sum += i
return sum
如果用递归来实现的话
def sum2(n):
if n > 0:
return n + sum2(n - 1)
else:
return 0
看这个例子大概能理解递归函数是什么,怎么写了,那递归函数有什么有缺点呢
递归函数的优缺点
首先,优点:定义简单,逻辑清晰
理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
缺点: 使用时需要注意防止栈溢出
在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
Lambda函数
lambda函数也叫匿名函数,即,函数没有具体的名称
def f(x):
return x ** 2
print(f(4))
g = lambda x: x ** 2
print(g(4))
lambda函数和普通的函数相比,其实就是省去了函数名称,同时这样的匿名函数,又不能共享在别的地方调用
Lambda的作用
- 使用Python写一些执行脚本时,使用lambda可以省去定义函数的过程,让代码更加精简
- 对于一些抽象的,不会别的地方再复用的函数,有时候给函数起个名字也是个难题,使用lambda不需要考虑命名的问题
- 使用lambda在某些时候让代码更容易理解
Lambda基础
lambda语句中,冒号前是参数,可以有多个,用逗号隔开,冒号右边的返回值
from functools import reduce
foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]
print(filter(lambda x: x % 3 == 0, foo))
print(map(lambda x: x * 2 + 10, foo))
print(reduce(lambda x, y: x + y, foo))
----------------------------------
<filter object at 0x0000025F8F43FFA0>
<map object at 0x0000025F8F43FFD0>
139
在对象遍历处理方面,其实Python的for…in…if语法已经很强大,并且在易读上胜过了lambda。
defaultdict是字典类型,可以为defaultdict设置默认值,可以通过lambda设置默认值
from collections import *
x = defaultdict(lambda:0)
print(x[0])
y =defaultdict(lambda:defaultdict(lambda:0))
print(y[0])
z = defaultdict(lambda:[0,0,0])
print(z[0])
----------------------------------
0
defaultdict(<function <lambda>.<locals>.<lambda> at 0x000001BC7C427DC0>, {})
[0, 0, 0]
eval() 函数
eval() 函数用来执行一个字符串表达式,并返回表达式的值
语法
eval(expression[, globals[, locals]])
expression – 表达式。
globals – 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
locals–变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
返回值:返回表达式计算结果。
举例
n = input()
m = eval(input())
print(type(n))
print(type(m))
print(type(eval(n)))
使用eval()函数,将字符串还原为数字类型,和int()函数的作用类似
m = input()
n = eval(input())
print(type(m))
print(type(n))
print(type(n[0]))
将输入的字符串转为对应的数据类型,列表、元组等数据类型都可以使用这种方式输入
s1 = '3*7'
s2 = 'pow(2,3)'
n = eval(s1)
m = eval(s2)
print(n, m)
对表达式的结果进行计算,返回计算后的值