Python函数

1. 初识函数

1.1 何为函数,何时用函数

什么是函数?

函数,可以当做是一大堆功能代码的集合。

什么时候用函数

一般在项目开发中有会有两种应用场景:

  1. 有重复代码,用函数增加代码的重用性。
  2. 代码太长,用函数增强代码的可读性。

1.2 函数的语法结构

def my_len(a, b):
    '''函数注释'''
    # 函数体
    return '123'
  1. def 就是定义函数的关键字,它不能够省略,必须写
  2. my_len():函数名,函数名的命名遵循变量的命名规范,他也是必填的,不能省略的,括号不能省略
  3. a, b称为是函数的参数,是可选的,可有可无
    参数就是执行函数的一些前提条件
  4. ‘’‘函数注释’‘’
    注释不参与函数的执行,只起到函数功能的提示作用,以及各个参数是什么意思
    # 可有可无的,但是,推荐每个函数都加上函数注释
  5. 函数体
    才是函数的核心,它是函数所具备的核心功能,应该是必须有的,pass
    没有函数体的函数是没有意义的函数,一般情况都是要有函数体的
  6. 返回值 return 函数的返回值
    # 返回值也是可选的,可以有返回值,也可以没有返回值,但是一般情况下都要有返回值
    # 一个函数只能有一个返回值…

1.3 函数的定义与调用


  1. 函数必须先定义后调用,没有定义函数是一定不能够调用的
  2. 函数在定义阶段,只检测语法是否错误,不检测逻辑是否有问题
  3. 逻辑上的错误只会在调用阶段检测
  4. 函数一定是被调用之后才会执行函数内部的代码块,不调用函数一定不会执行函数的

如何调用函数?

函数名() # 只要函数名加括号一定会执行函数体代码
函数如果在定义阶段有参数,那么,在调用的时候,连参数一块写上


函数调用的内部原理:

  1. 先在内存空间中申请一块空间地址来存储函数体代码
  2. 把函数名和函数体所在的空间地址绑定在一起
  3. 以后只需要通过函数名()来访问函数体代码即可

1.4 函数的分类

1. 内置函数
	# Python解释器中原本就存在的,可以直接拿来使用,无需定义,直接调用
    len print input 之前学习的数据类型的各个方法


2. 自定义函数
	# 程序员自己写的函数,就必须先定义后调用
    1. 无参函数:函数在定义阶段没有参数
    	def my_len():
            pass
        
        my_len() 
    2. 有参函数:在函数定义阶段有参数的存在
    	def my_len(a, b):
            pass
        
        my_len(1, 2)
    3. 空函数
    	def my_len():
            pass
        
       """作用就在于可以帮助我们梳理业务逻辑""" 
 	def register():
        pass
    
    def login():
        pass
    
    def address():
        pass
    
    def order():
        pass

2. 参数

2.1 形参和实参

在定义函数时,如果在括号中添加变量,我们称它为函数的形式参数:

# ###### 定义有三个参数的函数(a1/a2/a3一般称为形式参数-形参) #####
def func(a1,a2,a3):
    print(a1+a2+a3)

# 执行函数并传入参数(执行函数传值时一般称为实际参数-实参)
func(11,22,33)

# 执行函数并传入参数
func(9,2,103)

2.2 位置传参

位置传参是一种函数参数传递的方式,它通过顺序给函数传递参数,将实参与形参进行一一对应。

def func(a, b, c):
    print(a, b, c)
func(1,2,3)  # 实参1,2,3会按照顺序传给形参a,b,c

2.3 关键字传参

关键字传参是指在函数调用时,使用指定的参数名来传递参数值,而不是按照参数在函数定义时的顺序来传递参数。

关键字传参的基本语法如下:

函数名(参数1=1,参数2=2...,参数n=值n)

例如:

def func(x, y, z):
    print("x=", x, "y=", y, "z=", z)

func(x=1, y=2, z=3)

