Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyYAML parse into arbitary object

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 existing 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
like image 402
Philip Fourie Avatar asked Mar 14 '10 16:03

Philip Fourie


1 Answers

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

like image 54
Alex Martelli Avatar answered Sep 19 '22 00:09

Alex Martelli