109

我正在使用urllib.urlencode构建 web POST 参数,但是有一些值我只想在None它们存在以外的值时添加。

apple = 'green'
orange = 'orange'
params = urllib.urlencode({
    'apple': apple,
    'orange': orange
})

这很好,但是如果我将orange变量设为可选,如何防止它被添加到参数中?像这样的东西(伪代码):

apple = 'green'
orange = None
params = urllib.urlencode({
    'apple': apple,
    if orange: 'orange': orange
})

我希望这已经足够清楚了,有人知道如何解决这个问题吗?

4

12 回答 12

109

在创建初始值之后,您必须单独添加密钥dict

params = {'apple': apple}
if orange is not None:
    params['orange'] = orange
params = urllib.urlencode(params)

Python 没有将键定义为条件的语法;如果您已经按顺序排列了所有内容,则可以使用 dict 理解:

params = urllib.urlencode({k: v for k, v in (('orange', orange), ('apple', apple)) if v is not None})

但这不是很可读。

如果您使用的是 Python 3.9 或更高版本,则可以使用新的 dict 合并运算符支持和条件表达式:

params = urllib.urlencode(
    {'apple': apple} | 
    ({'orange': orange} if orange is not None else {})
)

但我发现可读性受到影响,因此可能仍会使用单独的if表达式:

params = {'apple': apple}
if orange is not None:
    params |= {'orange': orange}
params = urllib.urlencode(params)

另一种选择是使用字典 unpacking,但对于一个不是那么可读的单个键:

params = urllib.urlencode({
    'apple': apple,
    **({'orange': orange} if orange is not None else {})
})

我个人永远不会使用它,它太 hacky 并且不像使用单独的语句那样明确和清晰。if正如Python 之禅所说:可读性很重要。

于 2013-01-10T17:36:16.217 回答
39

为了搭载 sqreept 的答案,这里有一个子类,dict其行为符合要求:

class DictNoNone(dict):
    def __setitem__(self, key, value):
        if key in self or value is not None:
            dict.__setitem__(self, key, value)


d = DictNoNone()
d["foo"] = None
assert "foo" not in d

这将允许将现有键的值更改None,但分配None给不存在的键是无操作的。如果您想将一个项目设置为从字典None删除它(如果它已经存在),您可以这样做:

def __setitem__(self, key, value):
    if value is None:
        if key in self:
            del self[key]
    else:
        dict.__setitem__(self, key, value)

如果你在构建过程中传递它们的值,它们None 就可以进入。如果你想避免这种情况,添加一个__init__方法来过滤掉它们:

def __init__(self, iterable=(), **kwargs):
    for k, v in iterable:
        if v is not None: self[k] = v
    for k, v in kwargs.iteritems():
        if v is not None: self[k] = v

您也可以通过编写它来使其通用,这样您就可以在创建字典时传入所需的条件:

class DictConditional(dict):
    def __init__(self, cond=lambda x: x is not None):
        self.cond = cond
    def __setitem__(self, key, value):
        if key in self or self.cond(value):
            dict.__setitem__(self, key, value)

d = DictConditional(lambda x: x != 0)
d["foo"] = 0   # should not create key
assert "foo" not in d
于 2013-01-10T17:51:53.507 回答
17

很老的问题,但这里有一个替代方法,即用空字典更新字典什么都不做。

def urlencode_func(apple, orange=None):
    kwargs = locals().items()
    params = dict()
    for key, value in kwargs:
        params.update({} if value is None else {key: value})
    return urllib.urlencode(params)
于 2015-04-23T07:23:43.753 回答
6

我这样做了。希望这有帮助。

apple = 23
orange = 10
a = {
    'apple' : apple,
    'orange' if orange else None : orange
}

预期输出:{'orange': 10, 'apple': 23}

虽然,如果orange = None,那么将有一个条目None:None。例如考虑这个:

apple = 23
orange = None
a = {
    'apple' : apple,
    'orange' if orange else None : orange
}

预期输出:{None: None, 'apple': 23}

于 2018-05-13T01:30:45.477 回答
4

