Categorías
Python

Python API REST con el frasco, Connexion, y SQLAlchemy – Parte 4

 

Tabla de Contenidos

  • ¿Cuáles son Python Descriptores?
  • cómo funcionan los descriptores en InternalsPython Descriptores de Python en PropertiesPython descriptores en métodos y funciones
  • Python descriptores en Propiedades
  • Descriptores Python en Métodos y funciones
  • cómo los atributos se tiene acceso con la cadena de búsqueda
  • cómo utilizar Python Descriptores Correctamente
  • ¿Por qué utilizar descriptores Python? Lazy PropertiesD.RY Código
  • Lazy Propiedades
  • D.R.Y. Código
  • Conclusión
  • Python Descriptores en Propiedades
  • Descriptores Python en Métodos y funciones
  • Lazy Propiedades
  • D.R.Y. Código

Descriptores son una característica específica de Python que el poder una gran cantidad de la magia oculta bajo el capó del lenguaje. Si alguna vez has pensado que los descriptores de Python son un tema avanzado con pocas aplicaciones prácticas, entonces este tutorial es la herramienta perfecta para ayudarle a entender esta característica de gran alcance. Llegará a entender por qué descriptores de Python son un tema tan interesante, y qué tipo de uso de los casos se pueden aplicar a.

Al final de este tutorial, usted sabrá: descriptores

  • Lo Python son
  • Cuando se usan en partes internas de Python
  • Cómo implementar sus propios descriptores
  • Al utilizar Python descriptores

Este tutorial está destinado para el intermedio a los desarrolladores avanzados de Python que se refiere a componentes internos de Python. Sin embargo, si no estás en este nivel, sin embargo, a continuación, sólo sigue leyendo! Encontrará información útil sobre Python y la cadena de búsqueda. Bono

gratuito: Haga clic aquí para obtener acceso a una conexión «El poder de Python decoradores» guía que le muestra 3 patrones y técnicas avanzadas decorador se puede utilizar para escribir a más limpio y más programas Pythonic.

¿Cuáles son los descriptores de Python?

descriptores son objetos de Python que poner en práctica un método del protocolo descriptor , que le da la capacidad de crear objetos que tienen un comportamiento especial cuando se acceda a ellos como atributos de otros objetos. Aquí se puede ver la definición correcta del protocolo de descriptor :

__get__(self, obj, type=None) -> object
__set__(self, obj, value) -> None
__delete__(self, obj) -> None
__set_name__(self, owner, name)

Si sus implementos descriptor solo consiguen .__ __ (), entonces se dice que es un descriptor de datos no . Si implementa .__ conjunto __ () o de eliminación .__ __ (), entonces se dice que es un descriptor de datos . Tenga en cuenta que esta diferencia no es sólo sobre el nombre, pero es también una diferencia en el comportamiento. Esto se debe a descriptor de datos s tienen precedencia durante el proceso de búsqueda, como se verá más adelante.

Ven a ver el siguiente ejemplo, que define un descriptor que los registros de algo en la consola cuando se accede:

# descriptors.py
class Verbose_attribute():
def __get__(self, obj, type=None) -> object:
print("accessing the attribute to get the value")
return 42
def __set__(self, obj, value) -> None:
print("accessing the attribute to set the value")
raise AttributeError("Cannot change the value")

class Foo():
attribute1 = Verbose_attribute()

my_foo_object = Foo()
x = my_foo_object.attribute1
print(x)

En el ejemplo anterior, Verbose_attribute () implementa el protocolo descriptor. Una vez que se crea una instancia como un atributo de Foo, se puede considerar un descriptor.

como un descriptor, tiene comportamiento de enlace cuando se accede utilizando la notación de punto. En este caso, el descriptor registra un mensaje en la consola cada vez que se accede a obtener o establecer un valor:

  • Cuando se accede a .__ get __ () el valor, siempre devuelve el valor 42.
  • Cuando se accede a .__ conjunto __ () un valor específico, se genera una excepción AttributeError, que es la forma recomendada para implementar descriptores de sólo lectura.

Ahora, ejecute el ejemplo anterior y verá el descriptor ingrese el acceso a la consola antes de devolver el valor constante:

