接下来开始学习函数,函数这个知识点很重要,需要认真学习
先来了解下什么是函数?
函数是组织好的,可重复使用的,用来实现单一或相关功能的代码块。
如何使用函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率,Python提供了许多内建函数,比如print()。
但也可以自己创建函数,这被叫做用户自定义函数。
定义函数
函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()
def关键词必须后跟函数名称和带括号的形式参数列表
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明
return语句返回一个函数的值。 return没有表达式参数返回None。从函数的末尾掉落也会返回None
现在以Fibonacci数作为例子:
复制 def fib(n): # 定义一个函数名称,用来计算fibonacci数
a,b = 1,1 # 赋值
for i in range(n):
print(a,end=' ')
a,b = b,a+b
print()
调用函数
已经知道了如何定义函数,那么该如何调用这个函数,接下来继续以上面的那个例子作为示例,进行讲解
复制 def fib(n): # 定义一个函数名称,用来计算fibonacci数
a,b = 1,1 # 赋值
for i in range(n):
print(a,end=' ')
a,b = b,a+b
print()
fib(10) # 调用函数, 并传入一个参数
看,这样就调用了,是不是很简单
运行结果:
复制 1 1 2 3 5 8 13 21 34 55
函数引用
复制 def outFunc():
print("outFunc()正在被调用")
# 引用函数
outfunc = outFunc
# 通过引用调用函数
outfunc()
# 调用函数
outFunc()
print(id(outfunc))
print(id(outFunc))
运行结果:
复制 outFunc()正在被调用
outFunc()正在被调用
2357814673888
2357814673888
函数的参数
现在已经都知道了如何定义函数和调用函数了,接下来,讲解一下函数里面的参数,这一点很重要,要认真学喔
形参和实参
参数从调用的角度来说,分为形式参数和实际参数。
形参指的是函数创建和定义过程中小括号里的参数,而实参则指的是函数在被调用的过程中传递进来的参数。
下面举个小例子
复制 def person(name):
print("I am " + str(name))
person("angle")
在person(name)中name是形参,因为这只是代表一个位置,一个变量名而已;而person("angle")中传递的字符串"angle"是实参,因为这是一个具体的内容,是赋值到变量中的值
关键字参数
由于在调用函数的时候,有时候可能会很粗心不小心把参数位置的顺序给弄错了,从而导致函数无法按照预期实现要求。
因此关键字参数是为了解决这个问题而出现的,有了这个就可以很简单的解决这个潜在的问题了。
举个例子
复制 def person(name,age):
print('{0}的年龄是{1}'.format(name,age))
person("angle","18")
person("18","angle")
person(age="18",name="angle")
person(name="angle",age="18")
运行结果:
复制 angle的年龄是18
18的年龄是angle
angle的年龄是18
angle的年龄是18
从结果可以看到关键字参数是很有用的,因为参数的顺序一不小心弄错,会导致程序无法完成预期的结果的
默认参数
默认参数可以在参数定义的过程中,为形参进行赋值,当函数调用的时候,不传递实参,则默认使用形参的初始值代替。
举个小栗子
复制 def person(name,age,sex='未知'):
print('{0}的年龄是{1},性别{2}'.format(name,age,sex))
person(name='angle',age='18')
person(name='angle',age='18',sex='男')
是不是很简单,那么继续往下学习
收集参数
什么是收集参数?
在定义函数的时候,不知道这个函数有多少个参数,因为在工作开发的时候,需要根据实际情况添加和删除参数的,这就造成了参数的不确定,由于添加和删除参数工作有点繁琐,所以这时才有了收集参数,收集参数是为了解决这个问题而出现的,收集参数两种,一种是将参数打包元组,一种是以字典形式打包
args
先来讲下第一种,只需要在参数前面加上一个星号(*)就可以了
复制 def person(*args):
print('{0}的年龄是{1},性别{2}'.format(args[0],args[1]))
person('angle',18)
运行结果
参数在传过去的时候,会进行打包成元组传递过去
如果想要单独额外指定参数,可以使用关键字参数
复制 def person(*args,sex='未知'):
print('{0}的年龄是{1},性别{2}'.format(args[0],args[1],sex))
person('angle','18',sex='男')
运行结果
星号其实既可以打包又可以解包。什么是解包?
举个小栗子
将列表a传入test参数的收集参数*args中
复制 def test(*args):
print("{} 个参数".format(len(args)))
print("第二个参数:{}".format(args[1]))
a = [i for i in range(10)]
print(a)
test(a)
运行结果
复制 Traceback (most recent call last):
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 个参数
File "E:/JetBrains/Code_practice_project/test/fibonacci.py", line 8, in <module>
test(a)
File "E:/JetBrains/Code_practice_project/test/fibonacci.py", line 3, in test
print("第二个参数:{}".format(args[1]))
IndexError: tuple index out of range
那么调用test(a)时便会出错,此时需要在a前面加上个星号(*)表示实参需要解包后才能使用
复制 def test(*args):
print("{} 个参数".format(len(args)))
print("第二个参数:{}".format(args[1]))
a = [i for i in range(10)]
print(a)
test(*a)
运行结果
复制 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
10 个参数
第二个参数:1
kwargs
另一种收集方式,用两个星号(**)表示
复制 def test(**kwargs):
for key,value in kwargs.items():
print("key:{},value:{}".format(key,value))
params = {
"name":"angle","name":"angle",
"age":18,
"sex":"男",
}
test(**params)
运行结果
复制 key:name,value:angle
key:age,value:18
key:sex,value:男
慢慢来解析一下是什么意思?
复制 test(**params)这个是为了收集多个字典参数,打包传递过去
def test(**kwargs)是为了将字典进行解包,和前面讲过的是一样的
函数文档
在程序的时候,有时候为了使代码可读性更高,有时候会在程序旁边进行注释,可是这样影响python代码的美观,这时就可以使用函数文档。
举个例子
复制 def money(dollar):
"""
美元 --> 人民币
汇率暂定为6.5
:param dollar:
:return: dollar * 6.5
"""
return dollar * 6.5
m = money(10)
print(m)
运行结果
这样做还有个好处,这样可以不用打开源码慢慢去找文档注释,这时可以通过特殊属性
复制 __doc__获取(注意doc两边分别两条下划线)
运行结果
复制 美元 --> 人民币
汇率暂定为6.5
:param dollar:
:return: dollar * 6.5
在不确定函数的用法的时候,可以使用help()函数来查看函数的文档
运行结果
复制 Help on function money in module __main__:
money(dollar)
美元 --> 人民币
汇率暂定为6.5
:param dollar:
:return: dollar * 6.5
None
返回函数
python函数的返回值,可以返回一个值,也能够返回多个值
单个值
复制 def test():
return 1
print(test())
运行结果
多个值
复制 def test():
return 'angle',18,[1,2,3]
print(test())
运行结果
复制 ('angle', 18, [1, 2, 3])
再返回多个值得时候,会打包成元组返回
递归
递归就是不停的调用自己,也就是不停重复做某一个事情
举个小栗子
复制 def f(n):
if n <= 1:
return 1
return f(n-1)*n
n = int(input('请输入一个数字:'))
number = f(n)
print("阶乘结果:{}".format(number))
怎么来理解这个函数了
这里假设输入3
运行结果
匿名函数lambda
Lambda表达式(有时称为lambda表单)用于创建匿名函数
如何使用
先来看看语法格式,然后看下例子
语法
复制 lambda [parameter_list]:expression
实例
等价于
复制 >>> def add(x,y):
... return x+y
...
>>> add(3,5)
8
这样感觉是不是很繁琐,还是感觉lambda好用吧
在举一下其他的例子
比如前面讲过的列表排序
列表排序
复制 >>> a = [(1,2),(4,1),(1,3),(2,-5)]
>>> a.sort(key=lambda v:v[0])
>>> a
[(1, 2), (1, 3), (2, -5), (4, 1)]
>>> a.sort(key=lambda v:v[1])
>>> a
[(2, -5), (4, 1), (1, 2), (1, 3)]
内嵌函数和闭包
讲解了什么是函数,如何使用,现在来学习下什么是内嵌函数和闭包
内嵌函数
内嵌函数其实就是一个函数在其内部在套一个函数,和嵌套循环一样,又可以叫做内部函数
示例:
复制 def outFunc(name):
print("outFunc()正在被调用")
def inFunc(name):
print("inFunc()正在被调用")
return name
return inFunc(name)
print(outFunc("angle"))
运行结果:
复制 outFunc()正在被调用
inFunc()正在被调用
angle
是不是很简单,但是这个只能调用outFunc()函数,而inFunc()函数只能在outFunc()函数中调用,而在outFunc()函数外调用,会进行报错,因为inFunc()函数的作用域只在outFunc()函数内才生效
复制 def outFunc(name):
print("outFunc()正在被调用")
def inFunc(name):
print("inFunc()正在被调用")
return name
return inFunc(name)
inFunc("angle")
运行结果:
复制 def outFunc(name):
print("outFunc()正在被调用")
def inFunc(name):
print("inFunc()正在被调用")
return name
return inFunc(name)
inFunc("angle")
闭包
python中的闭包从表现形式上定义为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。
举个小栗子
复制 def outFunc(param1):
def inFunc(param2):
print("inFunc()函数,param2参数值为{}".format(param2))
return param1 * param2
# 注意,这里返回的闭包的结果
return inFunc
test = outFunc(3)
# 这里其实调用的是inFunc(param2),参数5传给的是param2
result = test(5)
print(result)
运行结果:
复制 inFunc()函数,param2参数值为5
15
可以将
复制 test = outFunc(3)
result = test(5)
缩写为
这两者之间是等价的
复制 def outFunc(param1):
def inFunc(param2):
print("inFunc()函数,param2参数值为{}".format(param2))
return param1 * param2
return inFunc
result = outFunc(3)(5)
print(result)
运行结果
复制 inFunc()函数,param2参数值为5
15
装饰器
什么是装饰器?
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。
装饰器有什么用?
抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能,也称之为扩展功能。
先看一个简单的例子:
复制 def foo():
print("this is a foo")
foo()
现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码
复制 import logging
def foo():
print("this is a foo")
logging.warning("foo is running")
foo()
可是如果函数 bar()、bar2() 也有类似的需求,怎么做?再写一个 logging 在 bar 函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个新的函数:专门处理日志 ,日志处理完之后再执行真正的业务代码
复制 import logging
def use_logging(func):
logging.warning("foo is running")
func()
def foo():
print("this is a foo")
use_logging(foo)
这样做逻辑上是没问题的,功能是实现了,但是调用的时候不再是调用真正的业务逻辑 foo 函数,而是换成了 use_logging 函数,这就破坏了原有的代码结构, 现在不得不每次都要把原来的那个 foo 函数作为参数传递给 use_logging 函数,那么有没有更好的方式的呢?当然有,答案就是装饰器。
简单装饰器
复制 import logging
def use_logging(func):
def wrapper():
logging.warning("foo is running")
# 注意,这把foo()函数当做参数传递进来了,执行func()相当于执行foo()
return func()
return wrapper
def foo():
print("this is a foo")
# 因为装饰器use_logging(foo)返回的是函数对象wrapper,这条语句相当于 foo = wrapper
foo = use_logging(foo)
# foo = wrapper
# 执行foo()就相当于执行wrapper()
foo()
# wrapper()
# 上面的两句等价于下面的语句
# use_logging(foo)()
use_logging 就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 foo 被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程。
@语法糖
@符号是装饰器的语法糖
借鉴维基百科的看下什么是语法糖?
语法糖(Syntactic sugar) ,也译为糖衣语法 ,是由英国计算机科学家彼得·蘭丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。 语法糖让程序更加简洁,有更高的可读性
复制 import logging
def use_logging(func):
def wrapper():
logging.warning("foo is running")
return func()
return wrapper
@use_logging
def foo():
print("this is a foo")
foo()
有了@,就可以省去foo = use_logging(foo)这一句了,能够直接调用foo()得到想要的答案,并且提高了程序的可重复利用性,并增加了程序的可读性
如何传参
传入一个参数
复制 import logging
def use_logging(func):
# 传入参数name
def wrapper(name):
logging.warning("{} is running".format(name))
return func(name)
return wrapper
@use_logging
def foo(name):
print("this is a {}".format(name))
foo("foo")
传入多个参数
复制 import logging
def use_logging(func):
def wrapper(*args,**kwargs):
logging.warning("{} is running".format(func.__name__))
return func(*args,**kwargs)
return wrapper
@use_logging
def foo(name,age,**params):
print("name:{},age:{},sex:{}".format(name,age,params.get("sex")))
params = {
"sex":"男"
}
foo("angle","18",**params)
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许在调用时,提供其它参数,比如@decorator(a)
。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。
复制 import logging
def use_logging(level):
def decorator(func):
def wrapper(*args,**kwargs):
if level == "warning":
logging.warning("{} is running".format(func.__name__))
elif level == "info":
logging.info("{} is running".format(func.__name__))
return func(*args,**kwargs)
return wrapper
return decorator
@use_logging(level="info")
def foo(name,age,**params):
print("name:{},age:{},sex:{}".format(name,age,params.get("sex")))
params = {
"sex":"男"
}
foo("angle","18",**params)
上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")
调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。
复制 @use_logging(level="warn")`等价于`@decorator
等价于如下
复制 import logging
def use_logging(level):
def decorator(func):
def wrapper(*args,**kwargs):
if level == "warning":
logging.warning("{} is running".format(func.__name__))
elif level == "info":
logging.info("{} is running".format(func.__name__))
return func(*args,**kwargs)
return wrapper
return decorator
def foo(name,age,**params):
print("name:{},age:{},sex:{}".format(name,age,params.get("sex")))
params = {
"sex":"男"
}
# 调用最外围函数
test = use_logging(level="warning")
# test = decorator
test(foo)("angle","18",**params)
类装饰器
没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
复制 class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name 、参数列表,先看例子:
复制 # 装饰器
def logged(func):
def with_logging(*args, **kwargs):
print( func.__name__) # 输出 'with_logging'
print (func.__doc__) # 输出 None
return func(*args, **kwargs)
return with_logging
# 函数
@logged
def f(x):
"""does some math"""
return x + x * x
logged(f)
不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。
复制 from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
装饰器顺序
假如一个函数同时有多个装饰器如下
复制 def a(func):
def wrapper(*args,**kwargs):
print("a")
return func(*args,**kwargs)
return wrapper
def b(func):
def wrapper(*args, **kwargs):
print("b")
return func(*args, **kwargs)
return wrapper
def c(func):
def wrapper(*args, **kwargs):
print("c")
return func(*args, **kwargs)
return wrapper
def d(func):
def wrapper(*args, **kwargs):
print("d")
return func(*args, **kwargs)
return wrapper
@a
@b
@c
@d
def e1():
print("e1")
def e2():
print("e2")
e1()
print("-"*30)
a(b(c(d(e2))))()
运行结果
复制 a
b
c
d
e1
------------------------------
a
b
c
d
e2
执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器
高级函数
接下来讲下高级函数,这些都是python的内置函数
filter
简述
filter()函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器,产生可迭代对象,如果转换为列表,需要使用list()。
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。是真的。 如果function为None,则返回true的项。
语法
复制 filter(function or None,iterable) - > filter对象
参数
返回值
返回一个可迭代对象
实例
复制 >>> def isOdd(n):
... return n%2 == 1
...
>>> test = filter(isOdd,[1,2,3,4,5,6,7])
>>> new_test = list(test)
>>> new_test
[1, 3, 5, 7]
sorted
简述
sorted()函数从iterable中 的项返回一个新的排序列表
语法
复制 sorted(iterable,*,key = None,reverse = False )
参数
key指定一个参数的函数,用于从每个列表元素中提取比较键:key=str.lower。默认值为None (直接比较元素)。
reverse是一个布尔值。如果设置为True,则对列表元素进行排序,并且反转
返回值
一个参数返回对象类型, 三个参数,返回新的类型对象
实例
复制 >>> a = [5,6,7,3,6]
>>> a
[5, 6, 7, 3, 6]
>>> sorted(a)
[3, 5, 6, 6, 7]
>>> sorted(a,reverse=True) # 反转
[7, 6, 6, 5, 3]
>>> a.sort() # 注意sort只为list定义,而sorted()函数可以接收任何的iterable
>>> a
[3, 5, 6, 6, 7]
>>> test = [4,3,8,7,6,1,0,2]
>>> test
[4, 3, 8, 7, 6, 1, 0, 2]
# 利用key进行排序
>>> result = sorted(test,key=lambda x:x)
>>> result
[0, 1, 2, 3, 4, 6, 7, 8]
>>> result = sorted(test,key=lambda x:-x)
>>> result
[8, 7, 6, 4, 3, 2, 1, 0]
温馨提示
sort 与 sorted 区别:
sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
map
简述
map() 会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
语法
参数
返回值
返回迭代器
实例
复制 >>> def func(x):
... return x*2
...
>>> map(func,[1,2,3,4,5])
<map object at 0x000001B76E4DABA8>
>>> list(map(func,[1,2,3,4,5]))
[2, 4, 6, 8, 10]