Методы оформления Python с переменным числом позиционных аргументов и необязательным аргументом

Я пишу свое первое приложение на Python (3.4) с использованием SQLalchemy. У меня есть несколько методов, которые все имеют очень похожий характер. Они принимают необязательный аргумент session который по умолчанию None, Если session передается, функция использует этот сеанс, в противном случае она открывает и использует новый сеанс. Например, рассмотрим следующий метод:

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

Так что, будучи новичком в Python и желая изучить всю его мощь, я подумал, что это пахнет идеальным временем, чтобы немного узнать о декораторах Python. Поэтому после долгих чтений, таких как эта серия постов в блоге и этот фантастический ответ SO, я написал следующий декоратор:

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

И, похоже, отлично работает. Оригинальный метод теперь сокращен до:

@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()]

ОДНАКО, когда я применяю декоратор к функции, которая принимает некоторые позиционные аргументы в дополнение к необязательному sessionЯ получаю ошибку. Итак, пытаюсь написать:

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

и работает как print(client.manager.stock_exists('AAPL')) дает AttributeError со следующей трассировкой:

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]

Так что я догадываюсь по трассировке, что я путаю порядок аргументов, но я не могу понять, как правильно их упорядочить. У меня есть функции, которые я хочу украсить, которые могут принимать 0-3 аргумента в дополнение к session, Может кто-нибудь указать на ошибку в моей методологии?

2 ответа

Решение

+ Изменить

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

в

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

а также

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

в

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

Оно работает:

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

Выход:

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

Когда вы используете def inner(that, session=None, *args, **kwargs) любой второй позиционный аргумент (считая self) рассматривается как session аргумент. Поэтому, когда вы звоните manager.stock_exists('AAPL')session получает значение AAPL,

Первое, что я заметил, это то, что Вы дважды вызываете оформленную функцию

@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

Во время второго разговора сеанс будет уже закрыт. Кроме того, вам не нужно явно принимать that а также session параметры в функции декоратора, они уже находятся в args а также kwargs, Посмотрите на это решение:

@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

Вы также можете добавить код закрытия сессии в finally блок, тогда вы будете уверены, что он закрыт, даже если оформленная функция выдает исключение

Другие вопросы по тегам