【问题标题】:Try-clause containing multiple statements包含多个语句的 Try 子句
【发布时间】:2014-09-22 22:49:59
【问题描述】:

假设我有以下函数/方法,它计算一堆东西,然后设置很多变量/属性:calc_and_set(obj)。

现在我想做的是用不同的对象多次调用该函数,如果一个或多个失败,那么什么都不应该设置。

我以为我可以这样做:

try:
  calc_and_set(obj1)
  calc_and_set(obj2)
  calc_and_set(obj3)
except:
  pass

但这显然行不通。例如,如果错误发生在对函数的第三次调用中,那么第一次和第二次调用将已经设置了变量。

谁能想到一种“干净”的方式来做我想做的事?我能想到的唯一解决方案是相当丑陋的解决方法。

【问题讨论】:

  • 没有内置或简单的方法可以做到这一点;您必须保存原始状态才能返回,或者有一些方法可以撤消更改并在失败时展开它们。
  • @DSM 这或多或少是相反的,不是吗?我想,这里的 OP 想要要么全部要么什么都不想要。
  • @jonrsharpe:是的,你是对的。我在 OP 的解释中添加了一些词,直到它匹配.. :-/
  • @jonrsharpe:我写“丑陋的变通办法”时就是这么想的……
  • @Aniss:乔恩说得很好——恰恰相反。在那里,OP 想要单独控制,而不必复制 try/excepts。您希望能够还原整个事情,如果您的函数是纯函数(即无副作用),这相对容易做到,否则很难。

标签: python function variables try-catch statements


【解决方案1】:

我在这里看到了一些选项。

A.有一个“反向功能”,它是健壮的。所以如果

def calc_and_set(obj):
    obj.A = 'a'

def unset(obj):
    if hasattr(obj, 'A'):
        del obj.A

try:
  calc_and_set(obj1)
  calc_and_set(obj2)
except:
  unset(obj1)
  unset(obj2)

注意,在这种情况下,unset 不关心calc_and_set 是否成功完成。

B.将 calc_and_set 分离到 try_calc_and_set,测试它是否有效,和 set,它不会抛出错误,并且只有在所有 try_calc_and_set 都没有失败时才会调用。

try:
  try_calc_and_set(obj1)
  try_calc_and_set(obj2)
  calc_and_set(obj1)
  calc_and_set(obj2)
except:
  pass

C. (我最喜欢的) - 让calc_and_set 返回一个新变量,而不是就地操作。如果成功,请将原始参考替换为新参考。这可以通过在calc_and_set 中添加副本作为第一条语句,然后返回变量来轻松完成。

try:
  obj1_t = calc_and_set(obj1)
  obj2_t = calc_and_set(obj2)
  obj1 = obj1_t
  obj2 = obj2_t
except:
  pass

那个镜像当然是用来保存你之前的对象的:

obj1_c = deepcopy(obj1)
obj2_c = deepcopy(obj2)
try:
  calc_and_set(obj1)
  calc_and_set(obj2)
except:
  obj1 = obj1_c
  obj2 = obj2_c

作为一般性评论(如果这只是一个示例代码,请原谅我)- 如果没有指定异常类型,就不要有异常。

【讨论】:

    【解决方案2】:

    您也可以尝试缓存您要执行的操作,然后在所有人都通过时一次性完成所有操作:

    from functools import partial
    
    def do_something (obj, val):
        # magic here
    
    def validate (obj):
        if obj.is_what_you_want(): 
           return partial(do_something, obj, val)
        else: 
           raise ValueError ("unable to process %s" % obj)
    
    
    instructions = [validate(item) for item in your_list_of_objects]
    for each_partial in instructions:
        each_partial()
    

    只有当列表综合收集无任何异常时,操作才会被触发。您可以将其包装起来以确保异常安全:

    try: 
            instructions = [validate(item) for item in your_list_of_objects]
            for each_partial in instructions:
                each_partial()
            print "succeeded"
     except ValueError:
            print "failed"
    

    【讨论】:

    • 很酷的设计,但是如果你已经有了is_what_you_want 方法,那不是更容易做到吗:if all(item.is_what_you_want()): for item in your_list): for item in your_list: item.do_something()
    • 真...如果 is_what_you_want 是异常安全的
    【解决方案3】:

    如果没有这样做的“内置”方式,我认为毕竟“最干净”的解决方案是将功能分为两部分。像这样的:

    try:
        res1 = calc(obj1)
        res2 = calc(obj2) 
        res3 = calc(obj3)
    except:
        pass
    else:
        set(obj1, res1)
        set(obj2, res2)
        set(obj3, res3)
    

    【讨论】:

    • 这实际上不是一个论坛;你已经在答案部分发布了你的感谢信息,它可能很快就会被删除,因为它不是一个答案。 Stack Overflow 专门用于阻止此类消息,因为设计师认为此类消息会分散问题和答案的注意力。最接近被认可的表示“谢谢”的方式是“接受回答”按钮。
    • @user2357112:对不起。就像我说的......第一次。但是我的帖子是一个答案..这是解决问题的可能方法。不管怎样,谢谢你让我知道..
    • @Aniss:也许,但如果三个临时对象非常大,则不会。这是分解中的一个有趣问题。
    • @smci:也许这不是最值得推荐的解决方案,只是我认为最简单、最直观的解决方案。此外,如果我使用字典保存结果,我认为分解没有问题。但就像我说的......仍在考虑选项......
    • 不要永远在回答时说谢谢。它是一个可能的解决方案,所以像一个一样张贴和用词。希望我的编辑实现了这一点,如果您不喜欢它,请随时重新编辑!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-14
    • 1970-01-01
    • 2021-08-05
    • 2011-01-06
    • 2016-03-18
    • 1970-01-01
    相关资源
    最近更新 更多