2011-11-05 14 views
5

Piszę moduł, który zapewnia jedną funkcję i wymaga kroku inicjalizacji, jednak ze względu na pewne ograniczenia muszę zainicjować przy pierwszym wywołaniu, więc szukam właściwego idiomu w python, który pozwoliłby mi pozbyć się warunkowego.Zmiana implementacji funkcji w Python

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

chciałbym się pozbyć, że warunkowe z czymś takim (nie działa):

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

Zdaję sobie sprawę, że nie można po prostu używać nazw w module i zmienić je na środowisko wykonawcze, ponieważ moduły korzystające z from module import function nigdy nie zostaną zmienione przez moduł function=other_fun.
Czy istnieje jakiś pythonowy idiom, który mógłby zrobić to we właściwy sposób?

+0

google dla "dekorator Pythona – Triptych

+0

nie widzę, w jaki sposób może to zrobić dekorator, warunkowego pozostaje tam, jeśli testujesz rozwiązanie @graphox, używając generatora działającego zgodnie z oczekiwaniami, chociaż wydaje się nieco dziwne. –

Odpowiedz

8

Sposób nic nadzwyczajnego-(metod I post tutaj, jest to chyba najlepszy sposób, żeby to zrobić):

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 

Pomysł jest prosty: po prostu dodajesz warstwę pośrednią. function zawsze wywołuje _function, ale _function punktów najpierw na firsttime, a następnie na zawsze po do_the_thing.

test.py:

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

Running test.py plony

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

Moją pierwszą myślą było użyć generatora, ale, jak zaznacza Tryptyk istnieje nie ma możliwości przekazania argumentów do funkcji, jeśli używasz generatora. Więc ...

o to sposób za pomocą współprogram (który, w przeciwieństwie do generatora, umożliwia wysyłanie argumenty do - jak otrzymać wartości od - do współprogram):

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)) 

Running

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

daje

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

ten prowadzi krok inicjowane podczas czasu ładowania modułu, a nie jak w pierwszym wywołaniu wymagane OP ... – Triptych

+0

@Triptych: Nie wierzę, że to prawda. Testowałeś to? – unutbu

+0

ah błędnie przeczytał 'next', jakby był' next() '. Mój zły ... Wciąż ciekawy, jak piszesz, żeby brać argumenty. Czy 'next' nie musi być funkcją zerowego argumentu? Wydaje mi się ... dziwne. – Triptych

2

Można również użyć dekorator, to może bardziej elastyczne, jeśli masz kilka funkcji, aby zainicjować:

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 

(należy poprawić w zależności od potrzeb)

+0

Hmm, czy kod opakowania wykonuje każde połączenie? Chodzi mi o to, że warunkowe będzie nadal obecne w drugim zaproszeniu, po prostu oceniając na fałszywe, czyż nie? –

+1

użyję '@ functools.wraps (FN)' 'na def wrapper'. W ten sposób funkcja, którą otrzymasz, wygląda jak oryginalna funkcja ('first_fn', itp.). Prawdopodobnie użyłbym również dekoratora opartego na klasach. –

+0

Właśnie testowałem go i rzeczywiście, „jeśli nie zainicjowany” nadal zdarza każde połączenie, które jest to, co chciałem usunąć. :( –

3

moje zdanie na ten temat: nie powinieneś tego robić.

Jeśli potrzebujesz "funkcji", która ma "krok inicjalizacji" i normalny tryb pracy, potrzebujesz instancji klasy.Nie staraj się być mądry, przyszłych czytelników kodzie nienawidzę cię za to :)

# module.py 

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

    def do_the_thing(self, *args): 
     # ... 
Powiązane problemy