处理 None 和 with Statement 的 Pythonic 方法

Pythonic Method for handling None and with Statement

我想调用一个函数并传递一个 File 类对象或 None。但是,使用类似 File 的对象,我想使用 with 语句正确处理其资源分配。但是,如果我的 with 表达式的计算结果为 None,Python 将引发异常 (AttributeError)。我想我可以手写完整的 try/except 块,但是是否存在处理这种情况的简洁 Pythonic 方法?

def call_method(o1, o2, f_in):
    if f_in:
        pass # Do something optional if we have the f_in value

# ...

with (open(path, 'rb') if flag else None) as f_in:
    call_method(opt1, opt2, f_in)
    # Throws an AttributeError, since None does not have __exit__

这样做的方法是使用 try/except:

try:
    with open(path) as f:
        ...
except OSError:
    pass

上面的代码是如果你打算对文件做些什么/改变它以避免竞争条件。

事实上,您不能将 None 传递给 with,因为 with 完成后会调用 __exit__。相反,重新排列为:

def call_method(o1, o2, f_in):
    # If nothing special is done with None (just a pass) then you don't
    # even need to call this with None, otherwise, you can check f_in like:
    if f_in is None:
        pass
    else:
        pass

if flag:
    with open(path, 'rb') as f_in:
        call_method(opt1, opt2, f_in)
else:
    # only really needed if call_method does something if None is passed
    call_method(opt1, opt2, None)

另一种选择是使用 nullcontext() 代替另一个答案中所述的 None

如果您想要一个什么都不做的上下文管理器,那就是 contextlib.nullcontext(),而不是 None:

import contextlib

with (open(whatever) if flag else contextlib.nullcontext()) as f:
    do_whatever()

fnot flag 的情况下将是 None - 分配给 f 的东西是 __enter__ 的 return值,不必是上下文管理器本身。

对于 Python < 3.7,我用 lambda 表达式完成了这个:

def call_method(o1, o2, get_file):
    if get_file:
        with (get_file() as f_in):
            pass # Do something optional if we have the f_in value

# ...

get_file = (lambda: open(path, 'rb')) if flag else None
call_method(opt1, opt2, get_file)