2016-04-08 2 views
0

Zunächst verwende ich die Begriffe "Container" und "Sammlung" auf eine sehr allgemeine Art und Weise , nicht mit irgendeiner Python-Terminologie verbunden.Verwandeln Sie eine "Sammlung" (Liste, Set, einzelnes Objekt, ...) in eine neue Sammlung des gleichen Typs in Python

Ich habe die folgende Funktion in Python. Es nimmt eine Liste von IDs idlist und gibt eine Liste der Objekte von objs zurück, die diesen IDs entsprechen. Das funktioniert gut:

def findObj(idlist, objs): 
    return [next(o for o in objs if id == o.id) for id in idlist] 

Das Problem ist: idlist und der Rückgabewert sollte nicht unbedingt ein Python list sein. Ich würde wünschen, dass idlist könnte auch eine einfache ID sein, und die Funktion würde ein einzelnes Objekt zurückgeben. Oder idlist wäre ein Python set und die Funktion würde eine set von Objekten zurückgeben.

Wie kann ich erreichen, dass ich verschiedene "Container" -Typen (einschließlich einer einfachen ID) für idlist verwenden und den gleichen "Container" -Typ erhalten kann?

+0

Was Rückgabetyp (idlist) ([next (o für o in objs wenn id == o.id) für id in idlist))? –

+0

'type (idlist)' gibt Ihnen den Typ des Objekts, das an die Funktion übergeben wird. Nun müssen Sie diesen Typkonstruktor aufrufen ['t = type (idlist); t (input_arguments) '] und alle Eckfälle herausfinden (wie nicht Sequenz, aber Objekt etc.) –

Antwort

2

ich argumentieren, dass das, was Sie haben daran gedacht ist nicht eine gute API. Es ist einfacher, robuster und weniger fehleranfällig, dass die Funktion einen bestimmten Typ zurückgibt und der Benutzer die eventuelle Konvertierung übernimmt.

Insbesondere würde ich es vorziehen, die Funktion faul machen einen Generator mit:

def find_obj(ids, objs): 
    try: 
     for id in ids: 
      yield next(o for o in objs if o.id == id) 
    except TypeError: 
     # ids not iterable? assume it is a plain id 
     yield next(o for o in objs if o.id == ids) 

Jetzt kann ein Benutzer einfach tun:

list(find_obj(...)) 
set(find_obj(...)) 
next(find_obj(...)) # 1 element 

und erhalten die Sache, die er will.

Weitere Vorteile:

  • Explicit ist besser als implizite: hier die Typumwandlung ist explizit. Bildcode, wo die Anrufe von der Art find_obj(some_var_defined_elsewhere, objects) sind Nun, woher wissen Sie, welcher Typ zurückgegeben wird, wenn die Definition der Eingabe nicht in der Nähe ist?
  • können Sie einen Typ X als Eingabe übergeben und konvertieren Y ohne Verschwendung Zwischenraum und dabei eine unnötige Umwandlung
  • Keine besonderen Fällen erforderlich einzugeben.Der Anrufer kann eine Eingabe zur Verfügung stellen, die nicht die übliche Art und Weise folgt Behälter zu konstruieren (beachten Sie, dass es keine Standardmethode ist es, einen Behälter zu bauen)

Alternative, dass spezielle Fälle der einzige ID Fall:

def find_obj(ids, objs): 
    try: 
     return (next(o for o in objs if o.id == id) for id in ids) 
    except TypeError: 
     for o in objs: 
      if o.id == id: 
       return o 

Die oben gibt einen Generator, wenn eine Folge von ids gegeben und gibt ein einfaches Objekt (anstelle eines 1-Element-Generator), wenn in einem einzigen id weitergegeben.


Schließlich: meiste Zeit (aber nicht immer) Sequenzen einen Konstruktor, der eine iterable und baut den Behälter mit diesen Elementen annimmt. Dies bedeutet, dass:

type(some_iterable)(something for el in some_iterable) 

wird, einen Behälter der gleichen Art, wie some_iterable erzeugen.

Beachten Sie, dass einige Klassen ein list anstelle eines generischen iterable erfordern, so dass Sie type(some_iterable)([<as-before>]) und andere Behälter tun verwenden müssten nicht haben so einen Konstruktor. In diesem letzten Fall konnte nur der Aufrufer die Konvertierung durchführen. Die erste Lösung geht das ohne Sonderfälle gut an.

Sie könnten mehr die Funktion verallgemeinern, indem man einen Parameter Hinzufügen der Umwandlung auszuführen:

def find_obj(ids, objs, converter=None): 
    if converter is None: 
     converter = type(ids) 

    try: 
     return converter(next(o for o in objs if o.id == id) for id in ids) 
    except TypeError: 
     return next(o for o in objs if o.id == ids) 

auf diese Weise der Anrufer die Konvertierung anpassen kann, wenn er mit fremden Arten zu tun ist.


Ein zusätzlicher Hinweis: in Python wir „Duck Typing“ verwenden, das heißt verwenden nur das Objekt, als ob es den richtigen Typ war und wenn es eine Ausnahme Rückfall wirft andere Sachen zu tun. In einigen Fällen ist es einfacher, zunächst bestimmter Operationen für die Unterstützung zu überprüfen, dass Fälle, die Sie isinstance mit den abstrakten Basisklassen in collections.abc gefunden verwenden könnten, um zu sehen, ob ein Objekt Iterable ist, ein Sequence, ein Mapping usw.

1

Sie Ihren Code ändern:

import collections 



def findObj(idlist, objs): 
    if isinstance(idlist, collections.Iterable): 
     return type(idlist)([next(o for o in objs if id == o.id) for id in idlist]) 
    else: 
     #cover case when idlist is just plain object 
     pass 
1

dies Ihre Ebene des Objekts id übergeben (für idliist), wenn funktionieren sollte als int (nicht String)

def findObj(idlist, objs): 
    t = type(idlist) 
    try: 
     iter(idlist) 
     return t([next(o for o in objs if id == o.id) for id in idlist]) 
    except TypeError, te: #not iterator. So single id and object 
     return idlist if idlist == objs.id else False 
+0

Ist es eine gute Idee, Ausnahmen für etwas zu verwenden, das eigentlich keine" Ausnahme "ist? Ich weiß nicht, was die Python-Gemeinde darüber denkt. – Michael

+1

https://docs.python.org/2/glossary.html#term-eafp – LearnerEarner

Verwandte Themen