Python 进阶语法:with...as...

1.  Python with…as…是什么

Python 的 with...as... 语句,就像一个贴心的管家,负责照顾你的资源,让你不再担心忘记关闭文件、网络连接或数据库事务等。这个管家在你进入“房间”时自动打开门,离开时帮你把门关上,真的是非常贴心!

这个管家的工作方式很简单,你只需要在 with 后面跟上你要管理的资源,然后在 as 后面给这个资源起个名字。一旦你进入了这个“房间”,这个资源就被自动管理了。

1.1  with...as...语法结构

with expression [as target]:
    with body

参数说明:

expression:是一个需要执行的表达式;
target:是一个变量或者元组,存储的是expression表达式执行返回的结果,[ ]表示该参数为可选参数。

1.2  with...as...用法示例

例如,当你打开一个文件时,你可能会这样写:

file = open("example.txt", "r")  # 手动打开
content = file.read()  
file.close()  # 手动关闭

但是这样写有个问题,如果代码在读取文件内容或关闭文件时出现异常,文件可能不会被正确关闭。这时,你就可以请出 with...as... 语句来帮忙:

with open("example.txt", "r") as file:  
    content = file.read()  
    # 在这里你可以放心地使用file,不必担心忘记关闭它。

这样,无论在 with 块中的代码是否出现异常,文件都会在退出块时被自动关闭,你就不必再担心忘记关闭文件了。

with...as...语句,在Python中叫做:上下文管理器,它在 Python 中实现了自动分配并释放资源。

2.  Python with...as...工作原理

2.1  with...as...的由来

with…as 是 python 的控制流语句,像 if ,while一样。

with…as 语句是简化版的 try...except...finally语句。

先理解一下 try…except…finally 语句是干啥的。

实际上 try…except 语句和 try…finally 语句是两种语句,用于不同的场景。但是当二者结合在一起时,可以“实现稳定性和灵活性更好的设计”。

2.1.1  try…except 语句

用于处理程序执行过程中的异常情况,比如语法错误、从未定义变量上取值等等,也就是一些python程序本身引发的异常、报错。比如你在python下面输入 1 / 0:

a = 1 / 0
print(a)
# ZeroDivisionError: division by zero

是的,上面的代码会引发一个ZeroDivisionError异常,因为它在尝试将1除以0。在Python中,任何数字除以0都是未定义的,所以会引发这个错误。

如果你想避免这个错误,你可以使用tryexcept语句来捕获这个异常:

# 声明变量a,并赋值为1除以0,这会导致ZeroDivisionError  
a = 1 / 0  
  
# 尝试执行接下来的代码块,如果发生异常,则执行except块  
try:  
    # 打印变量a的值,因为a = 1 / 0,所以这里会引发ZeroDivisionError  
    print(a)  
  
# 如果try块中的代码引发ZeroDivisionError异常,则执行此except块  
except ZeroDivisionError:    
    # 当除以零时,打印一个错误消息  
    print("Division by zero is not allowed!")

这样,当尝试除以零时,程序不会崩溃,而是会输出一个错误消息。

try…except 的标准格式

try:
    # normal block
except A:
    # exc A block
except:
    # exc other block
else:
    # noError block 

程序执行流程是:

–>执行normal block

–>发现有A错误,执行 exc A block(即处理异常)

–>结束

如果没有A错误呢?

–>执行normal block

–>发现B错误,开始寻找匹配B的异常处理方法,发现A,跳过,发现except others(即except:),执行exc other block

–>结束

如果没有错误呢?

–>执行normal block

–>全程没有错误,跳入else 执行noError block

–>结束

我们发现,一旦跳入了某条except语句,就会执行相应的异常处理方法(block),执行完毕就会结束。不会再返回try的normal block继续执行了。

try:
    a = 1 / 2  # a normal number/variable
    print(a)
    b = 1 / 0  # an abnormal number/variable
    print(b)
    c = 2 / 1  # a normal number/variable
    print(c)
except:
    print("Error")

结果是,先打出了一个0.5,又打出了一个Error。就是把ZeroDivisionError错误捕获了。

先执行 try 后面这一堆语句,由上至下:

  • step1: a 正常,打印a. 于是打印出0.5 (python3.x以后都输出浮点数)
  • step2: b, 不正常了,0 不能做除数,所以这是一个错误。直接跳到except报错去。于是打印了Error。
  • step3: 其实没有step3,因为程序结束了。c是在错误发生之后的b语句后才出现,根本轮不到执行它。也就看不到打印出的c了

