Python语言之函数


Python语言之函数

前言

人生苦短,我用Python。

一、函数的定义和调用

什么是函数?

函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段,它能够提高应用的模块性和代码的重复利用率。

为什么需要函数
复用代码、隐藏实现细节、提高可维护性、提高可读性便于调试

函数的定义和调用

Python定义函数使用def关键字,格式如下:

def 函数名(参数列表):
    函数体

例:

def printInfo():
    print('-------------------------------------')
    print('         人生苦短,我用Python         ')
    print('-------------------------------------')

定义了函数之后,想要让这些代码能够执行,需要调用函数。通过“函数名()” 即可完成调用。

# 调用刚才定义的函数
printInfo()

二、函数的参数

函数的参数

如果希望定义的函数,可以计算任何两个数的和,我们在定义函数的时候,让函数接收数据,这就是函数的参数。

#定义接收两个参数的函数
def add2num(a, b):
    c = a+b
    print(c)

a和b就是函数的参数(形参),调用的时候,可以传入任意两个数(实参)。

#调用带有参数的函数
add2num(11, 22)

函数在被调用时会将实参按照相应的位置依次传递给形参,也就是说将第一个实参传递给第一个形参,将第二个实参传递给第二个形参,以此类推。

例:

def get_max(a, b):
    if a > b:
        print(a,"是较大的值!")
    else:
        print(b,"是较大的值!")
get_max(8, 5)

关键字参数的传递

关键字参数的传递是通过“形参=实参”的格式将实参与形参相关联,将实参按照相应的关键字传递给形参。

例:

def connect(ip, port):
    print(f"设备{ip}:{port}连接!")
connect(ip="127.0.0.1", port=8080)

默认参数

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

例:

def printinfo( name, age = 26 ):
    print("Name:", name)
    print("Age:", age)
printinfo(name="jack")
printinfo(name="jack",age=35)

定义函数时,带有默认值的参数一定要位于参数列表的最后面,否则程序会报错。

不定长参数

有时可能需要一个函数能处理比当初声明时更多的参数,这些参数叫做不定长参数,声明时不会命名。

def functionname([formal_args,] *args, **kwargs):
    "函数_文档字符串"
    function_suite
  • 加了星号(*)的变量args会存放所有未命名的变量参数,args为元组;
  • 加**的变量kwargs会存放命名参数,即形如key=value的参数, kwargs为字典。

代码举例

1.

def test(a,b,*args):
    print(a)
    print(b)
    print(args)
test(11,22,33,44,55,66,77,88,99)

参数11传递给a,参数22传递给b,其余的未命名参数被放到了args(元组)中。

2.

def test(a,b,**kwargs):
    print(a)
    print(b)
    print(kwargs)
test(11,22,name='hello',id='12345')

参数11传递给a,参数22传递给b,其余的命名参数(key=value)被放到了kwargs(字典)中。

3.

def test(a,b,**kwargs):
    print(a)
    print(b)
    print(kwargs)
test(11,22,a=2,b=3)

一个形参只能接受一个实参,第一个实参11要传递给a,a=2中实参2也要传递给a,所以程序报错。

4.

def test(a, b, *args, **kwargs):
    print(a)
    print(b)
    print(args)
    print(kwargs)
test(1, 2, 3, 4, name = "hello", age = 20)

参数1传递给a,参数2传递给b,未命名参数放到args中,命名参数放到kwargs中。

5.

def fun(arg1,arg2):
    print('arg1=',arg1)
    print('arg2=', arg2)
    arg1 = 100
    arg2.append(10)
    print('arg1=', arg1)
    print('arg2=', arg2)
n1 = 11
n2 = [22,33,44]
print(n1)
print(n2)
fun(n1,n2) # 位置传参,实参名称与形参名称可以不一致
print(n1) # 11
print(n2) # [22, 33, 44, 10]

在函数调用过程中,进行参数的传递:

  • 如果是不可变对象,在函数体的修改不会影响实参的值

  • 如果是可变对象,在函数体的修改会影响到实参的值

三、函数的返回值

  1. 函数的返回值是使用return语句来完成的。
def add2num(a, b):
    c = a+b
    return c

函数add2num中包含return,意味着这个函数有一个返回值,其返回值就是a和b相加的结果。

  1. 函数返回多个值时,结果为元组。
def fun(num):
    l1 = []
    l2 = []
    for i in num:
        if i%2: # 0的布尔值为false,1的布尔值为true
            l1.append(i)
        else :
            l2.append(i)
    return l1,l2
