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
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
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.
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.