Fügen Sie einem Diktat nur hinzu, wenn eine Bedingung erfüllt ist

75

Ich verwende urllib.urlencodezum Erstellen von Web-POST-Parametern, es gibt jedoch einige Werte, die nur hinzugefügt werden sollen, wenn ein anderer Wert als Noneder für sie vorhanden ist.

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

Das funktioniert gut, aber wenn ich die orangeVariable optional mache , wie kann ich verhindern, dass sie zu den Parametern hinzugefügt wird? So etwas (Pseudocode):

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

Ich hoffe das war klar genug, weiß jemand wie man das löst?

user1814016
quelle
Wenn es einen akzeptablen Standardwert gibt, können Sie die 'orange': orange if orange else defaultSyntax verwenden.
jpm

Antworten:

68

Sie müssen den Schlüssel nach dem Erstellen der Initiale separat hinzufügen dict:

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

Python hat keine Syntax, um einen Schlüssel als bedingt zu definieren. Sie könnten ein Diktatverständnis verwenden, wenn Sie bereits alles in einer Sequenz hätten:

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

aber das ist nicht sehr lesbar.

Eine andere Möglichkeit ist das Entpacken des Wörterbuchs , aber für einen einzelnen Schlüssel ist das nicht viel besser lesbar:

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

Ich persönlich würde dies niemals verwenden, es ist zu hackig und bei weitem nicht so explizit und klar wie die Verwendung einer separaten ifAussage. Wie das Zen von Python sagt: Lesbarkeit zählt.

Martijn Pieters
quelle
1
Für Python 3.5 und höher : Da PEP-0448 implementiert wurde (vorgeschlagen am 29. Juni 2013), sollte stackoverflow.com/a/55341342/563970 die Antwort sein
Bart
@ Bart: Das ist eine sehr stilistische Wahl. Für nur einen Schlüssel ist die Verwendung **({key: value} if test else {})wirklich nicht besser lesbar.
Martijn Pieters
1
Sicher, es ist eine stilistische Wahl, und für eine einzelne Option kann es übertrieben sein. Ich habe es verwendet, um {key: value}einem verschachtelten Diktat Paare hinzuzufügen , bei denen sowohl Schlüssel als auch Wert von anderen Schlüsseln und Werten abgeleitet (zusammengesetzt) ​​wurden. Dies if something: ...würde in meinem Fall definitiv die Lesbarkeit beeinträchtigen (aufgrund der Verschachtelung, die dann zweimal oder öfter angewendet werden müsste). YMMV hier von Fall zu Fall.
Bart
Kurze Illustration: In meinem heutigen Fall befindet sich mein bedingter Diktatschlüssel genau in der Mitte einer großen Struktur verschachtelter Diktat- und Listenliterale (eine MongoDB-Aggregationspipeline). Es ist WIRKLICH hilfreich, die Bedingung an der Stelle des Diktats innerhalb der Struktur zu haben (obwohl ich morgen vielleicht entscheiden könnte, dass es zu sehr nach einer Injektionsanfälligkeit aussieht!).
Dave Gregory
35

Um auf die Antwort von sqreept zurückzugreifen dict, verhält sich eine Unterklasse davon wie gewünscht:

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

Auf diese Weise kann Werte von vorhandenen Schlüsseln werden geändert zu None, aber die Zuordnung Nonezu einem Schlüssel, der nicht existiert ein no-op ist. Wenn Sie Einstellung ein Element wollte entfernen sie aus dem Wörterbuch , wenn es bereits vorhanden ist , können Sie dies tun:None

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

Werte von None können eintreten, wenn Sie sie während des Baus weitergeben. Wenn Sie dies vermeiden möchten, fügen Sie eine __init__Methode hinzu, um sie herauszufiltern:

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

Man könnte es auch generische machen , indem es zu schreiben , so dass Sie kann in passiert den gewünschten Zustand , wenn das Wörterbuch zu erstellen:

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
irgendwie
quelle
Vielen Dank. Ich konnte es in Verbindung mit dieser Antwort herausfinden. Stackoverflow.com/a/2588648/2860243
Leonardo Ruiz
1
Eine neue Aktualisierungsmethode kann dies möglicherweise nicht alleine tun. CPython umgeht spezielle Methoden, wenn ein Diktat-zu-Diktat-Update durchgeführt wird (das anhand der Speicherstruktur des Objekts ermittelt wird). Möglicherweise müssen Sie vermeiden, dikt direkt zu unterklassifizieren (Sie können __class__diktieren, anstatt isinstance-Prüfungen zu bestehen). Es ist möglich, dass dies in diesem Fall nicht zutrifft (ich habe die Umkehrung durchgeführt und Schlüssel und Werte beim Extrahieren und nicht bei der Eingabe transformiert), aber ich hinterlasse diesen Kommentar nur für den Fall, dass es hilfreich ist
DylanYoung
Dies funktioniert zum Hinzufügen neuer Werte. Sie müssen die Kwargs-Werte für Init und Process Filter Down für None ebenfalls überschreiben, wenn der Konstruktor auch funktionieren soll.
Oli
19
params = urllib.urlencode({
    'apple': apple,
    **({'orange': orange} if orange else {}),
})
Данила Фомин
quelle
1
Können Sie bitte erklären, wie dieser Code tatsächlich funktioniert?
Pascal
17

