Me gustaría saber si es posible controlar la definición de la función Python en función de la configuración global (por ejemplo, SO). Ejemplo:
@linux
def my_callback(*args, **kwargs):
print("Doing something @ Linux")
return
@windows
def my_callback(*args, **kwargs):
print("Doing something @ Windows")
return
Luego, si alguien usa Linux, se usará la primera definición de my_callback
y la segunda se ignorará en silencio.
No se trata de determinar el sistema operativo, se trata de la definición de funciones / decoradores.
my_callback = windows(<actual function definition>)
, por lo que el nombremy_callback
se sobrescribirá, independientemente de lo que el decorador pueda hacer. La única forma en que la versión de Linux de la función podría terminar en esa variable es si sewindows()
devuelve, pero la función no tiene forma de conocer la versión de Linux. Creo que la forma más típica de lograr esto es tener las definiciones de funciones específicas del sistema operativo en archivos separados, y condicionalmenteimport
solo una de ellas.functools.singledispatch
, que hace algo similar a lo que desea. Allí, elregister
decorador conoce el despachador (porque es un atributo de la función de despacho y específico de ese despachador en particular), por lo que puede devolver el despachador y evitar los problemas con su enfoque.uuid.getnode()
,. (Dicho esto, la respuesta de Todd aquí es bastante buena.)Respuestas:
Si el objetivo es tener el mismo tipo de efecto en su código que #ifdef WINDOWS / #endif ... aquí hay una manera de hacerlo (estoy en una Mac por cierto).
Caso simple, sin encadenamiento
Entonces, con esta implementación, obtienes la misma sintaxis que tienes en tu pregunta.
Lo que está haciendo el código anterior, esencialmente, es asignar zulu a zulu si la plataforma coincide. Si la plataforma no coincide, devolverá zulu si se definió previamente. Si no se definió, devuelve una función de marcador de posición que genera una excepción.
Los decoradores son conceptualmente fáciles de entender si tienes en cuenta que
es análogo a:
Aquí hay una implementación que usa un decorador parametrizado:
Los decoradores parametrizados son análogos a
foo = mydecorator(param)(foo)
.He actualizado bastante la respuesta. En respuesta a los comentarios, amplié su alcance original para incluir la aplicación a los métodos de clase y para cubrir las funciones definidas en otros módulos. En esta última actualización, he podido reducir en gran medida la complejidad involucrada en determinar si una función ya ha sido definida.
[Una pequeña actualización aquí ... simplemente no pude dejar esto - ha sido un ejercicio divertido] He estado haciendo algunas pruebas más de esto, y descubrí que funciona en general en callables, no solo en funciones comunes; También puede decorar declaraciones de clase, ya sean invocables o no. Y es compatible con las funciones internas de las funciones, por lo que cosas como esta son posibles (aunque probablemente no sea un buen estilo, esto es solo un código de prueba):
Lo anterior demuestra el mecanismo básico de los decoradores, cómo acceder al alcance de la persona que llama y cómo simplificar múltiples decoradores que tienen un comportamiento similar al tener una función interna que contiene el algoritmo común definido.
Encadenamiento de apoyo
Para admitir el encadenamiento de estos decoradores que indican si una función se aplica a más de una plataforma, el decorador podría implementarse así:
De esa manera, apoyas el encadenamiento:
fuente
macos
ywindows
se definen en el mismo módulo quezulu
. Creo que esto también dará como resultado que la función se deje comoNone
si la función no estuviera definida para la plataforma actual, lo que conduciría a algunos errores de tiempo de ejecución muy confusos.Si bien la
@decorator
sintaxis se ve bien, obtienes exactamente el mismo comportamiento deseado con un simpleif
.Si es necesario, esto también permite hacer cumplir fácilmente que algún caso coincidió.
fuente
def callback_windows(...)
ydef callback_linux(...)
, luegoif windows: callback = callback_windows
, etc. Pero de cualquier manera, esto es mucho más fácil de leer, depurar y mantener.elif
, ya que nunca será el caso esperado que más de uno delinux
/windows
/macOS
sea cierto. De hecho, probablemente solo definiría una sola variablep = platform.system()
, luego usaríaif p == "Linux"
, etc. en lugar de múltiples banderas booleanas. Las variables que no existen no se pueden sincronizar.elif
sin duda tiene sus ventajas - en concreto, un arrastreelse
+raise
para asegurar que al menos un caso hizo partido. En cuanto a la evaluación del predicado, prefiero que se evalúen previamente: evita la duplicación y desacopla la definición y el uso. Incluso si el resultado no se almacena en variables, ahora hay valores codificados que pueden salir de la sincronización de la misma manera. Puedo no recordar las diversas cadenas mágicas para los diferentes medios, por ejemplo,platform.system() == "Windows"
frentesys.platform == "win32"
, ...Enum
o solo con un conjunto de constantes.A continuación se muestra una posible implementación para esta mecánica. Como se señaló en los comentarios, puede ser preferible implementar una interfaz de "despachador maestro", como la que se ve en
functools.singledispatch
, para realizar un seguimiento del estado asociado con las múltiples definiciones sobrecargadas. Espero que esta implementación al menos ofrezca una idea de los problemas que puede tener que enfrentar al desarrollar esta funcionalidad para una base de código más grande.Solo he probado que la implementación a continuación funciona como se especifica en los sistemas Linux, por lo que no puedo garantizar que esta solución permita adecuadamente la creación de funciones especializadas en la plataforma. No utilice este código en una configuración de producción sin probarlo usted mismo primero.
Para utilizar este decorador, debemos trabajar a través de dos niveles de indirección. Primero, debemos especificar a qué plataforma queremos que responda el decorador. Esto se logra mediante la línea
implement_linux = implement_for_os('Linux')
y su contraparte de la ventana anterior. A continuación, debemos transmitir la definición existente de la función que se está sobrecargando. Este paso debe realizarse en el sitio de definición, como se demuestra a continuación.Para definir una función especializada en la plataforma, ahora puede escribir lo siguiente:
Las llamadas a se
some_function()
enviarán adecuadamente a la definición específica de plataforma proporcionada.Personalmente, no recomendaría usar esta técnica en el código de producción. En mi opinión, es mejor ser explícito sobre el comportamiento dependiente de la plataforma en cada ubicación donde se producen estas diferencias.
fuente
implement_for_os
no devuelve un decorador en sí, sino que devuelve una función que producirá el decorador una vez que se proporcione la definición previa de la función en cuestión.Escribí mi código antes de leer otras respuestas. Después de terminar mi código, encontré que el código de @ Todd es la mejor respuesta. De todos modos, publico mi respuesta porque me sentí divertido mientras resolvía este problema. Aprendí cosas nuevas gracias a esta buena pregunta. El inconveniente de mi código es que existe una sobrecarga para recuperar diccionarios cada vez que se llaman funciones.
fuente
Una solución limpia sería crear un registro de funciones dedicado que se despache
sys.platform
. Esto es muy similar afunctools.singledispatch
. El código fuente de esta función proporciona un buen punto de partida para implementar una versión personalizada:Ahora se puede usar de manera similar a
singledispatch
:El registro también funciona directamente en los nombres de las funciones:
fuente