# 函数 在 Python 中,函数是一段组织好的、可重复使用的代码,用于执行特定的任务。函数可以接受输入(称为参数),并可以返回输出(称为返回值)。使用函数可以帮助我们组织代码,提高可读性和重用性。 # Python函数式编程 ## 函数的定义 在 Python 中,函数的定义使用 `def` 关键字,后面跟函数名和参数列表。函数体由缩进的代码块组成。 **语法:** ```python def function_name(parameters): """函数的文档字符串(可选)""" # 函数体 # 执行任务的代码 ``` **示例:** ```python def greet(name): """提示/注释 信息""" print("Hello, %s" % name) # 使用占位符进行格式化输出 ``` ## 函数的调用 调用函数时,需要使用函数名后跟括号,并传入必要的参数。 ```python greet("牛老师") # 输出: Hello, 牛老师! ``` **案例:**计算字符串的长度 ```python def my_len(): s = 'hello world' length = 0 for i in s: length = length + 1 print(length) my_len() # Output: 11 ``` ## 函数的返回值 函数可以使用 `return` 语句返回值。如果没有 `return`,函数默认返回 `None` ```python def my_len(): s = 'hello world' length = 0 for i in s: length = length + 1 return length str_len = my_len() print(str_len) # Output: 11 ``` **return关键字的作用** - return 是一个关键字,这个词翻译过来就是“返回”,所以我们管写在return后面的值叫“返回值”。 - 不写return的情况下,会默认返回一个None - 一旦遇到return,结束整个函数。 - 返回多个值会被组织成元组被返回,也可以用多个值来接收 ```python def ret_demo(): return 1,2,'a',['hello','world'] ret = ret_demo() print(ret) # Output: (1, 2, 'a', ['hello', 'world']) ``` 使用多个值接收(值的数量必须跟返回值数量对应) ```python def ret_demo(): return 1,2,'a',['hello','world'] ret1,ret2,ret3,ret4 = ret_demo() print(ret1,ret2,ret3,ret4) # Output: 1 2 a ['hello', 'world'] ``` ## 函数的参数 在 Python 中,函数的参数是用于向函数传递输入数据的变量。通过参数,函数可以根据不同的输入执行不同的操作。Python 支持多种类型的参数,每种参数类型都有其特定的用途和特点。 ### 形参 在函数定义时用来占位置的字符,可以是任何字符,用来表示函数需要有一个参数传递进来... - 形参是在函数定义时指定的参数。它们用于接收函数调用时传递的值。 - 形参在函数体内作为变量存在,作用域仅限于函数内部。 ### 实参 在函数调用的时候实际传递进去的参数,是一个具体的值,参与函数内部的处理... - 实参是在函数调用时传入的实际值。实参可以是常量、变量、表达式或其他函数的返回值。 - 实参的值被传递给形参。 **示例:** ```python def greet(name): # name 是形参 print(f"Hello, {name}!") greet("Alice") # "Alice" 是实参 ``` 也可以更过分一点,写成如下形式(不推荐,容易被喷) ```python def greet(占位置的): # name 是形参 print(f"Hello, {占位置的}!") greet("Alice") # "Alice" 是实参 ``` **案例:函数版本简单计算器实现** ```python def calculate(operation, num1, num2): # operation, num1 和 num2 是形参 if operation == 'add': return num1 + num2 elif operation == 'subtract': return num1 - num2 elif operation == 'multiply': return num1 * num2 elif operation == 'divide': if num2 == 0: return "错误:除数不能为零!" return num1 / num2 else: return "错误:未知操作!" # 用户输入 user_operation = input("请输入操作(add, subtract, multiply, divide): ") # 实参 user_num1 = float(input("请输入第一个数字: ")) # 实参 user_num2 = float(input("请输入第二个数字: ")) # 实参 # 调用函数 result = calculate(user_operation, user_num1, user_num2) # user_operation, user_num1 和 user_num2 是实参 print(f"结果: {result}") # Output: 请输入操作(add, subtract, multiply, divide): add 请输入第一个数字: 10 请输入第二个数字: 20 结果: 30.0 ``` ### 位置参数 位置参数是最常用的参数类型,调用时根据位置传递给函数。参数的数量和顺序必须与函数定义时一致。 ```python def add(a, b): return a + b result = add(2, 3) # 2 和 3 分别对应 a 和 b print(result) # 输出: 5 ``` ### 默认参数 默认参数允许为函数的参数设置默认值。调用函数时,如果没有提供该参数的值,则使用默认值。 ```python def greet(name="Guest"): print(f"Hello, {name}!") greet() # 输出: Hello, Guest! greet("Alice") # 输出: Hello, Alice! ``` ```python def stu_info(name,age=18): print('Name:',name) print('age:',age) stu_info('nls') stu_info('xls',age=30) # Output: Name: nls age: 18 Name: xls age: 30 ``` ### 关键字参数 按照形参的参数名进行精确传参,使用关键字参数的时候可以不用考虑形参的具体位置和顺序 ```python def maxnumber(x,y): the_max = x if x > y else y return the_max ret = maxnumber(y = 10,x = 20) print(ret) # Output: 20 ``` ### 动态参数 动态参数是指在函数定义时允许接受可变数量的参数。这种特性使得函数可以处理不同数量的输入,而不需要在定义时明确列出所有参数。Python 提供了两种主要方式来实现动态参数:`*args` 和 `**kwargs`。 `*args` 允许函数接受任意数量的位置参数。这些参数会被收集到一个元组中。 ```python def sum_numbers(*args): total = sum(args) # args 是一个元组 return total result = sum_numbers(1, 2, 3, 4, 5) # 可以传入任意数量的参数 print(result) # 输出: 15 ``` `**kwargs` 允许函数接受任意数量的关键字参数,这些参数会被收集到一个字典中。 ```python def print_info(**kwargs): for key, value in kwargs.items(): # kwargs 是一个字典 print(f"{key}: {value}") print_info(name="Alice", age=30, city="China") # Output: name: Alice age: 30 city: China ``` 可以在同一个函数中同时使用 `*args` 和 `**kwargs`,但 `*args` 必须在 `**kwargs` 之前。 ```python def display_info(*args, **kwargs): print("位置参数:", args) # args 是一个元组 print("关键字参数:", kwargs) # kwargs 是一个字典 display_info(1, 2, 3, name="Alice", age=30) # Output: 位置参数: (1, 2, 3) 关键字参数: {'name': 'Alice', 'age' : 18} ``` ### 案例:书籍信息管理 ```python def add_book(book_title, *authors, **details): """添加书籍信息并打印""" print(f"书籍标题: {book_title}") print("作者:", ", ".join(authors)) # 将作者元组转换为字符串 for key, value in details.items(): print(f"{key}: {value}") print("---") # 添加书籍 add_book("Python 编程", "Alice", "Bob", year=2023, publisher="XYZ出版社") add_book("数据科学入门", "Charlie", year=2022, pages=350, genre="教育") add_book("机器学习概论", "David", "Ella", "Frank", year=2021) # Output: 书籍标题: Python 编程 作者: Alice, Bob year: 2023 publisher: XYZ出版社 --- 书籍标题: 数据科学入门 作者: Charlie year: 2022 pages: 350 genre: 教育 --- 书籍标题: 机器学习概论 作者: David, Ella, Frank year: 2021 --- ``` # 命名空间和作用域 在 Python 中,**命名空间**(Namespace)和**作用域**(Scope)是两个重要的概念,它们帮助我们理解变量的可见性和生命周期。下面将详细解释这两个概念,并展示它们之间的关系。 ## 命名空间 命名空间是一个容器,用于存储变量名(或标识符)与对象(值)之间的映射关系。命名空间确保了变量名的唯一性,不同的命名空间可以包含同名的变量而不会产生冲突。 #### 类型 - **内置命名空间**:包含 Python 内置的函数和对象,如 `len()`、`print()` 等。 - **全局命名空间**:模块级别的命名空间,包含模块中定义的变量和函数。 - **局部命名空间**:函数内部的命名空间,包含函数内部定义的变量。 #### 示例 ```python def my_function(): x = 10 # 局部命名空间中的 x print("局部 x:", x) x = 5 # 全局命名空间中的 x my_function() print("全局 x:", x) ``` ## 作用域 作用域定义了变量的可访问性或可见性。它决定了在程序的某一部分可以访问哪些变量。 #### 类型 - **局部作用域**:函数内部定义的变量,仅在函数内部可见。 - **全局作用域**:模块级别定义的变量,在整个模块内可见。 - **内置作用域**: Python 内置的名字,始终可用。 #### 示例 ```python def outer_function(): outer_var = "我是外部变量" def inner_function(): inner_var = "我是内部变量" print(inner_var) # 访问内部变量 print(outer_var) # 访问外部变量 inner_function() # print(inner_var) # 会导致错误,因为 inner_var 在外部不可见 outer_function() # print(outer_var) # 会导致错误,因为 outer_var 在外部不可见 ``` ## 命名空间与作用域的关系 - **命名空间**提供了一个上下文,使得变量名可以在不同的上下文中存在,而不会发生冲突。 - **作用域**决定了这些命名空间的可见性和可访问性。 ## globals和locals方法 `globals()` 和 `locals()` 是两个内置函数,用于访问全局命名空间和当前所在的命名空间。 ```python x = 10 y = 20 print(globals()) print(locals()) def func(): a = 12 b = 20 print(globals()) print(locals()) func() ``` 区别在于`globals()`不管再什么地方,都是访问全局命名空间的变量,而`locals()`是访问当前(所在命名空间)的变量 ## global关键字 `global` 关键字用于在函数内部声明一个变量是全局变量,从而允许你在函数内部对其进行修改。 ```python x = 10 # 全局变量 def modify_global(): global x # 声明 x 为全局变量 x = 20 # 修改全局变量 modify_global() print(x) # 输出: 20 ``` **注意:**如果不使用 `global`,在函数内部直接赋值会创建一个新的局部变量,而不会影响全局变量。 ```python def try_modify(): x = 30 # 创建一个局部变量 x,未声明为 global print(x) # 输出: 30 try_modify() print(x) # 输出: 20,仍然是全局变量的值 ``` 对可变数据类型(list,dict,set)可以直接引用不用通过global ```python li = [1,2,3] dic = {'name':'aaron'} def change(): li.append(4) dic['age'] = 18 print(dic) print(li) change() print(dic) print(li) ``` ## nonlocal关键字 `nonlocal` 关键字用于在嵌套函数中声明一个变量是外层函数的局部变量。它允许内层函数修改外层函数的变量。 在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。 ```python def outer_function(): x = 10 # 外层函数的局部变量 def inner_function(): nonlocal x # 声明 x 为外层函数的局部变量 x = 20 # 修改外层函数的变量 inner_function() print(x) # 输出: 20 outer_function() ``` **注意:**`nonlocal` 仅在嵌套函数中有效,不能用于声明全局变量。 # 函数的嵌套和作用域链 **函数的嵌套**是指在一个函数内部定义另一个函数。内层函数可以访问外层函数的变量,这种结构在 Python 中非常常见。 ## 嵌套声明 ```python def f1(): print('in f1...') def f2(): print('in f2...') f2() f1() ``` ## 嵌套调用 嵌套调用指的是在一个函数内部调用另一个函数,从而使用另一个函数的功能 ```python # 先写一个两个数字比较大小并且返回较大数字的函数 def max_num(x,y): if x > y: return x else: return y def number(a,b,c,d): # 有四个数据比较,通过多次调用max_num找出最大的数字 res1 = max_num(a,b) res2 = max_num(res1,c) res3 = max_num(res2,d) return res3 ret = number(10,200,-20,40) print(ret) # Output: 200 ``` ## 作用域链 **作用域链**是指在查找变量时,Python 按照一定的顺序查找变量的规则。它遵循 LEGB 规则: 1. **L**ocal (局部作用域):当前函数内定义的变量。 2. **E**nclosing (包围作用域):外层函数中的局部变量。 3. **G**lobal (全局作用域):模块级别定义的变量。 4. **B**uilt-in (内置作用域):Python 内置的名字,如 `len()`、`print()` 等。 ```python x = "全局变量" def outer_function(): x = "外层变量" def inner_function(): x = "内层变量" print("内层函数中的 x:", x) # 访问内层变量 inner_function() print("外层函数中的 x:", x) # 访问外层变量 outer_function() print("全局变量 x:", x) # 访问全局变量 # Output: 内层函数中的 x: 内层变量 外层函数中的 x: 外层变量 全局变量 x: 全局变量 ``` # 函数名的本质 **函数名**的本质可以理解为一个指向函数对象的引用。也可以理解为该函数的**内存地址** **函数名**实际上是一个标签,用来指向函数对象。你可以用这个标签来调用函数,但它本身并不是函数的本体。所以可以被赋值给变量、作为参数传递给其他函数,以及从函数中返回。 - 可以被赋值给变量 ```python def greet(): return "Hello, World!" # 将函数赋值给变量 greeting = greet print(greeting()) # Output: Hello, World! ``` - 可以被当作容器类型的元素 ```python def f1(): print('f1') def f2(): print('f2') def f3(): print('f3') l = [f1,f2,f3] d = {'f1':f1,'f2':f2,'f3':f3} #调用 l[0]() d['f2']() ``` - 可以当作函数的参数和返回值 ```python def f1(): print("in f1") def func(argv): argv() return argv f = func(f1) f() # Output: in f1 in f1 ``` # 闭包 内部函数包含对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数 ## 闭包的特点 1. **嵌套函数**:闭包函数通常是在另一个函数内部定义的。 2. **持有外部变量**:闭包可以访问其外部函数的局部变量,即使外部函数已经执行完毕。 3. **状态保持**:闭包允许你在多个函数调用之间保持状态。 ```python def func(): name = '张三' def inner(): print(name) return inner f = func() f() ``` 下面是一个简单的闭包函数示例,演示如何使用闭包来保持一个计数器的状态。 ```python def make_counter(): count = 0 # 外部变量 def counter(): # 嵌套函数 nonlocal count # 声明使用外层变量 count += 1 return count return counter # 返回嵌套函数 # 创建一个计数器 my_counter = make_counter() # 测试计数器 print(my_counter()) # 输出: 1 print(my_counter()) # 输出: 2 print(my_counter()) # 输出: 3 ``` ## 判断闭包函数 判断闭包函数的方法`__closure__`或者查看函数的`__code__.co_freevars`属性 ```python def func(): name = 'aaron' def inner(): print(name) return inner f = func() print(f.__code__.co_freevars) # 可以通过f.__code__.co_freevars属性来查看到该函数是否应用了外部作用域的变量 print(f.__closure__) # 如果打印出的内容非空,说明该函数是闭包函数 name = 'aaron' def func(): def inner(): print(name) return inner f = func() print(f.__code__.co_freevars) print(f.__closure__) # 如果答应出的内容是None,说明该函数没有应用外部作用域的变量,不满足闭包函数的条件 ``` ## 案例:获取网页内容 假定我们需要多次访问一个静态网站,但是由于该网站访问较慢,所以导致每次访问都需要等待,比较浪费时间。这里我们可以使用闭包函数将第一次访问到的网页内容封装到外部作用域的变量中,以后我们只需要调用该变量就可以了... **普通写法:** ```python from urllib.request import urlopen def func(): content = urlopen('http://myip.ipip.net').read().decode('utf-8') print(content) for i in range(5): func() # Output: 当前 IP:114.229.63.127 来自于:中国 江苏 镇江 电信 当前 IP:114.229.63.127 来自于:中国 江苏 镇江 电信 当前 IP:114.229.63.127 来自于:中国 江苏 镇江 电信 当前 IP:114.229.63.127 来自于:中国 江苏 镇江 电信 当前 IP:114.229.63.127 来自于:中国 江苏 镇江 电信 ``` 会发现每循环一次,都会去访问一下`https://myip.ipip.net`这个网站来获取IP地址。 **如果用闭包的方式改写如下:** ```python from urllib.request import urlopen def func(): content = urlopen('http://myip.ipip.net').read().decode('utf-8') def inner(): print(content) return inner f = func() for i in range(5): f() print(f.__code__.co_freevars) ```