跳到主要内容
Python 进阶与高级语法详解 | 极客日志
Python 算法
Python 进阶与高级语法详解 综述由AI生成 Python 进阶教程涵盖鸭子类型、类与对象、私有属性、元类、迭代器与生成器等核心概念。文章通过代码示例解释了魔法函数、装饰器、上下文管理器及高阶函数的用法,并对比了列表推导式与生成器表达式的区别。内容旨在帮助开发者深入理解 Python 动态特性、内存管理及面向对象编程的高级实践,避免多继承陷阱,掌握惰性求值等关键机制。
协议工匠 发布于 2025/2/6 更新于 2026/6/2 20 浏览Python 进阶与高级语法详解
一、简介
Python 是一门很容易入门的语言,但是要进阶其实需要花费大量的时间和精力,而且还需要不断的练习使用。以下内容绝大部分都是我在项目中用过的,很多描述是我自己的理解,可能会和官方有一定出入,但相信大差不差。
内容涵盖一些简单好用的标准库、三方库,旨在用一种比较轻松、简单的方式说明其中的重点且常用的内容。
二、类和对象
1、鸭子类型
'当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。'这是百科上对它的解释。
鸭子类型(duck typing)是动态类型的一种风格,对于 Python 编码来讲非常重要。理解它能让你真正理解什么是一切皆对象,更有助于我们理解这门语言的设计思想和实现原理。
鸭子类型始终贯穿于 Python 代码当中,一个对象它是什么类型取决于它实现了什么协议,因此可以说 Python 是一种基于协议的编程语言。
这里的协议,更多的时候我们称为魔法函数或魔法方法,因为它具有很多神奇的魔力。在 Python 里面,所有以双下划线开头,且以双下划线结尾的函数都是魔法函数,如 __init__。它们是 Python 语言天然自带的,不是通过某个类去继承而来的。
2、类型判断
在判断数据类型的时候常见的有两种方法:isinstance 和 type。
isinstance ("123" , str )
type ("123" )
isinstance 主要用于判断对象的类型。
type 可以查看类型,但它能做的远不止于此,它主要用于动态的创建类。
t = type ("Mikigo" , (), {"name" : "huangmingqiang" })
T = t()
print (t)
print (T.name)
print (type (t))
输出结果:
<class '__main__.Mikigo' >
huangmingqiang
<class 'type' >
你看,我们定义了一个类并赋值给 t,类名为 Mikigo,t 是类对象的引用,name 是其中的属性。Python 中一切都是对象,类也是对象,只不过是一种特殊的对象,是 type 的对象。
type 的参数说明:
当 type() 只有一个参数时,其作用就是返回变量或对象的类型。
当 type() 有三个参数时,其作用就是创建类对象:
参数 1:what 表示类名称,字符串类型;
参数 2:bases 表示继承对象(父类),元组类型,单元素使用逗号;
参数 3:dict 表示属性,这里可以填写类属性、类方式、静态方法,采用字典格式, 为属性名, 为属性值。
key
value
@staticmethod
def my_static ():
print ("this is static" )
t = type ("Mikigo" , (), {"name" : "huangmingqiang" , "static" : my_static})
T = t()
t.static()
T.static()
3、类变量和实例变量 (1)类变量是在类里面直接定义的变量,它可以被类对象访问和赋值,也可以被实例对象访问和赋值。
class Test :
b = 1
def __init__ (self ):
self .a = 1
T = Test()
print (T.b)
print (Test.b)
T.b = 2
print (T.b)
Test.b = 2
print (Test.b)
(2)实例变量是在构造函数里面定义的变量,它可以被实例对象访问和赋值,不能被类对象访问和赋值。
class Test :
b = 1
def __init__ (self ):
self .a = 1
T = Test()
print (T.a)
T.a = 2
print (T.a)
print (Test.a)
4、类方法、静态方法和实例方法 (1)实例方法又称对象方法,是类中最常见的一种方法。
class Test :
def obj_method (self ):
print ("this is obj method" )
实例方法参数必须传入 self,self 表示实例对象本身,实例方法的调用也必须通过实例对象来调用。
class Test :
@classmethod
def cls_method (cls ):
print ("this is class method" )
方法前面必须加装饰器 classmethod。
参数传入 cls,cls 表示类对象,但是注意不是必须的写法,写 cls 是一种约定俗成的写法。
(3)静态方法,实际上就是普通的函数,和这个类没有任何关系,它只是进入类的名称空间。
class Test :
@staticmethod
def static_method ():
print ("this is static method" )
不需要传入任何参数。同样,可以通过类对象调用,也可以通过实例对象调用。
5、类和实例属性的查找顺序 这里需要引入一个概念:MRO(Method Resolution Order),直译过来就是'方法查找顺序'。
在 Python2.3 之后,方法的查找算法都统一为叫 C3 的查找算法。__mro__ 可以查看方法的查找顺序。
class A :
pass
class B (A ):
pass
class C (A ):
pass
class D (B, C):
pass
print (D.__mro__)
说明什么问题?如果你在 B 和 C 里面都重载了 A 里面的一个方法,此时如果你想调用的是 C 里面的方法,实际上是无法调用的,因为根据方法的查找顺序,会先找到 B 里面的方法。
因此,重点来了:在 Python 中虽然是支持多继承的,但是在实际项目中不建议使用多继承,因为如果继承关系设计得不好,很容易造成逻辑关系的混乱,原因就是 MRO。
有一种比较流行且先进的设计模式:Mixin 混合模式,完美解决这个问题。
class Animal :
pass
class Mammal (Animal ):
pass
class Dog (Mammal ):
pass
class RunnableMixIn :
def run (self ):
print ('Running...' )
class FlyableMixIn :
def fly (self ):
print ('Flying...' )
class Dog (Mammal, RunnableMixIn):
pass
class Bat (Mammal, FlyableMixIn):
pass
功能独立、单一;
只用于拓展子类的功能,不能影响子类的主要功能;
自身不应该进行实例化,仅用于被子类继承。
6、破解私有属性 私有属性就是在类的内部能访问,外部不能访问。在 Python 中没有专门的语句进行私有化,而通过在属性或方法前面加'两个下划线'实现。
class Test :
def __init__ (self ):
self .__mi = "Mikigo"
def __ki (self ):
print ("Mikigo" )
def go (self ):
print (self .__mi)
泄露天机了哈,这是 Python 一种很奇妙的结构化处理,实际上 Python 拿到双下划线之后,对其进行了变形,在前面加了一个下划线和类名,我们通过这种方式可以访问:
print (Test()._Test__mi)
Test()._Test__ki()
所以说,从语言的角度是没有绝对的安全,任何语言都是这样,更多的是一种编程上的约束。
通常在大多数实践中,我们更倾向于使用一个下划线来表示私有属性,这不是真正的私有,而是一种更友好的编程规范,社区称之为'受保护的'属性。
7、对象的自省机制 自省 (introspection),即自我反省,而对象的自省实际上就是查看对象实现了哪些属性或方法。
Python 的常用的自省函数有四个:dir()、type()、hasattr()、isinstance()
除了自定义方法以外,其他的方法都是魔法函数,即最开始我们提到的协议。
(2)hasattr() 主要用于判断对象中是否包含某个属性,返回布尔值。
print (hasattr (Test, "go" ))
print (hasattr (Test, "wo" ))
其他还有一些自省函数可以了解一下,偶尔用到也挺好的:
__doc__ 获取到文档字符串;
__name__ 获取对象的名称;
__dict__ 包含了类里可用的属性名 - 属性的字典;
__bases__ 返回父类对象的元组。
8、super class A :
def mi (self ):
print ("=== mi ===" )
class B (A ):
def ki (self ):
super ().mi()
B().ki()
准确的讲它不是调用父类的方法,而是调用的 MRO 顺序上的下一个方法。
9、上下文管理器 大家都知道打开一个文件之后是需要关闭的,但是在操作文件的过程中很容易报错,这时候我们需要进行异常处理,要保证无论是否存在异常的情况下,文件都能正常的被关闭。
with open ("test.txt" , "w" ) as f:
f.write(some_txt)
要实现上下文管理器,需要实现两个魔法函数:__enter__ 和 __exit__。
class Context :
def __init__ (self, file_name ):
self .file_name = file_name
self .f = None
def __enter__ (self ):
print ("进入 with" )
self .f = open (self .file_name, "r" )
return self .f
def __exit__ (self, exc_type, exc_val, exc_tb ):
print ("退出 with" )
if self .f:
self .f.close()
from contextlib import contextmanager
@contextmanager
def context_test (file_name ):
print ("进入 with" )
try :
f = open (file_name, "r" )
yield f
finally :
print ("退出 with" )
f.close()
利用生成器的原理,yield 之前是进入,yield 之后是退出,同样可以实现一个上下文管理器。
上下文管理器是 Python 提供给我们的一个非常方便且有趣的功能,经常被用在打开文件、数据库连接、网络连接等场景下。
10、装饰器 装饰器就是使用 @ 符号,像帽子一样扣在函数的头上,是 Python 中的一种语法糖。
原理实际上就是将它所装饰的函数作为参数,最后返回这个函数。
def logger (func ):
def wrapper (*args, **kw ):
print ('我要开始搞 {} 函数了' .format (func.__name__))
func(*args, **kw)
print ('搞完了' )
return wrapper
@logger
def add (x, y ):
print ('{} + {} = {}' .format (x, y, x+y))
from functools import wraps
def logger (say_some ):
@wraps
def wrapper (func ):
def deco (*args, **kw ):
print ("搞之前我先说两句:{}" .format (say_some))
print ('我要开始搞 {} 函数了:' .format (func.__name__))
func(*args, **kw)
print ('搞完了' )
return deco
return wrapper
@logger("别整,不得劲儿~" )
def add (x, y ):
print ('{} + {} = {}' .format (x, y, x+y))
基于类装饰器的实现,必须实现 __call__ 和 __init__ 两个魔法函数。
class logger :
def __init__ (self, func ):
self .func = func
def __call__ (self, *args, **kwargs ):
print ('我要开始搞 {} 函数了' .format (self .func.__name__))
f = self .func(*args, **kwargs)
print ('搞完了' )
return f
class logger :
def __init__ (self, say_some ):
self .say_some = say_some
def __call__ (self, func ):
def wrapper (*args, **kwargs ):
print ("搞之前我先说两句:{}" .format (self .say_some))
print ('我要开始搞 {} 函数了' .format (func.__name__))
func(*args, **kwargs)
print ('搞完了' )
return wrapper
三、自定义序列
1、可切片对象 切片大家都很熟悉,这里我们主要讲怎么实现一个可切片对象。
隆重请出魔法函数:__getitem__,它是我们实现可切片对象的关键。
class AutoTest :
def __init__ (self, name_list ):
self .name_list = name_list
def __getitem__ (self, item ):
return self .name_list[item]
AT = AutoTest(["mikigo" , "lt" , "jjb" , "hhz" ])
print (AT[1 ])
print (AT[2 ])
这里再补充一点没有用的小知识,实现了 __getitem__ 方法实际上也是一个可迭代的对象了,也就是说可以使用 for 循环。
2、列表推导式 列表推导是 Python 提供的一种独有特性,可以用一行代码生成一个列表。
my_list = []
for i in range (10 ):
my_list.append(i)
这样生成一个列表,至少需要 3 行,来看看列表推导式:
my_list = [i for i in range (10 )]
一行就搞定,多么的简洁优雅,而且可读性和性能都非常高。
app_id_list = [int (_id ) for _id in app_id if _id ]
这里要提醒一下,不要为了推导而推导,如果你的逻辑很复杂,加了多重判断和处理,不建议使用推导式,老老实实分开写,因为这样写出来的表达式会很复杂,就失去了我们编码最重要的一点,就是可读性。
3、生成器表达式 前面讲了列表推导式,是用中括号里面写表达式,那把中括号换成小括号是什么呢?好多同学聪明的小脑袋肯定想到了,元组推导式。
注意元组是不可变序列,没法推导的,小括号的表达式实际上是生成器表达式。
my_gen = (i for i in range (10 ))
from collections.abc import Generator
print (isinstance (my_gen, Generator))
print (my_gen)
你看,确实是一个生成器吧。生成器细节,咱们也放到后面讲哈。
4、字典推导式 my_dict = {i: None for i in range (10 )}
第一个元素就是字典的 key 和 value,注意字典的 key 是唯一的(可哈希),值无所谓。
就这,简直没难度,还是要注意一点,代码可读性哈,别整复杂了。
四、对象引用
1、变量到底是什么 在 Python 中变量到底是什么,有一个比喻我觉得非常好,变量就像便利贴。
为什么这么讲,我们定义一个数据,比如定义一个字符串或者整数,在内存中都会分配一个空间来保存,这个内存空间相当于一个小盒子,我们使用等号将这个数据赋值给一个变量时,实际上就像用便利贴贴到这个小盒子上,便利贴上还写了名称,就是变量名。所以说,变量和数据的关系只是一个指向的关系。
一个数据可以赋值给多个变量,相当于这个小盒子上面贴了多个便利贴;一个变量也可以被重新赋值,相当于把这个盒子上的便利贴撕了,贴到另一个盒子上。
变量和数据的关系,就是盒子和便利贴的关系,理解起来很容易。
函数名也是变量,是可以传参的变量,也同样是便利贴。
2、== 和 is 是一样的吗 这两个在编程中经常用到,好多同学经常搞不清楚应该用哪个。
== 是比较两边的'值'是否相等;
is 是判断是否为同一个对象,即 id 是否一样。
a = 1000
b = 1000
print (a == b)
print (a is b)
print (id (a), id (b))
这里有个很神奇的地方,分别定义了两个变量 a, b,他们的值相等,但是这样定义应该是分配了 2 个内存空间,更有意思的是,如果你通过命令行执行以上代码,结果会不一样。
上面是使用 Pycharm 执行的,实际上 Python 解释器已经对经常使用到的小整数做了特殊处理,解释器会提前将 256 以内的整数申请内存空间,不会回收,以提升执行效率,所以在这个范围内的整数 id 永远是一样的。
Pycharm 在解释器的基础之上做了进一步的优化。
a = 1000000
b = 1000000
print (id (a), id (b))
你看,这么大的数字 id 也是相同的,Pycharm 就是这么酷。
3、del 语句和垃圾回收 在 Python 中的垃圾回收机制是:引用计数(Reference Counting)。
简单讲就是每个对象内部有一个引用计数器,对象被创建或者被引用就会 +1,对象被销毁或者被赋予新的对象就会 -1
a = 1
b = a
del a
print (b)
之前看到国外的一个大佬讲 open 的这种写法不用关闭:
open ("test.txt" , "r" ).read()
很有意思是吧,这点没有用的小知识,相信你在网上应该查不到。当时觉得不太理解,后面理解垃圾回收之后才明白,使用 open 打开的文件对象创建之后,没有被其他引用,所以会被内存回收的,因而不用关闭也不影响。
邪门歪道哈,用 open 还是老老实实用 with 吧。
五、元类编程
1、动态属性和属性描述符 有些同学可能知道 @property,它的主要用于将一个方法变成属性,访问的时候直接通过名称访问,不需要加括号。注意加了 @property 函数不能有参数,你想嘛,人家调用的时候都不用括号,怎么传参,对吧。
class Mikigo :
@property
def age (self ):
return "我晕,今年 30 了"
print (Mikigo().age)
你看,调用 age 方法没加括号吧,那我要修改 age 的值怎么做呢?
class Mikigo :
def __init__ (self ):
self ._age = 30
@property
def age (self ):
return self ._age
@age.setter
def age (self, value ):
if not isinstance (value, int ):
raise ValueError
self ._age = value
mi = Mikigo()
mi.age = 25
print (mi.age)
注意上例中装饰器的写法,setter 是固定写法,setter 前面是你定义的函数名。
没什么问题哈,做了参数的类型检查,整体看起来不算复杂,其实了解到这里已经差不多了。但是,如果我们还有其他属性要处理,就得写好多个这样的,挺费劲不说,关键是不够优雅。
这里又要介绍两个魔法函数:__get__, __set__。
class UserAttr :
def __init__ (self, user_age ):
self ._age = user_age
def __get__ (self, instance, owner ):
print ("get_instance:" , instance)
print ("get_owner:" , owner)
return self ._age
def __set__ (self, instance, value ):
print ("set_instance:" , instance)
print ("gse_value:" , value)
if not isinstance (value, int ):
raise ValueError
self ._age = value
class Mikigo :
age = UserAttr(30 )
mi = Mikigo()
print (mi.age)
在对象访问 age 的时候,首先是进入了 __get__ 方法,因为先打印了 get_instance 和 get_owner,instance 是 Mikigo 实例对象,也就是 mi,owner 是 Mikigo 类对象。
因此,到这里,我们知道了第一个小知识,在访问值的时候,调用的是 __get__。
mi.age = 25
print (mi.age)
第二个小知识,赋值是调用的 __set__ 方法,一般为了使属性描述符成为只读的,应该同时定义 __get__() 和 __set__(),并在 __set__() 中引发 AttributeError。
还有一个魔法函数 __delete__ 也是属性描述符,使用 del 会调用,由于不咋使用,不讲了,还有网上好多区分数据描述符和非数据描述符的,我感觉不用管也没必要,咱们是通俗易懂版,不整那些。
2、属性拦截器 属性拦截器就是在访问对象的属性时要做的一些事情,你想嘛,拦截就是拦路抢劫,拦截下来肯定要搞点事情才放你走。
主要介绍 2 个魔法函数:__getattr__ 和 __getattribute__
这两个函数特别神奇,两个函数功能相反,一个是找到属性要做的事,另一个是没找到属性要做的事。
class Mikigo :
def __init__ (self ):
self .age = 30
def __getattribute__ (self, item ):
print (f"找到{item} ,我先搞点事情" )
def __getattr__ (self, item ):
print (f"没找到{item} ,我想想能搞点啥事情" )
mi = Mikigo()
print (mi.age)
找到属性,会先调用 __getattribute__,并没有调用 __getattr__。
这里就需要注意了,访问一个不存在的属性,首先还是会进入 __getattribute__,说明它是无条件进入的,然后才是调用 __getattr__。
再扩展一个 __setattr__ 用于修改属性值的:
class Mikigo :
def __init__ (self ):
self .age = 30
def __setattr__ (self, key, value ):
print (f"修改{key} 的值为{value} " )
self .__dict__[key] = value
mi = Mikigo()
mi.age = 25
print (mi.age)
你看,age 的值被修改了,但是 __setattr__ 貌似被调用了 2 次,那是因为在类实例化的时候就会进入一次,第一次是将 __init__ 里面的值添加到类实例的 __dict__ 属性中,第二次修改再次进入,将 __dict__ 属性中的值修改掉。
属性拦截一定要谨慎使用,一般情况下不建议使用,因为如果处理不好,会造成类里面属性关系的混乱,抛异常往往不容易定位。
class Config :
default = {
"SMB_URL" : "SMB://10.8.10.214" ,
"SMB_IP" : "10.8.10.214" ,
}
def __getattr__ (self, key ):
try :
return Config.default[key]
except KeyError:
raise AttributeError(f"{key} is not a valid option!" ) from KeyError
def __setattr__ (self, key, value ):
if key not in Config.default:
raise AttributeError(f"{key} is not a valid option!" ) from KeyError
Config.default[key] = value
试着分析下他们的作用吧,逻辑很简单的,你一定能看懂。
3、自定义元类 元类(metaclass)就是生成类的类,先定义 metaclass,就可以创建类,最后创建实例。
其实最开始讲 type 的时候已经有所接触了,type 生成了所有类,它就是顶层元类,metaclass 也是要继承 type 的,排行顶多老二。
来,咱们定义一个元类,用途是添加一个属性 age:
class AutoTestMetaClass (type ):
def __new__ (cls, name, bases, dct ):
x = super ().__new__(cls, name, bases, dct)
x.age = 30
return x
class Mikigo (metaclass=AutoTestMetaClass):
...
mi = Mikigo()
print (mi.age)
print (Mikigo.age)
__new__ 也是构造函数,和 __init__ 有区别,__new__ 是用来构造类对象的,你看它的参数是 cls,必须 return 一个对象。
name, bases, dct 这三个参数和 type 的三个参数是一个意思,不清楚可以回看前面讲 type 的章节。
元类有了,咱们使用一下,既然元类是用来生成类的类,那咱们就来生成一个类:
class Mikigo (metaclass=AutoTestMetaClass):
...
mi = Mikigo()
print (mi.age)
print (Mikigo.age)
咱们定义一个类除了省略号没有任何属性,省略号也是一个对象,你也可以用 pass,但是仍然可以访问 age 属性。因为我们是通过元类,向 Mikigo 这个类添加了一个属性,元类有时称为类工厂。
六、迭代器和生成器
1、迭代协议 迭代就是可以使用循环将数据挨个挨个取出来,这个好理解是吧,比如,咱们常见的对一个列表进行迭代:
for i in [1 , 2 , 3 ]:
print (i)
那列表里面究竟是实现了什么协议,或者说一个对象实现什么魔法函数就可以迭代呢,这就是迭代协议:__iter__
一个类只要实现了魔法函数 __iter__ 就是可迭代的(Iterable),但是它还不是迭代器 (Iterator),品一下区别。
class IterTest :
def __iter__ (self ):
...
from collections.abc import Iterable
from collections.abc import Iterator
print ("是否可迭代:" , isinstance (IterTest(), Iterable))
print ("是否为迭代器:" , isinstance (IterTest(), Iterator))
你看实现了迭代协议,就是可迭代的,想起鸭子类型了吗。
2、迭代器和可迭代对象 我们现在知道一个对象只要实现了 __iter__ 就是一个可迭代的对象,现在咱们来试试对一个可迭代对象使用 for 循环进行迭代,放个简单的列表进去看看:
class IterTest :
def __iter__ (self ):
return [1 , 2 , 3 ]
for i in IterTest():
print (i)
运行报错了,说 iter 返回了一个不是迭代器的对象。说明在 __iter__ 里面需要返回一个迭代器,对吧,其他的先不管,咱们放一个迭代器进去,保证程序跑起来不报错。
class IterTest :
def __iter__ (self ):
return (i for i in range (3 ))
for i in IterTest():
print (i)
但是,此时仍然还不是一个迭代器,要实现迭代器,还必须要实现另外一个魔法函数:__next__
class IterTest :
def __iter__ (self ):
return (i for i in range (3 ))
def __next__ (self ):
...
print ("是否为迭代器:" , isinstance (IterTest(), Iterator))
你看,实现 __next__ 之后,就是一个迭代器了。那 __next__ 应该怎么写,前面我们已经看到, __iter__ 里面是不负责逻辑处理的,它只管返回,逻辑处理需要在 __next__ 里面去做。
class Fib :
def __init__ (self, n ):
self .a, self .b = 0 , 1
self .n = n
def __iter__ (self ):
return self
def __next__ (self ):
if self .n > 0 :
self .a, self .b = self .b, self .a + self .b
self .n -= 1
return self .a
else :
raise StopIteration
这里面 n 是用来限制迭代次数的,不然这个循环将一直进行下去,直到宇宙的尽头,抛 StopIteration 异常会被 for 循环自动处理掉。
for i in Fib(10 ):
print (i)
简单一句话总结一下:迭代器就是使对象可以进行 for 循环,它需要实现 __iter__ 和 __next__ 两个魔法函数。
有同学要说了,就这?不就用 for 循环嘛,搞这么复杂嘎哈,我为什么要用迭代器啊?
节省资源消耗,迭代器并不会计算每一项的值,它只在你访问这些项的时候才计算,也就是说它保存的是一种计算方法,而不是计算的结果。能理解吗,相当于迭代器是鱼竿,而不是一池子的鱼,需要鱼的时候钓就行了,而不用把所有鱼都搬回家。
平时可能感受不到哈,当你需要计算一个非常大的数据时,你就能感受到了,这就是'惰性求值'的魅力。
你可以试试前面的斐波那契数列的列子,对比一个普通的列表,然后给一个很大的数字,区别就很明显了。
3、生成器 生成器也是一种迭代器,特殊的迭代器,它也可以用 for 循环来取值,但是大部分的情况下是使用 next() 函数进行取值。
前面我们讲生成器表达式已经见识过,这是一种便携的写生成器的方法:
my_gen = (i for i in range (10 ))
print (next (my_gen))
print (next (my_gen))
print (next (my_gen))
print (next (my_gen))
前面讲的好多对象都是在类里面定义的,而生成器对象就不是在类里面了,而是在函数里面定义,在一个函数里面只要出现了 yield 它就不是普通函数,而是一个生成器。
def my_gen ():
print ("setp 1" )
yield 1
print ("setp 2" )
yield 2
g = my_gen()
next (g)
next (g)
yield 的用途是让函数暂停,并保存对象状态在内存中,下次再使用 next 调用同一个对象时,又开始从之前暂停的位置开始执行,直到运行到下一个 yield 又暂停,如果后面没有 yield 了,则会抛 StopIteration 异常。
yield 和 return 都能返回数据,但是有区别,return 语句之后的代码是不执行的,而 yield 后面还可以执行。
有同学要问了,生成器函数里面能用 return 吗?好问题,不愧是你。
生成器里面是可以用 return 的,但是,return 后面的数据不会被返回。
def my_gen ():
yield 1
yield 2
return 3
for i in my_gen():
print (i)
你看,3 并没有被返回,所以说生成器里面的 return 只是一个结束的标志,它不会把后面的值返回给调用者,这跟函数里面的 return 是不一样的。
4、总结 看完前面迭代器和生成器的内容,可能有些同学有点晕了,没关系,多看几遍,经常看,经常晕。
迭代器需要实现两个魔法函数:__iter__ 和 __next__;
迭代器允许惰性求值,只有在请求下一个元素时迭代器对象才会去生成它,它保存的是一种生成数据的方法;
生成器是迭代器的一种更 Pythonic 的写法,可以在函数里面用 yield 创建一个迭代器;
生成器表达式是生成器的一种更加 Pythonic 的写法。
七、高阶函数 高阶函数是通过组合简单函数成一个复杂表达式的函数。你可以理解成,函数套函数。函数式编程是一种编程范式,这部分内容可以体现 Python 在函数式编程上的应用。
1、lambda 匿名函数(lambda),这个函数没有函数名,用于一行创建一个函数,并返回一个函数对象,也是一种语法糖。
def add_one (n ):
return n + 1
你看,确实很简洁哈,my_lb 不是函数名哈,有函数名它就不是匿名函数了,而是函数对象,咱可以调用它。
我个人觉得,匿名函数很尴尬,基本上都是用在下面几个高阶函数里面的,如果你平时也想用它,大多数情况下是不符合社区规范的。简单的表达式还行,复杂的表达式可读性太差。
传言 Python 之父 Guido 也不推荐使用它,甚至曾想过移除它,后来放弃了,估计是不好搞。就像 GIL 一样,大家都知道不好,但是这么多年下来太多库都用到了,哪是你想删就能删的,社区不答应,我也不答应。
2、map map 函数是给一个序列做映射,然后返回结果序列。
简单通俗讲就是:拿到一个序列,给序列中元素一顿操作之后,返回序列。
my_map = map (lambda x: x + 1 , [1 , 2 , 3 ])
print (my_map)
my_list = list (my_map)
print (my_list)
你看,map 返回的是一个对象,转 list 之后每个元素的加了 1。
3、reduce reduce 函数就是对一个序列做累积,即将序列中前一个元素和后一个元素进行逻辑组合,然后结果再和后面一个元素组合。
简单通俗讲就是:拿到一个或多个序列,给序列中元素一顿操作之后,返回操作结果。
from functools import reduce
my_rd = reduce(lambda x, y: x + y, [1 , 2 , 3 ])
print (my_rd)
你看,把列表中的元素都相加了,注意组合关系不一定是相加,你可以换成相乘试试。
乍一看和上面的 map 是一个意思哈,确实用法一样,区别就是 reduce 函数里面的 lambda 函数有两个参数,而 map 函数参数理论上可以多个,但是每个参数对应一个序列,也就是说,有多少个参数,就要有多少个序列。
4、filter filter 函数用于过滤的,即将序列中的每个元素进行判断,然后返回为 True 的元素。
my_ft = filter (lambda x: x % 2 == 1 , [1 , 2 , 3 ])
print (my_ft)
my_list = list (my_ft)
print (my_list)
判断序列中哪些数是奇数,filter 返回的是一个对象,转列表之后,可以看到结果。
5、sorted sorted 函数用于排序,好多同学可能用过它的参数 reverse=False 升序(默认),reverse=True 降序,但是还有个参数 key 可能没咋用过,这里可以给表达式。
my_st = sorted ([1 , 5 , 3 ])
print (my_st)
my_st = sorted ([1 , 5 , 3 ], reverse=True )
print (my_st)
数字排序还是挺好用的哈,处理简单的字符串也都可以,但是如果是处理比较复杂字符串排序就有点费劲了,不信试试看:
test_list = ["test_mi_001" ,"test_ki_012" ,"test_go_008" ,"test_lt_003" ]
my_st = sorted (test_list)
print (my_st)
my_st = sorted (test_list, key=lambda x: x.split("_" )[-1 ])
print (my_st)
唉,这就对了,我们在表达式里面将结尾的序号取出来,key 就是关键字,意思就是按照我取出来的关键字排序。这里稍微理解一下哈,里面的表达式比较灵活,你也可以用正则表达式来做:
import re
my_st = sorted (test_list, key=lambda x: re.findall(r"\d+" , x))
它不仅可以对列表排序,只要是可迭代对象都可以,列表对象的内建方法 sort 也可以这样用,但区别是 sort 是对原列表进行排序,不返回新列表。
这里再补充一个小知识,我们经常往一个列表中去添加数据,然后对其进行排序,这样做没啥问题,但是如果数据量大了之后,性能会比较低。
维护一个排序序列,建议使用 Python 的标准库 bisect 来做,它是采用二分查找算法,性能较高。
6、zip zip 就是将多个序列打包成一个个元组,然后返回由这些元组组成的列表。
a = [1 , 2 , 3 ]
b = [4 , 5 , 6 ]
c = zip (a, b)
print (c)
my_list = list (c)
print (my_list)
zip 返回的是一个对象,实际上是一个迭代器对象。
转列表之后,可以看到,相当于是把元素纵向分别取出来,放到一个元组里面,然后元组组成一个列表。做数据处理的时候经常用到,了解一下。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online