lambda语法及其常用场景
在Python里,lambda 是一种用于创建匿名函数的关键字。所谓匿名函数,就是没有具体名称的函数。lambda 函数的语法简洁,基本形式如下:
1lambda arguments: expression
其中:
lambda :定义匿名函数的关键字。
arguments :函数的参数,可以是零个或多个,多个参数之间用逗号分隔。
: :分隔参数和函数体的符号。
expression :函数的返回值表达式,lambda 函数会自动返回该表达式的结果,不需要使用 return 语句。
下面是几个简单示例:
- 无参数的
lambda函数:
1f = lambda: 10
2print(f()) # 输出: 10
- 单参数的
lambda函数:
1square = lambda x: x ** 2
2print(square(5)) # 输出: 25
- 多参数的
lambda函数:
1add = lambda x, y: x + y
2print(add(3, 4)) # 输出: 7
作为参数传递给高阶函数
高阶函数是指那些接受函数作为参数或返回函数的函数。lambda 函数常作为参数传递给 sorted()、map()、filter() 等内置高阶函数。
sorted() 函数:用于对可迭代对象进行排序,可以通过 key 参数指定排序规则。
1students = [('Alice', 20), ('Bob', 18), ('Charlie', 22)]
2sorted_students = sorted(students, key=lambda x: x[1])
3print(sorted_students) # 输出: [('Bob', 18), ('Alice', 20), ('Charlie', 22)]
map() 函数:对可迭代对象中的每个元素应用指定的函数。
1numbers = [1, 2, 3, 4]
2squared_numbers = list(map(lambda x: x ** 2, numbers))
3print(squared_numbers) # 输出: [1, 4, 9, 16]
filter() 函数:用于过滤可迭代对象中的元素,返回满足条件的元素组成的迭代器。
1numbers = [1, 2, 3, 4, 5, 6]
2even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
3print(even_numbers) # 输出: [2, 4, 6]
简单的回调函数
在一些需要临时定义简单函数作为回调的场景中,lambda 函数非常实用。例如,在 tkinter 图形界面编程中,为按钮绑定简单的事件处理函数。
1import tkinter as tk
2
3root = tk.Tk()
4button = tk.Button(root, text="Click me", command=lambda: print("Button clicked!"))
5button.pack()
6root.mainloop()
在这个例子中,使用 lambda 函数作为按钮点击事件的回调函数,简洁地实现了事件处理逻辑。
捕获异常链
处理异常链主要涉及到使用 try 和 except 语句,以及可能使用 raise 语句来重新引发异常。异常链是指一个异常导致了另一个异常的发生。在Python中,你可以通过 raise 语句的参数来显式地构建异常链。
1try:
2 # 可能会引发异常的代码块
3 try:
4 result = 10 / 0
5 except ZeroDivisionError as e:
6 raise ValueError("无效的值,除以零") from e
7except ValueError as v:
8 print(f"捕获到异常: {v}")
9 print(f"原始异常: {v.__cause__}")
原始异常: division by zero
在上面的代码中,当 ValueError 被引发时,可以使用 __cause__ 属性访问原始异常 ZeroDivisionError。这使得异常链的调试和处理更加方便。
闭包
闭包(Closures)是Python中一个重要的概念,它允许在函数内部定义另一个函数,并使这个内部函数能够访问外部函数的局部变量。
闭包通常在以下场景中非常有用:
- 数据封装:可以隐藏某些数据,只通过提供的函数来访问。
- 工厂函数:可以根据需求动态地生成函数。
- 延迟计算:可以创建一个函数,它在被调用时才计算值。
我们来看两个简单的闭包示例:
- HTML标签生成器(数据格式化)
闭包非常适合创建专门的函数。在这个例子中,外部函数配置HTML标签,内部函数将文本包裹起来。
1def make_html_tag(tag):
2 def wrap_text(msg):
3 return f"<{tag}>{msg}</{tag}>"
4 return wrap_text
5
6# Create specific tag generator functions
7print_heading = make_html_tag("h1")
8print_paragraph = make_html_tag("p")
9
10# Use the closures
11print(print_heading("Welcome to My Website"))
12print(print_paragraph("This is a paragraph of text."))
<h1>Welcome to My Website</h1><p>This is a paragraph of text.</p>- 访问令牌检查器(模拟隐私/安全)
你可以用闭包来隐藏数据,或者防止变量被代码的其他部分意外更改。
1def create_secure_service(secret_token):
2 def access_data(provided_token):
3 if provided_token == secret_token:
4 return "Access Granted: Loading sensitive database..."
5 else:
6 return "Access Denied: Invalid Token!"
7 return access_data
8
9# Set up the service with a master token hidden inside the closure
10my_service = create_secure_service("Admin123")
11
12# Try to access it
13print(my_service("WrongToken")) # Output: Access Denied: Invalid Token!
14print(my_service("Admin123")) # Output: Access Granted: Loading sensitive database...
Access Granted: Loading sensitive database…
“Admin123” 字符串被安全地藏在 my_service 的环境中。函数外部无法直接读取或修改 secret_token。
闭包的特性
- 函数嵌套:闭包的形成依赖于函数嵌套。
- 外部变量保持状态:外部函数的局部变量在内部函数中仍然是可访问的。
- 可以存储状态:通过闭包,可以存储某种状态。
如果外部函数有多个执行,返回的闭包将保留最后一次执行的外部状态。
如果未使用 nonlocal 关键字,内部函数不能修改外部函数的局部变量。
闭包是一个强大的特性,允许我们编写灵活而简洁的代码,尤其是在需要创建状态保持的函数或者需要进行数据封装时。
列表推导式和生成器表达式
在Python中,列表推导式和生成器表达式都是用于创建新列表或生成器的简洁语法,能使代码更加清晰和简洁。
列表推导式
列表推导式用来生成一个新的列表,语法如下:
1[expression for item in iterable if condition]
expression: 生成列表中元素的表达式。
item: 当前迭代的元素。
iterable: 可迭代对象(如列表、元组、字符串等)。
condition: 可选的过滤条件,仅在条件为 True 时,item 才会被包含到新的列表中。
1squares = [x**2 for x in range(10) if x % 2 == 0]
2# 输出: [0, 4, 16, 36, 64]
生成器表达式
生成器表达式与列表推导式类似,但它生成的是一个生成器对象,而不是一个完整的列表。其语法如下:
1(expression for item in iterable if condition)
与列表推导式不同,生成器表达式并不立即计算全部值,而是按需生成,因此更节省内存。
1squares_gen = (x**2 for x in range(10) if x % 2 == 0)
2
3for square in squares_gen:
4 print(square)
5# 输出: 0, 4, 16, 36, 64 (每次迭代生成一个值)
生成器表达式:返回一个生成器对象,元素是按需生成的,适合处理较大的数据集,节省内存。
选择使用哪种方式,通常取决于具体的需求和上下文。
列表去重和排序
去重
集合是无序且元素唯一的数据结构,将列表转换为集合可自动去重,之后再将集合转换回列表。不过这种方法会打乱原列表的顺序。
1my_list = [3, 1, 2, 2, 3, 4]
2unique_list = list(set(my_list))
3print(unique_list) # output: [1, 2, 3, 4]
通过遍历列表,利用条件判断来保留唯一元素,可保持原列表顺序。
1my_list = [3, 1, 2, 2, 3, 4]
2unique_list = []
3for item in my_list:
4 if item not in unique_list:
5 unique_list.append(item)
6print(unique_list) # output: [3, 1, 2, 4]
字典的键具有唯一性,可利用这一点来去重,同时保持原列表顺序。
1my_list = [3, 1, 2, 2, 3, 4]
2unique_list = list(dict.fromkeys(my_list))
3print(unique_list) # output: [3, 1, 2, 4]
在 Python 中,dict.fromkeys(iterable) 是一个内置方法,它的作用是以序列中的元素作为“键(Key)”,创建一个新字典(默认值都是 None)。因为 Python 字典的键(Key)是唯一的,不能重复,所以当它遇到重复的元素时,后面的就会被自动忽略。
以上面的列表 [3, 1, 2, 2, 3, 4] 为例,当它被传入这个方法时:
遇到 3,创建键 3;遇到 1,创建键 1;遇到 2,创建键 2;
又遇到 2,发现键 2 已经存在了,跳过;
又遇到 3,发现键 3 已经存在了,跳过;
遇到 4,创建键 4。
最后,这一步生成了一个这样的字典:
{3: None, 1: None, 2: None, 4: None}
从 Python 3.7 开始,Python 官方正式保证了字典会按照元素插入的先后顺序来排列键。因此,这个字典里的键 3, 1, 2, 4 完美保留了它们在原列表中第一次出现的先后顺序。
最后 list() 函数会把刚才字典里的所有键(Keys)提取出来,重新组装成一个列表:[3, 1, 2, 4]
排序
sort() 方法是列表对象的一个方法,它会直接修改原列表,对列表进行原地排序。可以通过 reverse 参数指定升序或降序。
1my_list = [3, 1, 2, 4]
2my_list.sort() # 升序排序
3print(my_list) # output: [1, 2, 3, 4]
4
5my_list.sort(reverse=True) # 降序排序
6print(my_list) # output: [4, 3, 2, 1]
sorted() 函数会返回一个新的已排序列表,原列表不会被修改。同样可以通过 reverse 参数指定升序或降序。
1my_list = [3, 1, 2, 4]
2sorted_list = sorted(my_list) # 升序排序
3print(sorted_list) # output: [1, 2, 3, 4]
4
5sorted_list_desc = sorted(my_list, reverse=True) # 降序排序
6print(sorted_list_desc) # output: [4, 3, 2, 1]
去重并排序:先去重再排序
1my_list = [3, 1, 2, 2, 3, 4]
2unique_list = list(dict.fromkeys(my_list))
3sorted_unique_list = sorted(unique_list)
4print(sorted_unique_list) # output: [1, 2, 3, 4]
*args 和 **kwargs
在 Python 中,*args 和 **kwargs 主要用于函数定义中,它们允许函数接收可变数量的参数。这使得函数更加灵活,能够处理不同数量和类型的输入。
*args 的作用
功能:用于接收可变数量的位置参数(Non-keyword arguments)。
数据类型:在函数内部,args 是一个元组(tuple),包含了所有传递进来的位置参数。
使用场景:当你不确定函数会被传入多少个位置参数时使用。
**kwargs 的作用
功能:用于接收可变数量的关键字参数(Keyword arguments)。
数据类型:在函数内部,kwargs 是一个字典(dict),键是参数名,值是对应的参数值。
使用场景:当你不确定函数会被传入多少个命名参数,或者需要处理带名称的参数时使用。
混合使用时的顺序
当函数中同时使用位置参数、*args、**kwargs 以及默认参数时,必须遵循以下顺序: func(位置参数, *args, 默认参数, **kwargs)
1def func_example(a, b, *args, c=10, **kwargs):
2 print(f"a: {a}, b: {b}")
3 print(f"args: {args}")
4 print(f"c: {c}")
5 print(f"kwargs: {kwargs}")
6
7func_example(1, 2, 3, 4, c=20, d=30, e=40)
args: (3, 4)
c: 20
kwargs: {’d’: 30, ’e’: 40}
参数解包
除了在函数定义中使用,* 和 ** 也可以在函数调用时用于解包参数。* 用于解包列表或元组。** 用于解包字典。
1def my_func(x, y, z):
2 print(x, y, z)
3
4args_list = [1, 2, 3]
5my_func(*args_list) # 等同于 my_func(1, 2, 3)
6# output: 1, 2, 3
7
8kwargs_dict = {'x': 1, 'y': 2, 'z': 3}
9my_func(**kwargs_dict) # 等同于 my_func(x=1, y=2, z=3)
10# output: 1, 2, 3
对象的魔术方法
数值转换
__int__(self) 与 __float__(self)当你对一个自定义对象调用 int(obj) 或 float(obj) 时,Python 会在底层分别调用 obj.__int__() 和 obj.__float__()。核心原理这允许你将一个复合对象(比如一个代表分数的类,或者带有单位的数值类)安全地降维成标准的 Python 数值。
1class Price:
2 def __init__(self, item: str, dollars: int, cents: int):
3 self.item = item
4 self.dollars = dollars
5 self.cents = cents # 分
6
7 def __int__(self):
8 # 转换成整数时,只取元的部分(或者转成总分,取决于你的业务定义)
9 return self.dollars
10
11 def __float__(self):
12 # 转换成浮点数时,合并元和分
13 return self.dollars + (self.cents / 100)
14
15# 测试
16p = Price("书包", 49, 95)
17print(int(p)) # 输出: 49
18print(float(p)) # 输出: 49.95
字符串表现
如果一个类只定义了 __repr__ ,那么在需要 __str__ 的场景下,Python 会自动用 __repr__ 代替。
1class User:
2 def __init__(self, username, user_id):
3 self.username = username
4 self.user_id = user_id
5
6 def __str__(self):
7 # 给普通用户看的友好内容
8 return f"用户: {self.username}"
9
10 def __repr__(self):
11 # 给开发者调试看的标准格式
12 return f"User(username='{self.username}', user_id={self.user_id})"
13
14# 测试
15u = User("Alice", 1001)
16print(str(u)) # 输出: 用户: Alice
17print(repr(u)) # 输出: User(username='Alice', user_id={1001})
18
19# 注意:当对象放在列表中时,打印列表会调用内部元素的 __repr__
20user_list = [u]
21print(user_list) # 输出: [User(username='Alice', user_id=1001)]
布尔判定逻辑
__bool__(self)在 Python 中,任何对象都可以直接用于 if 条件判断。这背后遵循的是 Truthy(真值) 和 Falsy(假值) 的判定逻辑。当你执行 bool(obj) 或 if obj: 时,Python 的寻找顺序如下:
- 首先寻找并调用
obj.__bool__(),必须返回 True 或 False。 - 如果
__bool__没有定义,Python 会退而求其次寻找__len__(self)。如果__len__()返回 0,则为False;否则为True(这就解释了为什么空列表 [ ] 是False)。 - 如果两者都没有定义,该对象默认永远为
True。
1class ShoppingCart:
2 def __init__(self):
3 self.items = []
4
5 def add_item(self, item):
6 self.items.append(item)
7
8 def __bool__(self):
9 # 购物车为空时判定为 False,有商品时为 True
10 return len(self.items) > 0
11
12# 测试
13cart = ShoppingCart()
14if not cart:
15 print("您的购物车是空的!") # 这行会被执行
16
17cart.add_item("Python 进阶指南")
18if cart:
19 print(f"购物车已有 {len(cart.items)} 件商品。") # 这行会被执行
与 type() 有点像的 isinstance()
isinstance() 是一个非常实用且高效的内置函数,主要用于检查一个对象是否是指定类或其子类的实例。
1isinstance(object, classinfo)
object:要检查的对象。classinfo:可以是一个类名、一个类型(如int,str),或者是由它们组成的元组(tuple)。- 返回值:如果对象是该类(或其子类)的实例,返回
True;否则返回False。
当同一个函数需要根据传入数据的不同类型做出不同的反应时,isinstance() 非常有用。
1def process_data(data):
2 if isinstance(data, list):
3 print(f"处理列表,元素按行打印:")
4 for item in data:
5 print(f"- {item}")
6 elif isinstance(data, dict):
7 print("处理字典,打印键值对:")
8 for k, v in data.items():
9 print(f"{k} => {v}")
10 elif isinstance(data, str):
11 print(f"处理字符串: {data.upper()}")
12 else:
13 print("未知数据类型,无法处理。")
14
15process_data(["苹果", "香蕉"]) # 处理列表
16process_data({"name": "小明"}) # 处理字典
17process_data("Python") # 处理字符串
18process_data(3.14159) # 无法处理数字
处理列表,元素按行打印:- 苹果- 香蕉处理字典,打印键值对:name => 小明处理字符串: PYTHON未知数据类型,无法处理。isinstance() 的一个重要特性是:它承认继承关系。如果一个类继承自另一个类,子类的实例也会被认定为父类的实例。
1class Animal:
2 pass
3
4class Dog(Animal):
5 pass
6
7my_dog = Dog()
8
9print(isinstance(my_dog, Dog)) # 输出: True
10print(isinstance(my_dog, Animal)) # 输出: True (因为 Dog 是 Animal 的子类)
isinstance() 考虑继承关系;type() 只检查对象的直接类型,不考虑子类。在面向对象编程中,由于多态的需求,通常推荐使用 isinstance(),因为它遵循了“扩展开放”的原则。
函数还是方法?
- 函数 (Function):一个独立的代码块。即便它定义在类内部,只要通过类名去访问(且未被装饰为类方法),它依然是一个函数(Function)。
- 方法 (Method):当一个函数与某个实例(Instance)或类(Class)绑定后,它就变成了方法。调用时会自动传入 self 或 cls 参数。
判断手段
- 使用 types 模块(最推荐) 这是最严谨的判断方式:
types.FunctionType:检查是否为普通函数。
types.MethodType:检查是否为绑定方法(Bound Method)。
1import types
2
3class MyClass:
4 def func(self): pass
5
6obj = MyClass()
7
8# 核心差异点:
9print(isinstance(MyClass.func, types.FunctionType)) # True (类访问是函数)
10print(isinstance(obj.func, types.MethodType)) # True (实例访问是方法)
- 检查
__self__属性
方法:拥有 __self__ 属性,指向它所绑定的实例。
函数:没有 __self__ 属性。
特例:类方法(@classmethod)也有 __self__,指向类对象本身。
- 使用 inspect 模块
inspect.isfunction(obj)
inspect.ismethod(obj)
总结
静态方法 (@staticmethod):无论通过类还是实例访问,其类型永远是 function,因为它不与任何对象绑定。
类方法 (@classmethod):无论通过类还是实例访问,其类型永远是 method,因为它始终与类对象绑定。
判断的关键不在于它在哪里,而在于它是否被绑定。在调用时,如果首个参数(self 或 cls)是由 Python 自动注入的,那么它就是方法;如果需要手动传入所有参数,那么它就是函数。