6. Dynamic Typing

6.1. Duck typing

Listing 341. Duck typing
{}              # dict
{1}             # set
{1, 2}          # set
{1: 2}          # dict
{1: 1, 2: 2}    # dict

my_data = {}
isinstance(my_data, (set, dict))  # True

isinstance(my_data, dict)  # True
isinstance(my_data, set)   # False

my_data = {1}
isinstance(my_data, set)   # True
isinstance(my_data, dict)  # False

my_data = {1: 1}
isinstance(my_data, set)   # False
isinstance(my_data, dict)  # True

6.2. Everything is an object

  • even function is an object!

6.2.1. Object properties

def add_numbers(a: int, b: float) -> float:
    """Function add numbers"""
    return a + b


print(add_numbers.__doc__)
# Function add numbers

print(add_numbers.__name__)
# add_numbers

print(add_numbers.__annotations__)
# {'a': <class 'int'>, 'b': <class 'float'>, 'return': <class 'float'>}

print(add_numbers.__class__)
# <class 'function'>

6.2.2. Object methods

def add_numbers(a, b):
    """Function add numbers"""
    return a + b

add_numbers(1, 2)
# 3

add_numbers.__call__(1, 2)
# 3

add_numbers()
# TypeError: function() missing 2 required positional arguments: 'a' and 'b'

add_numbers.__call__()
# TypeError: function() missing 2 required positional arguments: 'a' and 'b'

6.2.3. Injecting properties

def add_numbers(a, b):
    """Function add numbers"""
    return a + b


add_numbers.my_variable = 10

print(add_numbers.my_variable)
# 10

6.2.4. Injecting methods

def add_numbers(a, b):
    """Function add numbers"""
    return a + b


add_numbers.say_hello = lambda name: print(f'Hello {name}')

add_numbers.say_hello('Jan Twardowski')
# Hello Jan Twardowski

6.3. Monkey Patching

6.3.1. Recap information about classes and objects

class User:
    def __init__(self):
        self.name = 'Jose Jimenez'

    def hello(self):
        print(f'My name... {self.name}')

u = User()
u.hello()
# My name... Jose Jimenez
class User:
    def __init__(self):
        self.name = 'Jose Jimenez'

    def hello(self):
        print(f'My name... {self.name}')

User.hello()
# TypeError: hello() missing 1 required positional argument: 'self'

6.3.2. Injecting fields

class User:
    def __init__(self):
        self.name = 'Jose Jimenez'

    def hello(self):
        print(f'My name... {self.name}')


User.agency = 'NASA'    # Injecting static field

print(User.agency)
# NASA

6.3.3. Injecting methods

class User:
    def hello(self):
        print('Hello from User')


def my_function():
    print('New Version')


User.hello = my_function
User.hello()
# 'New Version'
class User:
    pass


User.hello = lambda name: print(f'Hello {name}')

User.hello('Jan Twardowski')
# Hello Jan Twardowski
class User:
    pass

u = User()
u.hello = lambda name: print(f'Hello {name}')

u.hello('Jan Twardowski')
# Hello Jan Twardowski
class User:
    def __init__(self):
        self.name = 'Jan Twardowski'
    pass

u = User()
u.hello = lambda self: print(f'Hello {self.name}')

u.hello()
# TypeError: <lambda>() missing 1 required positional argument: 'self'
class User:
    pass

User.hello = lambda self: print(f'Hello {self.name}')

u = User()
u.name = 'Jan Twardowski'

u.hello()
# Hello Jan Twardowski

6.3.4. Use case

import datetime
import json


def datetime_encoder(self, obj):
    if isinstance(obj, datetime.date):
        return f'{obj:%Y-%m-%d}'
    else:
        return str(obj)

json.JSONEncoder.default = datetime_encoder

output = {"datetime": datetime.date(1961, 4, 12)}
json.dumps(output)
# {"datetime": "1961-04-12"}