Seite 1 von 1

Problem bei dynamischer "dict"-Generierung

Verfasst: Mittwoch 1. Juni 2016, 09:40
von Blackbenji
Hallo zusammen,

ich bräuchte eure Unterstützung bei einem Problem das ich habe.
Hier geht es um ein Codeschnipsel der aus einem "Gesamtwerk".

Hintergrund:
Ich möchte mit einem Script diverse URLs abfragen ob diese Erreichbar sind und mir Werte aus dem Output der URLs zusammen sammeln. Mittels Threading erfolgt das gleichzeitig. Wir sprechen hier von ca. 300 URLs / Rückgabewerten.
Dieser Teil funktioniert bereits.

Die URLs Teilen sich auf in unterschiedliche Umgebungen. Dabei gibt es Anwendungen die in allen, aber auch nur in teilweise dieser Umgebungen auftauchen.
Je nach Umgebungen kommt es also zu unterschiedlichen Antwortzeiten und auch unterschiedlichen Ergebnissen.


Mein Problem besteht nun die Werte sortierfähig zurück zu Ordnen.
Dazu habe ich mir eine Funktion geschrieben die das "in etwa" so hinbekommt wie ich das möchte:

Code: Alles auswählen

arr = {}
temp = {}


def add(env, app, value):
    if not temp:
        temp.setdefault(app, [])
    else:
        for key in temp.keys():
            if key is not app:
                temp.setdefault(app, [])

    temp[app].append(value)

    if not arr:
        arr.setdefault(env, [])
    else:
        for key in arr.keys():
            if key is not env:
                arr.setdefault(env, [])

    if arr[env]:
        print "hier"
        if app in arr[env][0]:
            del arr[env][0][app]
            arr[env][0].append({app: temp[app]})
    else:
        print "nein hier"
        arr[env].append({app: temp[app]})
Aufgerufen wird diese dann per:

Code: Alles auswählen

add("live", "app1", {"clone": "0", "status": "up", "version": "1.35"})
add("prelive", "app2", {"clone": "1", "status": "down", "version": "1.22"})
add("tui", "app3", {"clone": "1", "status": "failed", "version": "1.22"})
Der Output sieht dann wie folgt aus:

Code: Alles auswählen

{
  'tui': [
    {
      'app3': [
        {
          'status': 'failed',
          'clone': '1',
          'version': '1.22'
        }
      ]
    }
  ],
  'live': [
    {
      'app1': [
        {
          'status': 'up',
          'clone': '0',
          'version': '1.35'
        }
      ]
    }
  ],
  'prelive': [
    {
      'app2': [
        {
          'status': 'down',
          'clone': '1',
          'version': '1.22'
        }
      ]
    }
  ]
}
Soweit so gut.
Das Problem ist nun, das ich über "temp" stolpere.
Jede App hat mehrere Clone, also muss ich das irgendwie zwischenspeichern.

Aber: sobald die gleiche "app" in einer anderen Umgebungen vorkommt, wird für jede Umgebung/App aus allen Zusammengefasst und bei allen gespeichert.

Das sieht dann so aus (Aufruf):

Code: Alles auswählen

add("live", "app1", {"clone": "0", "status": "up", "version": "1.35"})
add("live", "app1", {"clone": "1", "status": "up", "version": "1.35"})
add("prelive", "app1", {"clone": "1", "status": "down", "version": "1.22"})
add("tui", "app3", {"clone": "1", "status": "failed", "version": "1.22"})
Output:

Code: Alles auswählen

{
  'tui': [
    {
      'app3': [
        {
          'status': 'failed',
          'clone': '1',
          'version': '1.22'
        }
      ]
    }
  ],
  'live': [
    {},
    {
      'app1': [
        {
          'status': 'up',
          'clone': '0',
          'version': '1.35'
        },
        {
          'status': 'up',
          'clone': '1',
          'version': '1.35'
        },
        {
          'status': 'down',
          'clone': '1',
          'version': '1.22'
        }
      ]
    }
  ],
  'prelive': [
    {
      'app1': [
        {
          'status': 'up',
          'clone': '0',
          'version': '1.35'
        },
        {
          'status': 'up',
          'clone': '1',
          'version': '1.35'
        },
        {
          'status': 'down',
          'clone': '1',
          'version': '1.22'
        }
      ]
    }
  ]
}
Ich hoffe das war verständlich ;o)
Hat hier jemand einen Tipp für mich?

