2

I've written a small function to formulate classes dynamically (or more specifically C-like structs, well, more or less) in Python that utilize slots. I was wondering if I did everything right. The classes can be instantiated, but I'm suspecting the slots are not being used correctly. Rather I have no idea if they indeed are used correctly.

def formulate(name, **data):
   def __init__(self, **data):
      self.__dict__.update(data)
   struct = type(name, (), data)
   struct.__init__ = __init__
   struct.__slots__ = data.keys()
   return struct

Thank you. Alisa.

Alisa D.
  • 134
  • 6

1 Answers1

3

Include the __slots__ and __init__ as keys in the dict passed to type:

def formulate(name, **data):
   def __init__(self):                 # 1
       for key, val in data.items():   
           setattr(self, key, val)     # 2
   struct = type(name, (), {'__slots__': tuple(data.keys()), '__init__': __init__})
   return struct

Bar = formulate('Bar', **dict(a=1, b=2))  # 3
bar = Bar()                               # 4
print('__dict__' in dir(bar))
# False
print('__slots__' in dir(bar))
# True
print(bar.__slots__)
# ['a', 'b']
print(bar.a)
# 1
  1. Note that I change the definition of __init__ from

    def __init__(self, **data):
    

    to

    def __init__(self):
    

    If you use the original version, then the data used to update the slot keys must be provided by the call to Bar:

    bar = Bar(**dict(a=1, b=2))
    

    That would be bad since it would require you to repeat the same dict twice -- once at #4 and also at #3.

    It seems more likely that you want #3 to control the initial values of the slot keys. In that case, do not list data as an argument to __init__. By omitting it, Python uses the LEGB rule to look up the value of data. It would find it in the enclosed scope of the formulate function. __init__ is then said to be a closure.

  2. Since a class with __slots__ does not have a __dict__ attribute by default, we can't/shouldn't use self.__dict__.update(data). Instead, set the slot keys with setattr.

Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • @eryksun: Thanks for the comments. I noticed if I left `__slots__` equal to `data.keys()`, then `bar.__slots__` ends up being `dict_keys(['b','a'])`. I don't know exactly how much space this requires, but I assumed it would require more space than `('a','b')`. Since the purpose of using `__slots__` is to save space, I thought `('a','b')` would be better. Is my thinking incorrect? Though maybe it really doesn't matter much since `__slots__` is attached to the class, not the instance? – unutbu Mar 13 '14 at 12:29
  • @erkysun: Please feel free to improve if you see it is still lacking something. – unutbu Mar 13 '14 at 13:12
  • Thank you, now I see where I went wrong. There's so much I still need to learn about Python. I'd like to add one thing though, I'd like to use the constructor, is there an alternative to your and mine versions of #1 that doesn't waste cycles, but still allows for the use of constructors? – Alisa D. Mar 13 '14 at 18:29
  • Um, I'm confused? Where are the wasted cycles? – unutbu Mar 13 '14 at 18:33
  • You said it yourself that data had to be walked twice in my (wrong) example. Let me reevaluate myself though. I might've misunderstood you. – Alisa D. Mar 13 '14 at 18:41
  • It's okay, I misunderstood you. It works now. Thanks. – Alisa D. Mar 13 '14 at 18:48
  • Oh good. I wasn't worried about wasted compute cycles as much as the annoyance of having to repeat oneself. – unutbu Mar 13 '14 at 18:51
  • Hmm, I might've misunderstood myself too by uncommenting my version instead of yours. Cannot use the constructors... which is only natural since __init__ only takes self. – Alisa D. Mar 13 '14 at 18:52
  • You can define `def __init__(self, **kwargs)`. Just don't call the `kwargs` parameter `data`, **if** you wish to access the `data` passed to `formulate`. – unutbu Mar 13 '14 at 18:57