函数
在 Python 中,函数是一段组织好的、可重复使用的代码,用于执行特定的任务。函数可以接受输入(称为参数),并可以返回输出(称为返回值)。使用函数可以帮助我们组织代码,提高可读性和重用性。
Python函数式编程
函数的定义
在 Python 中,函数的定义使用 def
关键字,后面跟函数名和参数列表。函数体由缩进的代码块组成。
语法:
def function_name(parameters):
"""函数的文档字符串(可选)"""
# 函数体
# 执行任务的代码
示例:
def greet(name):
"""提示/注释 信息"""
print("Hello, %s" % name)
# 使用占位符进行格式化输出
函数的调用
调用函数时,需要使用函数名后跟括号,并传入必要的参数。
greet("牛老师") # 输出: Hello, 牛老师!
案例:计算字符串的长度
def my_len():
s = 'hello world'
length = 0
for i in s:
length = length + 1
print(length)
my_len()
# Output:
11
函数的返回值
函数可以使用 return
语句返回值。如果没有 return
,函数默认返回 None
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,结束整个函数。
- 返回多个值会被组织成元组被返回,也可以用多个值来接收
def ret_demo():
return 1,2,'a',['hello','world']
ret = ret_demo()
print(ret)
# Output:
(1, 2, 'a', ['hello', 'world'])
使用多个值接收(值的数量必须跟返回值数量对应)
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 支持多种类型的参数,每种参数类型都有其特定的用途和特点。
形参
在函数定义时用来占位置的字符,可以是任何字符,用来表示函数需要有一个参数传递进来...
- 形参是在函数定义时指定的参数。它们用于接收函数调用时传递的值。
- 形参在函数体内作为变量存在,作用域仅限于函数内部。
实参
在函数调用的时候实际传递进去的参数,是一个具体的值,参与函数内部的处理...
- 实参是在函数调用时传入的实际值。实参可以是常量、变量、表达式或其他函数的返回值。
- 实参的值被传递给形参。
示例:
def greet(name): # name 是形参
print(f"Hello, {name}!")
greet("Alice") # "Alice" 是实参
也可以更过分一点,写成如下形式(不推荐,容易被喷)
def greet(占位置的): # name 是形参
print(f"Hello, {占位置的}!")
greet("Alice") # "Alice" 是实参
案例:函数版本简单计算器实现
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
位置参数
位置参数是最常用的参数类型,调用时根据位置传递给函数。参数的数量和顺序必须与函数定义时一致。
def add(a, b):
return a + b
result = add(2, 3) # 2 和 3 分别对应 a 和 b
print(result) # 输出: 5
默认参数
默认参数允许为函数的参数设置默认值。调用函数时,如果没有提供该参数的值,则使用默认值。
def greet(name="Guest"):
print(f"Hello, {name}!")
greet() # 输出: Hello, Guest!
greet("Alice") # 输出: Hello, Alice!
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
关键字参数
按照形参的参数名进行精确传参,使用关键字参数的时候可以不用考虑形参的具体位置和顺序
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
允许函数接受任意数量的位置参数。这些参数会被收集到一个元组中。
def sum_numbers(*args):
total = sum(args) # args 是一个元组
return total
result = sum_numbers(1, 2, 3, 4, 5) # 可以传入任意数量的参数
print(result) # 输出: 15
**kwargs
允许函数接受任意数量的关键字参数,这些参数会被收集到一个字典中。
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
之前。
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}
案例:书籍信息管理
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()
等。 - 全局命名空间:模块级别的命名空间,包含模块中定义的变量和函数。
- 局部命名空间:函数内部的命名空间,包含函数内部定义的变量。
示例
def my_function():
x = 10 # 局部命名空间中的 x
print("局部 x:", x)
x = 5 # 全局命名空间中的 x
my_function()
print("全局 x:", x)
作用域
作用域定义了变量的可访问性或可见性。它决定了在程序的某一部分可以访问哪些变量。
类型
- 局部作用域:函数内部定义的变量,仅在函数内部可见。
- 全局作用域:模块级别定义的变量,在整个模块内可见。
- 内置作用域: 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()
是两个内置函数,用于访问全局命名空间和当前所在的命名空间。
x = 10
y = 20
print(globals())
print(locals())
def func():
a = 12
b = 20
print(globals())
print(locals())
func()
区别在于globals()
不管再什么地方,都是访问全局命名空间的变量,而locals()
是访问当前(所在命名空间)的变量
global关键字
global
关键字用于在函数内部声明一个变量是全局变量,从而允许你在函数内部对其进行修改。
x = 10 # 全局变量
def modify_global():
global x # 声明 x 为全局变量
x = 20 # 修改全局变量
modify_global()
print(x) # 输出: 20
注意:如果不使用 global
,在函数内部直接赋值会创建一个新的局部变量,而不会影响全局变量。
def try_modify():
x = 30 # 创建一个局部变量 x,未声明为 global
print(x) # 输出: 30
try_modify()
print(x) # 输出: 20,仍然是全局变量的值
对可变数据类型(list,dict,set)可以直接引用不用通过global
li = [1,2,3]
dic = {'name':'aaron'}
def change():
li.append(4)
dic['age'] = 18
print(dic)
print(li)
change()
print(dic)
print(li)
nolocal关键字
nonlocal
关键字用于在嵌套函数中声明一个变量是外层函数的局部变量。它允许内层函数修改外层函数的变量。
在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。
def outer_function():
x = 10 # 外层函数的局部变量
def inner_function():
nonlocal x # 声明 x 为外层函数的局部变量
x = 20 # 修改外层函数的变量
inner_function()
print(x) # 输出: 20
outer_function()
注意:nonlocal
仅在嵌套函数中有效,不能用于声明全局变量。
函数的嵌套和作用域链
函数的嵌套是指在一个函数内部定义另一个函数。内层函数可以访问外层函数的变量,这种结构在 Python 中非常常见。
嵌套声明
def f1():
print('in f1...')
def f2():
print('in f2...')
f2()
f1()
嵌套调用
嵌套调用指的是在一个函数内部调用另一个函数,从而使用另一个函数的功能
# 先写一个两个数字比较大小并且返回较大数字的函数
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 规则:
- Local (局部作用域):当前函数内定义的变量。
- Enclosing (包围作用域):外层函数中的局部变量。
- Global (全局作用域):模块级别定义的变量。
- Built-in (内置作用域):Python 内置的名字,如
len()
、print()
等。
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: 全局变量
函数名的本质
函数名的本质可以理解为一个指向函数对象的引用。也可以理解为该函数的内存地址
函数名实际上是一个标签,用来指向函数对象。你可以用这个标签来调用函数,但它本身并不是函数的本体。所以可以被赋值给变量、作为参数传递给其他函数,以及从函数中返回。
- 可以被赋值给变量
def greet():
return "Hello, World!"
# 将函数赋值给变量
greeting = greet
print(greeting())
# Output: Hello, World!
- 可以被当作容器类型的元素
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']()
- 可以当作函数的参数和返回值
def f1():
print("in f1")
def func(argv):
argv()
return argv
f = func(f1)
f()
# Output:
in f1
in f1
闭包
内部函数包含对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数
闭包的特点
- 嵌套函数:闭包函数通常是在另一个函数内部定义的。
- 持有外部变量:闭包可以访问其外部函数的局部变量,即使外部函数已经执行完毕。
- 状态保持:闭包允许你在多个函数调用之间保持状态。
def func():
name = '张三'
def inner():
print(name)
return inner
f = func()
f()
下面是一个简单的闭包函数示例,演示如何使用闭包来保持一个计数器的状态。
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
属性
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,说明该函数没有应用外部作用域的变量,不满足闭包函数的条件
案例:获取网页内容
假定我们需要多次访问一个静态网站,但是由于该网站访问较慢,所以导致每次访问都需要等待,比较浪费时间。这里我们可以使用闭包函数将第一次访问到的网页内容封装到外部作用域的变量中,以后我们只需要调用该变量就可以了...
普通写法:
from urllib.request import urlopen
def func():
content = urlopen('https://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地址。
如果用闭包的方式改写如下:
from urllib.request import urlopen
def func():
content = urlopen('https://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)
课后作业
将之前写过的用户登录注册的功能用函数的方式呈现