<jupyter_start><jupyter_text>Inheriting from type In the last lectures, we saw how classes can be created by calling the `type` class.But what if we wanted to use something other than `type` to construct classes? Since `type` is a class, maybe we could define a class that inherits from `type` (so we can leverage the actual type creation process), and override some things that would enable us to inject something in the class creation process. Here we want to intercept the creation of the `type` instance before it is created, so we would want to use the `__new__` method. Remember that the `__new__` method basically needs to build and return the new instance. So we'll do the customizations we want, but ultimately we'll punt (delegate) to the `type` class to do the actual work, just adding the tweaks (before and/or after the class creation) we want. Just a quick reminder of how the static `__new__` method works in general:<jupyter_code>class Test:
    def __new__(cls, *args, **kwargs):
        print(f'New instance of {cls} being created with these values:', args, kwargs)
t = Test(10, 20, kw='a')<jupyter_output>New instance of <class '__main__.Test'> being created with these values: (10, 20) {'kw': 'a'}<jupyter_text>And it's really the same as doing this:<jupyter_code>Test.__new__(Test, 10, 20, kw='a')<jupyter_output>New instance of <class '__main__.Test'> being created with these values: (10, 20) {'kw': 'a'}<jupyter_text>Of course, it's now up to us to return an object from the `__new__` function. So, instead of calling `type` to create the class (type), let's create a custom type generator by subclassing `type`.We'll inherit from `type`, and override the `__new__` function to create the instance of the class.<jupyter_code>import math

class CustomType(type):
    def __new__(cls, name, bases, class_dict):
        # above is the signature that type.__new__ has - 
        # and args are collected and passed by Python when we call a class (to create an instance of that class)
        # we'll see where those actually come from later
        print('Customized type creation!')
        cls_obj = super().__new__(cls, name, bases, class_dict)  # delegate to super (type in this case)
        cls_obj.circ = lambda self: 2 * math.pi * self.r  # basically attaching a function to the class
        return cls_obj<jupyter_output><empty_output><jupyter_text>Now let's go through the same process to create our `Circle` class that we used in the last lecture, the manual way, but using `CustomType` instead of `type`.<jupyter_code>class_body = """
def __init__(self, x, y, r):
    self.x = x
    self.y = y
    self.r = r

def area(self):
    return math.pi * self.r ** 2
"""<jupyter_output><empty_output><jupyter_text>And we create our class dictionary by executing the above code in the context of that dictionary:<jupyter_code>class_dict = {}
exec(class_body, globals(), class_dict)<jupyter_output><empty_output><jupyter_text>Then we create the `Circle` class:<jupyter_code>Circle = CustomType('Circle', (), class_dict)<jupyter_output>Customized type creation!<jupyter_text>We basically customized the class creation, and `Circle` is just a standard object, but, as you can see below, the type of our class is no longer `type`, but `CustomType`.<jupyter_code>type(Circle)<jupyter_output><empty_output><jupyter_text>Of course, `Circle` is still an instance of `type` since `CustomType` is a subclass of `type`:<jupyter_code>isinstance(Circle, CustomType), issubclass(CustomType, type)<jupyter_output><empty_output><jupyter_text>And just like before, `Circle` still has the `__init__` and `area` methods:<jupyter_code>hasattr(Circle, '__init__'), hasattr(Circle, 'area')<jupyter_output><empty_output><jupyter_text>So we can use `Circle` just as normal:<jupyter_code>c = Circle(0, 0, 1)
c.area()<jupyter_output><empty_output><jupyter_text>Additionally, we injected a new function, `circ`, into the class while we were constructing it in the `__new__` method of `CustomType`:<jupyter_code>hasattr(Circle, 'circ')
c.circ()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0972972972972973, 'fraction_numerical': 0.005159705159705159, 'mean_word_length': 5.331259720062208, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 44, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '14526', 'license': 'Apache-2.0', 'n_tokens_mistral': 1277, 'n_tokens_neox': 1235, 'n_words': 500, 'path': 'dd_1/Part 4/Section 14 - Metaprogramming/04 - Inheriting from type.ipynb', 'repo_name': 'rebekka-halal/bg', 'size': '4070'}