Ankündigung

Einklappen
Keine Ankündigung bisher.

Wofür Generatoren verwenden ?

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Wofür Generatoren verwenden ?

    Hi, habe mir mal das Thema Generatoren- Stichwort yield - angeguckt und versuche gerade herauszufinden, wofür ich die im wirklichen Entwicklerleben einsetzen könnte. Klingt ja erst mal gut, dass man speicherlastige Arrays ersetzen kann.

    Wenn ich aber die Beispiele angucke, wo über aufsteigende Integers iteriert wird frage ich mich schon,wo der echte Nutzen ist. Ich würde ja sowas gern bei der Auswertung von Datenbeständen verwenden.


    Also statt Abfrage => Result => Result in Array übertragen

    irgendwie sowas:

    PHP-Code:
    function get_rand_entries($result) {
                while (
    $row $result->fetch_assoc()) {
                        yield 
    $row['id'] => $row;
                    }
        }


        
    $mysqli = new mysqli('localhost',bla,'blie','generator_test');
        
    $sql "SELECT id,num, str FROM rand_entries";
            if (
    $result $mysqli->query($sql)) {
                
    $sum 0;
                    foreach (
    get_rand_entries($result) as $id => $row) {
                        
    $sum += $row['num'] / 100;
                    }
            } 
    Aber macht das Sinn? Ich meine, man könnte ja auch nur das Result durchlaufen, statt da einen Generator zu bemühen.

    Also, ich glaube, ich habe das noch nicht richtig verstanden und fände es nett, wenn ihr mal Anwendungsfälle nennen könnt, wo Generatoren wirklich sinnvoll sind?!


    [B]Es ist schon alles gesagt. Nur noch nicht von allen.[/B]

  • #2
    Ich nutze sie vor allem für speicherlastige und komplexe Aufgaben. Genau dafür sind sie ja auch da. Generatoren bringen aber noch den Nachteil mit sich, dass man die nur einmal durchlaufen kann, und dass man sie nicht nach ihrer Größe fragen kann. Ist aber auch klar, wenn man weiß, wie sie funktionieren.

    Hier ein paar Szenarien, die man vielleicht nicht sofort auf dem Schirm hat:
    • Generatoren unterbrechen die Sub-Routine. Wenn man Daten aus einer API ließt und dabei eine Paginierung benutzt, dann muss man dies nach außen nicht zeigen. Man kann auch nach dem ersten Frame sofort anfangen, die Daten zu verarbeiten. Tritt bei der Verarbeitung ein Fehler auf, merkt man dies nicht erst, nachdem man alle Daten aus der API abgerufen hat.
    • Analog zum ersten Punkt verbrauche ich prinzipiell immer nur so viel Arbeitsspeicher, wie er für ca. einen Frame gebraucht wird. Wenn ich also sehr viele Daten aus einer API anrufen will, dann läuft das sehr leichtgewichtig. Der Abruf von Kategoriedaten etlicher Portale lässt sich so sehr einfach lösen. yield from hilft dabei noch mal enorm.
    • Wenn ich in einer Sub-Routine gleichförmige Daten aus unterschiedlichen Quellen abrufen kann, dann kann ich das mit Generatoren mit weniger Code machen und kann wie oben beschrieben auch sofort anfangen, die Daten zu verarbeiten. Ein passables Beispiel dafür wären die Operationen ListOrders und ListOrdersByNextToken aus dem Amazon MWS.
    • Dadurch, dass Generatoren den Programmfluss unterbrechen (und dadurch, dass manche IO-Operationen auf Non-Blocking gestellt werden können), lässt sich Nebenläufigkeit erschleichen. https://github.com/amphp (und andere) machen da hauptsächlich Gebrauch von.
    • Mit Generatoren kann man sich u.U. viel Code für IteratorAggregate-Methoden sparen.

    Kommentar


    • #3
      Vielen Dank für die ausführliche Antwort. Der Aspekt, dass Generatoren Sub-Routinen unterbrechen, ist mir in einem englischen Erklärvideo schon mal begegnet. Allerdings habe ich noch nicht ganz verinnerlicht, was das jetzt genau bedeutet. Hättest Du evtl. ein Codebeispiel der Art "Hätte man vor Generatoren früher so gemacht und macht man mit Generatoren besser jetzt so" ?
      [B]Es ist schon alles gesagt. Nur noch nicht von allen.[/B]

      Kommentar


      • #4
        Ich weiss nun nicht ob es das trifft, aber das hat mir mal weitergehofen...
        https://www.php.de/forum/webentwickl...22#post1439322

        Kommentar


        • #5
          Zitat von drsoong Beitrag anzeigen
          Allerdings habe ich noch nicht ganz verinnerlicht, was das jetzt genau bedeutet. Hättest Du evtl. ein Codebeispiel der Art "Hätte man vor Generatoren früher so gemacht und macht man mit Generatoren besser jetzt so" ?
          Stell dir vor das der Body deiner Schleife das yield ersetzt.

          Code:
          function get_rand_entries($result) {
              while ($row = $result->fetch_assoc()) {
                  yield $row['id'] => $row;
              }
          }
          
          foreach (get_rand_entries($result) as $id => $row) {
              $sum += $row['num'] / 100;
          }
          wird so ausgeführt: (die Scopes bleiben natürlich erhalten)

          Code:
          function get_rand_entries($result) {
              while ($row = $result->fetch_assoc()) {
                  $row = [$row['id'] => $row]; //yield $row['id'] => $row;
          
                  $sum += $row['num'] / 100; //der body deiner schleife
              }
          }

          Kommentar


          • #6
            Für mich zum Verstehen hab ich mir mal folgende Zeilen notiert:
            PHP-Code:
            function generate(){
              echo 
            "generate yield 1<br>\n";
              yield 
            1;

              echo 
            "generate yield 2<br>\n";
              yield 
            2;
            }

            foreach(
            generate() as $value) {
              echo 
            "value aus forech:".$value."<br>\n";

            Ausgabe:
            Code:
            generate yield 1
            value aus forech:1
            generate yield 2
            value aus forech:2
            Zum selber probieren hier in der Sandbox.

            Kommentar


            • #7
              Hier mal ein Real-Life Beispiel:

              PHP-Code:
              class ApiReader {
                  
              /** @var OrderFactory */
                  
              private $factory;
                  
              /** @var Client */
                  
              private $client;
                  
              /** @var EndPoint */
                  
              private $endPoint;

                  public function 
              __construct(OrderFactory $factoryClient $clientEndPoint $endPoint) {
                      
              $this->factory $factory;
                      
              $this->client $client;
                      
              $this->endPoint $endPoint;
                  }

                  
              /**
                   * @param DateTimeInterface $startTime
                   * @param DateTimeInterface $endTime
                   * @return Order[]|Generator
                   */
                  
              public function readOrders(DateTimeInterface $startTimeDateTimeInterface $endTime): Generator {
                      
              $offset 0;
                      
              $limit 50;
                      do {
                          
              $result $this->client->get($this->endPoint->concat("/v2/orders"), ['st' => $startTime->format('c'), 'et' => $endTime->format('c'), 'offset' => $offset'limit' => $limit]);
                          
              $result json_decode($result);
                          
              $orders $result->orders ?? [];
                          foreach(
              $orders as $order) {
                              
              $offset++;
                              yield 
              $this->factory->create($order);
                          }
                      } while(
              $offset < ($result->orderCount ?? 0));
                  }

              Immer wenn eine Seite gelesen wird, werden bis zu 50 Ergbenisse als Result zurückgeliefert. Über diese 50 Ergebnisse wird drüber-iteriert und für jedes Ergebnis ein Objekt vom Typ Order zurückgeliefert. Wenn dieses Objekt in der aufrufenden Schicht verarbeitet wurde und das nächste Order-Objekt gezogen wird, dann kann das vorherige Objekt schon wieder vom Garbage-Collector weggeräumt werden.

              Kommentar

              Lädt...
              X