pythonstructclass-factory

Class factory to produce simple struct-like classes?


While investigating Ruby I came across this to create a simple Struct-like class:

Person = Struct.new(:forname, :surname)
person1 = Person.new('John', 'Doe')
puts person1  #<struct Person forname="John", surname="Doe">

Which raised a few Python questions for me. I have written a [VERY] basic clone of this mechanism in Python:

def Struct(*args):
    class NewStruct:
        def __init__(self):
            for arg in args:
                self.__dict__[arg] = None

    return NewStruct

>>> Person = Struct('forename', 'surname')
>>> person1 = Person()
>>> person2 = Person()
>>> person1.forename, person1.surname = 'John','Doe'
>>> person2.forename, person2.surname = 'Foo','Bar'
>>> person1.forename
'John'
>>> person2.forename
'Foo'
  1. Is there already a similar mechanism in Python to handle this? (I usually just use dictionaries).

  2. How would I get the Struct() function to create the correct __init__() arguments. (in this case I would like to perform person1 = Person('John', 'Doe') Named Arguments if possible: person1 = Person(surname='Doe', forename='John')

I Would like, as a matter of interest, to have Question 2 answered even if there is a better Python mechanism to do this.


Solution

  • An update of ThomasH's variant:

    def Struct(*args, **kwargs):
        def init(self, *iargs, **ikwargs):
            for k,v in kwargs.items():
                setattr(self, k, v)
            for i in range(len(iargs)):
                setattr(self, args[i], iargs[i])
            for k,v in ikwargs.items():
                setattr(self, k, v)
    
        name = kwargs.pop("name", "MyStruct")
        kwargs.update(dict((k, None) for k in args))
        return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})
    

    This allows parameters (and named parameters) passed into __init__() (without any validation - seems crude):

    >>> Person = Struct('fname', 'age')
    >>> person1 = Person('Kevin', 25)
    >>> person2 = Person(age=42, fname='Terry')
    >>> person1.age += 10
    >>> person2.age -= 10
    >>> person1.fname, person1.age, person2.fname, person2.age
    ('Kevin', 35, 'Terry', 32)
    >>> 
    

    Update

    Having a look into how namedtuple() does this in collections.py. The class is created and expanded as a string and evaluated. Also has support for pickling and so on, etc.