【问题标题】:What should I use instead of assignment-in-an-expression in Python?我应该使用什么来代替 Python 中的赋值表达式?
【发布时间】:2010-12-03 13:28:51
【问题描述】:

根据this page 不能使用类似的代码

if variable = something():
#do something with variable, whose value is the result of something() and is true

所以如果我想拥有以下代码结构:

if a = something():
#do something with a
elif a = somethingelse():
#...
#5 more elifs

something() 函数是计算密集型的(我的意思是使用该函数然后再次执行它以将值分配给一个变量,以防第一个为真无法完成),我应该写什么Python?再添加 7 个变量而不是 1 个?

【问题讨论】:

  • 我不明白你的问题。您是否试图避免编写与 if 语句分开的显式赋值语句?如果是这样,为什么?额外的语句有什么问题?
  • @S.Lott:我能想到一些用途,因为它是一个非常标准的 C 风格的编码习惯。它经常用在循环中,例如:while ((p = getNextPointer()) != NULL){//use p}。在许多情况下,额外的语句会使代码变得更加丑陋(我猜这就是为什么这种情况如此普遍)。
  • @S.Lott:在我的代码示例中,如果第二个函数返回真实答案,则最后 5 个函数不会被执行,与计算和分配所有内容相比,这是一个巨大的性能优势

标签: python


【解决方案1】:

从 Python 3.8.0a1+ 开始,我们可以使用assignment expression 语法。

例如:

>>> if a := 0:
...     print('will not be printed')
... elif a := 1:
...     print('print value of a: %d, a should be 1' % a)
... else:
...     print('will not be printed')
...
print value of a: 1, a should be 1

【讨论】:

    【解决方案2】:

    如果我们使用字符串,这是可能的——因为我们可以将字符串转换为列表并使用方法extends 用于列表,该方法在逻辑上使内联将一个字符串附加到另一个字符串(以列表格式):

    >>> my_str = list('xxx')
    >>> if not my_str.extend(list('yyy')) and 'yyy' in ''.join(my_str):
    ...     print(True)
    True
    

    这里我们在if '添加到原始字符串' 中的新数据并试图找到它。也许这很难看,但这是表达式中的赋值:

    if my_str += 'yyy' and 'yyy' in my_str:
    

    【讨论】:

      【解决方案3】:

      我会像解决其他几个棘手的流控制问题一样解决这个问题:将棘手的位移动到函数中,然后使用return 提前退出函数。像这样:

      def do_correct_something():
          a = something()
          if a:
              # do something with a
              return a
      
          a = somethingelse()
          if a:
              # do something else with a
              return a
      
          # 5 more function calls, if statements, do somethings, and returns
      
          # then, at the very end:
          return None
      
      a = do_correct_something()
      

      我这样做的另一个主要“棘手的流量控制问题”是打破多个嵌套循环:

      def find_in_3d_matrix(matrix, x):
          for plane in matrix:
              for row in plane:
                  for item in row:
                      if test_function(x, item):
                          return item
          return None
      

      您还可以通过编写一个只迭代一次的 for 循环来解决上述问题,并使用“break”提前退出,但我更喜欢函数返回版本。它不那么棘手,而且更清楚发生了什么;并且 function-with-return 是打破 Python 中多个循环的唯一干净方法。 (将“if break_flag: break”放在每个 for 循环中,并在您想要中断时设置 break_flag,恕我直言不干净。)

      【讨论】:

      • 可以写成return next(item for plane in matrix for row in plane for item in row if test_function(x, item))
      • @PaulMcG 我觉得这个功能更容易理解,也更实用。调试时可以选择单步或跨过一个函数;那个生成器表达式不太方便。但是,如果您希望您的程序使用生成器机制来避免函数调用,它会起作用。
      【解决方案4】:

      你可以使用这样的装饰器来记忆这些函数——假设它们总是返回相同的值。请注意,您可以根据需要多次调用昂贵的foo和昂贵的bar,并且函数体只会执行一次

      def memoize(f):
          mem = {}
          def inner(*args):
              if args not in mem:
                  mem[args] = f(*args)
              return mem[args]
          return inner
      
      @memoize
      def expensive_foo():
          print "expensive_foo"
          return False
      
      @memoize
      def expensive_bar():
          print "expensive_bar"
          return True
      
      if expensive_foo():
          a=expensive_foo()
          print "FOO"
      elif expensive_bar():
          a=expensive_bar()
          print "BAR"
      

      【讨论】:

      【解决方案5】:

      几年前,在 2001 年,我遇到了这个问题——因为我是从 C 中大量使用分配和测试的参考算法音译到 Python,所以我热衷于为初稿保留类似的结构(当时一旦正确性得到很好的测试,稍后重构)。所以我在 Cookbook 中写了一个recipe(另见here),归结为...:

      class DataHolder(object):
          def set(self, value): self.value = value; return value
      

      所以if/elif 树可以变成:

      dh = DataHolder()
      if dh.set(something()):
        # do something with dh.value
      elif dh.set(somethingelse()):
        # ...
      

      DataHolder 类显然可以通过各种方式进行修饰(在线版和书籍版本中都进行了如此修饰),但这就是它的要点,足以回答您的问题。

      【讨论】:

        【解决方案6】:

        让自己成为一个简单的可调用对象,保存其返回值:

        class ConditionValue(object):
            def __call__(self, x):
                self.value = x
                return bool(x)
        

        现在像这样使用它:

        # example code
        makelower = lambda c : c.isalpha() and c.lower()
        add10 = lambda c : c.isdigit() and int(c) + 10
        
        test = "ABC123.DEF456"
        val = ConditionValue()
        for t in test:
            if val(makelower(t)):
                print t, "is now lower case ->", val.value
            elif val(add10(t)):
                print t, "+10 ->", val.value
            else:
                print "unknown char", t
        

        打印:

        A is now lower case -> a
        B is now lower case -> b
        C is now lower case -> c
        1 +10 -> 11
        2 +10 -> 12
        3 +10 -> 13
        unknown char .
        D is now lower case -> d
        E is now lower case -> e
        F is now lower case -> f
        4 +10 -> 14
        5 +10 -> 15
        6 +10 -> 16
        

        【讨论】:

          【解决方案7】:

          我可能遗漏了一些东西,但是您不能将顶级 if 语句中的每个分支分解为单独的函数,创建一个 testaction 元组并循环遍历它们?您应该能够应用此模式来模仿 if (value=condition()) {} else if (value=other_condition()) {} 样式逻辑。

          这实际上是redglyph's response 的扩展,并且可能可以压缩为iterator,一旦达到真值,它就会引发StopIteration

          #
          # These are the "tests" from your original if statements. No
          # changes should be necessary.
          #
          def something():
              print('doing something()')
              # expensive stuff here
          def something_else():
              print('doing something_else()')
              # expensive stuff here too... but this returns True for some reason
              return True
          def something_weird():
              print('doing something_weird()')
              # other expensive stuff
          
          #
          # Factor each branch of your if statement into a separate function.
          # Each function will receive the output of the test if the test
          # was selected.
          #
          def something_action(value):
              print("doing something's action")
          def something_else_action(value):
              print("doing something_else's action")
          def something_weird_action(value):
              print("doing something_weird's action")
          
          #
          # A simple iteration function that takes tuples of (test,action). The
          # test is called. If it returns a truth value, then the value is passed
          # onto the associated action and the iteration is stopped.
          #
          def do_actions(*action_tuples):
              for (test,action) in action_tuples:
                  value = test()
                  if value:
                      return action(value)
          
          #
          # ... and here is how you would use it:
          #
          result = do_actions(
                       (something, something_action),
                       (something_else, something_else_action),
                       (something_weird, something_weird_action)
          )
          

          【讨论】:

            【解决方案8】:

            另一种提供一定灵活性的替代方案:

            # Functions to be tested (can be expanded):
            tests = [something, somethingelse, yetsomethingelse, anotherfunction, another]
            for i, f in enumerate(tests):
                a = f()
                if a:
                    if i == 0:
                        # do something with a
                    elif 1 <= i <= 3:
                        # do something else with a
                    else:
                        # ...
                    break
            

            或者你可以显式比较函数:

            tests = [something, somethingelse, yetsomethingelse, anotherfunction, another]
            for i, f in enumerate(tests):
                a = f()
                if a: break
            if not a:
                # no result
            elif f == something:
                # ...
            elif f == somethingelse:
                # ...
            

            如果某些函数带参数,你可以使用 lambda 来保持函数范式:

            tests = [lambda: something(args), somethingelse, lambda: something(otherargs)]
            for i, f in enumerate(tests):
                a = f()
                if a: break
            if not a:
                # no result
            elif i == 0:
                # ...
            elif i == 1:
                # ...
            

            【讨论】:

              【解决方案9】:

              你可以这样做:

              a = something()
              if a:
                  #do something with a
              else:
                  a = somethingelse()
                  if a:
                      #...
                  else:
                      #5 more nested ifs
              

              或者,在函数内部,您可以在每个匹配的情况下使用 return 限制嵌套级别:

              def f():
                  a = something()
                  if a:
                      #do something with a
                      return
                  a = somethingelse()
                  if a:
                      #...
                      return
                  #5 more ifs
              

              【讨论】:

                猜你喜欢
                • 2010-11-05
                • 2010-12-06
                • 2012-01-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-06-06
                相关资源
                最近更新 更多