Tutorial :Polluting a class's environment



Question:

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 ? )


Solution:1

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  


Solution:2

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  


Solution:3

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)  


Solution:4

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 metaclass 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.


Solution:5

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.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »