pythonclass

How can I pass a class type into a method and use it to create new objects of the class?


I have a situation where I have multiple csv files that I want to transform into various objects. So I have something like:

#What works
car = []
with open("car.csv") as csv_file:
    data = iter(csv.reader(csv_file))
    next(data) # Discard header
    for row in data:
        car.append(Car(row[0], row[1], row[2])

#What I want to do
car = load("car.csv", Car.__class__)

def load(file, cls):
    arr = []
    with open(file) as csv_file:
        data = iter(csv.reader(csv_file))
        next(data) # Discard header
        for row in data:
            arr.append(cls.__init__(row)) #Should make a new object
    return arr

So then I could use load with Car, Boat, Graph, etc. As long as I pass in an orderly list that can satisfy the init function.


Solution

  • The comments gave the answer:

    class Car:
        def __init__(self, arg1, arg2):
            pass # ...
    
    class Boat:
        def __init__(self, arg1, arg2, arg3):
            pass # ...
    
    def load_csv(filename: str, cls: type) -> list:
        arr = []
        with open(filename) as csv_file:
            data = iter(csv.reader(csv_file))
            next(data) # Discard header
            for row in data:
                arr.append(cls(*row))
        return arr
    
    cars = load_csv("car.csv", Car)
    

    Classes are first class objects that can be passed around, so just pass the class in question.

    To invoke it, just call the argument that received the class object as if it were the class. In my modification of your code above, I used *row instead of row to expand the row into one argument per item in the row (cls(*[arg1, arg2, arg3]) is equivalent to cls(arg1, arg2, arg3)) -- this way your classes accept arguments as usual, rather than accepting a single argument holding a list of the instantiation arguments.

    One other modification would be to use a CSV DictReader and pass the arguments as keyword arguments:

    class Car:
        def __init__(self, make, model):
            pass # ...
    
    def load_csv(filename: str, cls: type):
        arr = []
        with open(filename) as csv_file:
            reader = csv.DictReader(csv_file)
            for record in reader:
                arr.append(cls(**record))
        return arr
    
    cars = load_csv("car.csv", Car)
    

    DictReader returns each CSV row as a dictionary with keys taken from the header row. This then uses **record to translate the dictionary into keyword arguments for the invocation.

    Given car.csv as...

    "make","model"
    "Toyota","Prius"
    "Honda","Accord"
    "Ford","F150"
    

    This would return the array of 3 cars instantiated as Car(make="Toyota", model="Prius"), Car(make="Honda", model="Accord"), and Car(make="Ford", model="F150").