views:

101

answers:

2

Hi,

I'm relatively new to Python and would like to know if I'm reinventing a wheel or do things in a non-pythonic way - read wrong.

I'm rewriting some parser originally written in Lua. There is one function which accepts a field name from imported table and its value, does some actions on value and stores it in target dictionary under a appropriate key name.

In the original code it's solved by long switch-like statement with anonymous functions as actions. Python code looks like the following:

class TransformTable:
    target_dict = {}
    ...
    def mapfield(self, fieldname, value):
        try:
            {
                'productid': self.fn_prodid,
                'name': self.fn_name,
                'description': self.fn_desc,
                ...
            }[fieldname](value)
        except KeyError:
            sys.stderr.write('Unknown key !\n')

    def fn_name(val):
        validity_check(val)
        target_dict['Product'] = val.strip().capitalize()
    ...

Every "field-handler" function does different actions and stores in different keys in target_dict, of course. Because Python does not support anonymous functions with statements (or did I missed something ?) the functions have to be written separately which does code less readable and unnecessarily complicated.

Any hints how to do such tasks in a more elegant and more pythonic way are appreciated.

Thx

David

+1  A: 

If by any means possible, you could name your member functions based on the field names and just do something like this:

getattr(self, "fn_" + fieldname)(value)

Edit: And you can use hasattr to check if the function exists, instead of expecting a KeyError. Or expect an AttributeError. At any rate, you should put only the access inside your try..except, and call it outside, since otherwise a KeyError caused within one of the field methods could get misunderstood.

Matti Virkkunen
Nice ideas with function names generalization.
David Unric
Nice ideas with function names generalization. Is there also a more general way of dictionary -> dictionary mappings (key to key with value transformation) ?
David Unric
I think that this is the standard way to switch on strings in a class context. I'm too lazy to find the link but I know Guido has referred to it as a very elegant solution. He's dutch so the one right way is instantly obvious to him.
aaronasterling
A: 

I applied an approach similar to @Matti Virkkunen'a a while ago in my answer to a question titled "switch case in python doesn't work; need another pattern". It also demonstrates a relatively easy and graceful way of handling unknown fields. Transliterated to terms in your example it would look like this:

class TransformTable:
    target_dict = {}

    def productid(self, value):
        ...
    def name(self, value):
        validity_check(value)
        self.target_dict['Product'] = value.strip().capitalize()
    def description(self, value):
        ...

    def _default(self, value):
        sys.stderr.write('Unknown key!\n')

    def __call__(self, fieldname, value):
        getattr(self, fieldname, self._default)(value)

transformtable = TransformTable() # create callable instance

transformtable(fieldname, value) # use it
martineau
That's a bit cleaner solution. Thank you. How well would this work if field names (extracted from import table) will contain unicode characters (with diacritic), ie. define functions with non-ascii names ?
David Unric
To use the mechanism shown requires some sort of mapping from what is being switched on to valid identifier name. Above it assumes it's 1:1. Another more general way to handle this would be to have a separate dictionary or lookup table which maps each valid input (`fieldname` here) to unique method name -- which could be simply `method1`, `method2`, etc. For switching on integer values, you have something like, `'case_' + str(number)`. The mapping need not be 1:1, in the sense that several inputs might all be handled by the same method for example. You're free to customize it any way you like.
martineau
Thx for clarification. So my original solution with 1:1 mapping by dictionary in try section was close to this.
David Unric
martineau