1
\$\begingroup\$

In a Mako template partial I accept an argument (step) that can be an instance of Step class, a dict or None. Can I in some way avoid repetition or do the check in other more 'pythonic' way? Or how could I gracefully avoid checking types at all?

<%include file="_step.mako" args="step=step" />

and then in _step.mako:

<%page args="step"/>
<%! from package.models.recipe import Step %>
<% 
if isinstance(step, Step):
    number = step.number
    time_value = step.time_value
    text = step.text
elif type(step) is dict:
    number = step['number']
    time_value = step['time_value']
    text = step['text']
else:
    number = 1
    time_value = ''
    text = ''
%>
\$\endgroup\$
10
  • 1
    \$\begingroup\$ actually, the most pythonic way would be to avoid passing different types to this partial. \$\endgroup\$ May 5, 2012 at 16:23
  • \$\begingroup\$ @WinstonEwert I wolud gladly avoid that, but how can I maintain a single template that is rendering a form sometimes empty and sometimes repopulated (with dict or instance of a class). \$\endgroup\$
    – yentsun
    May 5, 2012 at 18:58
  • \$\begingroup\$ you shouldn't. You should change the rest of your code so its consistently passes the same thing to this template. \$\endgroup\$ May 5, 2012 at 19:21
  • \$\begingroup\$ @WinstonEwert like create a dummy (empty) dict for none, valid or invalid data and pass that dict to the template? You know, I started with such a design but faced a problem: if I repopulate invalid data - it can only be a dict (I do not allow creating instances from invalid data) and if its valid - it is an instance and I have access to its attributes. So if I want to access its attributes in the template I have to check (or try) if its an instance. But if I unify the the type of object passed to the template, and it can only be a dict, I guess, no attributes like methods, can be used. \$\endgroup\$
    – yentsun
    May 5, 2012 at 20:05
  • 1
    \$\begingroup\$ You may find it useful to look into some of those projects, just to see how they solve this problem. Basically, they would have a StepForm class which can hold invalid/blank/valid data. The class would have methods to convert to/from an actual Step object. In some ways, its not dissimilar to always using a dictionary. But you get some benefits of using an object as well. You don't have to go for the whole form generation technique these libraries go for, but the separation between model and form objects has been widely adopted and seems to work well. \$\endgroup\$ May 5, 2012 at 23:22

2 Answers 2

1
\$\begingroup\$

1.I assume if type(step) is dict: should be elif type(step) is dict:.

2.isinstance is preferable than type because isinstance caters for inheritance, while checking for equality of type does not (it demands identity of types and rejects instances of subtypes).

3.You also can apply duck-typing.

try:
    number = step.number
except AttributeError:
    try:
        number = step['number']
    except KeyError:
        number = 1

And these ways are quite pythonic.

4.I suggest you can inherit your Step class from dict then you don't need to check types. Yo will just do

number = step.get('number', 1)
time_value = step.get('time_value', '')
\$\endgroup\$
4
  • \$\begingroup\$ Thanks for the detailed answer! I will apply duck-typing as you described for now and save inheriting dict for later experiments - that could finally be a perfect solution. \$\endgroup\$
    – yentsun
    May 5, 2012 at 6:24
  • \$\begingroup\$ You are welcome. But consider that number = step.get('number', 1) is shorter version of try: number = step['number'] except KeyError: number = 1 \$\endgroup\$
    – San4ez
    May 5, 2012 at 6:41
  • \$\begingroup\$ I will. The Step class is used as a model and an sqlalchemy mapping, thats why I'm afraid messing with it. \$\endgroup\$
    – yentsun
    May 5, 2012 at 6:58
  • \$\begingroup\$ I probably wouldn't use inheritance however. Just provide an implementation of the get() method and let the ducks do their thing. \$\endgroup\$ May 5, 2012 at 16:59
0
\$\begingroup\$

I came up with this function (thanks to San4ez):

def failsafe_get(object, name):
    if object:
        try:
            extractor = attrgetter(atrr_path)
            result = extractor(object)
        except AttributeError:
            try:
                result = object[atrr_path]
            except (KeyError, TypeError):
                result = None
    else:
        result = None
    return result

Now I can use it in a template like this:

<div class="number">${failsafe_get(ingredient, 'unit.abbr')}</div>

No repetition, but maybe performance issue.

UPD. I've implemented operator.attrgetter so now I can give dotted notation for a name.

\$\endgroup\$
2
  • \$\begingroup\$ I would check name is None out of try-except block \$\endgroup\$
    – San4ez
    May 5, 2012 at 7:16
  • \$\begingroup\$ @San4ez I am actually checking if object is None, providing name always a str. Corrected the code. \$\endgroup\$
    – yentsun
    May 5, 2012 at 16:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.