Tengo una función con un bucle while. Antes de comenzar esa función, quiero poder decidir si quiero exportar una imagen cada vez que la recorro o si solo quiero mostrarla, hacer ambas cosas o no hacer ninguna.

No quiero sino un "si" en el bucle que se pregunta cada vez que lo reviso, así que pensé qué tal si escribo 4 versiones diferentes de esa función (loop, loop_show, loop_export, loop_show_export). Pero pensé que debe haber una manera de no copiar y pegar el resto de la función.

Hay una manera de hacer eso ? ¿O hay una forma aún mejor con decoradores u otra cosa?

def main():
    ...
    while True:
        ...
        ####### code that I need to dis-/enable ####### 
        for image in images:
            video_in.write(image) # preparation for export
            Show.show_win('in', image) # show input
        ###############################################
        ...
2
le_lemon 10 oct. 2019 a las 14:16

5 respuestas

La mejor respuesta

Eso es muy bueno Entiendo muy bien lo que quieres decir. Acabo de pasar por eso últimamente. El if será inútil después de que se realice la verificación y cambie a la otra rama, que seguirá ejecutándose.

Por ejemplo, aquí un ejemplo para explicar el problema. Imaging tenemos un método o devolución de llamada que necesita ejecutar algo solo la primera vez. O después de cierto número de iteraciones. Una vez que se cambia, seguirá ejecutándose solo en el otro código o en el segundo.

Entonces tener un inútil si es una sobrecarga inútil. Si podemos cambiar completamente el método, será bueno.

Aquí cómo se puede hacer en JavaScript

const execCallback = version1WithCheck;

function version1WithCheck() {
    if (myCondition) {
        // do something
    } else {
        execCallback = version2NoCheck; //<----- changing the ref of your execCallback variable (which need to be global (in js it will be a closure))
        execCallback();
    }
}

function version2NoCheck() {

}


function someEventListener() {
    execCallback();
};

event.listen(someEventListener);

Aquí un ejemplo real:

    private _tradesInsertToDbWithCheck(trades: Trade[]) {
        if (!this._checkIfTradesNeedToBeInserted(trades)) {
            const sortedTradesToBeInserted = trades.filter((trade: Trade) => {
                const firstTradeInfo = this._firstTradeMapping[
                    objectToExchangeSymbolSignature<Trade>(trade)
                ];

                return trade.id >= firstTradeInfo.id;
            });

            this._insertTradesIntoDb(sortedTradesToBeInserted);
        } else {
//-------- here i switch -----
//                          ||
//                          \/
            this._insertToDbOnProcessCallback = this._tradesInsertToDbWithoutCheck; 

            this._insertToDbOnProcessCallback(trades);
        }
    }


Y otro ejemplo:

Esta solo la primera llamada tendrá que verificar. Y no será para todos los demás.

            exchangeClient.onTradeFeedCallback = this._onFirstFeedTrade as OnTradeFeedCallback;
//---------------------^^ we set our ref callback (to first time only version)

            exchangeClient.streams[symbol as string_symbol] = 
                exchangeClient.client.ws.trades(
                    symbol, 
                    (trade) => {   //<------------ here the callback         
                        (exchangeClient.onTradeFeedCallback as OnTradeFeedCallback)(trade as Trade, workers, exchangeClient); 
//----------------------------^^^^ here calling our refCallback
                    }
                );