上述代码中,定义了一个名为func的函数,函数有三个参数,分别为x、y、z。在调用函数时,使用了关键字传参方式,按照参数名指定了参数值,因此输出结果为x=1 y=2 z=3。

关键字传参的优点是可以使代码更加易读、易懂,尤其是当函数参数比较多时,可以清晰地表明每个参数的含义,避免了因参数顺序错误而产生的难以查找的错误。同时也可以使函数的调用更加灵活,可以只传递需要的参数,而不用按照参数顺序传递所有参数。

2.4 默认参数

默认参数是指在函数定义时,为一个或多个参数指定一个默认值,当函数调用时不传递这些参数时,默认值会被使用。

默认参数的基本语法如下:

def func(arg1, arg2=default_value):
    pass

其中,arg1是必须传递的参数,而arg2有一个默认值default_value,如果在函数调用时不传递arg2参数,则默认使用default_value作为参数值。

例如,下面的代码定义了一个函数greeting,其中name参数有一个默认值"Guest":

def greeting(name="Guest"):
    print("Hello,", name)

greeting()
greeting("Alice")

上述代码中,第一次调用greeting函数时,没有传递任何参数,因此使用了默认值"Guest",输出结果为Hello, Guest;第二次调用greeting函数时,传递了一个参数"Alice",因此输出结果为Hello, Alice。

使用默认参数的优点是可以减少函数调用时必须传递的参数数量,增加函数的灵活性,同时也可以使函数的调用更加简洁明了。不过需要注意的是,当默认参数被多次调用时,如果不小心修改了参数值,可能会影响到以后的函数调用,因此需要谨慎使用。

2.5 动态参数

动态参数是指在函数定义时,能够接收不定长度的参数列表,分为两种类型:可变参数和关键字参数。

2.5.1 可变参数

可变参数使用*变量(变量一般用args)来接收一个不定长度的参数列表,它表示接收一个元组类型的参数,在函数内部可以通过遍历操作访问元组中的每个元素。示例代码如下:

def add(*args):
    result = 0
    for num in args:
        result += num
    return result

print(add(1, 2, 3, 4))    # 输出:10
print(add(1, 2))          # 输出:3

2.5.2 关键字参数

关键字参数使用**变量(常用kwargs)来接收一个不定长度的关键字参数列表,它表示接收一个字典类型的参数,在函数内部可以通过访问字典中的键值对来获取参数值。示例代码如下:

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(key, ":", value)

print_info(name="Alice", age=20, gender="female")

在这个示例代码中,函数print_info接收一个不定长度的关键字参数列表**kwargs,并遍历字典中的所有键值对进行输出。

同时,还可以在函数的参数列表中混合使用可变参数和关键字参数,例如:

def function(arg1, arg2, *args, **kwargs):
    pass

这个函数定义中,arg1和arg2是必须传递的参数,*args接收一个不定长度的位置参数列表,**kwargs接收一个不定长度的关键字参数列表。当函数调用时,位置参数需要在关键字参数之前传递。

2.6 命名关键字参数

命名关键字参数是Python3中添加的一种函数参数形式,它可以用来限制关键字参数的名称,强制关键字参数的使用者必须通过指定参数名字对参数进行传递,在传递参数时其它名称将会报错。命名关键字参数通过在函数参数列表中使用*后缀来表示,其语法形式如下:

def function(arg1, arg2, *, kwarg1, kwarg2):
    pass

使用命名关键字参数有以下几个优点:

  1. 命名关键字参数可以提高函数的可读性和可维护性。因为在函数定义时,强制要求传递的参数必须使用关键字来指定,不能使用无意义的字符串或者数字,使得函数更加易于理解和维护。
  2. 命名关键字参数可以提高函数的调用安全。因为当参数非常多时,使用位置参数传递容易出现传递顺序错误,而使用命名关键字参数则可以明确指定传递参数的名称,从而避免这种问题。

示例如下:

def print_person_info(name, age, *, gender):
    print(f"Name: {name}")
    print(f"Age: {age}")
    print(f"Gender: {gender}")

