How to extend a builtin type

Please look at the example below.

"""
The simple example that explains the impossibility of adding a method to builtin type.

Author: shmakovpn <shmakovpn@yandex.ru>

Date: 2020-10-01
"""
import unittest


class TestAddMethodToClass(unittest.TestCase):
    """
    It is possible to add a method to a class outside of the class
    """
    def test_add_method_to_class(self):
        class A:
            x = 'hello'

        a = A()
        A.get_x = lambda self: self.x

        self.assertEqual(a.get_x(), 'hello')

    def test_add_method_to_list(self):
        """
        It is impossible to add a method to a built-in type
        :return:
        """
        try:
            list.hello = lambda self: f'hello from list'
            some_list = []
            self.assertEqual(some_list.hello(), 'hello from list')
        except TypeError as e:
            pass
        except Exception as e:
            self.assertTrue(False, msg='An unknown exception was raised instead of the expected TypeError')
        else:
            self.assertTrue(False, msg='The expected TypeError exception was not raised')

Let’s write our own context manager that will replace the builtin type to an extended one.

Extending builtins

Author: shmakovpn <shmakovpn@yandex.ru>

Date: 2020-10-01

class extend_builtins.ExtendedDict[source]

This class extends builtin dict.

It adds:

  • return_updated method

return_updated(*args, **kwargs)[source]

Updates this dict, then return a reference to this.

One can say that this approach violates some principles of clean architecture because the method changes the instance and return something not void.

However, this method allows you to use dictionary updates sequentially in one line of code, as well as in lambda functions.

the_extended_dict\
    .return_updated({'key': 'value'})\
    .return_updated({'foo', 'bar'})\
    .do_something_else(...)

reduce(
    lambda a, b: a.return_updated(b),
    my_data,
    ExtendedDict()
)
Parameters
  • args – look at the documentation of update

  • kwargs – look at the documentation of update

Returns

a reference to updated dict

extend_builtins.extend_builtin(cls: Type)AbstractContextManager[Type][source]

The context manager that replaces the corresponding builtin type to a custom one

Parameters

cls – the custom type (it have to be a subclass of the corresponding built-in type)

extend_builtin context manager usage example.

if __name__ == '__main__':
    print(f'before dict={dict}')  # <class 'dict'>
    with extend_builtin(ExtendedDict):
        print(f'with dict={dict}')  # <class '__main__.ExtendedDict'>
        d = dict()
        print(d.return_updated({'foo': 'bar'}))  # {'foo': 'bar'}
    print(f'after dict={dict}')  # <class 'dict'>

Using ExtendedDict with lambda functions

import unittest
from shmakovpn.extend_builtins import ExtendedDict
from functools import reduce
from typing import List, Dict, Any


class TestGroupByExtendedDict(unittest.TestCase):
    """
    This class contains tests of **groupby** using **ExtendedDict**
    """
    data: List[Dict[str, Any]] = [
        {'name': 'alex', 'score': 2, },
        {'name': 'john', 'score': 4, },
        {'name': 'dan', 'score': 1, },
        {'name': 'alex', 'score': 6, },
        {'name': 'dan', 'score': 3, },
    ]
    """the dataset for tests"""

    def test_group_by_extended_dict(self):
        """
        Test for **groupby** that uses **ExtendedDict**
        """
        self.assertEqual(
            reduce(
                lambda a, b: a.return_updated(
                    **{b['name']: a.pop(b['name'], []) + [b['score']]}
                ),
                self.data,
                ExtendedDict(),  # use **ExtendedDict** as an accumulator
            ), {
                'john': [4],
                'alex': [2, 6],
                'dan': [1, 3],
            }
        )