但这还不是 try...except 的所有用法,except后面还能跟表达式的。

所谓的表达式,就是错误的定义。也就是说,我们可以捕捉一些我们想要捕捉的异常。而不是什么异常都报出来。

异常分为两类:

  • python标准异常
  • 自定义异常

我们先抛开自定义异常(因为涉及到类的概念),看看 except 都能捕捉到哪些 python 标准异常。

请查看:Python 异常处理

2.1.2  try…finallly 语句

用于无论执行过程中有没有异常,都要执行清场工作。

try:  
    execution block  # 正常执行模块  
except A:  
    exc A block # 发生A错误时执行  
except B:  
    exc B block # 发生B错误时执行  
except:  
    other block # 发生除了A,B错误以外的其他错误时执行  
else:  
    if no exception, jump to here # 没有错误时执行  
finally:  
    final block  # 总是执行  

tips: 注意顺序不能乱,否则会有语法错误。如果用 else 就必须有 except,否则会有语法错误。

try:
    a = 1 / 2
    print(a)
    print(m)  # 抛出 NameError异常, 此后的语句都不在执行
    b = 1 / 0
    print(b)
    c = 2 / 1
    print(c)
except NameError:
    print("Ops!!")  # 捕获到异常
except ZeroDivisionError:
    print("Wrong math!!")
except:
    print("Error")
else:
    print("No error! yeah!")
finally:  # 是否异常都执行该代码块
    print("Successfully!")

代码分析:

  1. try 块开始。
  2. a = 1 / 2:这行代码是合法的,所以 a 被赋值为 0.5
  3. print(a):输出 0.5
  4. print(m):这行代码尝试打印变量 m,但在此之前没有定义 m,所以会抛出一个 NameError 异常。
  5. 由于在 try 块中抛出了 NameError 异常,所以会跳到相应的 except 块。该块捕获到异常并输出 "Ops!!"。
  6. 由于异常已经被捕获,后面的代码(包括 b = 1 / 0print(b)c = 2 / 1print(c))都不会执行。
  7. 跳过 else 块(因为有一个被捕获的异常)。
  8. 进入 finally 块,并输出 "Successfully!"。

总结:

  • 当运行此代码时,输出将是:
0.5 
Ops!! 
Successfully!
  • 注意:尽管有 b = 1 / 0c = 2 / 1,但它们不会被执行或输出,因为前面的 NameError 异常已被捕获。

说完上面两个概念,我们再来说说 with...as...语句。with...as...是从Python2.5引入的一个新的语法,它是一种上下文管理协议,目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,简化try...except...finlally的处理流程。

2.2  with...as...工作原理

with...as...语句相当于下面这段代码:

try:  
    # 尝试打开名为'example.txt'的文件,模式为'r',表示只读模式。  
    f = open('example.txt','r')  
except:  
    # 如果在尝试打开文件时发生任何异常(例如文件不存在),则执行此块。  
    print('fail to open')  # 打印错误消息,告知用户无法打开文件。  
    exit()  # 终止程序。  
else:  
    # 如果打开文件成功,则尝试读取文件内容并打印  
    print(f.read())
 
finally:  
    # 不论是否发生异常,最后都会执行此块。  
    f.close()  # 关闭文件。这一步很重要,因为它确保文件资源被正确释放。

这是不是很麻烦,但正确的方法就是这么写。
我们为什么要写finally,是因为防止程序抛出异常最后不能关闭文件,但是需要关闭文件有一个前提就是文件已经打开了。

下面,我们从try...finally...语法结构谈起:

set things up
try:
    do something
finally:
    tear things down

这东西是个常见结构,比如文件打开,set things up就表示f=open('xxx')tear things down就表示f.close()。在比如像多线程锁,资源请求,最终都有一个释放的需求。Try…finally结构保证了tear things down这一段永远都会执行,即使上面do something的工作没有完全执行。

如果经常用这种结构,我们首先可以采取一个较为优雅的办法,封装!

def controlled_execution(callback):
    set things up
    try:
        callback(thing)
    finally:
        tear things down

def my_function(thing):
    do something

controlled_execution(my_function)

