Python函数

30 篇文章 0 订阅

1 函数的意义

之前编写的输出5行*号的程序,需要单纯的多次调用print函数来进行输出。
然而,这样做存在很多问题,如大量重复的代码。功能不清晰

所以我们使用函数来解决问题,函数分为:

  • 内置函数
  • 自定义函数。

函数的作用:

  • 通过函数,可以对功能进行详细的划分,在需要使用功能时,就可以调用该函数。
  • 通过函数,可以避免代码的重复,实现代码的重用。

2 内置函数

  • print()
  • input()
  • abs()
  • max()
  • min()

3 函数的定义

函数是具有一定功能的代码块,该代码块可以重复的执行。

函数的定义方式如下:

# def 函数名(形参):形参是没有意义的,只是为了给实参一个接口
#   函数体
# 函数名(实参)

# 简单的函数
def num_fun(x):
    # 如果没有return则会返回一个none
    return x*x

print (num_fun(10))

3.1 空语句(pass的使用)

函数不允许没有函数体,此时,我们就可以使用pass来进行占位。
Pass表示空语句,用在函数中用来进行占位。不过,pass也可以放在选择与循环体中。

3.2 函数的返回值(return)

return 语句就是将结果返回到调用的地方,并把程序的控制权一起返回。当调用函数时,可以近似的认为是函数的返回值替换掉了函数的调用
程序运行到所遇到的第一个return即返回(退出def块),不会再运行第二个return。
如果在函数内没有显式使用return返回值,则返回值为None。

1.要返回两个数值,写成一行即可:

def a(x,y):
    if x==y:
        return x,y
  
print a(3,3)		#3,3

2.但是也并不意味着一个函数体中只能有一个return 语句,例如:

def test_return(x):
    if x > 0:
        return x
    else:
        return 0

3.函数没有 return,默认 return一个 None 对象。
4.return 和 print 的区别:

x = 1
y = 2
def add (x, y):
  z = x + y
  return z