Viele Grüße,
Blackbenji

Re: Problem bei dynamischer "dict"-Generierung

Verfasst: Mittwoch 1. Juni 2016, 10:58
von BlackJack
@Blackbenji: Also erstmal macht die Funktion eine ganze Menge mit verschachtelten Datenstrukturen, inklusive Werte auslesen und aufgrund der Werte bedingt etwas anderes mit den Strukturen anstellen. Das Nebenläufig aufzurufen ist mit an Sicherheit grenzender Wahrscheinlichkeit kaputt, solange der Aufruf nicht nicht von aussen durch eine Sperre gesichert ist, damit das immer nur ein Thread zur gleichen Zeit ausführen kann.

Das die Funktion globale Datenstrukturen verändert ist auch äusserst unschön. So etwas macht man nicht wenn einem die geistige Gesundheit was Wert ist. ;-) `arr` ist auch kein guter Name. `temp` eigentlich auch nicht.

`setdefault()` wird seit `collections.defaultdict` eigentlich kaum noch gebraucht.

Willst Du hier wirklich ``is not`` verwenden? Das prüft nicht auf gleichheit sondern auf Objektidentität! Ist relativ selten das was man will wenn man nicht mit Singletons arbeitet. Gleiche Zeichenketten müssen nicht identisch sein!

Code: Alles auswählen

In [32]: a is b
Out[32]: False

In [33]: a == b
Out[33]: True

In [34]: a
Out[34]: 'Peter Parker'

In [35]: b
Out[35]: 'Peter Parker'
Zumindest bei mir hat sich Deine Hoffnung nicht bestätigt. Ich hab's nicht verstanden. Ist das letzte Beispiel das was ausgegeben wird oder das was Du haben willst? Falls letzteres: Zeigst Du den Code der nicht wir gewünscht funktioniert ohne zu erklären *was* Du eigentlich gerne hättest? Erklär das was da gemacht werden soll doch mal in Worten, mit Eingabebeispielen und Ausgabebeispielen, was Du gerne als Ergebnis hättest. So das jemand das Problem versteht ohne es bereits zu kennen. Was ist eine Umgebung, was ist eine App, was bedeuten die Werte in `value`, und nach welchen Kriterien soll am Ende was wohin einsortiert werden in der Ergebnisdatenstruktur?

Re: Problem bei dynamischer "dict"-Generierung

Verfasst: Mittwoch 1. Juni 2016, 12:01
von snafu
Bitte mal mehr Info zum Hintergrund: Wieso werden innerhalb der Dictionaries nochmal Listen verwendet? Welchen Mehrwert soll das hier haben? Ist dies durch ein externes Format vorgeschrieben?

Ansonsten schlage ich nämlich vor, die inneren `dict`s direkt als Schlüssel des äußeren `dict`s zu erstellen. Dann wird der Code auch deutlich einfacher (mal abgesehen davon, dass man ihn auch in der jetzigen Form schon vereinfachen könnte).

EDIT:
Ich beziehe mich auf die `dict`s mit den App-Bezeichnungen. Warum die Stufe darunter in Listen landen soll, ist für mich klar.

Re: Problem bei dynamischer "dict"-Generierung

Verfasst: Mittwoch 1. Juni 2016, 12:29
von snafu
Falls auf der mittleren Stufe die Listen wegfallen können, dann wird `add()` zum Einzeiler:

Code: Alles auswählen

from collections import defaultdict
from pprint import pprint

DATA = defaultdict(lambda: defaultdict(list))

def add(env, app, value):
    DATA[env][app].append(value.copy())

