细说python函数相关的那些事
文章目录
函数
首先要了解的一点就是每个函数其实都是一个对象,即一块存储数据的空间,因为可以在使用时多次调用,所以很方便。
函数的定义
def 函数名(形参1,形参2,...形参n) :
代码块
函数的调用
函数名()
其实也就语法有些许差别,实际上与其他语言大同小异,但是python函数的门道还是不少的,且听博主细细道来。
函数的参数
定长参数
传入参数的数目确定,具体如下:
def sum(a=1,b=2):#下面讨论的定长参数均针对此函数
return a+b;
位置参数
1.如果这样传入1和2,那么计算的就是1+2;
sum(1, 2)
2.如果什么参数都不传,就使用定义函数时的默认值
sum()
关键字参数
使用关键字参数的时候,不必按照顺序传参。
sum(b=2, a=1)
位置参数和关键字参数的混合使用
位置参数一定要在关键字参数之前。
sum(1, b=2)
不定长参数
1.*形参
def sum(*nums):
# 定义一个变量,来保存结果
result = 0
# 遍历元组,并将元组中的数进行累加
for n in nums :
result += n
print(result)
大家可以很明显的看到参数带了一个星号,这就是不定长参数的重点,不定长参数调用的时候应该这么来:
sum(123,456,789,10,20,30,40)
这样在那一串参数传入的时候,就被赋值入nums元组(装包)中,并进行使用。
以上就是一个最典型的不定长参数样例,再来看看下面这个:
def fn2(a,b,*c):
print('a =',a)
print('b =',b)
print('c =',c)
这样就对传参的要求变高了,形参c要的是不定长参数,而a和b依旧只是普通的形参,在调用的时候就要稍加改动:
fn2(1,2,3,4,5,6)
输出:
a = 1
b = 2
c = (3, 4, 5, 6)
那如果可变参数并没有写在最后,而是像这样:
def fn2(a,*b,c):
print('a =',a)
print('b =',b)
print('c =',c)
那么传参的时候就要参考到不可变参数的传参方法:第一个参数给a,中间的都给b,c的传参使用关键字参数就好了。(*形参只能接收位置参数,而不能接收关键字参数)
**形参
# **形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中
# 字典的key就是参数的名字,字典的value就是参数的值
# **形参只能有一个,并且必须写在所有参数的最后
def fn3(b,c,**a) :
print('a =',a)
print('b =',b)
print('c =',c)
fn3(b=1,d=2,c=3,e=10,f=20)
输出:
a = {'d': 2, 'e': 10, 'f': 20}
b = 1
c = 3
参数的解包(拆包)
*参数的拆包
# 参数的解包(拆包)
def fn4(a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
# 创建一个元组
t = (10,20,30)
# 传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递
# 这里要求序列中元素的个数必须和形参的个数的一致
fn4(*t)
**参数的拆包
# 创建一个字典
d = {'a':100,'b':200,'c':300}
# 通过 **来对一个字典进行解包操作
fn4(**d)
在这里还有一个小东西可以科普下:
print(fn4)
print(fn4())
这两行代码的打印是不一样的,第一个打印地址,第二个运行函数并打印返回值。
文档字符串(doc str)
python中有这么一个函数:help()
大家可以试着运行下这行代码:help(print)
运行结果如下:
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
print是系统提供的函数,所以自带文档,那我们写的函数如何添加文档呢?
def fn(a:int,b:bool,c:str='hello') -> int:
'''
这是一个文档字符串的示例
函数的作用:。。。。。
函数的参数:
a,作用,类型,默认值。。。。
b,作用,类型,默认值。。。。
c,作用,类型,默认值。。。。
'''
return 10
好了我现在来解释下上面的操作:
两个’’'之前的就是文档,再次help时就会有doc,那么参数部分的int和bool又是什么呢?
那个不是规定参数只能传int或bool,而是一种文档,当别人help时可以看到,这里最好使用该类型,c:str='hello'
这一行的意思是c的默认值为hellow,str为doc str,可以不用管,-> int
定义的最后这行是提示查看文档的人:此函数的返回值是int类型。
作用域
全局作用域
- 全局作用域在程序执行时创建,在程序执行结束时销毁
- 所有函数以外的区域都是全局作用域
- 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问
函数作用域
- 函数作用域在函数调用时创建,在调用结束时销毁
- 函数每调用一次就会产生一个新的函数作用域
- 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问
做这块的内容就和别的语言是一模一样的,全局变量在任何地方都可以使用,函数内的变量只能在函数内使用。
命名空间(namespace)
命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中每一个作用域都会有一个它对应的命名空间
全局命名空间,用来保存全局变量。函数命名空间用来保存函数中的变量
命名空间实际上就是一个字典,是一个专门用来存储变量的字典locals()
可以获取当前命名空间的变量名和赋值,其实就是一个字典嘛,获取出来的东西就是键值对,甚至可以有这个骚操作:
name=locals
name[a]=100
这样就相当于是定义了一个值为100的变量a(一般不这么用)。
但是你在函数内部却想要看全局的变量怎么办呢?locals()显然是不可以的,那么这个时候可以使用globals()
,这个函数可以在任何位置获取全局命名空间。
递归
关于函数递归,这个应该是函数的应用,其实递归这个东西还是要多写,不然还真不好写,俗话说的好:递归难写好理解。
递归这块就举个栗子:
def factorial(n):
'''
该函数用来求任意数的阶乘
参数:
n 要求阶乘的数字
'''
# 基线条件 判断n是否为1,如果为1则此时不能再继续递归
if n == 1 :
# 1的阶乘就是1,直接返回1
return 1
# 递归条件
return n * factorial(n-1)
高阶函数
这个应该算是一个小重点了吧:
- 接收函数作为参数,或者将函数作为返回值的函数是高阶函数
- 当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函
我个人的理解就是:可以把函数当参数传来传去的就是高阶函数。
这样有什么用呢?
打个比方我现在想写这么一个函数:这个函数可以筛出列表中的偶数,也可以筛出大于三的数,怎么实现呢?
# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]
# 定义一个函数,用来检查一个任意的数字是否是偶数
def fn2(i) :
if i % 2 == 0 :
return True
return False
# 这个函数用来检查指定的数字是否大于5
def fn3(i):
if i > 5 :
return True
return False
def fn(func , lst) :
'''
fn()函数可以将指定列表中的所有偶数获取出来,并保存到一个新列表中返回
参数:
lst:要进行筛选的列表
'''
# 创建一个新列表
new_list = []
# 对列表进行筛选
for n in lst :
# 判断n的奇偶
if func(n) :
new_list.append(n)
# if n > 5 :
# new_list.append(n)
# 返回新列表
return new_list
print(fn(fn2 , l))
这样的一个筛选功能函数其实python内部是有提供的:filter()
,传入函数和列表就可以了filter(fn2,l)
匿名函数
lambda函数表达式专门用来创建一些简单的函数,他是函数创建的又一种方式
语法:lambda 参数列表 : 返回值
匿名函数一般都是作为参数使用,其他地方一般不会使用
lambda+filter
我们可以把lambda和filter相结合:r = filter(lambda i : i > 5 , l)
输出的时候记得转化为列表:print(list(r))
lambda+map
map()函数可以对可跌倒对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回
r=map(lambda a:a+1 , l)
闭包
形成闭包的条件
- 函数嵌套
- 将内部函数作为返回值返回
- 内部函数必须要使用到外部函数的变量
闭包的作用
- 可以将一些私有的数据藏到的闭包中
举个栗子
def make_averager():
# 创建一个列表,用来保存数值
nums = []
# 创建一个函数,用来计算平均值
def averager(n) :
# 将n添加到列表中
nums.append(n)
# 求平均值
return sum(nums)/len(nums)
return averager
averager = make_averager()#如此引用
print(averager(10))
print(averager(20))
num=[]#这里不会影响到输出,毕竟你全局num关我函数num什么事?两者不在一个命名空间内!
print(averager(30))
print(averager(40))
这样外部就不可能改变到闭包中的数据(数据更加安全)。
装饰器
现在有着这么一个需求:
希望函数可以在计算前,打印开始计算,计算结束后打印计算完毕
我们可以直接通过修改函数中的代码来完成这个需求,但是会产生以下一些问题
① 如果要修改的函数过多,修改起来会比较麻烦
② 并且不方便后期的维护
③ 并且这样做会违反开闭原则(OCP)
程序的设计,要求开发对程序的扩展,要关闭对程序的修改。
在这样的要求下,我们可以怎样完成这个需求呢:
def add(a , b):
'''
求任意两个数的和
'''
r = a + b
return r
def new_add(a,b):
'''
扩展函数功能
'''
print('计算开始~~~')
r = add(a,b)
print('计算结束~~~')
return r
但是这样有一个弊端:你要是有100个函数是不是要写100个扩展啊!
所以就要用到装饰器:
def begin_end(old):
'''
用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
参数:
old 要扩展的函数对象
'''
# 创建一个新函数
def new_function(*args , **kwargs):#参数的装包(这样就可以实现传入参数的随意变化)
print('开始执行~~~~')
# 调用被扩展的函数
result = old(*args , **kwargs)#参数拆包
print('执行结束~~~~')
# 返回函数的执行结果
return result
# 返回新函数
return new_function
f = begin_end(fn)
f2 = begin_end(add)
f3 = begin_end(mul)
其实就是函数的嵌套,我们把这样的函数就叫做装饰器。
还有这么一种用法:
@begin_end#前面定义好的装饰器
def say_hello():
print('大家好~~~')
say_hello()# 那么这个函数再用就是装饰后的功能
讲到这里就结束了,感谢观看~
推荐阅读
-
python匿名函数的相关操作
-
星球大战与Python之间的那些事
-
聊一聊,这些年我用Python爬虫挣钱的那些事
-
解析Python中的eval()、exec()及其相关函数
-
星球大战与Python之间的那些事
-
C++程序员应了解的那些事(47)函数之 传入传出参数 / 默认参数
-
C++程序员应了解的那些事(36)Effective STL第6条:当心C++编译器中最烦人的分析机制 --- 调用构造函数被误认为是函数声明的问题
-
C++程序员应了解的那些事(63)STL内建函数对象、仿函数
-
C++程序员应了解的那些事(62)~ list::splice()函数详解
-
C++程序员应了解的那些事(98)C++构造函数小结