Diferències
Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
| Ambdós costats versió prèvia Revisió prèvia Següent revisió | Revisió prèvia | ||
| info:cursos:pue:python-pcpp1:m1:5.1 [05/11/2023 21:35] – suprimit - edició externa (Unknown date) 127.0.0.1 | info:cursos:pue:python-pcpp1:m1:5.1 [06/11/2023 10:03] (actual) – [resposta] mate | ||
|---|---|---|---|
| Línia 1: | Línia 1: | ||
| + | = 5.1 Metaprogramming | ||
| + | == Introduction to metaclasses | ||
| + | Metaprogramming is a programming technique in which computer programs have the ability to modify their own or other programs’ codes. It may sound like an idea from a science fiction story, but the idea was born and implemented in the early 1960s. | ||
| + | For Python, code modifications can occure while the code is being executed, and you might have already experienced it while implementing decorators, overriding operators, or even implementing the properties protocol. | ||
| + | |||
| + | It may look like syntactic sugar, because in many cases metaprogramming allows programmers to minimize the number of lines of code to express a solution, in turn reducing development time. | ||
| + | |||
| + | But the truth is that this technique could be used for tool preparation; | ||
| + | |||
| + | Another example of metaprogramming is the **metaclass** concept, which is one of the most advanced concepts presented in this course. | ||
| + | |||
| + | |||
| + | Tim Peters, the Python guru who authored the **Zen of Python**, expressed his feelings about metaclasses in the **comp.lang.python** newsgroup on 12/22/2002: | ||
| + | |||
| + | // | ||
| + | |||
| + | Don’t worry, we'll touch on the “deeper magic” in a benign way. Understanding Python metaclasses is worthwhile, because it leads to a better understanding of what is happening under Python' | ||
| + | |||
| + | In Python, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass allows for the customization of class instantiation. | ||
| + | |||
| + | The functionality of the metaclass partly coincides with that of class decorators, but metaclasses act in a different way than decorators: | ||
| + | |||
| + | * decorators bind the names of decorated functions or classes to new callable objects. Class decorators are applied when classes are instantiated; | ||
| + | * metaclasses redirect class instantiations to dedicated logic, contained in metaclasses. Metaclasses are applied when class definitions are read to create classes, well before classes are instantiated. | ||
| + | |||
| + | Metaclasses usually enter the game when we program advanced modules or frameworks, where a lot of precise automation must be provided. | ||
| + | |||
| + | The typical use cases for metaclasses: | ||
| + | |||
| + | * logging; | ||
| + | * registering classes at creation time; | ||
| + | * interface checking; | ||
| + | * automatically adding new methods; | ||
| + | * automatically adding new variables. | ||
| + | |||
| + | In Python' | ||
| + | |||
| + | Run the code in the right pane to see the '' | ||
| + | |||
| + | <code python> | ||
| + | class Dog: | ||
| + | pass | ||
| + | |||
| + | |||
| + | age = 10 | ||
| + | codes = [33, 92] | ||
| + | dog = Dog() | ||
| + | |||
| + | print(type(age)) | ||
| + | print(type(codes)) | ||
| + | print(type(dog)) | ||
| + | print(type(Dog)) | ||
| + | |||
| + | </ | ||
| + | |||
| + | <code ; output> | ||
| + | <class ' | ||
| + | <class ' | ||
| + | <class ' | ||
| + | <class ' | ||
| + | </ | ||
| + | |||
| + | We can see that objects in Python are defined by their inherent classes. | ||
| + | |||
| + | The example also shows that we can create our own classes, and those classes will be instances of the **type** special class, which is the default **metaclass** responsible for creating classes. | ||
| + | |||
| + | Let's perform one more experiment that will respond to the question: what type of objects are built-in classes and the metaclass '' | ||
| + | |||
| + | <code python> | ||
| + | for t in (int, list, type): | ||
| + | print(type(t)) | ||
| + | </ | ||
| + | | ||
| + | The results are quite interesting: | ||
| + | <code ; output> | ||
| + | <class ' | ||
| + | <class ' | ||
| + | <class ' | ||
| + | </ | ||
| + | |||
| + | These observations lead us to the following conclusions: | ||
| + | |||
| + | * metaclasses are used to create classes; | ||
| + | * classes are used to create objects; | ||
| + | * the type of the metaclass '' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | To extend the above observations, | ||
| + | |||
| + | * '' | ||
| + | * metaclasses are subclasses of the '' | ||
| + | |||
| + | Before we start creating our own metaclasses, | ||
| + | |||
| + | We should get familiar with some special attributes: | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | <code python> | ||
| + | class Dog: | ||
| + | pass | ||
| + | |||
| + | dog = Dog() | ||
| + | print('" | ||
| + | print() | ||
| + | print(' | ||
| + | print(' | ||
| + | print() | ||
| + | print(' | ||
| + | print() | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | The output of the code presented in the right pane: | ||
| + | |||
| + | <code ; output> | ||
| + | " | ||
| + | |||
| + | class " | ||
| + | instance " | ||
| + | |||
| + | class " | ||
| + | |||
| + | class " | ||
| + | |||
| + | object " | ||
| + | </ | ||
| + | |||
| + | The same information stored in '' | ||
| + | <code python> | ||
| + | for element in (1, ' | ||
| + | print(element, | ||
| + | </ | ||
| + | <code ; output> | ||
| + | 1 is <class ' | ||
| + | a is <class ' | ||
| + | True is <class ' | ||
| + | </ | ||
| + | |||
| + | When the '' | ||
| + | |||
| + | For the invocation of '' | ||
| + | * the argument specifies the class name; this value becomes the '' | ||
| + | * the argument specifies a tuple of the base classes from which the newly created class is inherited; this argument becomes the __bases__ attribute of the class; | ||
| + | * the argument specifies a dictionary containing method definitions and variables for the class body; the elements of this argument become the __dict__ attribute of the class and state the class namespace. | ||
| + | |||
| + | A very simple example, when both '' | ||
| + | <code python> | ||
| + | Dog = type(' | ||
| + | |||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | </ | ||
| + | |||
| + | As a result, we have created the simple class “Dog”. | ||
| + | |||
| + | <code ; output> | ||
| + | The class name is: Dog | ||
| + | The class is an instance of: <class ' | ||
| + | The class is based on: (<class ' | ||
| + | The class attributes are: {' | ||
| + | </ | ||
| + | |||
| + | The more complex example that dynamically creates a fully functional class is presented in the right pane. | ||
| + | |||
| + | <code python> | ||
| + | def bark(self): | ||
| + | print(' | ||
| + | |||
| + | class Animal: | ||
| + | def feed(self): | ||
| + | print(' | ||
| + | |||
| + | Dog = type(' | ||
| + | |||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | print(' | ||
| + | |||
| + | doggy = Dog() | ||
| + | doggy.feed() | ||
| + | doggy.bark() | ||
| + | |||
| + | </ | ||
| + | As you can see, the Dog class is now equipped with two methods (feed() and bark()) and the instance attribute age. | ||
| + | <code ; output> | ||
| + | The class name is: Dog | ||
| + | The class is an instance of: <class ' | ||
| + | The class is based on: (<class ' | ||
| + | The class attributes are: {' | ||
| + | It is feeding time! | ||
| + | Woof, woof | ||
| + | </ | ||
| + | |||
| + | This way of creating classes, using the type function, is substantial for Python' | ||
| + | |||
| + | * after the '' | ||
| + | * the type is responsible for calling the '' | ||
| + | * '' | ||
| + | * '' | ||
| + | Metaclasses usually implement these two methods ('' | ||
| + | |||
| + | Now that we know what’s happening under Python' | ||
| + | |||
| + | It’s important to remember that metaclasses are classes that are instantiated to get classes. | ||
| + | |||
| + | The first step is to define a metaclass that derives from the '' | ||
| + | <code python> | ||
| + | class My_Meta(type): | ||
| + | def __new__(mcs, | ||
| + | obj = super().__new__(mcs, | ||
| + | obj.custom_attribute = 'Added by My_Meta' | ||
| + | return obj | ||
| + | </ | ||
| + | | ||
| + | Pay attention to the fact that: | ||
| + | |||
| + | * the class '' | ||
| + | * our own '' | ||
| + | * '' | ||
| + | * a class attribute is created additionally; | ||
| + | * the class is returned. | ||
| + | |||
| + | Let's make use of the metaclass to create our own, domain-specific class, and check if it’s armed with the custom attribute: | ||
| + | <code python> | ||
| + | class My_Object(metaclass=My_Meta): | ||
| + | pass | ||
| + | |||
| + | print(My_Object.__dict__) | ||
| + | </ | ||
| + | |||
| + | Pay attention to the fact that: | ||
| + | |||
| + | * a new class has been defined in a way where a custom metaclass is listed in the class definition as a metaclass. This is a way to tell Python to use '' | ||
| + | * we are printing the contents of the class '' | ||
| + | |||
| + | <code python> | ||
| + | class My_Meta(type): | ||
| + | def __new__(mcs, | ||
| + | obj = super().__new__(mcs, | ||
| + | obj.custom_attribute = 'Added by My_Meta' | ||
| + | return obj | ||
| + | |||
| + | class My_Object(metaclass=My_Meta): | ||
| + | pass | ||
| + | |||
| + | print(My_Object.__dict__) | ||
| + | |||
| + | </ | ||
| + | <code ; output> | ||
| + | {' | ||
| + | </ | ||
| + | Indeed, the class attribute has been created. | ||
| + | |||
| + | Congratulations! You have just examined you first metaclass! | ||
| + | |||
| + | == Metaprogramming – another metaclass | ||
| + | Let's run a more serious experiment: try to build a metaclass responsible for completing classes with a method (if missing) to ensure that all your classes are equipped with a method named ' | ||
| + | |||
| + | As you can see, there is a '' | ||
| + | |||
| + | In '' | ||
| + | |||
| + | In contrast, in '' | ||
| + | |||
| + | Both classes rely on the same metaclass. | ||
| + | |||
| + | When you run the code, you'll see that both class instances are equipped with '' | ||
| + | |||
| + | <code python> | ||
| + | def greetings(self): | ||
| + | print(' | ||
| + | |||
| + | class My_Meta(type): | ||
| + | def __new__(mcs, | ||
| + | if ' | ||
| + | dictionary[' | ||
| + | obj = super().__new__(mcs, | ||
| + | return obj | ||
| + | |||
| + | class My_Class1(metaclass=My_Meta): | ||
| + | pass | ||
| + | |||
| + | class My_Class2(metaclass=My_Meta): | ||
| + | def greetings(self): | ||
| + | print(' | ||
| + | |||
| + | myobj1 = My_Class1() | ||
| + | myobj1.greetings() | ||
| + | myobj2 = My_Class2() | ||
| + | myobj2.greetings() | ||
| + | </ | ||
| + | <code ; output> | ||
| + | Just a greeting function, but it could be something more serious like a check sum | ||
| + | We are ready to greet you! | ||
| + | </ | ||
| + | This is how metaclasses become very useful – they can control the process of class instantiation, | ||
| + | |||
| + | == LAB | ||
| + | === Scenario | ||
| + | * Imagine you’ve been given a task to clean up the code of a system developed in Python – the code should be treated as legacy code; | ||
| + | * the system was created by a group of volunteers who worked with no clear “clean coding” rules; | ||
| + | * the system suffers from a problem: we don’t know in which order the classes are created, so it causes multiple dependency problems; | ||
| + | * your task is to prepare a metaclass that is responsible for: | ||
| + | * equipping all newly instantiated classes with time stamps, persisted in a class attribute named '' | ||
| + | * equipping all newly instantiated classes with the '' | ||
| + | * The metaclass should have its own class variable (a list) that contains a list of the names of the classes instantiated by the metaclass (tip: append the class name in the '' | ||
| + | |||
| + | * Your metaclass should be used to create a few distinct legacy classes; | ||
| + | * create objects based on the classes; | ||
| + | * list the class names that are instantiated by your metaclass. | ||
| + | |||
| + | === resposta | ||
| + | <code python> | ||
| + | import time | ||
| + | import datetime | ||
| + | |||
| + | def get_instantiation_time(self): | ||
| + | human_readable = datetime.datetime.fromtimestamp(self.instantiation_time) | ||
| + | print(f" | ||
| + | |||
| + | class MetaClassInstantation(type): | ||
| + | instantiated = list() | ||
| + | def __new__(mcs, | ||
| + | if ' | ||
| + | dictionary[' | ||
| + | MetaClassInstantation.instantiated.append(name) | ||
| + | obj = super().__new__(mcs, | ||
| + | obj.instantiation_time = time.time() | ||
| + | return obj | ||
| + | |||
| + | class Uno(metaclass=MetaClassInstantation): | ||
| + | pass | ||
| + | |||
| + | class Dos(metaclass=MetaClassInstantation): | ||
| + | pass | ||
| + | |||
| + | uno = Uno() | ||
| + | dos = Dos() | ||
| + | |||
| + | uno.get_instantiation_time() | ||
| + | dos.get_instantiation_time() | ||
| + | |||
| + | print(MetaClassInstantation.instantiated) | ||
| + | </ | ||
| + | |||
| + | <code ; output> | ||
| + | Hora d' | ||
| + | Hora d' | ||
| + | [' | ||
| + | </ | ||
| + | |||
| + | <code python ; resposta oficial> | ||
| + | import time | ||
| + | |||
| + | def get_class_instantiation_time(self): | ||
| + | return self.class_instantiation_time | ||
| + | |||
| + | class CleanCodeGuard(type): | ||
| + | classes_created = [] | ||
| + | |||
| + | def __new__(mcs, | ||
| + | if ' | ||
| + | dictionary[' | ||
| + | obj = super().__new__(mcs, | ||
| + | |||
| + | obj.class_instantiation_time = time.time() | ||
| + | CleanCodeGuard.classes_created.append(name) | ||
| + | time.sleep(1) | ||
| + | return obj | ||
| + | |||
| + | class My_Class1(metaclass=CleanCodeGuard): | ||
| + | pass | ||
| + | |||
| + | class My_Class2(metaclass=CleanCodeGuard): | ||
| + | pass | ||
| + | |||
| + | my_object1 = My_Class1() | ||
| + | print(my_object1.get_class_instantiation_time()) | ||
| + | |||
| + | my_object2 = My_Class2() | ||
| + | print(my_object2.get_class_instantiation_time()) | ||
| + | |||
| + | print(CleanCodeGuard.classes_created) | ||
| + | |||
| + | </ | ||
| + | |||
| + | The concept of metaclasses looks hard at first glance, but if you’re responsible for API design or development, | ||
| + | |||
| + | When you want to change your classes automatically, | ||
| + | |||
| + | In all other cases, you should agree with Tim Peters that you shouldn’t worry about metaclasses, | ||
| + | |||
| + | But still, it’s beneficial to understand metaclasses in order to know when to employ them to solve a class type problem. | ||