python函数

一、函数

关注公众号“轻松学编程”了解更多。

1.函数的概述
1.1 认识函数

函数:在一个完整的项目中,某些功能会反复的使用,那么会将功能封装成函数,当我们要使用此功能的时候调用即可。

优点:
1.简化代码结构,增加了代码的复用性(重复使用的程度)
2.增加代码的可维护性,如果想修改某个BUG,只需要改对应的函数即可。

1.2 定义函数

格式:
def 函数(参数列表):
​ 语句块
​ return 表达式
解释:

def : 函数代码块以def关键字开始

函数名:遵循标识符规则

参数列表:任何传入函数的参数和变量,必须放在小括号之间,使用逗号分隔,函数从函数的调用者那里获取信息【调用者给函数传递的信息】

():是参数列表的开始以及结束

冒号:函数内容【封装的功能】以冒号开始,并且缩进

语句:函数封装的功能

return:一般用于结束函数,并且返回信息给函数的调用者

表达式:要返回给函数调用者的信息

注意:最后的return表达式可以不写,若不写的情况下默认return None

参数列表 = 参数1,参数2,…,参数n

1.3函数的调用

语法: 函数名(参数列表)
函数名:是要使用的功能的函数的函数名字
参数列表:函数的调用者给函数传递的信息
函数调用的本质:实参给形参赋值的过程

>>> int(1.3)
1
>>> abs(-10)
10
1.4 最简单的函数

说明:最简单的函数,是无参数,无返回值的函数

#定义函数
def myPrint():
	print("you are a good man")
#调用函数
myPrint()
1.5 函数的参数

参数列表:如果函数所实现的需求中涉及到未知项参与运算,就可以将未知项设置为参数。
格式:参数1,参数2,参数3,…
形式参数:在方法中定义的,用于接收时间参数的值
实际参数:在函数外面定义,实际参与运算的值,就是为了给形式参数赋值.

#函数的定义
#name是形参
def myPrint(name):
	print("%s is a good man !"%name)

#"lili" 是实参
myPrint("lili")
#结果
lili is a good man !
#形参 参数一:name  参数二:age
def myPrint(name, age):
	print("%s is %d year old"%(name, age))
#函数调用,传递两个参数	
myPrint("lili", 18)
#结果
lili is 18 year old
1.6 函数的返回值

函数的返回值表示一个函数执行完成之后得到的结果
使用return返回函数的返回值,用于结束函数,得到最终运算的结果。
需求:编写函数实现功能,给函数传递两个整数,获取这两个整数的之和

def add(num1, num2):
	sum = num1 + num2
	#将结果返回函数的调用者
	return sum
	#注意:return的作用是结束整个函数,因此return后面的语句不会被执行
	print("hello")

#调用函数
res = add(10, 20)
print(res)
输出:
30
1.7 参数传递

参数传递的本质:实参给形参赋值的过程。

1.7.1 位置参数之值传递

值传递指传递不可变类型,一般指string、tuple和number类型。

def func1(a):
	print(a)
	a = 10
	print(a)
	
temp = 20
#将temp作为实参传递给func1函数,将赋值给形参a
#相当于 a = temp
func1(temp)
print(temp)
输出:
20
10
20

打印地址,如下

def func1(a):
	print(id(a))
	a = 10
	print(id(a))
	
temp = 20
#将temp作为实参传递给func1函数,将赋值给形参a
#相当于 a = temp
print(id(temp))
func1(temp)
print(temp)
输出:
1852161072
1852161072
1852160752
20

1.7.2 位置参数之引用传递

引用传递一般传递的是可变类型,一般指list,dict和set。

def func1(c):
	c[0] = 100
	
d =[1, 2, 3, 4]
#将引用传递过去
func1(d)
print(d[0])
输出:
100

说明:引用传递的本质上还是值传递,只不过传递的是地址。

位置参数又称必选参数

1.7.3 关键字参数

概念:用于函数调用时,通过“键-值”的形式加以指定,可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求

def func(name, age):
	print("I am %s, and I am %d year old"%(name, age))
#一般调用
func("lili", 18)
#使用关键字参数
func(age = 18, name = "lili")
输出:
I am lili, and I am 18 year old
I am lili, and I am 18 year old