$ python descriptors.py
accessing the attribute to get the value
42

Aquí, cuando intenta acceder atributo1, el descriptor registra este acceso a la consola, como se se define en .__ __ get ().

Cómo Descriptores trabajo en Python Internos

Si usted tiene experiencia como desarrollador de Python orientado a objetos, entonces se puede pensar que el enfoque del ejemplo anterior es un poco excesivo. Se podría conseguir el mismo resultado mediante el uso de propiedades. Si bien esto es cierto, es posible que se sorprenda al saber que las propiedades en Python son sólo … descriptores! Verá más adelante que las propiedades no son la única característica que hacen uso de Python descriptores.

Python Descriptores en Propiedades

Si desea obtener el mismo resultado que el ejemplo anterior sin utilizar explícitamente un descriptor de Python, a continuación, el enfoque más sencillo es utilizar una propiedad . En el siguiente ejemplo se utiliza un propiedad que registra un mensaje en la consola cuando se accede:

# property_decorator.py
class Foo():
@property
def attribute1(self) -> object:
print("accessing the attribute to get the value")
return 42

@attribute1.setter
def attribute1(self, value) -> None:
print("accessing the attribute to set the value")
raise AttributeError("Cannot change the value")

my_foo_object = Foo()
x = my_foo_object.attribute1
print(x)

el ejemplo anterior hace uso de decoradores para definir una propiedad, pero como se sabe, decoradores son sólo azúcar sintáctico. El ejemplo antes, de hecho, se puede escribir de la siguiente manera:

# property_function.py
class Foo():
def getter(self) -> object:
print("accessing the attribute to get the value")
return 42

def setter(self, value) -> None:
print("accessing the attribute to set the value")
raise AttributeError("Cannot change the value")

attribute1 = property(getter, setter)

my_foo_object = Foo()
x = my_foo_object.attribute1
print(x)

ya se puede ver que la propiedad ha sido creado por el uso de propiedad (). La firma de esta función es la siguiente:

property(fget=None, fset=None, fdel=None, doc=None) -> object

inmueble () devuelve un objeto de propiedad que implementa el protocolo de descriptor. Utiliza los parámetros fget, FSET y Fdel para la aplicación efectiva de los tres métodos del protocolo.

Descriptores Python en métodos y funciones

Si alguna vez has escrito un programa orientado a objetos en Python, entonces usted sin duda utilizan métodos . Estas son funciones regulares que tienen el primer argumento reservado para la instancia del objeto. Cuando acceda a un método que utiliza la notación de puntos, que está llamando a la función correspondiente y que pasa a la instancia del objeto como primer parámetro.

La magia que transforma su obj.method (* args) llamada en el método (obj, * args) está dentro de una aplicación .__ obtener __ () del objeto función que es, de hecho, un descriptor no datos. En particular, los aperos de objetos función .__ consiguen __ () para que devuelva un método vinculado cuando se accede a él con la notación de punto. Los (* args) que siguen a invocar las funciones haciendo pasar todos los argumentos adicionales necesarios.

Para tener una idea de cómo funciona, echar un vistazo a este ejemplo de Python puro a partir de los documentos oficiales:

import types

class Function(object):
...
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
if obj is None:
return self
return types.MethodType(self, obj)

En el ejemplo anterior, cuando se accede a la función con la notación de puntos, .__ get __ () se llama y una cota se devuelve método.

Esto funciona para los métodos de instancia regulares al igual que lo hace por métodos de clase o métodos estáticos. Por lo tanto, si se llama a un método estático con obj.method (* args), entonces se transforma automáticamente en el método (* args). Del mismo modo, si usted llama a un método de clase con obj.method (tipo (obj), * args), entonces se transforma automáticamente en el método (tipo (obj), * args).

Nota: Para obtener más información sobre args *, echa un vistazo a args y kwargs Python: Desmitificar.

En los documentos oficiales, se pueden encontrar algunos ejemplos de cómo los métodos y los métodos de la clase estática se pondría en práctica si estuvieran escritos en Python puro en lugar de la ejecución real C. Por ejemplo, una posible implementación del método estático podría ser la siguiente:

class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f

