关于python:在if语句中重写赋值的pythonic方法

pythonic way to rewrite an assignment in an if statement

是否有一个Pythic的首选方式来做这一点,我将在C++中做:

1
2
3
for s in str:
    if r = regex.match(s):
        print r.groups()

我真的很喜欢这种语法,在我看来,它比到处都有临时变量要干净得多。唯一不太复杂的方法是

1
2
3
4
for s in str:
    r = regex.match(s)
    if r:
        print r.groups()

我想我在抱怨一个很学究的问题。我只是错过了以前的语法。


怎么样

1
2
3
for r in [regex.match(s) for s in str]:
    if r:
        print r.groups()

或者更实用一点

1
2
for r in filter(None, map(regex.match, str)):
    print r.groups()


也许有点老土,但是使用函数对象的属性来存储最后一个结果可以让您沿着这些行做一些事情:

1
2
3
4
5
6
7
def fn(regex, s):
    fn.match = regex.match(s) # save result
    return fn.match

for s in strings:
    if fn(regex, s):
        print fn.match.groups()

或者更一般地说:

1
2
3
4
5
6
7
def cache(value):
    cache.value = value
    return value

for s in strings:
    if cache(regex.match(s)):
        print cache.value.groups()

请注意,虽然保存的"值"可以是许多事物的集合,但这种方法仅限于一次保存一个这样的值,因此可能需要多个函数来处理需要同时保存多个值的情况,例如在嵌套函数调用、循环或其他线程中。因此,根据Dry原理,工厂功能可以帮助您,而不是编写每个工厂功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def Cache():
    def cache(value):
        cache.value = value
        return value
    return cache

cache1 = Cache()
for s in strings:
    if cache1(regex.match(s)):
        # use another at same time
        cache2 = Cache()
        if cache2(somethingelse) != cache1.value:
            process(cache2.value)
        print cache1.value.groups()
          ...


每当我发现我的循环逻辑变得复杂时,我都会用其他任何一点逻辑来做我想做的:将它提取到一个函数中。在Python中,要做到这一点比其他一些语言容易得多。

因此,提取只生成感兴趣项的代码:

1
2
3
4
def matching(strings, regex):
    for s in strings:
        r = regex.match(s)
        if r: yield r

然后,当您想要使用它时,循环本身就和它们得到的一样简单:

1
2
for r in matching(strings, regex):
    print r.groups()


做一些非Python式的事情没有Python式的方法。这是有原因的,因为1,如果在if语句的条件部分允许语句会使语法非常难看,例如,如果在if条件中允许赋值语句,为什么不也允许if语句?你会怎么写?类C语言没有这个问题,因为它们没有赋值语句。它们只使用赋值表达式和表达式语句。

第二个原因是

1
2
if foo = bar:
    pass

看起来很像

1
2
if foo == bar:
    pass

即使你足够聪明地输入正确的一个,即使你的团队中的大多数成员都足够敏锐地注意到它,你确定你现在看到的那个人正是应该在那里的吗?对于一个新开发人员来说,看到这一点并不是不合理的,只是修复它(一种方式或另一种方式),现在它肯定是错误的。


这可能是一个过于简单化的答案,但您是否认为:

1
2
3
for s in str:
    if regex.match(s):
        print regex.match(s).groups()


有一个制作作业表达的方法,但它非常简单。第一个选项不编译,所以第二个选项是前进的道路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## {{{ http://code.activestate.com/recipes/202234/ (r2)
import sys
def set(**kw):
    assert len(kw)==1

    a = sys._getframe(1)
    a.f_locals.update(kw)
    return kw.values()[0]

#
# sample
#

A=range(10)

while set(x=A.pop()):
    print x
## end of http://code.activestate.com/recipes/202234/ }}}

正如你所看到的,生产代码不应该用一根10英尺长的双袋装棍子来碰这个黑客。


另一个答案是使用"分配和测试"方法,允许在O'Reilly Media的2002年7月第1版《Python食谱》以及ActiveState网站上发布的单个语句中进行分配和测试。它是面向对象的,关键是:

1
2
3
4
5
6
7
8
9
# from http://code.activestate.com/recipes/66061
class DataHolder:
    def __init__(self, value=None):
        self.value = value
    def set(self, value):
        self.value = value
        return value
    def get(self):
        return self.value

这可以通过添加下面显示的自定义__call__()方法来稍微修改,以提供检索实例值的另一种方法——我认为,尽管不太明确,但对于"数据持有者"对象来说,调用时执行的操作似乎是完全合乎逻辑的。

1
2
    def __call__(self):
        return self.value

允许重新编写示例:

1
2
3
4
5
6
r = DataHolder()
for s in strings:
    if r.set(regex.match(s))
        print r.get().groups()
# or
        print r().groups()

正如在原始配方中所指出的,如果您经常使用它,那么将类和/或它的实例添加到__builtin__模块以使其全局可用是非常诱人的,尽管存在潜在的缺点:

1
2
3
import __builtin__
__builtin__.DataHolder = DataHolder
__builtin__.data = DataHolder()

正如我在回答这个问题时提到的,必须注意的是,这种方法仅限于一次只保存一个结果/值,因此需要多个实例来处理需要同时保存多个值的情况,例如在嵌套函数调用、循环或其他线程中。这并不意味着你应该使用它或其他答案,只是需要更多的努力。