def main():
    add("live", "app1", {"clone": "0", "status": "up", "version": "1.35"})
    add("prelive", "app2", {"clone": "1", "status": "down", "version": "1.22"})
    add("tui", "app3", {"clone": "1", "status": "failed", "version": "1.22"})
    pprint(DATA)

if __name__ == '__main__':
    main()
Diesen Teil verstehe ich nicht:
Aber: sobald die gleiche "app" in einer anderen Umgebungen vorkommt, wird für jede Umgebung/App aus allen Zusammengefasst und bei allen gespeichert.
So ins Blaue geraten vermute ich ein Referenzierungsproblem. Ich habe daher zur Sicherheit oben im Code `value.copy()` anstelle von `value` verwendet. Mag sein, dass das letztlich überflüssig ist.

Re: Problem bei dynamischer "dict"-Generierung

Verfasst: Mittwoch 1. Juni 2016, 14:39
von Blackbenji
@Blackjack

sorry, ich komme eigentlich aus der PHP Ecke und bin es gewohnt mit Multi-Arrays / JSON zu arbeiten.
Meine Python Skills bewegen sich irgendwo zwischen "blutiger Anfänger" und "ich google mir alles zusammen".
Ich werde mich aber immer weiter mit Python beschäftigen müssen und werde daher eure Tipps befolgen :)

@snafu:

danke, dein Einzeiler beseitigt meine Probleme und macht genau das was ich wollte.
Hintergrund warum ich so speichere: das dict wird später formatiert und per email verschickt.
es ist also "web-crawler" der bei Änderungen per Email informieren soll. ein monitoring script.

Es soll dann so aussehen:
- Umgebung
-- Anwendung
--- Anwendungs-clone + status + versionsnummer

Mit einem Beispiel:
- Live
-- APP1
--- Clone: 0, Versionsnummer: 1.55
--- Clone: 1, Versionsnummer: 1.55
- Prelive
-- APP1
--- Clone: 0, Versionsnummer: 1.56
--- Clone: 1, Versionsnummer: 1.56

Deine Änderung macht nun genau das und ich kann weiter arbeiten!

Vielen lieben Dank dafür!

Re: Problem bei dynamischer "dict"-Generierung

Verfasst: Donnerstag 2. Juni 2016, 00:02
von snafu
Falls du ernsthaft die Lösung nachvollziehen willst: Das `defaultdict()` ruft immer den Konstruktor* auf, der ihm übergeben wurde (um präziser zu sein: das übergebene Objekt wird ohne Argumente aufgerufen und ist sinnvollerweise eine Funktion/Methode oder eine Klasse), sobald ein bisher nicht existierender Schlüssel neu angelegt wird. Das `lambda` steht für eine anonyme Funktion und ist in etwa vergleichbar mit `function()` aus JavaScript. Im Klartext: Das obere `defaultdict` erstellt bei einem nicht existierenden Schlüssel ein weiteres `defaultdict`, welches seinerseits eine leere Liste bei einem nicht existierenden Schlüssel (auf der zweiten Ebene) erstellt, wenn das erste Mal auf einen Schlüssel zugegriffen wird.

* Es ist nich wirklich ein Konstruktor. Python unterscheidet zwischen dem Erstellen eines Objektes (`__new__()`) und der Initialisieriung eines Objektes (`__init__()`). Meistens meint man letzeres, aber der Kontruktor ist eigentlich `__new__()`.

Re: Problem bei dynamischer "dict"-Generierung

Verfasst: Donnerstag 2. Juni 2016, 07:05
von Sirius3
@Blackbenji: jetzt solltest Du nur noch die globale Variable loswerden:

Code: Alles auswählen

from collections import defaultdict
from pprint import pprint
 
def add(data, env, app, value):
    data[env][app].append(value.copy())
 
def main():
    data = defaultdict(lambda: defaultdict(list)) 
    add(data, "live", "app1", {"clone": "0", "status": "up", "version": "1.35"})
    add(data, "prelive", "app2", {"clone": "1", "status": "down", "version": "1.22"})
    add(data, "tui", "app3", {"clone": "1", "status": "failed", "version": "1.22"})
    pprint(data)
 
if __name__ == '__main__':
    main()