Professional Python. Luke Sneeringer
Чтение книги онлайн.

Читать онлайн книгу Professional Python - Luke Sneeringer страница 7

СКАЧАТЬ is worth noting that if a class decorated with @sortable_by_creation_time defined its own __lt__ and __gt__ methods, then this decorator would override them.

      The _created value by itself does little good if the class does not recognize that it is to be used for sorting. Therefore, the decorator also adds __lt__ and __gt__ magic methods. These cause the < and > operators to return True or False based on the result of those methods. This also affects the behavior of sorted and other similar functions.

      This is all that is necessary to make an arbitrary class's instances sortable by their instantiation time. This decorator can be applied to any class, including many classes with unrelated ancestry.

      Here is an example of a simple class with instances sortable by when they are created:

      Bear in mind that simply because a decorator can be used to solve a problem, that does not mean that it is necessarily the appropriate solution.

      For instance, when it comes to this example, the same thing could be accomplished by using a "mixin," or a small class that simply defines the appropriate __init__, __lt__, and __gt__ methods. A simple approach using a mixin would look like this:

      Applying the mixin to a class can be done using Python's multiple inheritance:

      This approach has different advantages and drawbacks. On the one hand, it will not mercilessly plow over __lt__ and __gt__ methods defined by the class or its superclasses (and it may not be obvious when the code is read later that the decorator was clobbering two methods).

      On the other hand, it would be very easy to get into a situation where the __init__ method provided by SortableByCreationTime does not run. If MyClass or MySuperclass or any class in MySuperclass's ancestry defines an __init__ method, it will win out. Reversing the class order does not solve this problem; it simply reverses it.

      By contrast, the decorator handles the __init__ case very well, simply by augmenting the effect of the decorated class's __init__ method and otherwise leaving it intact.

      So, which approach is the correct approach? It depends.

      Type Switching

      Thus far, the discussion in this chapter has only considered cases in which a decorator is expected to decorate a function and provide a function, or when a decorator is expected to decorate a class and provide a class.

      There is no reason why this relationship must hold, however. The only requirement for a decorator is that it is a callable that accepts a callable and returns the callable. There is no requirement that it return the same kind of callable.

      One more advanced use case for decorators is actually when they do not do this. In particular, it can be valuable for a decorator to decorate a function, but return a class. This can be a very useful tool for situations where the amount of boilerplate code grows, or for allowing developers to use a simple function for simple cases, but subclass a class in an application's API for more advanced cases.

      An example of this in the wild is a decorator used in a popular task runner in the Python ecosystem: celery. The celery package provides a @celery.task decorator that is expected to decorate a function. What the decorator actually does is return a subclass of celery's internal Task class, with the decorated function being used within the subclass's run method.

      Consider the following trivial example of a similar approach:

      What is happening here? The decorator creates a subclass of Task and returns the class. The class is callable calling a class creates an instance of that class and runs its _init_ method

      The value of doing this is that it provides a hook for lots of augmentation. The base Task class can define much, much more than just the run method. For example, a start method might run the task asynchronously. The base class might also provide methods to save information about the task's status. Using a decorator that swaps out a function for a class here enables the developer to only consider the actual body of his or her task, and the decorator does the rest of the work.

      You can see this in action by taking an instance of the class and running its identify method, as shown here:

A Pitfall

      This exact approach carries with it some problems. In particular, once a task function is decorated with the @task_class decorator, it becomes a class.

      Consider the following simple task function decorated in this way:

      Now, attempt to run it directly in the interpreter:

      That is a bad thing. This decorator alters the function in such a way that if the developer runs it, it does not do what anyone expects. It is usually not acceptable to expect the function to be declared as foo and then run using the convoluted foo().run() (which is what would be necessary in this case).

      Fixing this requires putting a little more thought into how both the decorator and the Task class are constructed. Consider the following amended version:

      A couple of key differences exist here. The first is the addition of the __call__ method to the base Task class. The second difference (which complements the first) is that the @task_class decorator now returns an instance of the TaskSubclass, rather than the class itself.

      Конец ознакомительного фрагмента.

      Текст предоставлен ООО «ЛитРес».

      Прочитайте эту книгу целиком, купив полную легальную версию на ЛитРес.

      Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.

iVBORw0KGgoAAAANSUhEUgAABAEAAAAhCAYAAACoYLI9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAY6SURBVHhe7dmNbdw6EEXhbSSVpBVXklZSiTtxN344Rq5x34DSSnHsONb5AMH6IzlDKgHJvT1LkiRJkqRLcBNAkiRJkqSLcBNAkiRJkqSLcBNAkiRJkqSLcBNAk СКАЧАТЬ