DEV Community

loading...

Practical Python - Duck-typing to the rescue!

alvarocavalcanti profile image Alvaro Cavalcanti Updated on ・1 min read

Duck-typing?

If it walks like a duck, quacks like a duck and swims like a duck: it's a duck.

That above is the simple definition of duck-typing, which is a great side-effect feature of non-static typed languages, like Python.

Alright, how can I use it?

Let's consider the following object, a Django model (simplified for this example):

class User(models.Model):
    username = models.CharField()
    full_name = models.CharField()
    address_street = models.CharField()
    address_zip = models.CharField()
    subscription = models.ForeignKey()
    pets = ArrayField(Pet())  # Consider a one-to-many relationship

Now, you are tasked with creating a change tracker for the user, but that change should only care about the username and pets collection (because reasons). You then create a new model similar to this:

class UserChange(models.Model):
    before_username = models.CharField()
    before_pets = ArrayField(Pet())
    after_username = models.CharField()
    after_pets = ArrayField(Pet())

All is well, you have implemented almost every scenario, until you get to the removal of a single Pet from a User. The issue is, since the collection is loaded from the database, and you should only track the change once it is completed, it gets tricky to load the previous state. Well, you can grab the initial collection before it gets modified, that's fine, but where to store it? You can also do a copy.deepcopy of it, but that might sound like using a cannon to kill an ant. Then again, you only need to store the username and pets collection.

Duck-typing to the rescue!

You can use a light-weighted and elegant solution as:

class Duck:
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

The above class takes whatever keyword arguments you pass in and adds them as proprieties to the class and can be used as simple as:

user_duck_before = Duck(username="foo", pets=["dog", "cat"])
user_duck_after = Duck(username="bar", pets=["cat"])

That's it. Now you can use these "ducks" on the deletion flow without much memory overhead.

This approach is similar to using unites.mock but with production code instead of testing code.

And to be fair, even though I already had the idea in my mind, I didn't came up with the code, I found it on this SO post which pointed me to the code recipe by Alex Martelli. Thanks, Alex!

Edit

I realized that something was not clear, but I didn't want to modify it in place, thus this section. What really motivated me in searching for a duck-type approach was the one-to-many collection. In my real world scenario this collection was a RelatedManager, which prevented me from loading the previous state after the item have been removed, thus I needed to store that info in a way that I would neither break my contract nor implement an almost identical method just for this new data structure.

Discussion (2)

pic
Editor guide
Collapse
arkocal profile image
Ali Rasim Koçal

Nice hack! But here is some little critique because the title implies a broader use for it.

Duct typing is awesome, but some Duck objects are dangerous, because

  1. They do not document themselves. In this case, it is OK because the variable names do it, but I would suggest not to use this approach if it obscures the intent of the object. To quote The Zen of Python (import this) explicit is better than implicit. Python avoids strong typing because it is mostly practical to allow anything that walks like a Duck, but the Duck itself should be implemented at some point, so the reader knows what it is about.
  2. A typo in argument name can lead to a long nasty debugging session. I am not sure whether that would work in your case, but a NamedTuple from the collections module is often a good fit for such use.
Collapse
alvarocavalcanti profile image
Alvaro Cavalcanti Author

You points are much appreciated, Ali! Thanks!

I do understand that the title is generic while the content only covers a smaller ground. I'm sorry for that. And to be fair, readability is a first-class concern of mine (I have a much over due unpublished article on the matter), thus the context-through-variable-name.

Also, I thought about using the NamedTuple but wasn't sure about the . accessor (and apparently was too lazy to search) which led me to the duck.