Методы оформления 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
блок, тогда вы будете уверены, что он закрыт, даже если оформленная функция выдает исключение