• Aucun résultat trouvé

A Decorator With or Without Arguments

Dans le document Pro Django (Page 48-52)

One other option for decorators is to provide a single decorator that can function in both of the previous situations: with arguments and without. This is more complex but worth exploring.

The goal is to allow the decorator to be called with or without arguments so it’s safe to assume that all arguments are optional; any decorator with required arguments can’t use this technique. With that in mind, the basic idea is to add an extra optional argument at the begin-ning of the list, which will receive the function to be decorated. Then, the decorator structure includes the necessary logic to determine whether it’s being called to add arguments or to decorate the target function.

:::`ab`a_kn]pa$bqj_9Jkja(lnabet9#@a_kn]pa`#%6

***`ab`a_kn]pa`$bqj_%6

***Pdeonapqnjopdabej]h(`a_kn]pa`

***bqj_pekj(nac]n`haookbdkseps]o_]hha`

***`absn]llan$&]nco(&&gs]nco%6

***napqnj#!o6!o#!$lnabet(bqj_$&]nco(&&gs]nco%%

***napqnjsn]llan

***ebbqj_eoJkja6

***Pda`a_kn]pkns]o_]hha`sepd]ncqiajpo

***`ab`a_kn]pkn$bqj_%6

***napqnj`a_kn]pa`$bqj_%

***napqnj`a_kn]pkn

***Pda`a_kn]pkns]o_]hha`sepdkqp]ncqiajpo

***napqnj`a_kn]pa`$bqj_%

***

:::<`a_kn]pa

***`abpaop$](^%6

***napqnj]'^

***

:::paop$-/(-3%

#@a_kn]pa`6/,#

:::<`a_kn]pa$lnabet9#=ncqiajpo#%

***`abpaop$](^%6

***napqnj]'^

***

:::paop$-/(-3%

#=ncqiajpo6/,#

This requires that all arguments passed to the decorator be passed as keyword arguments, which generally makes for more readable code. One downside is how much boilerplate would have to be repeated for each decorator that uses this approach.

Thankfully, like most boilerplate in Python, it’s possible to factor it out into a reusable form, so new decorators can be defined more easily, using yet another decorator. The follow-ing function can be used to decorate other functions, providfollow-ing all the functionality necessary to accept arguments or it can be used without them.

:::`abklpekj]h[]ncqiajpo[`a_kn]pkn$na]h[`a_kn]pkn%6

***`ab`a_kn]pkn$bqj_9Jkja(&&gs]nco%6

***Pdeoeopda`a_kn]pknpd]psehh^a

***atlkoa`pkpdanaopkbukqnlnkcn]i

***`ab`a_kn]pa`$bqj_%6

***Pdeonapqnjopdabej]h(`a_kn]pa`

***bqj_pekj(nac]n`haookbdkseps]o_]hha`

***`absn]llan$&](&&gs%6

***napqnjna]h[`a_kn]pkn$bqj_(](gs(&&gs]nco%

***napqnjsn]llan

***ebbqj_eoJkja6

***Pda`a_kn]pkns]o_]hha`sepd]ncqiajpo

***`ab`a_kn]pkn$bqj_%6

***napqnj`a_kn]pa`$bqj_%

***napqnj`a_kn]pkn

***Pda`a_kn]pkns]o_]hha`sepdkqp]ncqiajpo

***napqnj`a_kn]pa`$bqj_%

***napqnj`a_kn]pkn

***

:::<klpekj]h[]ncqiajpo[`a_kn]pkn

***`ab`a_kn]pa$bqj_(]nco(gs]nco(lnabet9#@a_kn]pa`#%6

***napqnj#!o6!o#!$lnabet(bqj_$&]nco(&&gs]nco%%

***

:::<`a_kn]pa

***`abpaop$](^%6

***napqnj]'^

***

:::paop$-/(-3%

#@a_kn]pa`6/,#

:::paop9`a_kn]pa$paop(lnabet9#@a_kn]pa`]c]ej#%

:::paop$-/(-3%

#@a_kn]pa`]c]ej6@a_kn]pa`6/,#

This makes the definition of individual decorators much simpler and more straightfor-ward. The resulting decorator behaves exactly like the one in the previous example, but it is able to be used with or without arguments. The most notable change that this new technique requires is that the real decorator being defined will receive the following three values:

฀ ฀bqj_—The function that was decorated using the newly generated decorator

฀ ฀]nco—A tuple containing positional arguments that were passed to the function

฀ ฀gs]nco—A dictionary containing keyword arguments that were passed to the function An important thing to realize, however, is that the ]nco and gs]nco that the decorator receives are passed as positional arguments, without the usual asterisk notation. Then, when passing them on to the wrapped function, the asterisk notation must be used to make sure the function receives them without having to know about how the decorator works.

