Métodos privados sin guiones bajos e interfaces en Python



Hola Habr Recientemente me volví loco por el diseño: modificadores de acceso e interfaces, luego lo transferí al lenguaje de programación Python. Pregunto bajo kat: comparto los resultados y cómo funciona. Para aquellos interesados, al final del artículo hay un enlace al proyecto en Github.

Modificadores de acceso


Los modificadores de acceso restringen el acceso a los objetos, a los métodos de su clase o a las clases secundarias, a los métodos de su clase principal. El uso de modificadores de acceso ayuda a ocultar datos en la clase para que nadie fuera pueda interferir con el trabajo de esta clase.

los métodos privados solo están disponibles dentro de la clase, protegidos (dentro), dentro de la clase y en las clases secundarias.

Cómo se implementan los métodos privados y protegidos en Python


Spoiler: al nivel de acuerdo de que los adultos simplemente no los llamarán fuera del aula. Antes de los métodos privados, debe escribir un guión bajo doble, antes de uno protegido. Y aún puede acceder a los métodos, a pesar de su acceso "limitado".

class Car: def _start_engine(self): return "Engine's sound." def run(self): return self._start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() assert "Engine's sound." == car._start_engine() 

Se pueden determinar las siguientes desventajas:

  • Si el método _start_engine actualizó algunas variables de clase o mantuvo el estado, y no solo devolvió un "cálculo tonto", podría haber roto algo para el trabajo futuro con la clase. No te permites reparar algo en el motor de tu automóvil, porque entonces no irás a ningún lado, ¿verdad?
  • El punto de fluidez del anterior, para asegurarse de que puede "con seguridad" (llamar al método no daña a la clase en sí misma) usar un método protegido, debe examinar su código y pasar tiempo.
  • Los autores de las bibliotecas esperan que nadie use los métodos protegidos y privados de las clases que usted usa en sus proyectos. Por lo tanto, pueden cambiar su implementación en cualquier versión (lo que no afectará los métodos públicos debido a la compatibilidad con versiones anteriores, pero sufrirá).
  • El autor de la clase, su colega, espera que no aumente la deuda técnica del proyecto utilizando un método privado o protegido fuera de la clase que creó. Después de todo, quien lo refactorice o modifique (método de clase privada) tendrá que asegurarse (por ejemplo, a través de pruebas) de que sus cambios no romperán su código. Y si lo rompen, tendrá que pasar tiempo tratando de resolver este problema (con una muleta, porque lo necesita ayer).
  • Tal vez se asegure de que otros programadores no utilicen métodos protegidos o privados en la revisión de código y "compitan", así que dedique tiempo.

Cómo implementar métodos protegidos usando una biblioteca


 from accessify import protected class Car: @protected def start_engine(self): return "Engine's sound." def run(self): return self.start_engine() if __name__ == '__main__': car = Car() assert "Engine's sound." == car.run() car.start_engine() 

Intentar llamar al método start_engine fuera de la clase dará como resultado el siguiente error (el método no está disponible de acuerdo con la política de acceso):

 Traceback (most recent call last): File "examples/access/private.py", line 24, in <module> car.start_engine() File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/main.py", line 92, in private_wrapper class_name=instance_class.__name__, method_name=method.__name__, accessify.errors.InaccessibleDueToItsProtectionLevelException: Car.start_engine() is inaccessible due to its protection level 

Usando la biblioteca:

  • No necesita utilizar guiones bajos feos (subjetivos) o guiones bajos dobles.
  • Obtiene un método hermoso (subjetivo) para implementar modificadores de acceso en el código: decoradores privados y protegidos .
  • Transfiera la responsabilidad de la persona al intérprete.

