Question

I have a function like this:

def foo(bar):
    ...

Now bar can either be a Django record or the slug pointing to a record. I need this flexibility so that I can call this function when I have the actual record at hand or am calling this from a more generic function that only has bar available as a string (pulled from a database).

I realize I can do something like:

def foo(bar):
    try:
        bar.pk
    except AttributeError:
        bar = Bar.objects.get(slug=bar)
    ...

But this doesn't seem elegant at all.

If I can I'd like to avoid using isinstance.

Was it helpful?

Solution

You are by definition not using Duck Typing. Duck Typing says that "if it talks like a duck and looks like a duck, it's a duck."

Duck Typing means you can pass two objects of completely different classes to the method and have it work because they both implement the same methods/attributes (or handles missing ones gracefully). This means that the method never cares about about what type it gets, just that whatever object you pass it has the attributes it expects to use.

In your case you want to pass an object sometimes and a string that can be used to look up said object other times. This has nothing to do with Duck Typing.

isinstance is the right way to solve this. In this case this is the clearest way to solve the problem, anything else is going to be more complicated, harder to understand have 0 benefits. You could use try/except on an attribute or hasattr, but that's likely to leave any future developers more confused than anything else. Duck Typing is great it replaces casting various subclasses around to match some particular function, but duck typing doesn't apply in this case.

In short. Just use isinstance. For your case it's the right (pythonic) way to do it.

OTHER TIPS

I'm not sure that that is a terrible way of handling that, but if I wanted to do something similar, I would probably use hasattr:

def foo(bar):
    if hasattr(bar,"pk"):
        bar.pk
    else:
        # I include the str in case some other object with a __str__ happens
        # to come through.
        bar = Bar.objects.get(slug=str(bar))

This is another way which will help in other functions you want to do the same. I'll asume the model's name you are using is 'Item'.

def slug_resilient_decorator(class_type):

    def slug_resilient_wrapper(obj):

            if obj.has_attr('pk'):
                    return obj
            else:
                    return class_type.objects.get(slug=obj)

    return wrapper

@slug_resilient_decorator(Item)
def slug_resilient_detail_view(obj):

    ...
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top