views:

56

answers:

2

I am trying to decorate the magic method __getitem__ to be a classmethod on the class. Here is a sample of what I tried. I don't mind using either classmethod or staticmethod decoration, but I am not too sure how to do it. Here is what I tried:

import ConfigParser

class Settings(object):
   _env = None
   _config = None

   def __init__(self, env='dev'):
    _env = env
    # find the file
    filePath = "C:\\temp\\app.config"

    #load the file
    _config = ConfigParser.ConfigParser()
    _config.read(filePath)

   @classmethod
   def __getitem__(cls, key): 
    return cls._config.get(cls._env, key)

   @classmethod
   def loadEnv(cls, env): 
    cls._env = env

However, when I try to call Settings['database'] I get the following error.

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: expected Array[Type], got str

Can anyone tell me what I am doing wrong. Also, could someone suggest if there is better way to do this? I even tried using MetaClasses, but with little success (as I don't know python too well).

 class Meta(type):
  def __getitem__(*args):
   return type.__getitem__(*args)

 class Settings(object):
  __metaclass__ = Meta

Thanks in advance.

+2  A: 

Python always looks up __getitem__ and other magic methods on the class, not on the instance. So, for example, defining a __getitem__ in a metaclass means that you can index the class (but you can't define it by delegating to a non-existent __getitem__ in type -- just as you can never define anything by delegating to other non-existent methods, of course;-).

So, if you need to index a class such as Settings, your custom metaclass must indeed define __getitem__, but it must define it with explicit code that performs the action you desire -- the return cls._config.get you want.

Edit: let me give a simplified example...:

>>> class MyMeta(type):
...   def __getitem__(cls, k):
...     return cls._config.get(k)
... 
>>> class Settings:
...   __metaclass__ = MyMeta
...   _config = dict(foo=23, bar=45)
... 
>>> print Settings['foo']
23

Of course, if that was all there was to it, it would be silly to architect this code as "indexing a class" -- a class had better have instances with states and methods, too, otherwise you should just code a module instead;-). And why the "proper" access should be by indexing the whole class rather than a specific instance, etc, is far from clear. But I'll pay you the compliment of assuming you have a good design reason for wanting to structure things this way, and just show you how to implement such a structure;-).

Alex Martelli
I hate it when I'm in the same timezone as you, grr :)
Thomas Wouters
I tried changing the Meta class to class Meta(type): def __getitem__(cls, key): print 'Meta getitem on ', cls, ' key: ', key return cls._config.get(cls._env, key)However, I still get Meta getitem on <class '__main__.Settings'> key: databaseTraceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in __getitem__AttributeError: 'NoneType' object has no attribute 'get'Any idea?
Bobby Chopra
@Bobby, code's impossible to read in comments -- look at my example code, which works just fine, and discover whatever differences there are between my working code and your non-working one (or, if they just look identical to you, edit your question, **not** a comment, to add the code "allegedly identical to mine" that's not working and we'll see if we can spot the difference for you;-).
Alex Martelli
@Thomas, ;-) -- then you'd better convince Tim O'Reilly to move OSCON to the East Coast (since that's where I am now, at OSCON in Portland, OR... maybe we could try Portland, MN instead?-).
Alex Martelli
@Alex: nah, I just need to not visit stackoverflow when I'm in Mountain View :)
Thomas Wouters
Thanks Alex, your example works. My MetaClass doesn't work due to the fact that the static _config (declared in my class) is set to None. It is initialized in the __init__ method of the Settings class. I think your explanation was helpful. I am starting to understand Python a bit more everyday.
Bobby Chopra
+1  A: 

Apart from Alex's (entirely correct) answer, it isn't clear what you actually want to do here. Right now you are trying to load the config when you instantiate the class. If you were to assign that _config to the class object, that would mean all instance of the class share the same config (and creating another instance would change all existing instances to point to the latest config.) Why are you trying to use the class to access this configuration, instead of a particular instance of the class? Even if you only ever have one configuration, it's much more convenient (and understandable!) to use an instance of the class. You can even store this instance in a module-global and call it 'Settings' if you like:

class _Settings(object):
    def __init__(self, fname):
        self._config = ...
    ...

Settings = _Settings('/path/to/config.ini')
Thomas Wouters
This is the way to go: if you find yourself wanting to make a class behave like an instance, you're better off just using an instance in the first place.
Ned Batchelder
You might be right. I am trying to make the Settings Globally accessible. Once you define the config environment, I want to load the settings (db, server, timeout) for that config.
Bobby Chopra