Cómo funciona

  1. El decorador privado o protegido: el decorador más "alto", se activa antes del método de clase , que se declaró un modificador de acceso privado o protegido.


  2. Usando la biblioteca de inspección incorporada, el decorador recupera el objeto actual de la pila de llamadas: inspect.currentframe () . Este objeto tiene los siguientes atributos que nos son útiles: el espacio de nombres (locales) y el enlace al objeto anterior desde la pila de llamadas (el objeto que llama al método con el modificador de acceso).


    (Ilustración muy simplificada)
  3. inspect.currentframe (). f_back : use este atributo para verificar si el objeto anterior de la pila de llamadas está en el cuerpo de la clase o no. Para hacer esto, mira el espacio de nombres - f_locals . Si hay un atributo propio en el espacio de nombres, el método se llama dentro de la clase, si no, fuera de la clase. Si llama a un método con un modificador de acceso privado o protegido fuera de la clase, habrá un error de política de acceso.

Interfaces


Las interfaces son un contrato de interacción con una clase que lo implementa. La interfaz contiene las firmas de método (el nombre de las funciones, los argumentos de entrada) y la clase que implementa la interfaz, siguiendo las firmas, implementa la lógica. En resumen, si dos clases implementan la misma interfaz, puede estar seguro de que ambos objetos de estas clases tienen los mismos métodos.

Ejemplo


Tenemos una clase de usuario que usa el objeto de almacenamiento para crear un nuevo usuario.

 class User: def __init__(self, storage): self.storage = storage def create(self, name): return storage.create_with_name(name=name) 

Puede guardar el usuario en la base de datos utilizando DatabaseStorage.create_with_name .

 class DatabaseStorage: def create_with_name(self, name): ... 

Puede guardar el usuario en archivos usando FileStorage.create_with_name .

 class FileStorage: def create_with_name(self, name): ... 

Debido al hecho de que las firmas de los métodos create_with_name (nombre, argumentos) son las mismas para las clases: la clase Usuario no tiene que preocuparse por el objeto que se sustituyó si ambas tienen los mismos métodos. Esto se puede lograr si las clases FileStorage y DatabaseStorage implementan la misma interfaz (es decir, están obligados por el contrato a definir algún método con lógica dentro).

 if __name__ == '__main__': if settings.config.storage = FileStorage: storage = FileStorage() if settings.config.storage = DatabaseStorage: storage = DatabaseStorage() user = User(storage=storage) user.create_with_name(name=...) 

Cómo trabajar con interfaces usando la biblioteca


Si una clase implementa una interfaz, la clase debe contener todos los métodos de la interfaz . En el ejemplo a continuación, la interfaz HumanInterface contiene el método eat, y la clase Human lo implementa, pero no implementa el método eat.

 from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: pass 

El script saldrá con el siguiente error:

 Traceback (most recent call last): File "examples/interfaces/single.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanInterface.eat(food, args, allergy, kwargs) 

Si una clase implementa una interfaz, la clase debe contener todos los métodos de la interfaz, incluidos todos los argumentos entrantes . En el ejemplo a continuación, la interfaz HumanInterface contiene el método eat, que lleva 4 argumentos a la entrada, y la clase Human lo implementa, pero implementa el método eat con solo 1 argumento.

 from accessify import implements class HumanInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food): pass 

El script saldrá con el siguiente error:

 Traceback (most recent call last): File "examples/interfaces/single_arguments.py", line 16, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 87, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedWithMismatchedArgumentsException: class Human implements interface member HumanInterface.eat(food, args, allergy, kwargs) with mismatched arguments 

Si una clase implementa una interfaz, la clase debe contener todos los métodos de la interfaz, incluidos los argumentos entrantes y los modificadores de acceso . En el ejemplo a continuación, la interfaz HumanInterface contiene el método eat privado, y la clase Human lo implementa, pero no implementa el modificador de acceso privado para el método eat.

 from accessify import implements, private class HumanInterface: @private @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass 