print(fun([11,22,33,44,55]))

函数的返回值:

  • 如果函数没有返回值(函数执行完毕后,不需要给调用处提供数据),return可以省略
  • 函数的返回值,如果是一个,直接返回类型
  • 函数的返回值,如果是多个,返回的结果为元组

四、函数的四种类型

根据函数的参数和返回值,函数可以分为四种类型:

  • 无参数,无返回值的函数

  • 无参数,有返回值的函数

  • 有参数,无返回值的函数

  • 有参数,有返回值的函数

无参数,无返回值的函数

def printMenu():
    print('-------------------------')
    print('         菜单系统         ')
    print('       1.羊肉涮涮锅       ')
    print('       2.牛肉涮涮锅       ')
    print('       3.猪肉涮涮锅       ')
    print('-------------------------')
printMenu()

无参数,有返回值的函数

# 获取温度
def getTemperature():
    #这里是获取温度的一些处理过程
    #为了简单起见,先模拟返回一个数据
    return 24
temperature = getTemperature()
print('当前的温度为:',temperature)

有参数,无返回值的函数

该类函数使用情景较少

 def test(num1,num2):
       result=num1+num2
       print('计算结果为:%d' %result)

有参数,有返回值的函数

像数据处理并需要结果的应用,可以使用此类函数。

def calculateNum(num):
    result = 0
    i = 1
    while i <= num:
        result = result + i
        i += 1
    return result
result = calculateNum(100)
print('1~100的累加和为:', result)

五、函数的嵌套

函数的嵌套调用:

def add_modify(a, b):
    result = a + b
    add() # 嵌套调用add函数
    print(result)
add_modify(10, 20)

函数的嵌套定义:

def add_modify(a, b):
    result = a + b
    print(result)
    def test():
        print("我是内层函数")
add_modify(10, 20)

注:

  • 函数外部无法直接调用内层函数

  • 只能通过外层函数间接调用内层函数

六、变量的作用域

作用域

  • 为了避免变量的名称发生冲突,所以Python引入了命名空间的概念。

  • 命名空间指的是名称到对象的映射,类似于字典,键名是变量的名字,值是变量的值。

命名空间是相互独立存在的,而且它们被安排在某个特定的层次,把这些层次结构组合起来就是作用域。

  • 本地作用域:函数体中声明的变量

  • 函数嵌套作用域:嵌套函数中,外层函数体声明的变量。

  • 文件作用域:py文件中声明的变量

  • 内置作用域:python环境的作用域,主要是一些内置的函数名、内置的异常等关键字。例如open,range,quit等。

局部变量

  • 所谓局部变量,就是在函数内部定义的变量。

  • 局部变量的作用域是函数内部,意味着它只在定义它的函数中有效,一旦函数结束就会消失。

全局变量

  • 定义在函数外的拥有全局作用域。

  • 全局变量可以在整个程序范围内访问。

  • 如果出现全局变量和局部变量名字相同的情况,则在函数中访问的是局部变量。

例:

a = 1
def test():
    a = 10
    print(f"局部变量a:{a}")
test()
print(f"全局变量a:{a}")

global和nonlocal关键字

  • 修改函数中变量时,函数中使用global关键字用于声明变量为全局变量。如果不使用global关键字,变量可读但不可修改。
a = 1
def test():
    global a
    a = 10
    print(f"声明变量a为全局变量:{a}")
test()
print(f"全局变量a:{a}")
  • 修改闭包函数中变量时,使用nonlocal关键字声明变量为外层函数的变量。如果不使用nonlocal关键字,外层函数变量可读但不可修改。
def test():
    b = 2
    c = 3
    def test2():
        b = 20
    def test3():
        nonlocal c
        c = 30
    test2()
    test3()
    print(b)
    print(c)
test()

闭包

Python函数是支持嵌套的。如果在一个内部函数中对外部函数作用域(非全局作用域)的变量进行引用,那么内部函数就会被称为闭包。闭包需要满足如下3个条件:

  • 存在于两个嵌套关系的函数中,并且闭包是内部函数

  • 内部函数引用了外部函数的变量(自由变量)

  • 外部函数会把内部函数的函数名称返回(不带括号)

例:

def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是 exponent_of 函数

square = nth_power(2) # 计算一个数的平方,传递exponent为2
cube = nth_power(3) # 计算一个数的立方,传递exponent为3
print(square(2))  # 计算 2 的平方,传递base为2
print(cube(2)) # 计算 2 的立方,传递base为2
print(nth_power(2)(4))# 直接计算 2 的4次方