print (add(x,y)


x = 1
y = 2
def add (x, y):
  z = x + y
  print z
print (add(x,y)

在交互模式下,return的结果会自动打印出来,而作为脚本单独运行时则需要print函数才能显示。

3.3 返回多个值

在函数中,当执行到return语句时,函数就会终止执行,返回给函数的调用端,这就是说,return之后的语句不会再执行。因此,函数体中不能通过执行多个return,返回多个值。然而,函数返回多个值的需求却是普遍存在的,我们可以通过将多个值放入元组(列表或字典)中,从而返回多个值。

3.4 函数的参数

3.4.1 位置的参数

针对实参而言。在我们调用函数时,如果函数定义了形式参数,则我们需要在调用时,传递相应的实际参数(如果形参具有默认值可不传递),形式为按顺序,一一对应的方式。即第一个实参传递给第一个形参,第二个实参传递给第二个形参……,我们称这种对位传递的实际参数为位置参数。

# 输入三个数,判断这三个数是否能组成三角形
# 并且判断是什么类型的三角形
def triangle(a,b,c):
    if (a + b) > c and (a + c) > b and (b + c) > a:
        print(a,b,c,"这三个数可以组成一个三角形")
        if a == b == c:
            print("dengbian")
        elif (a == b) or (a == c) or (b == c):
            print("dengyao")
        elif (a**2+b**2==c**2) or (a**2+c**2==b**2) or (c**2+b**2==a**2):
            print("zhijiao")
    else:
        print("不行")
        
print(triangle(3,4,5))

# 封装一个求绝对值的函数

def num_abs(x):
    if x>0:
        return x
    else:
        return -x

print(num_abs(-100))

# 求立方,或者n次方
def fun1(x,n):      # n 是幂指数    
     s = 1
     while n>0:
         s *=x
         n -=1
     return s

print(fun1(2,3))
3.4.2 默认值参数

如果形参含有默认值,则在函数定义时,含有默认值的形参一定要定义在不含默认值的形参之后。

# 默认是平方,如果给定次幂n数值,则按照n进行求值
# 位置参数一定要放在默认参数的前面
def fun2(x,n=2):        # n是默认参数,可变性较小,一般放在后面
    s = 1
    while  n> 0:
         s *=x
         n -=1
    return s

print(fun2(2)) 
print(fun2(2,4))   

# 不止有一个默认参数的时候,需要把形参的名字写上,让它把相对应的值传上去
def stu(name,sex,age=18,city="bj"):
    return name,sex,age,city
    
print(stu("wang","男"))
print(stu("wang","男","15"))
print(stu("wang","男","sh"))
print(stu("wang","男",city="sh"))

这是因为,如果形参含有默认值,则意味着,该参数是一个可选参数,在函数调用时,可以选择性的进行传值。如果允许含有默认值的形参定义在不含默认值的形参前面,就会造成混淆。
不要使用不可变对象作为默认值

3.4.3 关键字参数

除了位置参数之外,我们还可以使用关键字参数。所谓关键字参数,就是在调用函数时,显式指定“参数名=参数值”的形式,即指定要给哪一个形参传递值。

同时,我们也可以在调用函数时,混合使用位置参数与关键字参数。

1.关键字参数的作用
总结来说,关键字参数具有如下优势:

  • 关键字参数能够增加程序的可读性。
  • 关键字参数可以不用考虑传递的顺序。
  • 当多个形参存在默认值时,关键字参数能够指定赋值给哪个形参。
  • 增加程序的可读性

假设如下的函数:
def box_volume(length, width, height):

我们可以使用位置参数调用:
box_volume(10, 20, 30)
这样是没有问题的,不过,当参数较多的时候,可能会降低程序的可读性,因为我们需要确定每个参数具体代表的含义(此时可能就得重新查看一下函数的定义)。但是,使用关键字参数进行调用,就会变得清晰,增加了程序的可读性:
box_volume(length=10, width=20, height=30)
不用考虑传递顺序
其次,如果使用位置参数,则必须严格要求按照顺序进行传递,如果使用关键字参数,则无此限制,我们完全可以任意颠倒参数的顺序:
box_volume(width=20, length=10, height=30)
指定为哪一个形参赋值
再者,当函数存在多个默认值时:
def box_volume(length=3, width=4, height=5):
如果我们使用位置参数进行调用:
box_volume(10)
则实际参数10会赋值给length,然而,我们的原意未必总是想指定length,如果我们想指定width或height,而让另外两个参数取默认值,或者,想显式指定其中的两个,另外一个取默认值,该怎样实现呢?此时,我们就可以使用关键字参数来实现需求了。
box_volume(width=10)
2.注意事项
在进行参数传递时,我们需要注意以下几点:

  • 位置参数必须出现在关键字参数的前面。
  • 同一个参数不能传递多次。(位置参数与关键字参数,或多个关键字参数)
  • 关键字参数的顺序可以颠倒,但是提供的参数名必须存在。

3.强制使用关键字参数
之间我们介绍到,使用关键字参数可以显式指定实参要赋值给哪一个形参,这样就可以提高程序的可读性。通常情况下,对于可选的参数(含有默认值的参数),因为其存在的不确定性,因此,如果需要为可选参数传值,在调用时往往使用关键字参数是一种好的选择。
不过,到底使用位置参数,还是关键字参数,完全取决于函数的调用者,函数的定义者并没有能力进行控制。也就是说,程序员A在调用函数时,可能会使用关键字参数,而程序员B却使用位置参数。为了能够增强程序的可读性,同时实现编程风格的一致性,函数的定义者可以使用“”语法,来限制“”后面的所有参数必须都是以关键字参数的形式传递。
说明:
强制使用关键字往往针对的是具有默认值的参数,但从语法的角度讲,对于不含默认值的参数,也是可以的。

# 关键字参数
# 允许传入0或多个含参数名的参数
def people(name,sex,**kw):
    return name,sex,kw

print(people("jklove","17"))
print(people("jklove","17",job="adc",club="ig"))

dic = {"job":"teacher","city":"bj"}
print(people("王","girl",job=dic["job"],city=dic["city"]))
3.4.4 命名关键字参数
# 命名关键字参数
# *前面是位参,*后面是关键字参数,必须按照指定参数上传,和位参不同,不能不传,也不能多传
def person(name,sex,*,job,city):
    return name,sex,job,city

# 命名关键字参数必须传入参数名(必须有名,还要有值),和位参不同。如果没传,调用会报错。
# 报错person() missing 2 required keyword-only arguments: 'job' and 'city'
print(person("孙","男"))
print(person("王","女",job="teacher",city="bj"))
3.4.5 可变参数

现在,我们需要编写一个函数,求任意两个数的平方和。这个功能是非常容易实现的。
然而,类似这样的需求,很容易会产生需求变更:求3个数的平方和。此时,可以增加一个函数来实现。
不过,增加一个函数过于繁琐。可以采用含有默认值的参数来实现。
然而,这依然是一个指标不治本的方案。如果需求再次变更,求4个,5个,乃至更多数的平方和呢?
编写函数,可以求任意数的平方和:

# 计算平方和
# 通过位置参数
def num1(x):
    s = 0
    for i in x:
        s += i*i
    return s

x = [1,2,3]
print(num1(x))

# 计算任意数的平方和,无论多少个
# 通过可变参数 参数不固定 可以是 0 1 n
# 在形参前面加一个* ---可变参数 0---多个
def num1(*x):
    s = 0
    for i in x:
        s += i*i
    return s
print(num1(1,2,3))

1.在定义函数时,我们可以定义可变参数(形式参数),所谓可变参数,即可以接收不定数量的实际参数。可变参数有两种形式:

  • 位置可变参数(*)
  • 关键字可变参数(**)

2.对于可变参数,我们需要注意如下事项:

  • 可变参数只能接收未匹配的(剩下的)实际参数,如果实际参数已经匹配某形式参数,则不会由可变参数接收。
  • 在参数位置上,关键字可变参数必须位于函数参数列表的最后。
  • 在位置可变参数后面定义的参数,将自动成为关键字参数。
  • 位置可变参数与关键字可变参数各自最多只能定义一个。
    def f(a, *v, **kv)

3.参数打包
函数调用时,位置可变参数(形参)可以用来接收所有未匹配的位置参数(实参),而关键字可变参数(形参)可以用来接收所有未匹配的关键字参数(实参)。在函数调用过程中,所有未匹配的位置参数会打包成一个元组,而所有未匹配的关键字参数会打包成一个字典。
def f(*v, **kv):
print(type(v))
print(v)
print(type(kv))
print(kv)
4.位置可变参数
现在,让我们使用位置可变参数来实现。
def sum(*value):
5.关键字可变参数
对于关键字可变参数,可以用来接收一些可选的选项,设置等。对于选项较多时,如果将每个选项都用一个关键字参数来接收时,会显得函数定义特别庞大,此时,就可以使用一个关键字可变参数来实现。例如,在Requests库中,get函数定义为:
requests.get(url, params=None, **kwargs)
其中,kwargs可以接收很多选项,例如data,headers,cookies,files等。

3.4.6 最终参数顺序

位置参数>默认参数>可变参数>命名关键字参数>关键字参数

4 命名空间与作用域

4.1 命名空间

命名空间,可以认为就是保存命名的一个容器,当我们定义变量,函数,类等结构时,相关的名称就保存在相应的命名空间中。

1.根据名称在当前模块(文件)中定义的位置,我们可以将名称分为两类:

  • 局部名称:定义在函数内的名称(函数的参数,变量,嵌套函数等)。
  • 全局名称:定义在函数、类外的名称(处于当前模块的顶级)。

全局(局部)变量,顶层函数,嵌套函数

# 定义一个全局变量
y = 2
def num():
    # 强行定义全局变量
    global y
    y = 4
num()
# 如果不强行定义y的值应该是2,强行定义后是4
print(y)

2.命名空间可以分为如下几类:

  • 内建命名空间
    保存内建的名称。例如,print,sum等内建函数。内建命名空间在Python解释器启动时就会创建,直到程序运行结束。
  • 全局命名空间
    保存当前模块(文件)中出现在顶层位置的名称。例如:全局变量,顶层函数等。全局命名空间与模块相关,在读取模块定义时创建,直到程序运行结束。
  • 局部命名空间
    保存局部名称。例如,局部变量,函数参数,嵌套函数等。局部命名空间在函数执行时创建,在函数执行结束后销毁。

由此可知,我们定义的变量,函数等,其名称处于哪个命名空间,完全是由其定义的位置决定的,对于变量,定义的位置就是变量第一次赋值的位置。对于函数,定义的位置就是def出现的时刻

说明
要访问某个名称,必须要在该名称定义后才能访问

4.2 作用域

作用域,就是命名空间的有效区域。在命名空间的作用域内,我们可以直接访问命名空间中的名称。关于作用域,有如下说明:

  • 内建命名空间,作用域为所有模块(文件)。即在任何模块(文件)中均可直接访问内建命名空间内的名称。
  • 全局命名空间,作用域为当前模块(文件)。在当前模块(文件)中可直接访问全局命名空间内的名称,在其他模块(文件)中,需要先导入该模块,然后才能访问(模块导入的内容后面介绍)。
  • 局部命名空间,作用域为当前函数,在函数内可访问局部命名空间内的名称,在函数外则无法访问。

4.3 LEGB原则

1.在访问名称时,会根据LEGB的顺序来搜索名称,即搜索顺序为L -> E -> G -> B。描述如下:

  • L(Local):当前函数的局部命名空间
    本地作用域,即包含所访问名称的最小作用域
  • E(Enclosing)外围作用域:
    当函数嵌套时会存在这种情况,即访问外层函数的作用域,如果没有找到,并且外层函数依然还有外层函数(函数多层嵌套),则会继续搜索更外层的函数,直到顶层函数位置(对应外层函数的局部命名空间)
  • G(Global)全局作用域:
    当前模块的作用域(全局命名空间)
  • B(Build-in)内建作用域
    所有内建的名称(内建命名空间),python解释器下的所有文件。
#全局作用域
def num():  
    a = 10  #外围作用域
    def num1():
        b = 10  #局部作用域
        print(1)

2.对名称的访问以下情况:

  • 读取名称的值
  • 为名称赋值
  • 删除名称

3.几种情况下,效果是不同的。名称既可以是变量,也可以是函数。

(1)读取名称的值

当读取变量的值时,如果该变量在当前作用域没有发现,则会按照LEGB的方式依次进行查找,以先找到的为准。如果到最后也没有找到,则会产生NameError错误。

(2)为名称赋值

当为变量赋值时,行为有所不同,此时只会在程序执行处的最小作用域内寻找变量,如果变量存在,则修改该变量的绑定,如果该变量不存在,则不会再按照LEGB的顺序查找,而是在当前作用域的命名空间中,新建名称,并绑定所赋予的值。这也就是说,我们无法修改其他命名空间中名称的绑定。

(3)nonlocal与global

但是,有时候我们确实期望对外部作用域的名称进行修改,例如,嵌套函数中修改外围函数中定义的名称,或者是修改全局名称。此时,前者,我们可以使用nonlocal,后者,我们可以使用global。

细节:nonlocal指定的名称不存在,会产生错误,而global指定的名称不存在则不会产生错误,但是该名称也不会自动创建,如果访问该名称,依然会产生错误。

(4)vars,locals与globals函数

  • locals
  • vars
  • globals

5 Lambda表达式

对于函数而言,函数名也是一个名称,该名称绑定函数对象。因此,我们也可以像变量那样,对函数名进行相应操作,这包括:

函数名可以赋值给一个变量。
函数名可以作为实际参数,传递给其他函数。
函数名可以作为另外一个函数的返回值。
不过,用于排序的函数过于简单,为了这么一个简单的功能,单独定义一个函数,总有些“杀鸡用牛刀”的意味,我们可以使用Lambda表达式来创建函数,能够实现同样的功能。

# lambda 表达式,代替函数,实现简单功能或方法的实现
    # 格式
#     lambda 参数:代码体

lam = lambda x:x**2
print(lam(3))

其中,参数是可选的,表达式的结果值,就是函数的返回值。lambda表达式的函数体仅能存在一条语句,故lambda表达式适用于功能简单的函数。也因为lambda表达式创建的函数没有名字,因此也称为匿名函数。

6 递归

6.1 递归的含义

递归,就是一个函数直接或者间接调用自身的过程。

  • 直接递归:函数A在其函数体内直接调用其自身
# 1 1 2 3 5 8 13 21 34 55 斐波那契数列
# n=4 输出 3
def fibonacci(n):
    if n == 1 or n ==2:
        return 1
    else:
        return fibonacci(n-1)+fibonacci(n-2)

print(fibonacci(8))
  • 间接递归:函数A调用函数B,而函数B又调用了函数A(或者函数B再调用其他函数,而其他函数调用函数A)
    不过,函数如果无条件的调用自身,这是没有意义的,最终也会导致超过最大递归深度而产生错误。因此,我们必须能够找出,让递归终止的条件。
# 互相调用 间接递归
def man():
    eat()

def eat():
    man()

递归,我们可以分为递推与回归。所谓递推,就是解决问题,逐层的进行推理,得出递归的终止点。而回归,则是利用刚才递推取得终止点的条件,再一层层返回,最终取得答案的过程。

6.2 循环与递归

循环与递归具有一定的相似度,二者往往可以实现相同的效果。理论上,能够通过循环解决的问题,使用递归也能够实现。然而,二者还是有一定的差别的。从思想的角度讲,循环是重复性的执行一件相同或者相似的事情,进而得出结果。而递归则是将问题分解为更小的问题,直到该问题能够解决,然后再由最小问题的答案,逐步回归,从而解决更大的问题,最终解决整个问题的过程。

以我们刚才的求阶乘的程序为例,对于循环,则是对数据集中的每个数据,依次取出,重复性执行相同的累乘运算,最终得到阶乘的结果。而递归则是,将n的阶乘分解为更小数值的阶乘:

n! = n * (n – 1)!

这个过程会一直进行分解,直到分解成可以解决的问题(递推),即

1! = 1

然后,根据最小问题的答案,逐步解决更大问题(2,3,4的阶乘等),最终推导出最后的结果,即n的阶乘。

# n的阶乘
# 普通循环方法
x = int(input("输入一个数:"))
s = 1
for i in range(1,x+1):
    s*= i
print(x,"的阶乘是:",s)

# 递归的方法
def loop(x):
    s = 1
    for i in range(1,x+1):
        s *=i
    return s

def loop1(x):
    if x == 1:
        return 1
    else:
        return x*loop(x-1)
    
print(loop1(3))

对于递归而言,其实现的是函数的逐层调用,而每调用一次函数,就会为函数分配额外的空间资源(自己调用自己也不例外),因此,从效率角度来说,递归不如循环高效。然而,对于一些复杂的问题(例如即将介绍的汉诺塔递归),递归可以更有效的将问题进行分解,以更容易理解的方式解决问题,从这个角度讲,递归优于循环。

6.3 汉诺塔

汉诺塔的规则。

6.4 递归的深度

递归的默认深度为1000。

sys.getrecursionlimit()
sys.setrecursionlimit()
该方法名多少有些误解,我们应该将其理解为最大函数调用限制,可能会更好一些。

7 函数描述

7.1 函数说明文档

我们可以为函数编写说明文档。所谓说明文档,就相当于是函数的使用说明书,在文档中可以用来说明函数的功能,参数,返回值类型以及相关注意事项,使用示例等。要为函数编写说明文档,可以在函数体的上方,使用字符串进行标记。按照惯例,作为说明文档的字符串使用三个双引号界定,并且第一行为函数的一个简要说明,接下来是一个空行,然后是函数的具体说明。

def fun():
    """
    这是一个说明
    
    略略路
    """
    # 123
    """
    哈哈
    """
    pass
print(fun.__doc__)

输出结果

 这是一个说明
    
    略略路
    

也许大家会有所疑问,这好像就是普通的注释啊。效果上很像,但实则不是。如果是注释内容,则解释器会忽略,但是,作为函数文档,我们可以通过函数的__doc__属性获取这些说明,方便我们进行查看。同时,我们使用help函数来获取帮助时,返回的就是函数的说明文档信息。这就能够让我们在不打开函数所在的文件时,也能够方便的获取函数的帮助信息,如果是注释,则必须要定位到相应的函数,才能查看。

7.2 函数注解

在Python中,因为变量是没有类型的,因此对于函数来说,我们无法像C或Java语言那样,只要观看函数的定义,就可以轻松的确定函数的参数与返回值类型。为了弥补这种不足,使代码更具有可读性与易用性,我们可以使用函数注解来进行标注,显式指定函数参数或返回值的类型。

def add(a: int, b: int=3) ->int:
    pass
print(add.__annotations__)

输出结果:

{'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}

参数的说明是在参数后面加上一个:然后给出参数的类型。根据惯例,:与其后的类型使用一个空格分隔。返回值说明是在参数列表的)后,使用->指出。

我们可以使用函数对象的__annotations__属性获取函数的注解信息。该属性是一个字典类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值