2015-06-07 21 views
5

Piszę moją pierwszą aplikację Python (3.4) za pomocą SQLalchemy. Mam kilka metod, które wszystkie mają bardzo podobny wzór. Biorą opcjonalny argument session, który domyślnie przyjmuje wartość None. Jeśli zostanie przekazany session, funkcja używa tej sesji, w przeciwnym razie otwiera się i używa nowej sesji. Na przykład, rozważmy następujący sposób:Python udekoruj metody ze zmienną liczbą argumentów pozycyjnych i opcjonalnie arg

def _stocks(self, session=None): 
    """Return a list of all stocks in database.""" 
    newsession = False 
    if not session: 
     newsession = True 
     session = self.db.Session() 
    stocks = [stock.ticker for stock in session.query(Stock).all()] 
    if newsession: 
     session.close() 
    return stocks 

Tak, są nowe do Python i chętny do nauki całej swojej mocy, myślałem, że to pachniał jak doskonały czas, aby dowiedzieć się co nieco o dekoratorów Pythona. Więc po dużo czytania, jak to this series of blog posts i this fantastyczny SO odpowiedzieć, napisałem następujące dekorator:

from functools import wraps 

def session_manager(func): 
    """ 
    Manage creation of session for given function. 

    If a session is passed to the decorated function, it is simply 
    passed through, otherwise a new session is created. Finally after 
    execution of decorated function, the new session (if created) is 
    closed/ 
    """ 
    @wraps(func) 
    def inner(that, session=None, *args, **kwargs): 
     newsession = False 
     if not session: 
      newsession = True 
      session = that.db.Session() 
     func(that, session, *args, **kwargs) 
     if newsession: 
      session.close() 
     return func(that, session, *args, **kwargs) 
    return inner 

I wydaje się działać świetnie. Oryginalna metoda jest teraz zredukowana do:

@session_manager 
def _stocks(self, session=None): 
    """Return a list of all stocks in database.""" 
    return [stock.ticker for stock in session.query(Stock).all()] 

Jednak, kiedy zastosować dekorator do funkcji, która wymaga pewnego pozycyjnych argumentów oprócz opcjonalnego session, otrzymuję komunikat o błędzie. Więc próbuje napisać:

@session_manager 
def stock_exists(self, ticker, session=None): 
    """ 
    Check for existence of stock in database. 

    Args: 
     ticker (str): Ticker symbol for a given company's stock. 
     session (obj, optional): Database session to use. If not 
      provided, opens, uses and closes a new session. 

    Returns: 
     bool: True if stock is in database, False otherwise. 
    """ 
    return bool(session.query(Stock) 
       .filter_by(ticker=ticker) 
       .count() 
       ) 

i działa jak print(client.manager.stock_exists('AAPL')) daje AttributeError z następującym traceback:

Traceback (most recent call last): 
    File "C:\Code\development\Pynance\pynance.py", line 33, in <module> 
    print(client.manager.stock_exists('GPX')) 
    File "C:\Code\development\Pynance\pynance\decorators.py", line 24, in inner 
    func(that, session, *args, **kwargs) 
    File "C:\Code\development\Pynance\pynance\database\database.py", line 186, in stock_exists 
    .count() 
AttributeError: 'NoneType' object has no attribute 'query' 
[Finished in 0.7s] 

Więc jestem zgadywania przez traceback, że jestem brudząc się kolejność argumentów, ale nie mogę wymyślić, jak prawidłowo je zamówić. Mam funkcje, które chcę udekorować, które mogą przyjmować 0-3 argumentów oprócz session. Czy ktoś może wskazać błąd w mojej metodologii?

+0

Przekaż 'sesję' jako nazwany argument -' func (stuff, session = session) '. Ponadto, dlaczego wywołujesz 'func' dwa razy? Na koniec wygląda na to, że powinien istnieć menedżer kontekstu dla 'db.session'. – jwilner

+0

dzięki @jwilner! dwukrotne wywołanie 'func' było po prostu nieporozumieniem z mojej strony na temat składni. Zmieniłem wywołanie 'func' na' result = func() ', a następnie zwróć wynik. Tak, również o menedżerze kontekstu na 'db.session'. Próbowałem uciąć kilka kodów, żeby lepiej wyodrębnić moje pytanie. –

Odpowiedz

3

Zmień

def inner(that, session=None, *args, **kwargs): 

do

def inner(that, *args, session=None, **kwargs): 

i

return func(that, session, *args, **kwargs) 

do

return func(that, *args, session=session, **kwargs) 

Działa:

def session_manager(func): 

    def inner(that, *args, session=None, **kwargs): 
     if not session: 
      session = object() 
     return func(that, *args, session=session, **kwargs) 

    return inner 


class A(): 

    @session_manager 
    def _stocks(self, session=None): 
     print(session) 
     return True 

    @session_manager 
    def stock_exists(self, ticker, session=None): 
     print(ticker, session) 
     return True 

a = A() 
a._stocks() 
a.stock_exists('ticker') 

wyjściowa:

$ python3 test.py 
<object object at 0x7f4197810070> 
ticker <object object at 0x7f4197810070> 

Podczas korzystania def inner(that, session=None, *args, **kwargs) każdy drugi pozycyjny argument (licząc self) traktowany jest jakoArgument. Kiedy zadzwonisz pod numer manager.stock_exists('AAPL')session otrzymasz wartość AAPL.

+0

Dzięki, to naprawdę pomogło. Miałem przeczucie, że właśnie mieszam kolejność argumentów. –

1

Pierwszą rzeczą, jaką zauważyłem było to, że Ty dzwonisz funkcję urządzone dwukrotnie

@wraps(func) 
    def inner(that, session=None, *args, **kwargs): 
     newsession = False 
     if not session: 
      newsession = True 
      session = that.db.Session() 
     #calling first time 
     func(that, session, *args, **kwargs) 
     if newsession: 
      session.close() 
     #calling second time 
     return func(that, session, *args, **kwargs) 
    return inner 

Podczas drugiej sesji połączenia będzie już zamknięta. Ponadto, nie trzeba jawnie akceptować parametrów that i session w funkcji dekoratora, są one już w args i kwargs.Spójrz na to rozwiązanie:

@wraps(func) 
def inner(*args, **kwargs): 
    session = None 
    if not 'session' in kwargs: 
     session = that.db.Session() 
     kwargs['session'] = session 
    result = func(*args, **kwargs) 
    if session: 
     session.close() 
    return result 
return inner 

Można też umieścić kod zamknięcia sesji w finally bloku, wtedy będziesz mieć pewność, że jest ona zamknięta, nawet jeśli funkcja urządzone zgłasza wyjątek

+0

bez jawnego przekazywania 'this' w' inner() 'i' func() ', otrzymuję' NameError: name ', który "nie jest zdefiniowany" –