为什么使用闭包?

  1. 由于闭包用作回调函数,因此它们提供了某种数据隐藏,这有助于我们减少使用全局变量。

  2. 当我们的代码中没有几个函数时,闭包被证明是一种有效的方法,但是如果我们需要很多功能,那就需要使用类(OOP)。

七、递归函数和匿名函数

递归函数

一个函数的内部可以调用其他函数。但是,如果一个函数在内部不调用其它的函数,而是调用自己本身的话,这个函数就是递归函数。

递归函数的组成部分:递归调用与递归终止条件。

递归得调用过程:每递归调用一次函数,都会在栈内分配一个栈帧;每执行完一次函数,都会释放相应的空间。

递归的优缺点:占用内存多,效率低下;思路和代码简单。

例:

# 求阶乘
def fn(n):
    if n==1:
        return 1
    return n*fn(n-1)
print(fn(5))

使用递归,实现阶乘 n! = 1 * 2 * 3 * ... * n的计算。

把问题归纳为递归函数的方法:

  1. 明确函数干什么:定义传入的参数

  2. 寻找递归结束条件:思考什么时候终止递归并返回什么样的值

  3. 找出递归的关系式:例如f(n)和f(n-1)什么关系?

匿名函数

匿名函数就是没有名称的函数,也就是不再使用def语句定义的函数。如果要声明匿名函数,则需要使用lambda关键字,匿名函数的声明格式如下所示:

lambda [arg1 [,arg2,.....argn]]:expression

匿名函数示例代码:

sum = lambda arg1, arg2: arg1 + arg2
# sum 为函数名;lambda arg1,arg2 为参数;arg1 + arg2 为函数体(返回值)
print("运行结果:", sum( 10, 20 ))
print("运行结果:", sum( 20, 20 ))

注意:使用Lambda声明的匿名函数能接收任何数量的参数,可以返回一个或者多个值(多个值可以用元组、列表等方式)。

八、装饰器

装饰器的概念

假设我们已经开发了一个本有的函数,后续可能会增加临时的需求,例如插入日志,我们可以增加一个包裹函数,由它来负责这些额外的需求,这个包裹函数就是装饰器 。

装饰器主要应用在如下场景:

  • 引入日志;
  • 函数执行时间统计;
  • 执行函数前预备处理;
  • 执行函数后清理功能;
  • 权限校验;
  • 缓存。

装饰器是一个函数,它需要接收一个被修饰的函数作为参数。

例如,有如下一个装饰器函数:

def w1(func):
    print('正在装饰')
    def inner():
        print('正在验证权限')
        func()
    return inner
  • 装饰器是个嵌套函数

  • 内部函数是一个闭包

  • 外部函数接收的是被修饰的函数(func)

通过在函数定义的前面添加@符号和装饰器名,实现装饰器对函数的包装。

给f1函数加上装饰器,示例如下:

@w1
def f1():
    print('f1')

调用f1时候,程序会自动编译生成调用装饰器函数的代码,等价于:

f1 = w1(f1)

多个装饰器

多个装饰器应用在一个函数上,是一个嵌套的关系,调用顺序是从下至上,再从上至下。

def w1(func):
    print('正在装饰w1')
    def inner():
        print('正在验证权限w1')
        func()
    return inner

def w2(func):
    print('正在装饰w2')
    def inner2():
        print('正在验证权限w2')
        func()
    return inner2

@w1
@w2
def f1():
    print('f')
#f1() #先注释掉,观察运行结果
#相当于w1(w2(f1))()

装饰有参数的函数

def w1(func):
    def inner(a,b):
       print('开始验证权限')
       func(a,b)
    return inner

@w1
def test(a,b):
    print('a=%d,b=%d'%(a,b))
test(1,2)

装饰带有返回值的函数

def func(functionName):
    print('进入装饰器')
    def func_in():
        print('调用被装饰函数')
        return functionName()
    return func_in

@func
def test():
    return 'itheima'
print(test())

带有参数的装饰器

如果给装饰器添加参数,需要增加一层包装,先传递参数,然后再传递函数名。

def func_arg(args):
    def func(functionName):
        def func_in():
            print('记录日志:%s'%args)
            functionName()
        return func_in
    return func

@func_arg('这是一条日志')
def fun1():
    print('记录完成')
fun1()

装饰器常见Python内置函数

