views:

135

answers:

2

Hi all,

I have some code in Python where I'll have a bunch of classes, each of which will have an attribute _internal_attribute. I would like to be able to generate a mapping of those attributes to the original class. Essentially I would like to be able to do this:

class A(object):
    _internal_attribute = 'A attribute'
class B(object):
    _internal_attribute = 'B attribute'

a_instance = magic_reverse_mapping['A attribute']()
b_instance = magic_reverse_mapping['B attribute']()

What I'm missing here is how to generate magic_reverse_mapping dict. I have a gut feeling that having a metaclass generate A and B is the correct way to go about this; does that seem right?

+3  A: 

You need some data structure to store the list of applicable classes in the first place, but you don't have to generate it in the first place. You can read classes from globals instead. This naturally assumes that your classes extend object, as they do in your first post.

def magic_reverse_mapping(attribute_name, attribute_value):
    classobjects = [val for val in globals().values() if isinstance(val, object)]
    attrobjects = [cls for cls in classobjects if hasattr(cls, attribute_name)]
    resultobjects = [cls for cls in attrobjects if object.__getattribute__(cls, attribute_name) == attribute_value]
    return resultobjects

magic_reverse_mapping('_internal_attribute', 'A attribute')
#output:   [<class '__main__.A'>]

Note that this returns a list of classes with that attribute value, because there may be more than one. If you wanted to instantiate the first one:

magic_reverse_mapping('_internal_attribute', 'A attribute')[0]()
#output:   <__main__.A object at 0xb7ce486c>

Unlike in sth's answer, you don't have to add a decorator to your classes (neat solution, though). However, there's no way to exclude any classes that are in the global namespace.

Nikhil Chelliah
The "list of classes" is exactly what I want to avoid, it means that any time I add a new subclass I have to remember to add it to two places.
wxs
Hmm, I like this better now post-edit... I do think I'll go with the decorator solution, though. It's more explicit and feels less magic.
wxs
@wxs: Thanks. The way you worded it sounds correct...sth's solution is more Pythonic.
Nikhil Chelliah
+5  A: 

You can use a meta class to automatically register your classes in magic_reverse_mapping:

magic_reverse_mapping = {}

class MagicRegister(type):
   def __new__(meta, name, bases, dict):
      cls = type.__new__(meta, name, bases, dict)
      magic_reverse_mapping[dict['_internal_attribute']] = cls
      return cls

class A(object):
    __metaclass__ = MagicRegister
    _internal_attribute = 'A attribute'

afoo = magic_reverse_mapping['A attribute']()

Alternatively you can use a decorator on your classes to register them. I think this is more readable and easier to understand:

magic_reverse_mapping = {}

def magic_register(cls):
   magic_reverse_mapping[cls._internal_attribute] = cls
   return cls

@magic_register
class A(object):
    _internal_attribute = 'A attribute'

afoo = magic_reverse_mapping['A attribute']()

Or you could even do it by hand. It's not that much more work without using any magic:

reverse_mapping = {}

class A(object):
    _internal_attribute = 'A attribute'

reverse_mapping[A._internal_attribute] = A

Looking at the different variants I think the decorator version would be the most pleasant to use.

sth
Note that the class decorator requires Python >= 2.6.
nikow
The decorator solution seems perfect, for some reason I thought class decorators were Py3k only. I blame PEP-3129 for saying they're included in 3k and not mentioning that they're also in 2.6.
wxs