这段代码用于控制另一个函数的执行过程。下面是对代码的原理说明:

  1. 定义 controlled_execution 函数:这个函数接受一个回调函数 callback 作为参数。

    • 执行 set things up 代码,通常用于设置或准备执行回调函数所需的环境或资源。
    • 使用 try...finally 结构确保无论回调函数执行是否成功,都能执行清理操作。
    • try 块中,调用传入的回调函数 callback(thing) 并传入一个名为 thing 的参数。
    • finally 块,执行 tear things down 代码,用于清理准备阶段设置的环境或资源。
  2. 定义 my_function 函数:这个函数简单地执行某些操作(具体操作没有给出),并且它接受一个名为 thing 的参数。

  3. 最后,调用 controlled_execution(my_function) 执行整个流程。这意味着 my_function 会作为回调函数传递给 controlled_execution,并由它来控制执行过程。

这种模式通常用于封装和抽象执行细节,使得外部代码可以专注于其核心功能,而不必关心环境设置和清理等辅助任务。通过这种方式,可以更容易地管理和维护代码,特别是在处理可能引发异常的操作时。

封装是一个支持代码重用的好办法,但是这个办法很dirty,特别是当do something中有修改一些local variables(局部变量)的时候(变成函数调用,少不了带来变量作用域上的麻烦)。

另一个办法是使用生成器,但是只需要生成一次数据,我们用 for-in 结构去调用它:

def controlled_execution():
    # set things up
    try:
        yield thing
    finally:
        # tear things down

for thing in controlled_execution():
    # do something with thing

这段代码定义了一个生成器函数controlled_execution,并使用yield关键字在生成器函数中创建了一个可以返回值的迭代器。下面是对代码的原理说明:

  1. 定义 controlled_execution 函数:这个函数是一个生成器函数,因为它使用了yield关键字。生成器函数与普通函数不同,它们返回一个迭代器,可以在多次调用之间记住其状态。

    • 执行 set things up 代码,通常用于设置或准备执行回调函数所需的环境或资源。
    • 使用 try...finally 结构来确保无论回调函数执行是否成功,都能执行清理操作。
    • try 块中,使用 yield thing 返回一个值给调用者,并将这个值存储在生成器对象中。此时,生成器函数的执行被暂停,等待下一次迭代请求。
    • finally 块中,执行 tear things down 代码,这通常用于清理在准备阶段设置的环境或资源。
  2. 调用 controlled_execution 函数:通过使用for循环迭代调用controlled_execution()函数,可以逐个处理生成器返回的值。每次迭代都会从生成器中请求下一个值(使用next()函数),直到没有更多值可供请求。

  3. 处理返回的值:在for循环的每次迭代中,都会执行do something with thing代码,使用从生成器中获取的值(即yield thing返回的值)作为输入。

这种生成器模式在需要控制资源访问或处理潜在异常的情况下非常有用。通过使用生成器,可以将复杂的资源管理逻辑与主要的业务逻辑分开,使代码更加清晰和易于维护。

因为thing只有一个,所以yield语句只需要执行一次。当然,从代码可读性也就是优雅的角度来说这简直是糟糕透了。我们在确定for循环只执行一次的情况下依然使用了for循环,这代码给不知道的人看一定很难理解这里的循环是什么个道理。

最终的python-dev团队的解决方案。(python 2.5以后增加了with...as...表达式的语法)

class controlled_execution:
    def __enter__(self):
        # set things up
        return thing
    
    def __exit__(self,type,value,traceback):
        #tear things down

with controlled_execution() as thing:
    # do something 

在这里,python使用了with-as的语法。当python执行这一句时,会调用__enter__函数,然后把该函数return的值传给as后指定的变量。之后,python会执行下面do something的语句块。最后不论在该语句块出现了什么异常,都会在离开时执行__exit__。
另外,__exit__除了用于tear things down,还可以进行异常的监控和处理,注意后几个参数。要跳过一个异常,只需要返回该函数True即可。

下面的样例代码跳过了所有的TypeError,而让其他异常正常抛出。

def __exit__(self,type,value,traceback):
    return isinstance(value, TypeError) 

在python2.5及以后,file对象已经写好了__enter__和__exit__函数,我们可以这样测试:

f = open('example.txt','r')
print(f)
# 返回:<_io.TextIOWrapper name='example.txt' mode='r' encoding='cp936'>
print(f.__enter__())
# 返回:<_io.TextIOWrapper name='example.txt' mode='r' encoding='cp936'>
print(f.read())
# 返回:HELLO WORLD!
print(f.__exit__())
# 返回:None

之后,我们如果要打开文件并保证最后关闭他,只需要这么做:

with open("x.txt") as f:
    data = f.read()

    # do something with data

如果有多个项,我们可以这么写:

