Додайте до дикту лише якщо умова виконана


79

Я використовую urllib.urlencodeдля побудови веб-параметрів 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
})

Сподіваюсь, це було досить ясно, хтось знає, як це вирішити?


Якщо є прийнятне значення за замовчуванням, ви можете використовувати 'orange': orange if orange else defaultсинтаксис.
jpm

Відповіді:


72

Вам доведеться додати ключ окремо, після створення початкового dict:

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

Python не має синтаксису для визначення ключа як умовного; ви можете скористатись розумінням дикту, якщо у вас все було в послідовності:

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

але це не дуже читається.

Інший варіант - використовувати розпакування словника , але для одного ключа це не все так читабельніше:

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

Я особисто ніколи не використовував би це, це занадто хакі і майже не так явно та зрозуміло, як використання окремого ifвисловлювання. Як зазначає дзен Пітона : Читаність має значення.


1
Для Python 3.5 і новіших версій : з моменту реалізації PEP-0448 (запропоновано 29 червня 2013 р.), Stackoverflow.com/a/55341342/563970 має бути відповіддю
Барт,

1
@Bart: це дуже стильний вибір. Лише для однієї клавіші використання **({key: value} if test else {})насправді не є більш читабельним.
Мартін Пітерс

1
Звичайно, це стилістичний вибір, і для одного варіанту це може бути надмірним. Я використовував його для додавання {key: value}пар до вкладеного дикту, де і ключ, і значення були отримані (складені) з інших ключів та значень. Якщо це зробити, if something: ...це, безумовно, зменшить читабельність у моєму випадку (через вкладеність, яку потім доведеться застосовувати двічі або більше). YMMV тут від випадку до випадку.
Барт,

Коротка ілюстрація: У моєму сьогоднішньому випадку мій умовний ключ dict знаходиться посеред великої структури вкладених літералів dict і list (конвеєр агрегування mongoDB). ДЕЙСТВОРНО корисно мати умовне слово на місці дикту в структурі (хоча завтра я можу вирішити, що це занадто схоже на вразливість до ін’єкції!).
Дейв Грегорі

36

Щоб отримати відгук на відповідь 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

Дякую. Я був в змозі зрозуміти це , використовуючи це в conjuntion з цією відповіддю stackoverflow.com/a/2588648/2860243
Leonardo Ruiz

1
Новий метод оновлення може зробити це не самостійно. CPython обходить спеціальні методи при виконанні оновлення від дикту до дикту (який він визначає на основі структури пам'яті об'єкта). Можливо, вам доведеться уникати безпосереднього підкласу dict (ви можете встановити __class__dict замість того, щоб проходити перевірку обставин). Можливо, це не стосується цього випадку (я робив зворотне, трансформуючи ключі та значення при витягуванні, а не при введенні), але я залишаю цей коментар на всякий випадок, якщо це буде корисно
DylanYoung

Це працює для додавання нових значень. Вам потрібно перевизначити init та обробити фільтр вниз значення kwargs для None, якщо ви хочете, щоб конструктор також працював.
Олі,


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)

О, дуже акуратно. Ця відповідь мені найбільше подобається!
RCross

Погоджено, за винятком усієї тієї додаткової роботи, яку ви виконуєте, оновлюючи кілька разів у циклі: позбудьтесь циклу for і зробіть це:params.update({key: val for key, val in kwargs if val is not None})
DylanYoung

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}


1
Це акуратний фокус. Тоді у вас є тільки один ключ , щоб очистити в кінці: None. Я б запропонував лише виконати умову на ключі (якщо вас турбує значення, яке є там, просто додайте None: Noneяк останній рядок у декларації dict), а потім зробіть del a[None].
DylanYoung

1
Це найкраща відповідь. Просто додайте, a.pop(None)і це ідеально
raullalves

Це погана практика. Якщо мова не підтримує, краще не додавати зайвих операцій, передаючи це, (наприклад, a.pop, del a [None] та подібні дані).
Фернандо Мартінес

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)

3
еквівалентно,d = {k:v for k,v in d.items() if v}
Райан Артекона

8
Це також очистить значення, оцінені як False. Ви повинні зробити if dictparams[k] is Noneзамість цього.
PhilipGarnero

d = {k:v for k,v in d.items() if v is not None}, потім
CharlesB

3

Ще одна слушна відповідь полягає в тому, що ви можете створити власний контейнер, схожий на dict, який не зберігає значення None.

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'}

досить елегантно, я лише додав значення значення за замовчуванням def get (self, key, default_value = None): return self.container.get (key, default_value)
Cristóbal Felipe Fica Urzúa

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. Вміст "Немає значень" ніколи не буде існувати в дикті (на випадок, якщо ви забудете delпізніше)

Якщо вас не турбують ці речі, ви можете залишити це і обернути дел у try...except(або перевірити, чи є Noneключ перед введенням del). Щоб звернутися до номера 2, ви можете також поставити умовний чек на значення (на додаток до ключа).


0
fruits = [("apple", get_apple()), ("orange", get_orange()), ...]

params = urllib.urlencode({ fruit: val for fruit, val in fruits if val is not None })

Отже, нам потрібна а getterдля кожної змінної. Чому б не просто зробити:fruits={"apple", "orange"}; d=vars(); params = urllib.urlencode({ fruit: val for fruit, val in d.items() if fruit in fruits and val is not None })
DylanYoung

0

Існує протиінтуїтивний, але надійний хакер, щоб повторно використовувати інше ім’я реквізиту, яке ви хочете виключити.

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

У цьому випадку останнє "яблуко" замінить попереднє "яблуко", ефективно видаляючи його. Зверніть увагу, що умовні вирази повинні перевищувати реальні.


1
Я не буду пропонувати це, оскільки це залежить від порядку, в якому ви пишете ключі. Він схильний до помилок.
Nikhil Wagh

0

Ви можете мати справу з усіма додатковими елементами, використовуючи одну умову, використовуючи розуміння словника:

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

У цьому випадку dictparamsрезультат не міститиме "orange", оскільки orangeє None:

{'apple': 'green'}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.