Python - with 带有可选对象的语句

Python - with statement with optional object

我有一个处理一些数据的脚本,如果 database/file 存在,则将一些数据写入其中。我将数据库或文件指定为 configargparse(argparse) 参数。我需要以某种有组织的方式清理(关闭文件,数据库)以防发生异常。

这是我的初始化:

import sqlite3
import confargparse
import sys
parser.ArgParser(...) 
parser.add('--database', dest='database',
    help='path to database with grabbers', metavar='FILE',
    type=lambda x: arghelper.is_valid_file(parser, x))
parser.add('-f', '--file', type=configargparse.FileType(mode='r'))
args = parser.parse_args()

我使用 iftry:

if args.database:
    conn = sqlite3.connect(args.database)
    c = conn.cursor()
# same init for file

try:
    while True: # do something, it might be moved to some main() function
        result = foo()
        if args.database:
            c.execute('Write to database {}'.format(result))
        # same
            # for file        
finally:
    if args.database:
        conn.close() 
    # same
        # for file
except KeyboardInterrupt:
    print 'keyboard interrupt'

with语句可以吗?类似的东西(来自 C 的 ()?():()):

with ((args.database)?
      (conn = sqlite3.connect(args.database)):
      (None)) as db, same for file:

然后在with子句中引用数据库并检查它们是否存在?

在这种情况下,您可以创建自己的上下文管理器。创建一个处理两个连接的。上下文管理器是一个 class,它具有方法 __enter__()__exit__()。一种在进入 with 子句之前调用,一种在离开时调用(无论如何)。

下面是一个关于如何在您的情况下执行此操作的示例:

def f(cond1, cond2):

  class MultiConnectionContextManager(object):
    def __init__(self, cond1, cond2):
      self.cond1 = cond1
      self.cond2 = cond2
    def __enter__(self):
      print "entering ..."
      if self.cond1:
        # self.connection1 = open(...)
        print "opening connection1"
      if self.cond2:
        # self.connection1 = open(...)
        print "opening connection2"
      return self
    def __exit__(self, exc_type, exc_value, traceback):
      print "exiting ..."
      if self.cond1:
        # self.connection1.close()
        print "closing connection1"
      if self.cond2:
        # self.connection2.close()
        print "closing connection2"

  with MultiConnectionContextManager(cond1, cond2) as handle:
    if cond1:
      # handle.connection1.read()
      print "using handle.connection1"
    if cond2:
      # handle.connection2.read()
      print "using handle.connection2"

for cond1 in (False, True):
  for cond2 in (False, True):
    print "=====", cond1, cond2
    f(cond1, cond2)

你可以直接调用这个看看结果。将 print 替换为您打开、使用和关闭连接的真实语句。

先回答你的问题。可以使用 contextlib 来完成。但我不确定你能从中得到多少。

from contextlib import contextmanager

@contextmanager
def uncertain_conn(args):
    yield sqlite3.connect(args.database) if args.database else None

# Then you use it like this
with uncertain_conn(args) as conn:
    # conn will be the value yielded by uncertain_conn(args)
    if conn is not None:
        try:
            # ...

但正如我所说,虽然将生成器函数转换为上下文管理器很酷,而且我个人非常喜欢 contextmanager 装饰器,它确实为您提供了您想要的功能,但我不喜欢知道它在这里是否真的对您有很大帮助。如果我是你,我可能会对 if:

感到满意
if args.database:
    conn = sqlite3.connect(args.database)
    try:
       # ...

不过, 一些您可以使用 with 简化的事情。查看 closing,也来自 contextlib(非常简单,我只引用文档):

contextlib.closing(thing)

Return a context manager that closes thing upon completion of the block. This is basically equivalent to:

from contextlib import contextmanager

@contextmanager def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

所以上面的代码可以变成:

if args.database:
    conn = sqlite3.connect(args.database)
    with closing(conn):
        # do something; conn.close() will be called no matter what

但这不会为 KeyboardInterrupt 打印一条好消息。如果您真的需要它,那么我想您仍然必须自己写出 try-except-finally 。做任何更奇特的事情可能都不值得。 (注意 except 必须在 finally 之前,否则会出现语法错误。)

您甚至可以使用 suppress 执行此操作(但需要谨慎;见下文)

from contextlib import suppress

with suppress(TypeError):
    conn = sqlite3.connect(args.database or None)
    with closing(conn):
        # do business

with suppress(error): do_thing 等价于

try:
    do_thing
except error:
    pass

因此,如果 args.database 的计算结果为 False,则第二行实际上是 connect(None),它会引发一个 TypeError,它将被上下文管理器和下面的代码将被跳过。但风险在于它会抑制其范围内的所有 TypeError,而您可能不希望这样。