map函数

map函数会根据提供的函数对指定的序列做映射。

map函数的定义如下:

map(function, iterable,…)

第1个参数是函数的名称;第2个参数表示支持迭代的容器或者迭代器。

map函数的作用是以参数序列中的每个元素分别调用function函数,把每次调用后返回的结果保存为对象。

func = lambda x:x+2
result = map(func, [1,2,3,4,5])
print(list(result))

执行过程:

filter函数

filter函数会对指定序列执行过滤操作。

filter函数的定义如下:

filter(function,iterable)

第1个参数可以是函数的名称;第2个参数表示的是序列、支持迭代的容器或迭代器。

func = lambda x:x%2
result = filter(func, [1, 2, 3, 4, 5])
print(list(result))

执行过程:

reduce函数

reduce函数会对参数序列中的元素进行迭代运算。

reduce函数的定义如下:

reduce(function, iterable[, initializer])

  • function是一个带有两个参数的函数;
  • iterable是一个可迭代对象;
  • initializer表示固定的初始值。

函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:

用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。

from functools import reduce
func = lambda x,y:x+y
result = reduce(func, [1, 2, 3, 4, 5])
print(result)

注意:function函数不能为None。

在Python3中,reduce函数已经被从全局名字空间里面移除了,它现在被放置在fucntools模块中,使用时需要先引入,格式如下:

from functools import reduce

九、一些函数

时间函数

在Python中,通常有如下几种方式表示时间:

(1)时间戳类型

(2)格式化的时间字符串类型

(3)时间元组类型(struct_time)

时间戳

通常来讲,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量,返回浮点秒数。

import time  # 引入time模块
ticks = time.time()
print("当前时间戳为:", ticks)

格式化的时间字符串

我们可以使用time模块的strftime方法来格式化日期。

import time
# 格式化成2016-03-20 11:45:39形式
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))

格式化符号及其含义:

%y     两位数的年份表示(00-99)
%Y     四位数的年份表示(000-9999)
%m     月份(01-12)
%d     月内中的一天
%H     24小时小时数(0-23)
%I     12小时制小时数(01-12)
%M     分钟数(00-59)
%S     秒(00-59)

时间元组

返回struct_time类型的函数主要有gmtime()、localtime()和strptime()。

序号            字段             值
 0            tm_year          2008
 1            tm_mon           1到12
 2            tm_mday          1到31
 3            tm_hour          0到23
 4            tm_min           0到59
 5            tm_sec           0到61
 6            tm_wday          0到6
 7            tm_yday          1到366
 8            tm_isdst         -1,0,1,-1是决定是否为夏令时的旗帜

随机数函数

什么是随机?

真正的随机涉及到了物理的量子,现实生活不是完全的随机
通俗来讲,越难以被猜到=随机性越好
严格来讲,随机数有评判标准:NIST测试方法,TestU01的测试工具等等

  • random.random()

用于生成一个0到1的随机符点数: 0 <= n < 1.0 。

import random
# 生成第一个随机数
print("random():", random.random())
# 生成第二个随机数
print("random():", random.random())
  • random.uniform(a,b)

返回a,b之间的随机浮点数,范围[a,b]或[a,b]取决于四舍五入,a不一定要比b小。

import random
print("random:",random.uniform(50,100))
print("random:",random.uniform(100,50))
  • random.randint(a,b)

返回a,b之间的整数,范围[a,b],注意:传入参数必须是整数,a一定要比b小。

import random
#生成的随机数n: 12 <= n <= 20
print(random.randint(12,20))
  • random.randrange([start], stop[, step])

返回有个区间内的整数,可以设置step。只能传入整数,random.randrange(10, 100, 2),结果相当于从[10, 12, 14, 16, … 96, 98]序列中获取一个随机数。

  • random.choice(sequence)

从sequence(序列,是有序类型的)中随机获取一个元素。列表、元组、字符串都属于sequence。

random.randrange(10,100,2)
结果等效于random.choice(range(10,100,2))
  • random.shuffle(x[,random])

用于将列表中的元素打乱顺序,俗称为洗牌。

import random
p = ["Python","is", "powerful","simple"]
random.shuffle(p)
print(p)
  • random.sample(sequence,k)

从指定序列中随机获取k个元素作为一个片段返回

import random
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
s = random.sample(list1, 5)
print(list1)
print(s)

结尾

生活总让我们遍体鳞伤,但到后来,那些受伤的地方一定会变成我们最强壮的地方。