def __get__(self, obj, objtype=None):
return self.f

Del mismo modo, esto podría ser una posible implementación del método de clase:

class ClassMethod(object):
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f

def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc

Tenga en cuenta que, en Python, un método de clase es sólo un método estático que toma la referencia de clase como el primer argumento de la lista de argumentos.

cómo los atributos se tiene acceso con la cadena de búsqueda

Para entender un poco más acerca de los descriptores de Python y Python internos, es necesario comprender lo que sucede en Python cuando se accede a un atributo. En Python, cada objeto tiene un built-in __dict__ atributo. Este es un diccionario que contiene todos los atributos definidos en el objeto mismo. Para ver esto en acción, consideremos el siguiente ejemplo:

class Vehicle():
can_fly = False
number_of_weels = 0

class Car(Vehicle):
number_of_weels = 4

def __init__(self, color):
self.color = color

my_car = Car("red")
print(my_car.__dict__)
print(type(my_car).__dict__)

Este código crea un nuevo objeto e imprime el contenido del atributo __dict__ tanto para el objeto y la clase. Ahora, ejecute la secuencia de comandos y analizar la salida para ver el __dict__ atributos establecidos:

{'color': 'red'}
{'__module__': '__main__', 'number_of_weels': 4, '__init__': , '__doc__': None}

Los __dict__ atributos se establecen como se esperaba. Tenga en cuenta que, en Python, todo es un objeto . Una clase es en realidad un objeto así, por lo que también tendrá un atributo __dict__ que contiene todos los atributos y métodos de la clase.

Por lo tanto, lo que está pasando bajo el capó cuando se accede a un atributo en Python? Vamos a hacer algunas pruebas con una versión modificada del ejemplo anterior. Considere este código:

# lookup.py
class Vehicle(object):
can_fly = False
number_of_weels = 0

class Car(Vehicle):
number_of_weels = 4

def __init__(self, color):
self.color = color

my_car = Car("red")

print(my_car.color)
print(my_car.number_of_weels)
print(my_car.can_fly)

En este ejemplo, se crea una instancia de la clase de coche que hereda de la clase de vehículo. A continuación, puede acceder a algunos atributos. Si ejecuta este ejemplo, a continuación se puede ver que se obtiene todos los valores que se pueden esperar:

$ python lookup.py
red
4
False

Aquí, cuando se accede al atributo color de la instancia my_car, en realidad está accediendo a un único valor del atributo del objeto __dict__ mi coche. Cuando se accede a los atributos de la number_of_wheels my_car objeto, en realidad está accediendo a un único valor del atributo __dict__ del coche de clase. Por último, cuando se accede al atributo can_fly, en realidad está accediendo a ella mediante el atributo __dict__ de la clase de vehículo.

Esto significa que es posible reescribir el ejemplo anterior así:

# lookup2.py
class Vehicle():
can_fly = False
number_of_weels = 0

class Car(Vehicle):
number_of_weels = 4

def __init__(self, color):
self.color = color

my_car = Car("red")

print(my_car.__dict__['color'])
print(type(my_car).__dict__['number_of_weels'])
print(type(my_car).__base__.__dict__['can_fly'])

Al probar este nuevo ejemplo, usted debe obtener el mismo resultado:

$ python lookup2.py
red
4
False

Por lo tanto, lo que ocurre cuando se accede al atributo de un objeto con la notación de punto ? ¿Cómo sabe el intérprete de lo que realmente necesita? Bueno, aquí es donde un concepto llamado la cadena de búsqueda entra en juego:

  • En primer lugar, obtendrá el resultado devuelto desde el método __get__ del descriptor de datos lleva el nombre del atributo que está buscando.
  • Si eso falla, entonces obtendrá el valor de __dict__ de su objeto para la llave lleva el nombre del atributo que está buscando.
  • Si eso falla, entonces obtendrá el resultado devuelto desde el método __get__ del descriptor de datos no lleva el nombre del atributo que está buscando.
  • Si eso falla, entonces obtendrá el valor de __dict__ de su tipo de objeto para la llave lleva el nombre del atributo que está buscando.
  • Si eso falla, entonces obtendrá el valor de su __dict__ objeto de tipo padre de la llave lleva el nombre del atributo que está buscando.
  • Si eso falla, entonces el paso anterior se repite para todos los tipos de los padres en el orden de resolución de métodos de su objeto.
  • Si todo lo demás ha fallado, entonces obtendrá una excepción AttributeError.

