Duck types and monkey patches in Python

Hello, everyone, I am Lao Wang.

Python developers have probably heard of duck type and monkey patch, and if they haven’t, they’ve probably written about them. They just don’t know the technical point behind them.

I’ve been asking about these two concepts recently when INTERVIEWING candidates, and a lot of people don’t respond very well. But when I explain it to them, it’s like, “Oh, that’s it. I used it.”

So I decided to write an article that explores both techniques.

The duck type

To quote an explanation from Wikipedia:

Duck typing is a style of dynamic typing in programming. In this style, the valid semantics of an object are determined not by inheriting from a particular class or implementing a particular interface, but by the “current collection of methods and attributes.”

To put it more generally:

When a bird is seen walking like a duck, swimming like a duck, and quacking like a duck, it can be called a duck.

In other words, in the duck type, the focus is on the behavior of the object, what it can do; Instead of focusing on the type the object belongs to.

Let’s look at an example to illustrate it more graphically:

# This is a Duck class
class Duck:
    def eat(self) :
        print("A duck is eating...")

    def walk(self) :
        print("A duck is walking...")


# This is a Dog class
class Dog:
    def eat(self) :
        print("A dog is eating...")

    def walk(self) :
        print("A dog is walking...")


def animal(obj) :
    obj.eat()
    obj.walk()


if __name__ == '__main__':
    animal(Duck())
    animal(Dog())
Copy the code

Program output:

A duck is eating...
A duck is walking...
A dog is eating...
A dog is walking...
Copy the code

Python is a dynamic language with no strict type checking. Duck and Dog can be called if they implement eat and walk methods respectively.

Or the list.extend() method. In addition to lists, dict and tuple can be called, as long as they are iterable.

After reading the above example, you should have a better understanding of the “behavior of objects” and “the type of objects”.

As an extension, the duck type is similar to an interface, except that no interface is explicitly defined.

For example, to implement the duck type in Go, the code looks like this:

package main

import "fmt"

// Define an interface that contains the Eat method
type Duck interface {
	Eat()
}

// Define the Cat structure and implement the Eat method
type Cat struct{}

func (c *Cat) Eat(a) {
	fmt.Println("cat eat")}// Define the Dog structure and implement the Eat method
type Dog struct{}

func (d *Dog) Eat(a) {
	fmt.Println("dog eat")}func main(a) {
	var c Duck = &Cat{}
	c.Eat()

	var d Duck = &Dog{}
	d.Eat()

	s := []Duck{
		&Cat{},
		&Dog{},
	}
	for _, n := range s {
		n.Eat()
	}
}
Copy the code

This is done by explicitly defining a Duck interface, with each structure implementing the methods in the interface.

The monkey patch

Monkey Patch gets a bad rap because it dynamically modifies modules, classes, or functions at run time, usually by adding features or fixing bugs.

The monkey patch works in memory and does not modify the source code, so it only works on the currently running program instance.

However, if abused, the system can be difficult to understand and maintain.

There are two main problems:

  1. Patches break encapsulation and are often tightly coupled to the target, making them vulnerable
  2. Two patched libraries may stumble over each other, as the second library may undo the patches of the first

Therefore, it is seen as a temporary workaround and not a recommended way to integrate code.

As usual, here’s an example:

Define a Dog class
class Dog:
    def eat(self) :
        print("A dog is eating ...")


# Add a monkey patch to the Dog class outside the class
def walk(self) :
    print("A dog is walking ...")


Dog.walk = walk

The class is called in the same way as internally defined properties and methods
dog = Dog()
dog.eat()
dog.walk()
Copy the code

Program output:

A dog is eating ...
A dog is walking ...
Copy the code

This is equivalent to adding a walk method to the Dog class outside of the class and calling it in the same way that properties and methods are defined inside the class.

Take another practical example, such as our common JSON standard library, if you want to replace with higher performance uJSON, it is necessary to introduce each file:

import json
Copy the code

To:

import ujson as json
Copy the code

If you change it this way, the cost will be higher. At this point, you can consider using the monkey patch. Just add:

import json  
import ujson  


def monkey_patch_json() :  
    json.__name__ = 'ujson'  
    json.dumps = ujson.dumps  
    json.loads = ujson.loads  


monkey_patch_json()
Copy the code

This makes it easy to call uJSON packages when the dumps and loads methods are called later.

The monkey patch is a double-edged sword, however, as mentioned above, so use it with caution if necessary.

The above is the content of this article, if you feel good, welcome to like, forward and follow, thank you for your support.


Recommended reading:

  • Learning Python (2022)
  • I write Python code that my colleagues agree on