Y en el método de versión por primera vez

    private _onFirstFeedTrade(trade: Trade, workers: Worker[], exchangeClient: ExchangeClientObject) {
        /**
         * this run only once
         */
        //_______________alter callback function (this no more will be called)
        exchangeClient.onTradeFeedCallback = this._onTradeFeed; 
// do some things

// next time this._onTradeFeed will be called 

Creo que ahora la idea es clara.

Y aquí cómo se puede hacer en Python

callback = None
def version1(index):
    global callback
    print('im version 1')
    if index == 5:
        callback = version2 // <---- switch upon that condition

def version2(index):
    global callback
    print('im vesrion 2')

callback = version1

for i in range(0,20):
    callback(i)

Y aquí el resultado de ejecución: ingrese la descripción de la imagen aquí

CPU, compiladores y ramificaciones

Y para terminar necesitamos incorporar el predictor de rama y cómo funciona. Y por qué la rama puede ser mala. Y por qué el predictor puede hacer grandes cosas. Y la CPU reciente hace un gran trabajo.

Para no demorar mucho en eso aquí los enlaces sobre el tema

https://stackoverflow.com/a/11227902/5581565

¿Qué hacen los compiladores con la ramificación en tiempo de compilación?

https://stackoverflow.com/a/32581909/7668448

Para no hacer la lista aún más, me detendré en eso. Y cuando se trata de usar if if dentro de una devolución de llamada que se llama muchas veces o un bucle. Si después de cierto tiempo solo sigue ejecutándose en una rama. Creo que el predictor de rama que usa las estadísticas de ejecución se optimizará para la rama que sigue ejecutándose. Entonces puede que no importe en absoluto de esta manera. Investigaré el asunto aún más y haré algunos puntos de referencia y luego actualizaré la respuesta. Pero ese punto es algo a tener en cuenta o considerar.

Espero que esto haya sido útil. Feliz codificación

3
Mohamed Allal 10 oct. 2019 a las 12:37

Como la mayoría de las personas respondieron, puede ser mejor no usar la forma más compleja porque una declaración if no necesita mucho tiempo. Sin embargo, es posible intercambiar una función.

Encontré un ejemplo aquí: python cómo cambiar la funcionalidad de un método durante el tiempo de ejecución

class Dog:
    def talk(self):
        print "bark"
dog1 = Dog()
dog1.talk()  # --> Bark

def cat_talk(self):
    print "Meow"

Dog.talk = cat_talk
dog1.talk()  # --> Meow

Así que podría usarlo para intercambiar mi método principal con loop (), loop_show (), ... y así sucesivamente, pero eso generalmente está mal visto porque podría hacer que el código sea menos legible. Además, es posible que no dé un rendimiento tan alto porque, como @Mohamed Allal sugirió que el predictor de rama podría optimizarse para la rama que se usa todo el tiempo de todos modos.

0
le_lemon 10 oct. 2019 a las 12:46

Si entendí correctamente, esto es lo que necesitas:

def main(show=False, export=False):
    ...
    while True:
        ...
        ####### code that I need to dis-/enable ####### 
        for image in images:
            if export:
                video_in.write(image) # preparation for export
            if show:
                Show.show_win('in', image) # show input
        ###############################################
        ...

Al llamar a su función como main() los argumentos show y export son por defecto False, por lo que los dos if en el bucle no se ejecutan.
Si llama a main(show=True), se ejecuta if show:.
Si llama a main(export=True), se realiza if export:.
Si llama a main(show=True, export=True), se realizarán ambos if.

Como dijo la gente en el comentario, verificar un booleano casi no lleva tiempo, el código es legible y no hay líneas duplicadas en funciones casi iguales. No es necesario buscar soluciones más elaboradas.

3
Valentino 10 oct. 2019 a las 11:42

Pasaría la función dos booleanos, así:

def loop(export=False, show=False):
    while True:
        ...
        for image in images:
            if export:
                video_in.write(image) # preparation for export
            if show:
                Show.show_win('in', image) # show input
        ...

Ahora aún puede escribir sus tres funciones de conveniencia, como esta:

def loop_show():
    return loop(show=True)

def loop_export():
    return loop(export=True)

def loop_show_export():
    return loop(show=True, export=True)

O usando partial:

from functools import partial

loop_show = partial(loop, show=True)
loop_export = partial(loop, export=True)
loop_show_export = partial(loop, export=True, show=True)
0
isaactfa 10 oct. 2019 a las 11:48

En su caso, la sugerencia de Valentino de usar declaraciones 'si' tiene mucho más sentido.

Sin embargo, hay algunos casos en los que no puede o no desea codificar todas las opciones posibles (por ejemplo, demasiadas opciones, o el usuario debería poder definir opciones personalizadas). Esto es similar al Patrón de estrategia

Luego, puede proporcionar explícitamente los métodos necesarios a la clase.

def main(postprocessing=None):
   ...
   while True:
      ...
      if postprocessing:
          for image in images: 
               postprocessing(image)

Luego puede llamarlo, por ejemplo, con: main(postprocessing=video_in.write)

Si lo desea, también puede admitir una lista de pasos en los argumentos de postprocesamiento, para seguir siendo súper flexible. No hice eso aquí para mantener el código más simple.

Una desventaja de esta estrategia es que cada paso posterior al procesamiento debe usar la misma API. Por lo tanto, su método de presentación no funcionaría de fábrica. Tendría que usar algo como:

main(postprocessing = partial(Show.show_win, 'in'))

Esto, por supuesto, agrega una capa adicional de complejidad. Entonces, para su caso, repita si las declaraciones serán mucho más claras.

1
Ivo Merchiers 10 oct. 2019 a las 11:57
58321645