En primer lugar, obtendrá el resultado devuelto desde el método __get__ del descriptor de datos lleva el nombre del atributo que está buscando.

Si eso falla, entonces obtendrá el valor de __dict__ de su objeto para la llave lleva el nombre del atributo que está buscando.

Si eso falla, entonces obtendrá el resultado devuelto desde el método __get__ del descriptor de datos no lleva el nombre del atributo que está buscando.

Si eso falla, entonces obtendrá el valor de __dict__ de su tipo de objeto para la llave lleva el nombre del atributo que está buscando.

Si eso falla, entonces obtendrá el valor de su __dict__ objeto de tipo padre de la llave lleva el nombre del atributo que está buscando.

Si eso falla, entonces el paso anterior se repite para todos los tipos de los padres en el orden de resolución de métodos de su objeto.

Si todo lo demás ha fallado, entonces obtendrá una excepción AttributeError.

ya se puede ver por qué es importante saber si un descriptor es un descriptor de datos o un descriptor de datos no ? Están en diferentes niveles de la cadena de búsqueda, y verá más adelante que esta diferencia de comportamiento puede ser muy conveniente.

cómo utilizar Python Descriptores correctamente

Si desea utilizar descriptores de Python en su código, a continuación, sólo tiene que implementar el protocolo descriptor . Los métodos más importantes de este protocolo son .__ get __ () y .__ conjunto __ (), que tiene la siguiente firma:

__get__(self, obj, type=None) -> object
__set__(self, obj, value) -> None

Cuando se implementa el protocolo, tenga en cuenta lo siguiente: auto

  • es la instancia del descriptor se ‘volver a escribir.
  • obj es la instancia del objeto de su descriptor está conectada. tipo
  • es el tipo del objeto el descriptor está unido a.

En conjunto .__ __ (), que no tienen la variable de tipo, porque sólo se puede llamar .__ conjunto __ () en el objeto. Por el contrario, puede llamar .__ obtener __ () en el objeto y la clase.

Otra cosa importante a saber es que los descriptores de Python se crean instancias sola vez por clase. Eso significa que cada instancia de una clase que contiene un descriptor acciones esa instancia descriptor. Esto es algo que no puede esperar y puede conducir a una trampa clásica, así:

# descriptors2.py
class OneDigitNumericValue():
def __init__(self):
self.value = 0
def __get__(self, obj, type=None) -> object:
return self.value
def __set__(self, obj, value) -> None:
if value > 9 or value < 0 or int(value) != value: raise AttributeError("The value is invalid") self.value = value class Foo(): number = OneDigitNumericValue() my_foo_object = Foo() my_second_foo_object = Foo() my_foo_object.number = 3 print(my_foo_object.number) print(my_second_foo_object.number) my_third_foo_object = Foo() print(my_third_foo_object.number)

Aquí, usted tiene una clase Foo que define un número de atributos, que es un descriptor. Este descriptor acepta un valor numérico de un dígito y lo almacena en una propiedad del descriptor de sí mismo. Sin embargo, este enfoque no funcionará, ya que cada instancia de las acciones de la misma instancia de Foo descriptor. Lo que ha creado esencialmente es sólo un nuevo atributo de nivel de clase.

intenta ejecutar el código y examine la salida:

$ python descriptors2.py
3
3
3

Se puede ver que todas las instancias de Foo tienen el mismo valor para el número de atributos, a pesar de que el último fue creado después de que se establece el atributo my_foo_object.number.

Así que, ¿cómo se puede resolver este problema? Se podría pensar que sería una buena idea usar un diccionario para guardar todos los valores del descriptor para todos los objetos al que está conectado. Esto parece ser una buena solución ya .__ __ get () y set .__ __ () tienen el atributo obj, que es la instancia del objeto que está conectada. Se podría utilizar este valor como clave para el diccionario.