print_person_info("Alice", 20, gender="female")    # 正确的调用方式
print_person_info("Bob", 25, "male")              # 报错,必须使用关键字参数

2.7 混用注意事项

在Python中,函数的参数支持多种形式的传参方式,如位置参数、默认参数、动态参数和命名关键字参数。在使用这些参数的时候需要注意以下几点:

  1. 参数顺序
    位置参数必须在默认参数、动态参数和命名关键字参数之前,否则会导致语法错误。
  2. 默认参数
    默认参数必须放在位置参数后面,但在调用函数时可以省略默认参数。
  3. 动态参数
    动态参数必须放在位置参数和命名关键字参数之后,否则会导致语法错误。
  4. 命名关键字参数
    命名关键字参数必须在动态参数之后,否则会导致语法错误。
  5. 混合使用
    在使用多种参数形式时,应该避免出现歧义和混淆。在函数定义时应该结合函数的实际需求,合理地选用不同的参数形式,从而提高代码的可读性和可维护性。

示例代码:

def func(a, b=10, *args, c, d):
    pass

# 正确的调用方式
func(1, c=3, d=4)
func(1, 2, 3, 4, c=5, d=6)

# 错误的调用方式
func(1, 2, 3, 4, 5, d=6)    # 参数太多,缺少c参数
func(1, 2, 3, c=4, d=5)     # 缺少必要的位置参数b
func(1, b=2, 3, 4, c=5, d=6)    # 参数位置错误

# 1. ** 必须放在 * 的后面
def func1(*args, **kwargs):
    print(args, **kwargs)


# 2. 参数和动态参数混合时,动态参数只能放在最后。
def func2(a1, a2, a3, *args, **kwargs):
    print(a1, a2, a3, args, **kwargs)


# 3. 默认值参数和动态参数同时存在
def func3(a1, a2, a3, a4=10, *args, a5=20, **kwargs):
    print(a1, a2, a3, a4, a5, args, kwargs)


func3(11, 22, 33, 44, 55, 66, 77, a5=10, a10=123)

3. 名称空间

所谓名称空间是程序中用来组织和管理标识符的一种机制,它将标识符分成了不同的组,确保同名标识符之间不会冲突。一个名称空间可以被看作是一个存储标识符的容器,每个容器中的标识符都有其特定的作用域。在一个名称空间中,每个标识符都有一个唯一的名称,该名称可以在程序中被引用和操作。在程序执行期间最多存在三种名称空间,即内建名称空间,全局名称空间,局部名称空间。

3.1 内建名称空间

内建名称空间是Python解释器自带的一些名称空间,其中包含了Python语言中的内建函数和常量。内建名称空间属于最外层的名称空间,也被称为全局名称空间。在Python程序中,可以使用内建函数dir()来查看当前作用域下的所有名称,包括内建名称空间中的名称。例如,在Python交互式命令行中输入dir(__builtins__),即可查看内建名称空间中的所有名称。
示例如下:

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

3.2 全局空间名称

全局空间(Global Namespace)是指在Python程序中没有被定义在任何函数或类的作用域中的名称空间。在Python程序执行时,全局空间会在程序启动时创建,并在整个程序执行期间一直存在。在全局空间中定义的变量,函数,类等名称都可以在程序的任何地方被调用和使用。
在Python程序中,使用globals()函数可以查看当前全局空间中的所有名称。例如,在Python交互式命令行中输入print(globals()),即可查看当前全局空间中的所有名称极其对应的值。

>>> print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}

3.3 局部名称空间

局部名称空间(Local Namespace)是指在Python程序中定义在函数或类作用域中的名称空间。每个函数或类都有自己的局部名称空间,该名称空间只有在函数或类被调用时才被创建,当函数或类执行完毕后,局部名称空间也会被销毁。
在Python程序中,可以使用locals()函数查看当前局部名称空间中的所有名称,例如,在一个函数内部输入print(locals()),即可查看该函数的局部名称空间中的所有名称极其对应的值。

