2011-11-05 7 views
5

Estoy escribiendo un módulo que proporciona una función y necesita un paso de inicialización, sin embargo debido a ciertas restricciones que necesito inicializar en la primera llamada, entonces estoy buscando el idioma apropiado en python que permita yo para deshacerme del condicional.Cambiando una implementación de función en Python

#with conditional 
module.py 
initialized = False 
def function(*args): 
    if not initialized: initialize() 
    do_the_thing(*args) 

me gustaría deshacerse de ese condicional con algo como esto (que no funciona):

#with no conditional 
module.py 
def function(*args): 
    initialize() 
    do_the_thing(*args) 
    function = do_the_thing 

Soy consciente de que no puedo usar nombres en el módulo y cambiarlos en tiempo de ejecución porque los módulos que usan from module import function nunca se verán afectados con un function=other_fun dentro del módulo.
Entonces, ¿hay algún idioma pitónico que pueda hacer esto de la manera correcta?

+0

en Google de 'pitón decorador' – Triptych

+0

No veo cómo un decorador podría hacer esto, el condicional sigue siendo allí si prueba la solución @graphox, usando un generador funciona como se espera, aunque se siente un poco raro. –

Respuesta

8

La forma no-lujo (de los métodos que publico aquí, esta es probablemente la mejor manera de hacerlo):

module.py:

def initialize(): 
    print('initialize') 
def do_the_thing(args): 
    print('doing things',args) 
def function(args): 
    _function(args) 
def firsttime(args): 
    global _function 
    initialize() 
    do_the_thing(args) 
    _function=do_the_thing 
_function=firsttime 

La idea es simple: simplemente agrega una capa de indirección. function siempre llama a _function, pero _function señala primero en firsttime, y luego para siempre en do_the_thing.

test.py:

from module import function 
function(1) 
function([2,3]) 

Correr test.py rendimientos

initialize 
('doing things', 1) 
('doing things', [2, 3]) 

Mi primera idea fue utilizar un generador, pero, como señala el tríptico, existe no hay forma de pasar argumentos a la función si usas un generador. Así que ...

aquí es una manera de utilizar un co-rutina (que, a diferencia de un generador, le permite enviar a args -, así como recibir los valores de - la co-rutina):

module.py :

def coroutine(func): 
    # http://www.dabeaz.com/coroutines/index.html 
    def start(*args,**kwargs): 
     cr = func(*args,**kwargs) 
     cr.next() 
     return cr 
    return start 

def initialize(): 
    print('initialize') 

def do_the_thing(*args, **kwargs): 
    print('doing things', args, kwargs) 
    return ('result', args) 

@coroutine 
def _function(): 
    args, kwargs = (yield) 
    initialize() 
    while True: 
     args, kwargs = (yield do_the_thing(*args, **kwargs)) 
_function = _function().send 
def function(*args, **kwargs): 
    # This is purely to overcome the limitation that send can only accept 1 argument 
    return _function((args,kwargs)) 

Correr

print(function(1, x = 2)) 
print(function([2, 3])) 

produce

initialize 
('doing things', (1,), {'x': 2}) 
('result', (1,)) 
('doing things', ([2, 3],), {}) 
('result', ([2, 3],)) 
+0

esto ejecuta el paso de inicialización en el tiempo de carga del módulo, no en la primera invocación como OP solicitó ... – Triptych

+0

@Triptych: No creo que eso sea cierto. ¿Lo has probado? – unutbu

+0

ah misread 'next' como si fuera' next() '. Mi mal ... Todavía tengo curiosidad por cómo escribirías para tomar argumentos. ¿No 'next' tiene que ser una función de argumento cero? Parece simplemente ... raro para mí. – Triptych

2

También es posible usar un decorador, que es tal vez más flexible si usted tiene varias funciones para inicializar:

import functools  

def initialize(initialize_function): 
    def wrap(fn): 
     fn.initialized = False 
     @functools.wraps(fn) 
     def wrapper(*args, **kwargs): 
      if not fn.initialized: 
       initialize_function() 
       fn.initialized = True 
      return fn(*args, **kwargs) 
     return wrapper 
    return wrap 

def initialize_first_fn(): 
    print('first function initalized') 

def initialize_second_fn(): 
    print('second function initalized') 

@initialize(initialize_first_fn) 
def first_fn(*args): 
    print(*args) 

@initialize(initialize_second_fn) 
def second_fn(*args): 
    print(*args) 


>>>first_fn('initialize', 'please') 
first function initalized 
initialize please 
>>> first_fn('it works') 
it works 
>>> second_fn('initialize', 'please') 
second function initalized 
initialize please 
>>> second_fn('it also works') 
it also works 

(tiene que ser mejorado en función de sus necesidades)

+0

Hmm, ¿el código del contenedor ejecuta cada llamada? Quiero decir, el condicional seguiría allí en la segunda llamada, simplemente evaluando a falso, ¿no? –

+1

Usaría '@ functools.wraps (fn)' en el 'def wrapper'. De esta forma, la función que recupera se parece a la función original ('first_fn', etc.). Probablemente también use un decorador basado en clases. –

+0

Acabo de probarlo y, de hecho, el mensaje "si no se ha inicializado" sigue apareciendo en todas las llamadas, que es lo que quería eliminar. :( –

3

Mi opinión sobre esto: no deberías hacer esto

En caso de que necesite una "función" que tenga "paso de inicialización" y modo de trabajo normal, necesita una instancia de clase.No trate de ser inteligente, futuros lectores del código se odiarte por eso :)

# module.py 

class ThingDoer(object): 
    def __init__(self): 
     # initialize 

    def do_the_thing(self, *args): 
     # ... 
Cuestiones relacionadas