5

The principle of duck typing says that you shouldn't care what type of object you have - just whether or not you can do the required action with your object. For this reason the isinstance keyword is frowned upon. - -Definition

In below snippet(function) group_tweets_by_state, following definition of duck typing, action setdefault is performed on the object tweets_by_state by appending object tweet

def group_tweets_by_state(tweets):
    tweets_by_state = {}
    USA_states_center_position = {n: find_center(s) for n, s in us_states.items()}
    for tweet in tweets:                     # tweets are list of dictionaries 
        if hassattr(tweet, 'setdefault'):    # tweet is a dictionary 
            state_name_key = find_closest_state(tweet, USA_states_center_position)
            tweets_by_state.setdefault(state_name_key, []).append(tweet)
    return tweets_by_state

My understanding is, function hasattr(tweet, 'setdefault') is type checking tweet to be of <class 'dict'> type in duck typing style, before append.

Is my understanding correct? Does function group_tweets_by_state follow duck typing?

  • 1
    I have no idea what "does function x smell duck typing" means. Functions do not have a sense of smell. – RemcoGerlich Jun 26 '15 at 10:07
  • Anyway it's daft to check for the existence of setdefault if you're not going to use it. If you want to know whether it's a dict, use isinstance, that's what it's for. – RemcoGerlich Jun 26 '15 at 10:08
  • Using isinstance is not duck typing. – overexchange Jun 26 '15 at 10:09
  • 6
    No, but this is just doing isinstance in another way, it's the same thing. Duck typing would be to just assume that it is a dict, and if that throws an exception, well then so be it. Should have passed something that wouldn't throw an exception. – RemcoGerlich Jun 26 '15 at 10:11
  • 3
    Duck typing simply means that in your functions you expect objects to follow certain protocol and you don't care about the type. Here, you expect that tweets is iterable and that its elements behave as dicts. They need not be dicts, they can be OrderedDicts for all you care. – avakar Jun 26 '15 at 10:14
11

A test like hassattr(tweet, 'setdefault') to make sure tweet is a dictionary is not a good one, since it obviously does not assure tweet provides all methods/properties of a dictionary. So as long tweet.setdefault is not the only method called by find_closest_state (which I think is unlikely), this test is not strict enough. On the other hand, a test like isinstance(tweet, dict) is too strict, because it forbids the usage of other, dictionary-like structures, which is exactly the idea of duck typing.

In your example the requirement is not really that tweet is a dictionary, the requirement is that find_closest_state can process the tweet, whatever methods it calls from a tweet, independently of the real type. The following solution will handle this in a generic manner, without the need of knowing exactly what methods inside find_closest_state are used:

def group_tweets_by_state(tweets):
    tweets_by_state = {}
    USA_states_center_position = {n: find_center(s) for n, s in us_states.items()}
    for tweet in tweets:              # a  tweet should behave like a dictionary
        try: 
            state_name_key = find_closest_state(tweet, USA_states_center_position)
            tweets_by_state.setdefault(state_name_key, []).append(tweet)
        except (AttributeError, TypeError):
            pass
    return tweets_by_state

The code checks for an AttributeError because that is the exception you get when find_closest_state calls a method not provided by tweet. It also checks for a TypeError, because that is what you get when you call tweet["abc"] on a non-dictionary. You may need to add some other exceptions, depending on how find_closest_state is implemented internally, but you should not add any artificial constraints.

And that's how duck typing should really be applied - by not making assumptions about the type of the object passed, only by testing whether or not you can do the required action (here: call find_closest_state without getting one of the above exceptions).

4

First, I want to say that by far not everybody agrees that duck typing is a good thing at all, let alone that it is some sort of holy principle that should be followed. Often duck typing leads to error prone, hard to debug code, although it can be very flexible in how you can use it.

Duck typing just takes an object and does with the object what it wants to do E.g. if it expects an open file object that it wants to call .write() on, then it just does that. If that throws an exception because the object doesn't have that method, then you passed in a non-duck. Your fault. If you passed in a completely different kind of object that also works because it has a .write(), that is the benefit of duck typing.

So your code would use "duck typing" if it simply assumes that tweets is a list of dictionaries (like the comment says), and doesn't go to any trouble to check that.

def group_tweets_by_state(tweets):
    tweets_by_state = {}
    USA_states_center_position = {n: find_center(s) for n, s in us_states.items()}
    for tweet in tweets:                     # tweets are list of dictionaries 
        state_name_key = find_closest_state(tweet, USA_states_center_position)
        tweets_by_state.setdefault(state_name_key, []).append(tweet)
    return tweets_by_state
  • The problem here is that some of the tweets maybe dictionaries, others not, and the original code tried to handle this without throwing an exception, just by skipping the non-dictionaries. That is not what your supposed solution does any more. – Doc Brown Jun 26 '15 at 11:47
  • I know that may be the case, but I'm not sure because of his "# tweets are lists of dictionaries" comment. I don't think changing the functionality is by itself a problem, one would expect that a function might do something slightly different if it uses duck typing compared to one that doesn't . And having lists with different kinds of things in them is not a good fit for the duck typing style of writing functions, I think. – RemcoGerlich Jun 26 '15 at 11:54
  • To rephrase that, without knowing more than we do about his data or find_closest_state, I think using isinstance and forgetting about duck typing is the way to go. Surely using a bare except: like in your answer has its issues too. – RemcoGerlich Jun 26 '15 at 11:56
  • There is no bare except in my answer any more, maybe you missed my edit?. – Doc Brown Jun 26 '15 at 12:03
  • I did, but now it's the other problem: we have no way of knowing whether it will raise AttributeError. But I'll grant that it does the same thing as the original now. – RemcoGerlich Jun 26 '15 at 12:07

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.