Desafortunadamente, esta solución tiene un gran inconveniente, que se puede ver en el siguiente ejemplo:

# descriptors3.py
class OneDigitNumericValue():
def __init__(self):
self.value = {}

def __get__(self, obj, type=None) -> object:
try:
return self.value[obj]
except:
return 0

def __set__(self, obj, value) -> None:
if value > 9 or value < 0 or int(value) != value: raise AttributeError("The value is invalid") self.value[obj] = value class Foo(): number = OneDigitNumericValue() my_foo_object = Foo() my_second_foo_object = Foo() my_foo_object.number = 3 print(my_foo_object.number) print(my_second_foo_object.number) my_third_foo_object = Foo() print(my_third_foo_object.number)

En este ejemplo, se utiliza un diccionario para almacenar el valor del atributo numérico de todos los objetos dentro de su descriptor. Cuando se ejecuta este código, verá que funciona muy bien y que el comportamiento es el esperado:

$ python descriptors3.py
3
0
0

Por desgracia, la desventaja aquí es que el descriptor es mantener una fuerte referencia al objeto propietario. Esto significa que si se destruye el objeto, entonces la memoria no se libera porque el recolector de basura sigue encontrando una referencia a ese objeto dentro del descriptor!

Usted puede pensar que la solución a este problema podría ser la utilización de referencias débiles. Mientras que puede, que tendría que lidiar con el hecho de que no todo se puede hacer referencia como débil y que, cuando los objetos quedan recogidos, desaparecen de su diccionario.

La mejor solución en este caso es simplemente no almacenar valores en el descriptor en sí, sino para almacenarlos en el objeto que el descriptor está conectada. Pruebe este enfoque siguiente:

# descriptors4.py
class OneDigitNumericValue():
def __init__(self, name):
self.name = name

def __get__(self, obj, type=None) -> object:
return obj.__dict__.get(self.name) or 0

def __set__(self, obj, value) -> None:
obj.__dict__[self.name] = value

class Foo():
number = OneDigitNumericValue("number")

my_foo_object = Foo()
my_second_foo_object = Foo()

my_foo_object.number = 3
print(my_foo_object.number)
print(my_second_foo_object.number)

my_third_foo_object = Foo()
print(my_third_foo_object.number)

En este ejemplo, cuando se establece un valor para el atributo número de su objeto, las tiendas de descriptores de TI en el atributo __dict__ del objeto al que está conectado con el mismo nombre del descriptor de sí mismo.

El único problema aquí es que cuando se ejemplariza el descriptor tiene que especificar el nombre como un parámetro:

number = OneDigitNumericValue("number")

¿No sería mejor simplemente el número de escritura = OneDigitNumericValue ()? Podría, pero si se está ejecutando una versión de Python menor que 3,6, entonces se necesita un poco de magia aquí con metaclases y decoradores. Si utiliza Python 3.6 o superior, sin embargo, es que el protocolo descriptor tiene un nuevo método .__ nombre_conjunto __ () que hace toda esta magia para usted, como se propone en PEP 487:

__set_name__(self, owner, name)

Con este nuevo método, cada vez que se instancia un descriptor de este método se llama y el parámetro de nombre establece automáticamente.

Ahora, trata de reescribir el ejemplo anterior para Python 3.6 y hasta:

# descriptors5.py
class OneDigitNumericValue():
def __set_name__(self, owner, name):
self.name = name

def __get__(self, obj, type=None) -> object:
return obj.__dict__.get(self.name) or 0

def __set__(self, obj, value) -> None:
obj.__dict__[self.name] = value

class Foo():
number = OneDigitNumericValue()

my_foo_object = Foo()
my_second_foo_object = Foo()

my_foo_object.number = 3
print(my_foo_object.number)
print(my_second_foo_object.number)

my_third_foo_object = Foo()
print(my_third_foo_object.number)

Ahora, .__ init __ () ha sido retirado y .__ nombre_conjunto __ () se ha aplicado. Esto hace posible la creación de su descriptor sin especificar el nombre del atributo interno que debe usar para almacenar el valor. El código también se ve mejor y más limpio ahora!

Ejecutar este ejemplo una vez más para hacer que todo funciona seguro:

$ python descriptors5.py
3
0
0

Este ejemplo debería funcionar sin problemas si usa Python 3.6 o superior.

¿Por qué utilizar Python Descriptores?

Ahora usted sabe qué descriptores de Python son y cómo ellos sí Python utiliza para alimentar algunas de sus características, como los métodos y propiedades. También ha visto cómo crear un descriptor de Python y evitar algunos errores comunes. Todo debe estar claro ahora, pero todavía se puede preguntarse por qué debe usarlos.

En mi experiencia, he conocido un montón de desarrolladores avanzados Python que nunca han utilizado esta función antes y que no tienen necesidad de ella. Eso es bastante normal, ya que no hay muchos casos de uso donde los descriptores de Python son necesarios. Sin embargo, eso no quiere decir que los descriptores de Python son sólo un tema académico para los usuarios avanzados. Todavía hay algunos casos un buen uso que pueden justificar el precio de aprender cómo usarlos.

Lazy Propiedades

El primero y más sencillo ejemplo es propiedades perezosos . Estas son propiedades cuyos valores inicial no se cargan hasta que se acceda a ellos por primera vez. A continuación, se cargan su valor inicial y mantener ese valor almacenado en caché para su posterior reutilización.

Consideremos el siguiente ejemplo. Tiene una clase DeepThought que contiene una meaning_of_life método () que devuelve un valor después de una gran cantidad de tiempo invertido en la concentración pesada:

# slow_properties.py
import random
import time

class DeepThought:
def meaning_of_life(self):
time.sleep(3)
return 42

my_deep_thought_instance = DeepThought()
print(my_deep_thought_instance.meaning_of_life())
print(my_deep_thought_instance.meaning_of_life())
print(my_deep_thought_instance.meaning_of_life())

Si ejecuta este código y tratar de acceder al método de tres veces, entonces se obtiene una respuesta cada tres segundo, que es la duración del tiempo de sueño en el interior del método.

Ahora, en lugar de una propiedad perezoso puede evaluar este método sólo una vez cuando se ejecuta primero. A continuación, puede cachear el valor resultante de manera que, si una vez más lo necesita, lo puede conseguir en ningún momento. Todo esto es posible con el uso de descriptores de Python:

# lazy_properties.py
import random
import time

class LazyProperty:
def __init__(self, function):
self.function = function
self.name = function.__name__

def __get__(self, obj, type=None) -> object:
obj.__dict__[self.name] = self.function(obj)
return obj.__dict__[self.name]

class DeepThought:
@LazyProperty
def meaning_of_life(self):
time.sleep(3)
return 42

my_deep_thought_instance = DeepThought()
print(my_deep_thought_instance.meaning_of_life)
print(my_deep_thought_instance.meaning_of_life)
print(my_deep_thought_instance.meaning_of_life)

Tome su tiempo para estudiar el código y entender cómo funciona. Se puede ver el poder de Python descriptores de aquí? En este ejemplo, cuando se utiliza el descriptor @LazyProperty, estás instancias de un descriptor y pasando a ella .meaning_of_life (). Este descriptor almacena tanto el método como su nombre como variables de instancia.

Dado que es un descriptor no datos, cuando se accede por primera vez el valor del atributo meaning_of_life, .__ obtener __ () es llamado automáticamente y ejecuta .meaning_of_life () en el objeto my_deep_thought_instance. El valor resultante se almacena en el atributo __dict__ del objeto mismo. Al acceder a la meaning_of_life atributo nuevo, Python utilizará el de búsqueda cadena para encontrar un valor para ese atributo en el atributo __dict__, y que el valor será devuelto inmediatamente.

Tenga en cuenta que esto funciona porque, en este ejemplo, sólo ha utilizado un método .__ obtener __ () del protocolo de descriptores. También ha implementado un descriptor no datos. Si se ha puesto en marcha un descriptor de datos, entonces el truco no habría funcionado. Siguiendo la cadena de búsqueda, habría tenido prioridad sobre el valor almacenado en __dict__. Para probar esto, ejecute el siguiente código:

# wrong_lazy_properties.py
import random
import time

class LazyProperty:
def __init__(self, function):
self.function = function
self.name = function.__name__