注意:有位置参数时,位置参数必须在关键字参数的前面,但是关键字参数之间是不存在先后顺序的。

1.7.4 默认参数

概念:用于定义函数,为参数提供默认值,调用函数时可传可不传该默认参数的值。

调用函数时,如果没有传递参数则会使用默认参数

比如:在使用sort()排序的时候,不更改其参数的时候我们一般情况下默认会升序排列。

def func(name, age=18):
	print("I am %s, and I am %d year old"%(name, age))

#一般的函数调用
func('lilei', 19)
#关键字参数调用
func(name = 'leilei')
#使用默认参数调用
func('lili')
输出:
I am lilei, and I am 19 year old
I am leilei, and I am 18 year old
I am lili, and I am 18 year old

使用默认参数可以简化函数的调用。

使用默认参数的时候需注意:

1.必选参数在前,默认参数在后,否则python解释器会报错

2.如何设置默认参数

当函数有多个参数时,把变化大的参数放在前面,变化小的放在后面,变化小的可以作为默认参数。

练习:

我们写一个小学生入学注册信息,要求输入姓名,性别,年龄,城市等信息,设计一个函数。

def student(name, sex, age = 6, city = '广州'):
	print("name :", name)
	print("sex :",sex)
	print("age :",age)
	print("city :",city)
#函数调用
student('lili', 'boy')
student('lili', 'boy', 7)
student('lili', 'boy', city='Beijing')

注意:有多个默认参数的时候,可以按顺序提供默认参数,当不按顺序提供参数的时候需要使用关键字参数进行调用。

1.7.5 不定长参数【可变参数】

概念:定义函数时,有时候我们不确定调用的时候会传递多少个参数(或者传递参数的个数为0),此时我们可以使用包裹位置参数或者包裹关键字参数来进行参数传递。

特点:能处理比当初声明时更多的参数,换句话说,就是传递多少参数,我就处理多少参数,不传递则不处理。

*1.包裹位置传递–args

#与之前形参不同的是在hoby的变量名之前添加了“*”
#添加了星号(*)的变量名会存放所有未命名的变量参数
#如果在函数调用时没有指定参数,它就是一个空的元组
#一般情况下使用*args
def func(name, *hoby):
	print("I am ",name,"I like",hoby)
	
func('lili','flower','money')
输出:
I am  lili I like ('flower', 'money')

注意:我们传进去的所有参数都会被args变量收集,他会根据传进参数的位置合并成一个元组(tupe),args是元组类型,这就是包裹位置传递。

2.包裹关键字传递–**kwargs
#若是两个*,则当做dict处理
# 注意:在使用**kwargs的时候,传递参数必须使用关键字传参
def func(**kwargs):
	print(kwargs)
	print(type(kwargs))
func(x = 1, y = 2)
输出:
{'x': 1, 'y': 2}
<class 'dict'>
#能处理任意长度的参数
def func(*args, **kwargs):
	#代表一个空语句
	pass

kwargs是一个字典(dict),收集所有的关键字参数。

注意:在python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这四种参数可以一起使用,或者是只用其中的某些,但是注意,参数定义与调用的顺序必须是:必选参数【位置参数】、默认参数、可变参数【包裹位置】和关键字参数【包裹关键字】

1.7.6 匿名函数

概念:是指一类无需定义标识符(函数名)的函数或者子程序。

特点:匿名函数不使用def定义函数,使用lambda来创建匿名函数

1.lambda只是一个表达式,函数体比def简单

2.lambda的主体是一个表达式,而不是一个代码块,仅仅只能在lambda表达式中封装简单的逻辑

3.lambda函数有自己的命名空间,且不能访问自由参数列表之外的或全局命名的空间参数(只能用自己的参数,其他的用不了)

4.虽然lambda是一个表达式且看起来只能写一行,但是与c和c++的内联函数不同。

语法:

lambda 参数1,参数2,…,参数n: expression[表达式]

sum = lambda num1, num2: num1 + num2
print(sum(1, 2))
输出:
3

二、装饰器

1.1 概述

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)

装饰器实际上就是一个闭包,把一个函数当做函数然后返回一个替代版函数,本质上就是一个返回函数的高阶函数