El script saldrá con el siguiente error:

 Traceback (most recent call last): File "examples/interfaces/single_access.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 77, in decorator interface_method_name=interface_method.name, accessify.errors.ImplementedInterfaceMemberHasIncorrectAccessModifierException: Human.eat(food, args, allergy, kwargs) mismatches HumanInterface.eat() member access modifier. 

Una clase puede implementar varias interfaces (número ilimitado de). Si una clase implementa varias interfaces, la clase debe contener todos los métodos de todas las interfaces, incluidos los argumentos entrantes y los modificadores de acceso . En el ejemplo a continuación, la clase Human implementa el método eat de la interfaz HumanBasicsInterface, pero no implementa el método love de la interfaz HumanSoulInterface.

 from accessify import implements class HumanSoulInterface: def love(self, who, *args, **kwargs): pass class HumanBasicsInterface: @staticmethod def eat(food, *args, allergy=None, **kwargs): pass if __name__ == '__main__': @implements(HumanSoulInterface, HumanBasicsInterface) class Human: def love(self, who, *args, **kwargs): pass 

El script saldrá con el siguiente error:

 Traceback (most recent call last): File "examples/interfaces/multiple.py", line 19, in <module> @implements(HumanSoulInterface, HumanBasicsInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string, accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanBasicsInterface.eat(food, args, allergy, kwargs) 

Característica asesina: un método de interfaz puede "indicar" qué errores debe "arrojar" un método de una clase que lo implementa. En el ejemplo a continuación, se "declara" que el método "amor" de la interfaz "HumanInterface" debe lanzar una excepción "HumanDoesNotExistError" y
"HumanAlreadyInLoveError", pero el método "amor" de la clase "Humano" no "arroja" uno de ellos.

 from accessify import implements, throws class HumanDoesNotExistError(Exception): pass class HumanAlreadyInLoveError(Exception): pass class HumanInterface: @throws(HumanDoesNotExistError, HumanAlreadyInLoveError) def love(self, who, *args, **kwargs): pass if __name__ == '__main__': @implements(HumanInterface) class Human: def love(self, who, *args, **kwargs): if who is None: raise HumanDoesNotExistError('Human whom need to love does not exist') 

El script saldrá con el siguiente error:

 Traceback (most recent call last): File "examples/interfaces/throws.py", line 21, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 103, in decorator class_method_arguments=class_member.arguments_as_string, accessify.errors.DeclaredInterfaceExceptionHasNotBeenImplementedException: Declared exception HumanAlreadyInLoveError by HumanInterface.love() member has not been implemented by Human.love(self, who, args, kwargs) 

Para resumir, usando la biblioteca:

  • Puede implementar una o más interfaces.
  • Las interfaces se combinan con modificadores de acceso.
  • Obtendrá una separación de interfaces y clases abstractas ( módulo abc en Python ), ahora no necesita usar clases abstractas como interfaces si lo hizo (lo hice).
  • En comparación con las clases abstractas. Si no ha definido todos los argumentos del método desde la interfaz, obtendrá un error usando una clase abstracta - no.
  • En comparación con las clases abstractas. Al usar interfaces, obtendrá un error al crear la clase (cuando escribió la clase y llamó al archivo * .py ). En las clases abstractas, obtendrá un error ya en la etapa de llamar a un método de un objeto de clase.

Cómo funciona

  1. Usando la biblioteca de inspección incorporada en el decorador de implementos , se obtienen todos los métodos de la clase y sus interfaces: inspect.getmembers () . Un índice único de un método es una combinación de su nombre y tipo (método estático, propiedad, etc.).
  2. Y con inspect.signature () , los argumentos del método.
  3. Recorremos todos los métodos de la interfaz y vemos si existe dicho método (por un índice único) en la clase que implementa la interfaz, si los argumentos entrantes son los mismos, si los modificadores de acceso son los mismos, si el método implementa los errores declarados en el método de la interfaz.

Gracias por su atención al artículo. Enlace al proyecto en Github .

Source: https://habr.com/ru/post/443192/


All Articles