5
\$\begingroup\$

I'm writing a struct class in Python and was wondering if this were a good way to write it:

class Struct:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            if isinstance(getattr(self, key), str):
                setattr(self, key, value)

    def create(*args):
        newStruct = type("Struct", (Struct,), {"__init__": Struct.__init__})
        for arg in args:
            setattr(newStruct, arg, str())
        return newStruct

and to initialize the object:

myStruct = Struct.create('x', 'y')
variable = myStruct(x=2, y=4)

Also, is it actually worth using?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ FWIW: github.com/lihaoyi/macropy uses a "case" macro which is similar to what you have. \$\endgroup\$ Commented Feb 13, 2016 at 8:50

1 Answer 1

4
\$\begingroup\$

This looks like a mutable version of namedtuple, with some oddities.

Following from your example (myStruct = Struct.create('x', 'y'); variable = myStruct(x=2, y=4)), I would expect variable.z to fail — and indeed it does raise an AttributeError: 'Struct' object has no attribute 'z'. I would also like to see variable.z = 5 fail with the same error, but it doesn't. So, if it doesn't enforce what members can be set, what's the point of this object? Why wouldn't I just use a regular dict?

The __init__ function looks like a constructor, such that I would be tempted to write myStruct = Struct('x', 'y') instead of myStruct = Struct.create('x', 'y')) — but it actually doesn't work that way. Also, I'd expect the constructor to work like namedtuple, accepting a typename, followed by field_names as either a list or a space-delimited string.

Defaulting values to an empty string is weird; I'd expect the default values to be None. You can initialize the dictionary using dict.fromkeys().

It would be nice to have __repr__() overridden, to make it easy to inspect the objects' contents for debugging.

Suggested implementation

def Struct(typename, field_names):
    class StructType:
        def __init__(self, **kwargs):
            for key, value in kwargs.items():
                setattr(self, key, value)

        def __setattr__(self, key, value):
            if hasattr(self, key):
                super().__setattr__(key, value)
            else:
                getattr(self, key) # Trigger AttributeError

        def __repr__(self):
            return repr(self.__dict__)

    return type(
        typename,
        (StructType,),
        dict.fromkeys(field_names.split() if isinstance(field_names, str)
                      else field_names)
    )

Sample run:

>>> myStruct = Struct('myStruct', 'x y')
>>> variable = myStruct(x=2)
>>> variable
{'x': 2}
>>> variable.y
>>> variable.y = 4
>>> variable.y
4
>>> variable.z = 5
Traceback (most recent call last):
  …
AttributeError: 'myStruct' object has no attribute 'z'
>>> variable.z
Traceback (most recent call last):
  …
AttributeError: 'myStruct' object has no attribute 'z'
\$\endgroup\$
2
  • \$\begingroup\$ Thanks! I had originally thought that z=5 would not work. \$\endgroup\$ Commented Feb 13, 2016 at 18:35
  • \$\begingroup\$ getattr(self, key) seems needlessly cryptic. How about raise AttributeError(key) instead. \$\endgroup\$ Commented Jun 2, 2016 at 2:01

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.