tags:

views:

121

answers:

5

Hi, I have an object that holds lots of ids that are accessed statically. I want to split that up into another object which holds only those ids without the need of making modifications to the already existen code base. Take for example:

class _CarType(object):
    DIESEL_CAR_ENGINE = 0
    GAS_CAR_ENGINE = 1 # lots of these ids

class Car(object):
    types = _CarType

I want to be able to access _CarType.DIESEL_CAR_ENGINE either by calling Car.types.DIESEL_CAR_ENGINE, either by Car.DIESEL_CAR_ENGINE for backwards compatibility with the existent code. It's clear that I cannot use __getattr__ so I am trying to find a way of making this work (maybe metaclasses ? )

+3  A: 

Something like:

class Car(object):
    for attr, value in _CarType.__dict__.items():
        it not attr.startswith('_'):
            locals()[attr] = value
    del attr, value

Or you can do it out of the class declaration:

class Car(object):
    # snip

for attr, value in _CarType.__dict__.items():
    it not attr.startswith('_'):
        setattr(Car, attr, value)
del attr, value
Antoine P.
It works very well, but I am looking for a more elegant solution. If nothing comes up, you are the winner! Thank you very much.
hyperboreean
You should not modify `locals()`: http://docs.python.org/library/functions.html#locals
Roger Pate
This use of setattr is about the most elegant and simple solution you're going to get.
Roger Pate
You are right, behaviour of locals() is context-dependent.
Antoine P.
+4  A: 

Although this is not exactly what subclassing is made for, it accomplishes what you describe:

class _CarType(object):
    DIESEL_CAR_ENGINE = 0
    GAS_CAR_ENGINE = 1 # lots of these ids

class Car(_CarType):
    types = _CarType
balpha
Thnaks balpha for your answer, but I tend to not prefer it because I forgot to mention that Car will subclass some other class and I am not very fond of multiple inheritance. But this is a good idea, nevertheless!
hyperboreean
Multiple inheritance should not pose a problem in this case -- as long as `_CarType` only contains your constants, and they don't appear anywhere else, I don't see a risk for any MRO head scratching.
balpha
Actually answers the question, unlike my attempt, and this looks like the cleanest way to do it.
Andrew McGregor
+2  A: 

This is how you could do this with a metaclass:

class _CarType(type):
    DIESEL_CAR_ENGINE = 0
    GAS_CAR_ENGINE = 1 # lots of these ids
    def __init__(self,name,bases,dct):
        for key in dir(_CarType):
            if key.isupper():
                setattr(self,key,getattr(_CarType,key))

class Car(object):
    __metaclass__=_CarType

print(Car.DIESEL_CAR_ENGINE)
print(Car.GAS_CAR_ENGINE)
unutbu
Nice solution! Though metaclass programming is seen as somewhat of a dark art...
Rob Golding
I agree that simpler is better than complex, and metaclasses aren't really needed here.However, as one trying to understand metaclasses, I hope this might be useful to others going down the same road.
unutbu
-1 for metaclassing unnecessarily. Yikes!
Jason Orendorff
A: 
class _CarType(object):
    DIESEL_CAR_ENGINE = 0
    GAS_CAR_ENGINE = 1 # lots of these ids

class Car:
  types = _CarType
  def __getattr__(self, name):
    return getattr(self.types, name)

If an attribute of an object is not found, and it defines that magic method __getattr__, that gets called to try to find it.

Only works on a Car instance, not on the class.

Andrew McGregor
Have you even try this solution ? How would Car.DIESEL_CAR_ENGINE work? self refers to an already created object ...
hyperboreean
It works on a Car instance, but not on the class.
Andrew McGregor
Oops, it was supposed to... fixed the bugs.
Andrew McGregor
Yeah, but I need to access the types in a static manner, so your solution doesn't work ... that's why I even specified that __getattr__ won't work for me.
hyperboreean
Ah, yep. I wouldn't call that 'access the types in a static manner', I think the normal way to say that would be 'access the types through the class'. Anyway, I didn't get quite what you were trying to do...
Andrew McGregor
+1  A: 

Your options fall into two substantial categories: you either copy the attributes from _CarType into Car, or set Car's metaclass to a custom one with a __getattr__ method that delegates to _CarType (so it isn't exactly true that you can't use __getattr__: you can, you just need to put in in Car's *meta*class rather than in Car itself;-).

The second choice has implications that you might find peculiar (unless they are specifically desired): the attributes don't show up on dir(Car), and they can't be accessed on an instance of Car, only on Car itself. I.e.:

>>> class MetaGetattr(type):
...   def __getattr__(cls, nm):
...     return getattr(cls.types, nm)
... 
>>> class Car:
...   __metaclass__ = MetaGetattr
...   types = _CarType
... 
>>> Car.GAS_CAR_ENGINE
1
>>> Car().GAS_CAR_ENGINE
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Car' object has no attribute 'GAS_CAR_ENGINE'

You could fix the "not from an instance" issue by also adding a __getattr__ to Car:

>>> class Car:
...   __metaclass__ = MetaGetattr
...   types = _CarType
...   def __getattr__(self, nm):
...     return getattr(self.types, nm)
... 

to make both kinds of lookup work, as is probably expected:

>>> Car.GAS_CAR_ENGINE
1
>>> Car().GAS_CAR_ENGINE
1

However, defining two, essentially-equal __getattr__s, doesn't seem elegant.

So I suspect that the simpler approach, "copy all attributes", is preferable. In Python 2.6 or better, this is an obvious candidate for a class decorator:

def typesfrom(typesclass):
  def decorate(cls):
    cls.types = typesclass
    for n in dir(typesclass):
      if n[0] == '_': continue
      v = getattr(typesclass, n)
      setattr(cls, n, v)
    return cls
  return decorate

@typesfrom(_CarType)
class Car(object):
  pass

In general, it's worth defining a decorator if you're using it more than once; if you only need to perform this task for one class ever, then expanding the code inline instead (after the class statement) may be better.

If you're stuck with Python 2.5 (or even 2.4), you can still define typesfrom the same way, you just apply it in a slightly less elegant matter, i.e., the Car definition becomes:

class Car(object):
  pass
Car = typesfrom(_CarType)(Car)

Do remember decorator syntax (introduced in 2.2 for functions, in 2.6 for classes) is just a handy way to wrap these important and frequently recurring semantics.

Alex Martelli