需要注意的是,局部名称空间旨在函数或类作用域中有效,外部无法直接访问局部名称空间中的名称。如果需要在函数或类外部访问局部名称空间中的某些变量或函数,可以通过函数或类的返回值来实现。

4. 作用域

作用域是指一个标识符所存在的区域,该区域规定了标识符的可见范围和生命周期。在一个作用域内,同名的标识符被认为是相同的,因此它们之间会发生冲突。一般情况下,作用域可以分为全局作用域和局部作用域两种类型。在不同的作用域内,相同名称的标识符可以拥有不同的值和类型

4.1 全局作用域

位于全局名称空间、内建名称空间中的名字属于全局范围,全局作用域的生命周期与程序运行的生命周期相同,在程序运行期间都存在。如果程序运行结束,全局作用域也会随之销毁。

需要注意的是,全局作用域中的变量可以被任何函数或方法引用,因此需要谨慎使用全局变量,避免出现意料之外的结果或Bug。同时,为了使代码更加清晰易懂,应该尽量避免在全局作用域中定义过多的变量和函数。

4.2 局部作用域

位于局部名称空间中的名字属于局部范围,局部作用域只在函数或方法内部有效。其生命周期同函数或方法的生命周期相同,即函数或方法开始被定义,函数或方法执行完毕后,局部作用域也会随之被销毁。

局部作用域的存在可以使函数或方法之间的变量相互独立,避免了变量之间的冲突和混淆,同时也有利于代码的重复利用和模块化。

4.3 作用域优先级

Python中的作用域有:局部作用域、嵌套作用域和全局作用域。作用域的优先级顺序为:局部作用域 > 嵌套作用域 > 全局作用域。如果在一个作用域中找不到定义的变量或函数,Python解释器会按照这个优先级的顺序继续在上层作用域中查找,直到找到该变量或函数或查找到全局作用域仍然找不到为止。

具体来说,Python解释器在访问一个变量时,会按照以下的顺序进行查找:

  1. 当前作用域(包括局部作用域、嵌套作用域和全局作用域)中是否有该变量;

  2. 如果当前作用域中没有该变量,则依次向上一层嵌套作用域中查找,直到查找到该变量或查找到全局作用域为止;

  3. 如果全局作用域中也没有该变量,则会抛出NameError异常。

如果在函数或方法内部定义了一个与全局作用域中同名的变量,那么在函数或方法内部访问这个变量时,会优先访问局部变量而非全局变量。

4.4 global 和 nonlocal

在Python中,我们可以使用global关键字来在函数内部访问并修改全局变量。例如:

x = 10

def change_x():
   global x
   x = 20

change_x()
print(x)  # 输出 20

global 关键字用于在函数内部访问全局变量,以及在函数内部对全局变量进行修改。

默认情况下,在局部作用域对全局变量只能进行:读取和修改内部元素(可变类型),无法对全局变量进行重新赋值。

而 nonlocal 关键字则用于在函数内部访问嵌套作用域中的变量。具体来说,如果在函数内定义了一个函数,且内部函数访问了外部函数的变量,那么这个变量就属于嵌套作用域中的变量。例如:

def outer():
    x = 10
    def inner():
        nonlocal x
        x = 20
    inner()
    print(x)

outer()  # 输出 20

Ps:global 和 nonlocal 关键字的使用都会影响到函数中变量的作用域。因此,在使用这两个关键字时,要谨慎选择变量的命名,以免产生意外的错误。

5. 函数对象

在Python中,函数也是一种对象,我可以将其赋值给变量,也可以将其作为参数传递给其他函数,这便是函数对象。

可以简单理解为函数名也是一个变量,只不过此变量指代的是一个函数,指向的是函数所在的内存空间。
在解释型语言中,函数需要先定义再使用。

5.1 引用函数

将函数名赋值给其他变量,函数名其实就个变量,代指某函数;如果将函数名赋值给另外一个变量,则此变量也会代指该函数,例如:

