【问题标题】:What's the pythonic way to chain if statements given n variables?给定n个变量的if语句链接的pythonic方法是什么?
【发布时间】:2017-04-21 01:33:09
【问题描述】:

我有以下函数作为脚本inspired by this的一部分:

def view(a='', b='', c=''):
    if a=='All' and b=='All' and c=='All': return df 
    if a=='All' and c=='All' and b!='All': return df[(df['b']==b)]
    if a!='All' and c=='All' and b=='All': return df[(df['a']==a)]
    if a=='All' and c!='All' and b=='All': return df[(df['c']==c)]
    if a=='All' and c!='All' and b!='All': return df[(df['c']==c) & (df['b']==b)]                                                        
    if a!='All' and c=='All' and b!='All': return df[(df['a']==a) & (df['b']==b)]                                                                     
    if a!='All' and c!='All' and b=='All': return df[(df['a']==a) & (df['c']==c)]                                                                 
    return df[(df['a']==a) & (df['b']==b) & (df['c']==c)]

有没有一种很好的方法可以用一个漂亮的 Python 表达式来编写所有这些链式 if 语句。如果对 n 个变量进行泛化,则得到奖励。

注意: Perhaps related to this question,但我还是想不通。

【问题讨论】:

  • 第一行可以写a==b==c=='All'all(lambda x: x == 'All', [a, b, c])。不过,我没有看到足够的模式来概括其余部分。
  • 另外,由于这是一个“改进我的代码”请求,这确实更适合代码审查。但请注意,它们需要完整的、可运行的代码块。
  • 对于df['a']==a 比较,df 索引是否总是要与之比较的变量的名称?
  • @jwodder 正确
  • 我想了解一下df。

标签: python python-3.x if-statement


【解决方案1】:

你的函数基本上是这样做的:

if all parameters are 'All':
    return df
else:
    Take all the non-'All' parameters
    Test if each one is equal to df['name_of_parameter']
    Bitwise-AND them together
    Return df[result of previous line]

让我们首先获取所有非“全部”参数的列表来开始我们的重写:

notall = [x for x in [a,b,c] if x != 'All']
if not notall:
    return df
else:
    ???

障碍 #1:我们现在已经不知道哪个值与哪个参数对应。为什么我们需要知道这一点?这样我们就可以将参数与df 的正确元素进行比较。我们可以通过在notall 中存储参数的值以及它们的名称来解决这个问题:

notall = [(x, name) for (x, name) in [(a, 'a'), (b, 'b'), (c, 'c')] if x != 'All']
if not notall:
    return df
else:
    ???

两次写出每个参数的名称是丑陋的,但要么这样,要么用locals和/或**kwargs调皮。

考虑到这一点,与df 的元素进行比较很容易:

 compared = [df[name] == x for (x, name) in notall]

现在,我们如何将它们全部加在一起?我们可以使用functools.reduce()operator.and_,但是(除非你重载== 以返回非布尔值,我希望你没有这样做),compared 的元素都是布尔值,这意味着将它们与按位与组合与将它们与逻辑与组合是相同的,Python 已经有一个函数:all()

return df[all(compared)]

把它们放在一起:

def view(a='', b='', c=''):
    notall = [(x, name) for (x, name) in [(a, 'a'), (b, 'b'), (c, 'c')] if x != 'All']
    if not notall:
        return df
    else:
        compared = [df[name] == x for (x, name) in notall]
        return df[all(compared)]

或者,更紧凑:

def view(a='', b='', c=''):
    notall = [(x, name) for (x, name) in [(a, 'a'), (b, 'b'), (c, 'c')] if x != 'All']
    if not notall:
        return df
    else:
        return df[all(df[name] == x for (x, name) in notall)]

现在,关于前面提到的那个调皮:如果所有参数都在dict 中,那么notall 可以只包含键,这将允许我们查找参数值和df 值,而无需重复自己(太多)。我们如何获取dict 中的所有参数?与**kwargs

def view(**kwargs):
    notall = [name for name in NAMES if kwargs.get(name, '') != 'All']

(注意使用get 为参数提供默认值。)但是NAMES 应该是什么?不能是kwargs.keys(),因为它只包含用户传入的参数,可能不是全部(甚至可能包括我们没想到的键!)。选项 1 是在某处写出参数名称列表并使用它:

NAMES = ['a', 'b', 'c']

或者,如果df的键恰好与函数参数的名称相同,我们可以使用df.keys()

    notall = [name for name in df.keys() if kwargs.get(name, '') != 'All']

或者,略短:

    notall = [name for name in df if kwargs.get(name, '') != 'All']

在这之后,我们只需要更新notall的元素是如何使用的,改变这个:

return df[all(df[name] == x for (x, name) in notall)]

到这里:

return df[all(df[name] == kwargs.get(name, '') for name in notall)]

(请注意,我们仍然需要继续使用get 来设置默认值。)

重新组合起来:

NAMES = ['a', 'b', 'c']
def view(**kwargs):
    notall = [name for name in NAMES if kwargs.get(name, '') != 'All']
    if not notall:
        return df
    else:
        return df[all(df[name] == kwargs.get(name, '') for name in notall)]

或者,如果参数名称与df的键相同:

def view(**kwargs):
    notall = [name for name in df if kwargs.get(name, '') != 'All']
    if not notall:
        return df
    else:
        return df[all(df[name] == kwargs.get(name, '') for name in notall)]

编辑:根据下面的 cmets,df 的值显然会覆盖 ==,因此它不会返回布尔值。幸运的是,正如上面提到的,这只需要改变它:

return df[all(df[name] == kwargs.get(name, '') for name in notall)]

到这里:

import functools
import operator

return functools.reduce(operator.and_, [df[name] == kwargs.get(name, '') for name in notall])

【讨论】:

  • df[all(compared)] 及其变体将只是 df[False]df[True]。这似乎不等同于 OP 的 df[(df['a']==a) & (df['b']==b) & (df['c']==c)]
  • df["a"] == a 将是一个布尔系列,并且将其中的几个按位与将得到一个系列,然后将其用作掩码。
  • @DSM:OP 在哪里说df["a"] == a 不是bool
  • 我对比较如何返回一个系列感到困惑。根据我的经验,它往往是 True(它们相等)或 False(它们不相等)。类似地,按位和任何 python 布尔值似乎都会产生一个 python 布尔值。
  • @jwodder:不好意思。您需要知道 OP 的问题 :-) 或将 df 识别为任意数据框的标准名称。 OP 在编写“[...] 一个带有通用索引和列 'a'、'b''c' 的简单 df 时确认它是一个数据帧。我将此函数用作脚本的一部分,以使用小部件。“a”、“b”、“c”是分类变量。”在 cmets 中。
【解决方案2】:

这应该可以解决问题:

import itertools, functools
from operator import eq, ne, and_

def view(*args):
    Eq, Ne = functools.partial(eq, 'All'), functools.partial(ne, 'All')

     if all(Eq(var) for var in args):
         return df 

    for cond, ret in itertools.product((Eq, Ne), len(args)):
        if all(fun(var) for var, fun in zip(args, cond)):
            index = functools.reduce(and_, (df[var] == var for var, fun in cond if fun == Ne))
            return df[index]

唯一的问题是我知道没有简单的方法可以知道您当前使用的变量的名称。这就是我使用df[var] == var 的原因。

例如,通过使每个变量都带有其名称,这相对容易解决。所以,基本上,每个变量都是一个元组a = (variable, "variable")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 2017-04-06
    • 2016-07-07
    • 1970-01-01
    • 2022-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多