views:

134

answers:

1

I have the following Python 2.6 program and YAML definition (using PyYAML):

import yaml

x = yaml.load(
    """
        product:
           name     : 'Product X'
           sku      : 123
           features :
             - size    :  '10x30cm'
               weight  :  '10kg'

         """
    )

print type(x)
print x


Which results in the following output:
<type 'dict'>
{'product': {'sku': 123, 'name': 'Product X', 'features': [{'weight': '10kg', 'size': '10x30cm'}]}}

It is possible to create an object with fields from x?

I would like to the following:

print x.features[0].size

I am aware that it is possible to create and instance from an existent class, but that is not what I want for this particular scenario.

Edit:

  • Updated the confusing part about a 'strongly typed object'.
  • Changed access to features to a indexer as suggested Alex Martelli
+3  A: 

So you have a dictionary with string keys and values that can be numbers, nested dictionaries, lists, and you'd like to wrap that into an instance which lets you use attribute access in lieu of dict indexing, and "call with an index" in lieu of list indexing -- not sure what "strongly typed" has to do with this, or why you think .features(0) is better than .features[0] (such a more natural way to index a list!), but, sure, it's feasible. For example, a simple approach might be:

def wrap(datum):
  # don't wrap strings
  if isinstance(datum, basestring):
    return datum
  # don't wrap numbers, either
  try: return datum + 0
  except TypeError: pass
  return Fourie(datum)

class Fourie(object):
  def __init__(self, data):
    self._data = data
  def __getattr__(self, n):
    return wrap(self._data[n])
  def __call__(self, n):
    return wrap(self._data[n])

So x = wrap(x['product']) should give you your wish (why you want to skip that level when your overall logic would obviously require x.product.features(0).size, I have no idea, but clearly that skipping's better applied at the point of call rather than hard-coded in the wrapper class or the wrapper factory function I've just shown).

Edit: as the OP says he does want features[0] rather than features(0), just change the last two lines to

  def __getitem__(self, n):
    return wrap(self._data[n])

i.e., define __getitem__ (the magic method underlying indexing) instead of __call__ (the magic method underlying instance-call).

The alternative to "an existing class" (here, Fourie) would be to create a new class on the fly based on introspecting the wrapped dict -- feasible, too, but seriously dark-gray, if not actually black, magic, and without any real operational advantage that I can think of.

If the OP can clarify exactly why he may be hankering after the meta-programming peaks of creating classes on the fly, what advantage he believes he might be getting that way, etc, I'll show how to do it (and, probably, I'll also show why the craved-for advantage will not in fact be there;-). But simplicity is an important quality in any programming endeavor, and using "deep dark magic" when plain, straightforward code like the above works just fine, is generally not the best of ideas!-)

Alex Martelli
@Alex, thanks for the answer. Sorry I messed up with the indexer on features, you are right it should have been `.features[0]`. I am updating my post to avoid further confusion.
Philip Fourie
Just override `__getitem__` instead of `__call__` -- let me edit to show that.
Alex Martelli
@Alex, That will work perfectly. Being a newbie in Python I didn't know about magic methods, what a great concept! Hopefully I can pick your brain on a later occasion on creating classes on the fly. Brilliant solution to my problem.
Philip Fourie