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.
Top comments (2)
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
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.NamedTuple
from thecollections
module is often a good fit for such use.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.