with open ( "x.txt" ) as f1 , open ( 'xx.txt' ) as f2:

    # do something with f1,f2

上文说了__exit__函数可以进行部分异常的处理,如果我们不在这个函数中处理异常,他会正常抛出,这时候我们可以这样写(python 2.7及以上版本,之前的版本参考使用contextlib.nested这个库函数):

try :

    with open ( "a.txt" ) as f :

        # do something

except xxxError:

    # do something about exception

如果文件"x.txt"不存在,使用open("x.txt")会引发一个FileNotFoundError异常。为了处理这个异常,你可以使用try...except语句来捕获异常并执行相应的处理逻辑。

下面是一个示例代码,展示了如何处理文件不存在的情况:

try:  

    with open("x.txt") as f:  

        data = f.read()  

        print(data)  

except FileNotFoundError:  

    print("文件不存在,请检查文件路径或文件名是否正确。")

在上述代码中,我们使用try...except语句来捕获FileNotFoundError异常。如果文件不存在,会执行except块中的代码,打印出一条错误消息。

这样,当文件不存在时,程序不会崩溃,而是会输出一个友好的错误提示,帮助用户了解问题所在。

 总之,with-as表达式极大的简化了每次写finally的工作,这对保持代码的优雅性是有极大帮助的。

 

  • 37
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Python进阶语法是指对Python编程语言更深入理解和运用的一些高级特性和技巧。掌握这些进阶语法可以让我们编写更高效、灵活和复杂的Python程序。 首先,函数式编程是Python进阶语法中的重要一环。函数是一等公民,可以作为参数传递和返回值返回,使得程序的设计更加灵活和模块化。除了普通函数外,Python还支持匿名函数(lambda表达式)、高阶函数(map、reduce、filter等)和装饰器等函数式编程的特性。 其次,生成器和迭代器也是Python进阶语法中的关键概念。生成器函数可以通过yield关键字实现状态的保存和暂停执行,提高代码的可读性和性能。而迭代器则是一种用于迭代访问集合元素的对象,通过实现__iter__和__next__方法,可以自定义迭代器。 另外,元类(metaclass)是Python进阶语法中的高级特性。元类可以定制类的创建过程,可以在类定义之后对类进行修改,实现一些元编程的功能。它可以用于创建自己的类创建函数,对类的属性和方法进行自动初始化等。 此外,异步编程也是Python进阶语法中的重要内容。asyncio模块和async/await关键字的引入,使得Python可以更好地支持协程编程和异步IO操作,提高程序的并发性和响应性。 最后,对于性能优化和调试技巧,Python进阶语法中也涉及一些相关的知识。比如使用装饰器对函数进行性能统计,使用线程池或进程池并行执行任务,使用性能分析工具对代码进行调优等。 总而言之,Python进阶语法包含了函数式编程、生成器和迭代器、元类、异步编程以及性能优化和调试技巧等多个方面的知识,掌握这些知识可以使我们编写更高级、更复杂的Python程序。 ### 回答2: Python进阶语法是指在掌握基础语法的基础上进一步学习和应用的Python语言的一系列高级特性和用法。通过学习Python进阶语法,可以更加灵活地编写代码,提高代码的可读性和可维护性,实现更加复杂的功能。 Python进阶语法包括但不限于以下几个方面。 1. 函数式编程:函数是Python的核心组件之一,进阶语法中可以更加深入地理解函数的原理和特性,并学习使用高阶函数、匿名函数、闭包等概念和技巧,提高代码的简洁性和可重用性。 2. 迭代器和生成器:迭代器和生成器是处理可迭代对象的重要工具,进阶语法可以学习如何自定义迭代器和生成器,以及它们的底层原理和性能优化策略。 3. 装饰器:装饰器是Python中强大且灵活的语法特性,可以在不修改原函数代码的情况下为函数添加新的功能,比如日志记录、计时统计等,提高代码的灵活性和可扩展性。 4. 异常处理:进阶语法中可以学习如何更好地处理异常,包括自定义异常类、多个异常的处理顺序等,提高代码的健壮性和可靠性。 5. 面向对象编程:进阶语法中可以深入学习和应用面向对象编程的概念和技巧,包括类的继承和多态等,实现更加复杂的程序设计。 总之,Python进阶语法是在基础语法的基础上进一步扩展和应用的一系列高级特性和用法。掌握这些语法可以使我们更加灵活地编写代码,提高代码质量和开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我自纵横2023

您的鼓励将是我最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值