Ziemlich alte Frage, aber hier ist eine Alternative, die die Tatsache nutzt, dass das Aktualisieren eines Diktats mit einem leeren Diktat nichts bewirkt.

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)
lindsay.stevens.au
quelle
Oh, sehr ordentlich. Ich mag diese Antwort am besten!
RCross
Einverstanden, abgesehen von all der zusätzlichen Arbeit, die Sie durch mehrmaliges Aktualisieren in einer Schleife erledigen: params.update({key: val for key, val in kwargs if val is not None})
Entfernen Sie
6

Ich tat dies. Ich hoffe das hilft.

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

Erwartete Ausgabe : {'orange': 10, 'apple': 23}

Wenn ja orange = None, dann gibt es einen einzigen Eintrag für None:None. Betrachten Sie zum Beispiel Folgendes:

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

Erwartete Ausgabe : {None: None, 'apple': 23}

Nikhil Wagh
quelle
1
Das ist ein ordentlicher Trick. Dann müssen Sie am Ende nur noch einen Schlüssel löschen : None. Ich würde vorschlagen, nur die Bedingung für den Schlüssel auszuführen (wenn Sie sich Sorgen machen, dass der Wert vorhanden ist, fügen Sie ihn einfach None: Noneals letzte Zeile in der Diktatdeklaration hinzu) und anschließend del a[None].
DylanYoung
1
Dies ist die beste Antwort. Einfach hinzufügen a.pop(None)und es ist perfekt
Raullalves
Dies ist eine schlechte Praxis. Wenn die Sprache dies nicht unterstützt, sollten Sie keine zusätzlichen Operationen hinzufügen, um dies zu umgehen (wie a.pop, del a [None] und Ähnliches).
Fernando Martínez
3

Sie können Keine nach der Zuweisung löschen:

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)
sqreept
quelle
3
gleichwertig,d = {k:v for k,v in d.items() if v}
Ryan Artecona
8
Dadurch werden auch Werte gelöscht, die mit "Falsch" bewertet wurden. Sie sollten if dictparams[k] is Nonestattdessen tun .
Philip Garnero
d = {k:v for k,v in d.items() if v is not None}, dann
CharlesB
3

Eine andere gültige Antwort ist, dass Sie einen eigenen diktartigen Container erstellen können, in dem keine None-Werte gespeichert sind.

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

Ausbeuten:

{'orange': 'orange'}
sqreept
quelle
ziemlich elegant, ich habe nur einen Standard-Get-Wert hinzugefügt def get (self, key, default_value = None): return self.container.get (key, default_value)
Cristóbal Felipe Fica Urzúa
1

Ich mag den tollen Trick in der Antwort hier wirklich: https://stackoverflow.com/a/50311983/3124256

Aber es hat einige Fallstricke:

  1. Doppelte ifTests (wiederholt für Schlüssel und Wert)
  2. Lästiger None: NoneEintrag im Ergebnisdict

Um dies zu vermeiden, können Sie Folgendes tun:

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]

Erwartete Ausgabe : {'apple': 23}

Hinweis: Der None: NoneEintrag stellt zwei Dinge sicher:

  1. Der NoneSchlüssel ist immer vorhanden ( delwirft keinen Fehler)
  2. Der Inhalt von 'Keine Werte' wird im Diktat niemals existieren (falls Sie es delspäter vergessen )

Wenn Sie sich über diese Dinge keine Sorgen machen, können Sie sie weglassen und das Del in ein einwickeln try...except(oder prüfen, ob der NoneSchlüssel vorhanden ist, bevor Sie delloslegen). Um alternativ Nummer 2 zu adressieren, können Sie den Wert auch zusätzlich zum Schlüssel bedingt prüfen.

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

params = urllib.urlencode({ fruit: val for fruit, val in fruits if val is not None })
XORcist
quelle
Wir brauchen also eine getterfür jede Variable. Warum nicht einfach tun: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

Es gibt einen kontraintuitiven, aber zuverlässigen Hack, um den anderen Requisitennamen, den Sie ausschließen möchten, wiederzuverwenden.

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

In diesem Fall überschreibt der letztere "Apfel" den vorherigen "Apfel" und entfernt ihn effektiv. Beachten Sie, dass die bedingten Ausdrücke über die realen hinausgehen sollten.

Ilya Kharlamov
quelle
1
Ich werde dies nicht vorschlagen, da es von der Reihenfolge abhängt, in der Sie die Schlüssel schreiben. Es ist anfällig für Fehler.
Nikhil Wagh