Descriptors

Ordinarily, referencing an attribute on an object accesses the attribute’s value directly, with-out any complications. Getting and setting attributes directly affects the value in the object’s instance namespace. Sometimes, additional work has to be done when accessing these values.

฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀

฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀

฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀

฀ ฀ ฀ ฀ ฀ ฀ ฀฀ ฀ ฀ ฀ ฀ ฀ ฀ ฀

In some programming languages, this type of behavior is made possible by creating extra instance methods for accessing those attributes that need it. While functional, this approach leads to a few problems. For starters, these behaviors are typically more associated with the type of data stored in the attribute than some aspect of the instance it’s attached to. By requiring that the object supply additional methods for accessing this data, every object that contains this behavior will have to provide the necessary code in its instance methods.

One other significant issue is what happens when an attribute that used to be simple sud-denly needs this more advanced behavior. When changing from a simple attribute to a method, all references to that attribute also need to be changed. To avoid this, programmers in these lan-guages have adopted a standard practice of always creating methods for attribute access so that any changes to the underlying implementation won’t affect any existing code.

It’s never fun to touch that much of your code for a change to how one attribute is accessed, so Python provides a different approach to the problem. Rather than requiring the object to be responsible for special access to its attributes, the attributes themselves can provide this behavior. Descriptors are a special type of object that, when attached to a class, can intervene when the attribute is accessed, providing any necessary additional behavior.

:::eilknp`]papeia

:::_h]oo?qnnajp@]pa$k^fa_p%6

***`ab[[cap[[$oahb(ejop]j_a(ksjan%6

***napqnj`]papeia*`]pa*pk`]u$%

***`ab[[oap[[$oahb(ejop]j_a(r]hqa%6

***n]eoaJkpEilhaiajpa`Annkn$?]j#p_d]jcapda_qnnajp`]pa*%

***

:::_h]ooAt]ilha$k^fa_p%6

***`]pa9?qnnajp@]pa$%

***

:::a9At]ilha$%

:::a*`]pa

`]papeia*`]pa$.,,4(--(.0%

:::a*`]pa9`]papeia*`]pa*pk`]u$%

Pn]_a^]_g$ikopna_ajp_]hhh]op%6

***

JkpEilhaiajpa`Annkn6?]j#p_d]jcapda_qnnajp`]pa*

Creating a descriptor is as simple as creating a standard new- style class (by inheriting from k^fa_p), and specifying at least one of the following methods. The descriptor class can include any other attributes or methods as necessary to perform the tasks it’s responsible for, while the following methods constitute a kind of protocol that enables this special behavior.

__get__(self, instance, owner)

When retrieving the value of an attribute (r]hqa9k^f*]ppn), this method will be called instead, allowing the descriptor to do some extra work before returning the value. In addi-tion to the usual oahb representing the descriptor object, this getter method receives two arguments.

฀ ฀ejop]j_a—The instance object containing the attribute that was referenced. If the attri-bute was referenced as an attriattri-bute of a class rather than an instance, this will be Jkja.

฀ ฀ksjan—The class where the descriptor was assigned. This will always be a class object.

The ejop]j_a argument can be used to determine whether the descriptor was accessed from an object or its class. If ejop]j_a is Jkja, the attribute was accessed from the class rather than an instance. This can be used to raise an exception if the descriptor is being accessed in a way that it shouldn’t.

Also, by defining this method, you make the descriptor responsible for retrieving and returning a value to the code that requested it. Failing to do so will force Python to return its default return value of Jkja.

Note that, by default, descriptors don’t know what name they were given when declared as attributes. Django models provide a way to get around this, which is described in Chapter 3, but apart from that, descriptors only know about their data, not their names.

__set__(self, instance, value)

When setting a value to a descriptor (k^f*]ppn9r]hqa), this method is called so that a more specialized process can take place. Like [[cap[[, this method receives two arguments in addi-tion to the standard oahb.

฀ ฀ejop]j_a—The instance object containing the attribute that was referenced. This will never be Jkja.

฀ ฀r]hqa—The value being assigned.

Also note that the [[oap[[ method of descriptors will only be called when the attribute is assigned on an object and will never be called when assigning the attribute on the class where the descriptor was first assigned. This behavior is by design, and prohibits the descriptor from taking complete control over its access. External code can still replace the descriptor by assign-ing a value to the class where it was first assigned.

Also note that the return value from [[oap[[ is irrelevant. The method itself is solely respon-sible for storing the supplied value appropriately.

Dans le document Pro Django (Page 48-52)