我建议的一种技术是为此使用字典解包操作符

apple = 'green'
orange = None
params = urllib.urlencode({
    'apple': apple,
    **({ 'orange': orange } if orange else {})
})

解释

基本上,如果orangeNone,那么上面的字典简化为

{
    'apple': apple,
    **({})
}

# which results in just
{
    'apple': apple,
} 

相反,如果orange不是None

{
    'apple': apple,
    **({ "orange": orange })
}

# which results in just
{
    'apple': apple,
    'orange': orange
} 

可读性是有条件地添加内联键的缺点。可以创建一个函数来帮助调解可读性问题。

from typing import Callable

def cond_pairs(
        cond: bool, pairs: Callable[[], dict],
) -> dict:
    return pairs() if cond else {}

{
    'apple': apple,
    **cond_pairs(orange, lambda: { 'orange': orange })
}
于 2021-01-16T05:53:20.430 回答
3

您可以在分配后清除 None:

apple = 'green'
orange = None
dictparams = {
    'apple': apple,
    'orange': orange
}
for k in dictparams.keys():
    if not dictparams[k]:
        del dictparams[k]
params = urllib.urlencode(dictparams)
于 2013-01-10T17:41:15.380 回答
3

另一个有效的答案是,您可以创建自己的不存储 None 值的类似 dict 的容器。

class MyDict:
    def __init__(self):
        self.container = {}
    def __getitem__(self, key):
        return self.container[key]
    def __setitem__(self, key, value):
        if value != None:
            self.container[key] = value
    def __repr__(self):
        return self.container.__repr__()

a = MyDict()
a['orange'] = 'orange';
a['lemon'] = None

print a

产量:

{'orange': 'orange'}
于 2013-01-10T17:45:27.587 回答
3

我发现使用生成器函数更容易理解,并且足够灵活。它也适用于 Python 2 和 3。

def generate_request_items(apple, orange):
    yield "apple", apple
    if orange:
        yield "orange", orange
    # Add additional conditionals and yield statements here


apple = 'green'
orange = None
params = urllib.urlencode(dict(generate_request_items(apple, orange)))
于 2021-01-22T14:25:40.387 回答
1

我真的很喜欢这里答案中的巧妙技巧:https ://stackoverflow.com/a/50311983/3124256

但是,它有一些陷阱:

  1. 重复if测试(对键和值重复)
  2. 结果中令人讨厌None: None的条目dict

为避免这种情况,您可以执行以下操作:

apple = 23
orange = None
banana = None
a = {
    'apple' if apple else None: apple,
    'orange' if orange else None : orange,
    'banana' if banana else None: banana,
    None: None,
}
del a[None]

预期输出:{'apple': 23}

注意:该None: None条目确保了两件事:

  1. 密钥将None始终存在(del不会抛出错误)
  2. 'None values' 的内容永远不会存在于字典中(以防你del之后忘记)

如果您不担心这些事情,您可以将其排除在外并将 del 包装在 a 中try...except(或在ingNone之前检查密钥是否存在)。del要解决第 2 个问题,您还可以对值(除了键)进行条件检查。

于 2019-03-01T20:02:42.963 回答
0
fruits = [("apple", get_apple()), ("orange", get_orange()), ...]

params = urllib.urlencode({ fruit: val for fruit, val in fruits if val is not None })
于 2013-01-10T17:44:15.553 回答
0

有一个违反直觉但可靠的技巧,可以重用您想要排除它的其他道具名称。

{
    'orange' if orange else 'apple': orange,
    'apple': apple,
}

在这种情况下,后一个“苹果”将覆盖前一个“苹果”,有效地将其删除。请注意,条件表达式应高于真实表达式。

于 2019-12-11T14:08:29.483 回答
0

您可以使用字典推导使用单个条件处理所有可选项目:

apple = "red"
orange = None
dictparams = {
    key: value for key, value in
    {
        "apple": apple,
        "orange": orange
    }.items()
    if value is not None
}

在这种情况下,dictparams结果将不包含"orange",因为orangeNone

{'apple': 'green'}
于 2020-11-24T14:26:40.650 回答