def func(a1,a2):
    print(a1,a2)

xxxxx = func

# 此时,xxxxx和func都代指上面的那个函数,所以都可以被执行。
func(1,1)
xxxxx(2,2)

5.2 函数做元素

然函数就相当于是一个变量,那么在列表等元素中可以把函数当做元素。
例如:

def func():
    return 123

data_list = ["antony", "func", func , func() ]

print( data_list[0] ) # 字符串"antony"
print( data_list[1] ) # 字符串 "func"
print( data_list[2] ) # 函数 func
print( data_list[3] ) # 整数 123

res = data_list[2]()
print( res ) # 执行函数 func,并获取返回值;print再输出返回值。

print( data_list[2]() ) # 123

PS:函数同时也可被哈希,所以函数名通知也可以当做 集合的元素、字典的键。

def send_message():
    """发送消息"""
    pass


def send_image():
    """发送图片"""
    pass


def send_emoji():
    """发送表情"""
    pass


def send_file():
    """发送文件"""
    pass

def xxx():
    """收藏"""
    pass


function_dict = {
    "1": send_message,
    "2": send_image,
    "3": send_emoji,
    "4": send_file,
    "5": xxx
}

print("欢迎使用xx系统")
print("请选择:1.发送消息;2.发送图片;3.发送表情;4.发送文件")
choice = input("输入选择的序号") # "1"

func = function_dict.get(choice)
if not func:
    print("输入错误")
else:
    # 执行函数
    func()

5.3 函数做参数名和返回值

函数名其实就一个变量,代指某个函数,所以,他和其他的数据类型一样,也可以当做函数的参数和返回值。

参数示例

def plus(num):
    return num + 100

def handler(func):
    res = func(10) # 110
    msg = "执行func,并获取到的结果为:{}".format(res)
    print(msg) # 执行func,并获取到的结果为:110
   
# 执行handler函数,将plus作为参数传递给handler的形式参数func
handler(plus)

返回值示例:

def plus(num):
    return num + 100

def handler():
	print("执行handler函数")
    return plus
    
result = handler()
data = result(20) # 120
print(data)

6. 闭包

闭包函数(Closure)是一种在编程语言中常见的概念,它可以让一个函数访问其词法作用域外部的变量,即使在该函数在定义时所处的作用域已经不再存在。

通俗地说,闭包函数允许一个函数捕获和保持了其所在作用域的一些变量,使得这些变量可以在该函数被调用的时候依然存在。这使得闭包函数可以在其创建的环境中执行,而不受外部环境的影响。

“闭”代表函数是内部的,“包”代表函数外’包裹’着对外层作用域的引用。因而无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。

闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上 闭包是基于函数嵌套搞出来一个中特殊嵌套)。

闭包的特殊性质,使得闭包函数成为了一个强大的工具,可以在许多场景下被用来实现一些特殊的逻辑,尤其是在需要保持某些状态或配置的情况下。例如:

应用场景1:封装数据防止污染全局。

name = "antony"

def f1():
    print(name, age)

def f2():
	print(name, age)

def f3():
	print(name, age)
def func(age):
    name = "antony"

    def f1():
        print(name, age)

    def f2():
        print(name, age)

    def f3():
        print(name, age)

    f1()
    f2()
    f3()

func(123)

应用场景2:状态保持
闭包可以保持特定状态,使得函数在多次调用之间能够记住之前的状态。

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter1 = counter()
counter2 = counter()

print(counter1())  # 输出 1
print(counter1())  # 输出 2
print(counter2())  # 输出 1

应用场景3:函数工厂
通过闭包实现一个通用的函数工厂,该工厂可以用于生成不同的函数,从而避免了重复编写相似的代码。

示例:

def make_adder(n):
    def adder(x):
        return x + n
    return adder

add_2 = make_adder(2)
add_5 = make_adder(5)

print(add_2(3))  # 输出 5
print(add_5(3))  # 输出 8

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值