def __get__(self, obj, type=None) -> object:
obj.__dict__[self.name] = self.function(obj)
return obj.__dict__[self.name]

def __set__(self, obj, value):
pass

class DeepThought:
@LazyProperty
def meaning_of_life(self):
time.sleep(3)
return 42

my_deep_tought_instance = DeepThought()
print(my_deep_tought_instance.meaning_of_life)
print(my_deep_tought_instance.meaning_of_life)
print(my_deep_tought_instance.meaning_of_life)

En este ejemplo, se puede ver que acaba de implementar .__ conjunto __ (), incluso si no hace nada en absoluto , crea un descriptor de datos. Ahora, el truco de la propiedad perezoso deja de funcionar.

D.R.Y. Código

Otro caso de uso típico de descriptores es escribir código reutilizable y hacer que el código D.R.Y. descriptores Python proporcionan a los desarrolladores una gran herramienta para escribir código reutilizable que puede ser compartida entre los diferentes propiedades o incluso diferentes clases.

Consideremos un ejemplo donde usted tiene cinco propiedades diferentes con el mismo comportamiento. Cada propiedad se puede establecer en un valor específico sólo si es un número par. De lo contrario, su valor se establece en 0:

# properties.py
class Values:
def __init__(self):
self._value1 = 0
self._value2 = 0
self._value3 = 0
self._value4 = 0
self._value5 = 0

@property
def value1(self):
return self._value1

@value1.setter
def value1(self, value):
self._value1 = value if value % 2 == 0 else 0

@property
def value2(self):
return self._value2

@value2.setter
def value2(self, value):
self._value2 = value if value % 2 == 0 else 0

@property
def value3(self):
return self._value3

@value3.setter
def value3(self, value):
self._value3 = value if value % 2 == 0 else 0

@property
def value4(self):
return self._value4

@value4.setter
def value4(self, value):
self._value4 = value if value % 2 == 0 else 0

@property
def value5(self):
return self._value5

@value5.setter
def value5(self, value):
self._value5 = value if value % 2 == 0 else 0

my_values = Values()
my_values.value1 = 1
my_values.value2 = 4
print(my_values.value1)
print(my_values.value2)

Como se puede ver, hay una gran cantidad de código duplicado aquí. Es posible utilizar descriptores de Python comportamiento de la acción entre todas las propiedades. Puede crear un descriptor de EvenNumber y utilizarla para todas las propiedades de este tipo:

# properties2.py
class EvenNumber:
def __set_name__(self, owner, name):
self.name = name

def __get__(self, obj, type=None) -> object:
return obj.__dict__.get(self.name) or 0

def __set__(self, obj, value) -> None:
obj.__dict__[self.name] = (value if value % 2 == 0 else 0)

class Values:
value1 = EvenNumber()
value2 = EvenNumber()
value3 = EvenNumber()
value4 = EvenNumber()
value5 = EvenNumber()

my_values = Values()
my_values.value1 = 1
my_values.value2 = 4
print(my_values.value1)
print(my_values.value2)

Este código se ve mucho mejor ahora! Los duplicados se han ido y la lógica ahora se implementa en un solo lugar para que si es necesario cambiarlo, puede hacerlo fácilmente.

Conclusión

Ahora que ya sabe cómo Python utiliza descriptores al poder algunos de sus grandes características, que va a ser un desarrollador más consciente que entiende por qué algunas características de Python se han implementado como son.

Usted ha aprendido: descriptores

  • Lo Python son y cuándo utilizarlas
  • Cuando se utilicen descriptores en partes internas de Python
  • Cómo implementar sus propios descriptores de

¿Qué más, ahora se sabe de algunos específicos utilizar los casos en los descriptores de Python son particularmente útiles. Por ejemplo, los descriptores son útiles cuando se tiene un comportamiento común que tiene que ser compartida entre una gran cantidad de propiedades, aunque sean de diferentes clases.

Si tienes alguna pregunta, dejar un comentario más abajo o póngase en contacto conmigo en Twitter! Si quieres profundizar más en los descriptores de Python, a continuación, echa un vistazo a la Guía oficial de Python descriptor COMO.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *