5

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

martineau
  • 119,623
  • 25
  • 170
  • 301
David Unric
  • 7,421
  • 1
  • 37
  • 65

2 Answers2

1

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
  • 63,558
  • 9
  • 127
  • 159
  • Nice ideas with function names generalization. – David Unric Oct 20 '10 at 22:54
  • 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 Oct 20 '10 at 23:03
  • 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 Oct 21 '10 at 06:39
0

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
Community
  • 1
  • 1
martineau
  • 119,623
  • 25
  • 170
  • 301
  • 1
    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 Oct 21 '10 at 12:45
  • 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 Oct 21 '10 at 16:00
  • Thx for clarification. So my original solution with 1:1 mapping by dictionary in try section was close to this. – David Unric Oct 21 '10 at 16:33
  • @David Unric: Indeed, your solutions was. Your `mapfield()` function performs the same role as the use of `__call__()` & `getattr()` do in mine. BTW, with some slight modifications you could use the `get(key[, default])` dictionary method to provide a default method for handling unknown `fieldnames` instead of the `try/except` clause. – martineau Oct 21 '10 at 18:02