tags:

views:

171

answers:

7

Hey, I just started wondering about this as I came upon a code that expected an object with a certain set of attributes (but with no specification of what type this object should be).

One solution would be to create a new class that has the attributes the code expects, but as I call other code that also needs objects with (other) attributes, I'd have to create more and more classes.

A shorter solution is to create a generic class, and then set the attributes on instances of it (for those who thought of using an instance of object instead of creating a new class, that won't work since object instances don't allow new attributes).

The last, shortest solution I came up with was to create a class with a constructor that takes keyword arguments, just like the dict constructor, and then sets them as attributes:

class data:
    def __init__(self, **kw):
        for name in kw:
            setattr(self, name, kw[name])

options = data(do_good_stuff=True, do_bad_stuff=False)

But I can't help feeling like I've missed something obvious... Isn't there a built-in way to do this (preferably supported in Python 2.5)?

+6  A: 

Use collections.namedtuple.

It works well.

from collections import namedtuple
Data = namedtuple( 'Data', [ 'do_good_stuff', 'do_bad_stuff' ] )
options = Data( True, False )
S.Lott
Nice, even though that adds a lot of unneeded functionality in my case. Unfortunately, most code I write is limited to Python 2.5, and `namedtuple` was added in 2.6. +1 though =)
Blixt
@Blixt: "unneeded functionality"? So what? Most of Python can be called unneeded functionality for use cases. Consider upgrading. Nothing breaks; you get the `with` statement and the capability of using the `print` function.
S.Lott
I just meant that I could go with a simpler solution, this solution has extra tuple functionality that I don't really need (plus it creates a whole new class; I was just interested in creating a single instance with the specified attributes which I can then throw away). I can't consider upgrading because this is for a project at work, which has strict rules on what software is used.
Blixt
Your example created a whole new class, `data`. It's -- essentially -- a named tuple and nothing more. Your class has lots of functionality, all. I don't know what you can possibly mean by "extra" tuple functionality, since tuples do so little and your demo class does so much. Really. Check the methods you get for free. Regarding the upgrade: It's time to lobby for change.
S.Lott
`namedtuple` creates a new class every time it is called. I only need one single instance with the set of attributes. That's why my code will be more efficient since it does not need to create a new class to then create an instance of every time I want an instance with specific attributes. As for upgrading, the company I work for takes stability seriously, and we use what is available in the current stable Linux distribution we're using. Which is Python 2.5. There is no way we can upgrade until a new distribution is available.
Blixt
`options = Data( True, False )` does not create a new class. What are you talking about?
S.Lott
I'm talking about: "`namedtuple` creates a new class every time it is called." E.g., `Data = namedtuple(...)` creates a class (`Data`). Since I will not be creating multiple instances of `Data`, I think it's a waste to create the class. I might however need more attribute configurations, and then I would have to call `namedtuple` several times, creating a new class every time.
Blixt
@Blixt: "Since I will not be creating multiple instances" Really? Then why does your example show a class definition? I'm really confused. Perhaps you can clarify your question rather than posting all this back-and-forth in the comments.
S.Lott
Well there is nothing to clarify in my question. You seem to be taking my statements out of context. I did not say "Since I will not be creating multiple instances". I said "Since I will not be creating multiple instances **of `Data`**". The `Data` of your example. I may very well create multiple instances of the `data` class in my code above, because every instance of it can have a different set of attributes. I hope this clarifies any misunderstanding.
Blixt
@Blixt: You're creating a lot of distinct classes. And you're complaining about the overhead of Namedtuple which helps you create a lot of distinct classes. Something's missing in your question that would make it clear why it's okay to create named tuples "the hard way" (via `class`) and it's not okay to simply create named tuples the easy way. [Leaving aside the Python 2.5 issue.]
S.Lott
@Blixt: "Well there is nothing to clarify in my question". If folks don't understand your question, it's hard to make the case that it cannot be clarified. But, if you want to say I'm too stupid to understand the question, just say it.
S.Lott
I think I understand the concern. Blixt is saying that since using namedtuple you have to specify the attributes, he will have to call it multiple times for every different set of data to use it for.In his example, the empty class can be assigned any attributes with a single class definition.
Casey
A: 

If I understand your question correctly, you need records. Python classes may be used this way, which is what you do.

I believe the most pythonic way of dealing with "records" is simply... dictionaries! A class is a sort of dictionary on steroids.

Your class example data is essentially a way of converting a dictionary into a class.

(On a side note, I would rather use self.__setattr__(name, kw[name]).)

Olivier
Nevertheless You cannot use `d = {'field' : 123 }` as `d.field` what, I think, is his intention.
Dejw
Exactly what Dejw said. I would also have preferred to use `dict` in this particular case. I think using the `__setattr__` method in this case is pointless, because it's equivalent to `setattr`, but longer, and doesn't add any clarity to what is happening.
Blixt
Olivier, Using `__setattr_ in this case does not work, nor would it be better if it did.
Mike Graham
It does work. It just that the class must inherit from `object`, which I cannot recommend enough. I prefer it for, arguably debatable, taste reasons, because I think it makes clearer that you are calling an object method. It certainly did not claim it would work better than `setattr`.
Olivier
+1  A: 

This is the shortest way I know

>>> obj = type("myobj",(object,),dict(foo=1,bar=2))
>>> obj.foo
1
>>> obj.bar
2
>>> 

using dict instead of {} insures your attribute names are valid

>>> obj = type("myobj",(object,),{"foo-attr":1,"bar-attr":2})
>>> obj.foo-attr
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'myobj' has no attribute 'foo'
>>>
Tom Willis
Interesting solution! But what it actually does is to create a new type (which can be instantiated, i.e., `o = obj()`). As for your second example, you *could* do `getattr(obj, 'foo-attr')`, but I agree that you should avoid those kinds of names for attributes.
Blixt
yes, the fact that you can instantiate is definitely a weird side effect. I've also run into problems with using dicts that were deserialized from json having unicode strings as keys sometimes messing up the getattr machinery. It's not bulletproof, and I wouldn't use it everywhere, but it can be very handy in certain situations.
Tom Willis
A: 

You might be interested in the "Struct", which is part of the IPython package. It does what you want to do, with lots of useful methods.

http://ipython.scipy.org/doc/manual/html/api/generated/IPython.utils.ipstruct.html

Olivier
A: 

This is typically something you would use a dict for, not making a class at all.

Mike Graham
I agree. I'm working with code out of my control though.
Blixt
+2  A: 

This works in 2.5, 2.6, and 3.1:

class Struct(object):
    pass

something = Struct()
something.awesome = abs

result = something.awesome(-42)

EDIT: I thought maybe giving the source would help out as well. http://docs.python.org/tutorial/classes.html#odds-and-ends

EDIT: Added assignment to result, as I was using the interactive interpreters to verify, and you might not be.

AndrewBC
+2  A: 

The original code can be streamlined a little by using __dict__:

In [1]: class data:
   ...:     def __init__(self, **kwargs):
   ...:         self.__dict__.update(kwargs)
   ...: 

In [2]: d = data(foo=1, bar=2)

In [3]: d.foo
Out[3]: 1

In [4]: d.bar
Out[4]: 2
Dave Kirby
I think this solution might solve the concerns Blixt had with S.Lott's answer.
Casey