1.2 简单的装饰器

函数也可以是一个对象,而且函数对象可以被赋值给变量,所以通过变量也可以调用该函数

def now():
	print("2018-3-18")

f = now
f()
输出:
2018-3-18

函数对象有一个____name____属性,可以拿到函数的名字:

def now():
    print("2018-4-3")

f = now
f()
print(now.__name__)
print(f.__name__)
#结果
'now'
'now'

现在我们要增强now()的函数功能,比如在函数调用前打印一段分隔符。

def log(func):
	def wrapper():
		print("**********")
		return func()
	return wrapper

def now():
	print("2018-3-18")
	
#装饰器的调用
f = log(now)
f()
输出:
**********
2018-3-18
1.3 复杂一点的装饰器

需求:输入年龄,并打印出来

def getAge(age):
	print("Tom age is %d"%age)
getAge(10)

#调用getAge方法得到年龄,但是如果输入的是负数就会得到不符合常理的结果
getAge(-10)

def wrapper(func):
	def inner(num):
		if num < 0:
			num = 0
		return func(num)
	return inner
newGetAge = wrapper(getAge)
newGetAge(-10)

通过这样的方式,我们的代码变得更加简洁,将边界检查的逻辑隔离到单独的方法中,然后通过装饰器包装的方式应用到我们需要进行检查的地方。

另外一种方式通过在计算方法的开始处和返回值之前调用边界检查的方法能够达到同样的目的,但是不可置否的是,使用装饰器能够让我们以最少的代码量达到坐标边界检查的目的。

1.4 装饰器@标识符

使用@标识符将装饰器应用到函数。

python2.4及以上版本支持使用标识符@将装饰器应用在函数上,只需要的函数的定义之前上@和装饰器的名称

def logger(func):
	def inner(*args,**kwargs):
		print("***********")
		return func(*args, **kwargs)
	return inner

@logger
def myPrint():
	print("you are very good")

myPrint()
输出:
***********
you are very good

说明:比如在公司实际开发的过程中,如果有一个别人写好的函数,你想向其中添加某些功能或者修改某些功能,而人家要求你不能随意修改人家的函数,则这时就可采用装饰器,在不修改别人的源码的同时,还可以增加自己的功能。

1.5 使用装饰器查看函数的运行时间
from functools import wraps
import time

def decorate(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start = time.time()
        ret = func(*args,**kwargs)
        end = time.time()
        print(f'函数{func.__name__}执行时间:{round(end-start,4)}秒')
        return ret
    return wrapper

@decorate
def func_sleep(num):
    time.sleep(num)
    print('hello world')

func_sleep(2)
print('='*10)
# 使用原函数
func_sleep.__wrapped__(2)
输出:
hello world
函数func_sleep执行时间:2.0019秒
==========
hello world
使用@wraps后可以还原原来的函数,
即可以使用func.__wrapped__()来去掉装饰器。
比如   func_sleep.__wrapped__(2)

1.6装饰器的作用

装饰器本质上是一个Python函数。

它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

三、偏函数

python中的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。

简单来讲偏函数的作用就是把函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新的函数会更简单。

#functools 模块
import functools
#int()函数将字符串转换为整数,默认按十进制转换
#可以设置进制
print(int("100",base = 2))
#结果为4,100是二进制中4的表示

#类似于偏函数的功能
def int2(str, base=2):
	return int(str, base)
print(int2("10"))

#functools.partical可以帮助创建偏函数,不用自己定义int2函数
#作用:把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新的函数会更简单。
int3 = functools.partial(int, base=2)
print(int3("100"))
#在创建偏函数的时候,实际上固定了关键字参数base
#结果
4

例2

import functools
max2 = functools.partical(max, 10)
#实际上会把10作为*args的一部分自动加到左边
max2(5, 6, 7)
输出:
10
相当于
args =(10, 5, 6, 7)
max(args)

练习:

设计一个加法的偏函数,使用add()的时候需要导入operator模块,偏函数需要实现的功能是,求任意数与100相加的和。

import operator
import functools

add = functools.partial(operator.add,100)
print(add(12))
输出:
112

四、 变量的作用域

1.1 概述

在python程序中,创建,改变,查找变量名的时候,都是在一个保存变量名的空间中进行,我们称之为命名空间,也被称之为作用域。

简单来说,变量的作用域就是指变量可以使用的范围

程序的变量并不是在任意的位置都可以访问,访问权限取决于这个变量是在哪里赋值的。

1.2 作用域的划分

L(local) 局部作用域

局部变量:包含在def关键字定义的语句块中,即在函数中定义变量,每当函数被调用的时候都会创建一个新的局部作用域。

注意:如果需要在函数内部对全局变量赋值,需要在函数内部通过global语句声明该变量为全局变量。

E(enclosing) 嵌套作用域

又称函数作用域,在闭包函数外的函数中

G(global) 全局作用域

在模块层次中定义的变量,每个模块都是一个全局作用域

注意:全局作用域的作用范围仅限单个模块文件内。

B(built-in)内置作用域

系统内部固定模块定义的变量,比如预定义在builtin模块内部.。

#查看所有的内置变量
print(vars())
输出:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000225E691A390>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/Learn/python/qianfeng/jingdiancx/test.py', '__cached__': None}

1.3 变量名解析LEGB法则

搜索变量名的优先级:局部变量>嵌套作用域>全局作用域>内置作用域。

LEGB法则:

当在函数中使用未确定的变量名时,python会按照优先级依次搜索4个作用域,依此来确定变量名的意义。

首先搜索局部作用域(L),之后是上一层嵌套结构中的def或者是lambda函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。

按照这个查找原则,在第一处找到的地方停止,如果是都没找到,则会发生NameError错误。

def func():
	var = 300
	print("var",var)
var = 100
func()
print("var", var)
#结果
300
100
1.4 关键字global
#声明全局变量
global var

var = 200
def func():
    # var = 200
    print("var_", var)
    def fun():
        var = 100
        print("var", var)
    fun()
func()
输出:
var_ 200
var 100

# 修改全局变量
# global x 如果x在函数外,这条语句可要可不要,
x = 20
def func():
    # 在函数内修改全局变量要先使用global声明
    global x
    x = 100
    print(x)

func()
print(x)
输出:
100
100

注意:python中只有模块(module),类(class)以及函数(def,lambda)才会引入新的作用域,其他的代码块,比如if/else, try/except,for/while 等是不会引入新的作用域的,也就是说这些语句内定义的变量,在外部也可以使用。

if 1:
	num = 10
print(num)
输出:
10

五、 回调函数

回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为一个参数传递给另一个参数,当这个指针被用来调用其所指向的函数时,这就是我们说的回调函数。

简单来说:回调函数就是把函数当成一个参数传递到函数中.

需求:现在酒店提供免费叫醒服务,叫醒的方式有多种,你可以自己选择,比如夺命电话连环call, 或者是早起冷水迎头泼,随你自己喜欢,只要你提前预约,则酒店工作人员将在你指定的时间用你喜欢的方式叫醒你。

def  wake_call(time):
	#第一种叫醒服务
	print(time,"使用夺命电话连环call叫醒主人")

def wake_water(time):
	#第二种叫醒服务
	print(time,"使用早起泼冷水的方式叫醒主人")

def call_wake(time, func_name):
	# 这个很重要,这个就是酒店服务业务的系统业务
	#这是实现回调函数的核心
	# time :预约时间
	# func_time:回调函数名
	# return :调用的函数的结果
	return func_name(time)

#调用函数
#wake_call 被调用的函数就是回调函数
call_wake("凌晨7点", wake_call)

拓展: 编程分为两大类:系统编程和应用编程,所谓的系统编程简单来说就是编写库,而应用编程就是利用写好的各种库来编写具体的某种功能的程序,也就是应用. 系统程序员hi在自己写的库中留下API(应用编程接口),以供应用程序员使用。

当程序跑起来,一般情况下,应用程序会时常通过API调用库中所预备好的函数,但是有些库函数却要求应用先给它传递一个函数,好在合适的时候调用,以完成目标,这个被传入的后来又被调用的函数称为回调函数

六、 返回函数

函数作为返回值。

在python中除了可以接受函数作为参数外,还可以把函数作为结果值返回。

需求:实现一个可变参数的求和.通常是这么定义的:

def calc_sum(*args):
	sum = 0
	for i in args:
		sum += i
	return sum

现在需求有变,现在我不需要立即求和,而是在后面的代码中,根据需要再进行计算,这时候,我们可以不返回求和的结果,而是返回求和的函数。

def lazy_sum(*args):
	def calc_sum():
		sum = 0
		for i in args:
			sum += i
		return sum
	return calc_sum

当我们调用lazy_sum()时,返回的并不是求和的结果而是求和的函数。

>>> f = lazy_sum(1, 2, 3, 4)
>>> f
<function lazy_sum.<locals>.calc_sum at 0x101b61598>

这时候调用f时,才真正计算求和的结果

>>> f()
10

像如上的函数,我们在函数lazy_sum中又定义了函数calc_sum,并且内部函数calc_sum可以使用lazy_sum的参数和局部变量,当lazy_sum返回函数calc_sum时,相关的参数以及变量都保存在返回的函数中,这种称为"闭包"。

注意:当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数。

在python中除了能够把函数作为参数之外,也可以把函数作为返回值返回。

七、 闭包

若在一个函数内部定义了另一个函数,外部的我们暂且称之为外函数,内部的称之为内函数。

闭包: 在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用(内函数的函数名),这样就构成了一个闭包。

一般情况下,如果一个函数结束,函数内部所有的东西都会被释放掉,还给内存,局部变量也会消失,但是闭包是一种特殊的情况,如果外函数在结束的时候发现有自己的临时变量将来还会在内部变量中用到,就把这个临时变量绑定给了内函数,然后再自己结束。

#外函数, a与b都是临时变量
def outer(a):
	b = 10
	#内函数
	def inner():
		#在内函数中用到了外函数的临时变量
		print(a+b)
	#外函数的返回值是内函数的引用
	return inner
# 调用函数传入参数5
f = outer(5)
f()
#结果
15

在函数外访问函数内的东西.闭包也具有提高代码可复用性的作用。闭包有效的减少了函数所需定义的参数数目。

八、 递归函数

递归函数:在函数的内部,可以调用其他的函数,如果一个函数在内部调用自身本身,这个函数就是递归函数。

递归调用:一个函数调用自身,称为递归函数

需求:计算n! = 1x2x3x4x…x(n-1)xn

使用递归解决问题的思路

方法:

1.写出临界条件

2.找出这次与上次函数执行的关系

3.假设当前函数已经能用,调用自身计算上一次的结果,再求出本次的结果

# 关系: n!= (n-1)!xn
def fact(n):
	#临界条件
	if n==1:
		return 1
	#返回本次的调用结果
	return n*fact(n-1)

递归函数的优点是定义简单,逻辑清晰,理论上所有的递归函数都可以写成循环的方式,但是循环的逻辑不如递归清晰。

注意:使用递归函数需要注意防止栈溢出,在计算机中函数是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,就会增加一层栈帧,每当函数返回,栈就会减一层栈帧,栈的大小是有限制的,所以当调用的次数过多的时候,会导致栈溢出。

使用递归比较占内存。

求斐波那契数列:1,1,2,3,5,8,13,21,34,55,89…
需求:报一个数,直接获取这个位置上的数值

#关系 第n个位置上的数值=(n-1)+(n-2)
#临界值 第一个位置和第二个位置的值为1
def func1(n):
	if n==1 or n==2:
		#临界值
		return 1
	else:
		#返回本次调用的结果
		return func1(n-1) + func1(n-2)

练习从控制台输入一个数,递归求和。

def diguiSum(n):
    if n == 1:
        return 1
    return diguiSum(n-1)+n
print(diguiSum(6))
输出:
21

后记

【后记】为了让大家能够轻松学编程,我创建了一个公众号【轻松学编程】,里面有让你快速学会编程的文章,当然也有一些干货提高你的编程水平,也有一些编程项目适合做一些课程设计等课题。

也可加我微信【1257309054】,拉你进群,大家一起交流学习。
如果文章对您有帮助,请我喝杯咖啡吧!

公众号

公众号

赞赏码

关注我,我们一起成长~~

posted @ 2018-04-25 16:23  轻松学编程  阅读(99)  评论(0编辑  收藏  举报