【发布时间】:2017-10-08 08:19:15
【问题描述】:
我正在围绕 REST API 编写通用包装器。我有几个类似下面的函数,负责从用户的电子邮件地址中检索用户。感兴趣的部分是如何处理响应,基于预期状态代码列表(除了 HTTP 200)和与每个预期状态代码关联的回调:
import requests
def get_user_from_email(email):
response = requests.get('http://example.com/api/v1/users/email:%s' % email)
# define callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
expected_status_codes_and_callbacks = {
requests.codes.ok: return_as_json, # HTTP 200 == success
404: user_with_email_does_not_exist,
}
if response.status_code in expected_status_codes_and_callbacks:
callback = expected_status_codes_and_callbacks[response.status_code]
return callback(response)
else:
response.raise_for_status()
john_doe = get_user_from_email('john.doe@company.com')
print(john_doe is not None) # True
unregistered_user = get_user_from_email('unregistered.user@company.com')
print(unregistered_user is None) # True
上面的代码运行良好,所以我想重构和概括响应处理部分。我很想得到以下代码:
@process_response({requests.codes.ok: return_as_json, 404: user_with_email_does_not_exist})
def get_user_from_email(email):
# define callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
return requests.get('https://example.com/api/v1/users/email:%s' % email)
process_response 装饰器定义为:
import functools
def process_response(extra_response_codes_and_callbacks=None):
def actual_decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
response = f(*args, **kwargs)
if response.status_code in expected_status_codes_and_callbacks:
action_to_perform = expected_status_codes_and_callbacks[response.status_code]
return action_to_perform(response)
else:
response.raise_for_status() # raise exception on unexpected status code
return wrapper
return actual_decorator
我的问题是装饰器抱怨无法访问return_as_json 和user_with_email_does_not_exist,因为这些回调定义在内部包装函数中。
如果我决定将回调移动到包装函数之外,例如与装饰器本身在同一级别,则回调无法访问包装函数内的 response 和 email 变量。
# does not work either, as response and email are not visible from the callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
@process_response({requests.codes.ok: return_as_json, 404: user_with_email_does_not_exist})
def get_user_from_email(email):
return requests.get('https://example.com/api/v1/users/email:%s' % email)
这里的正确方法是什么?我发现装饰器语法非常简洁,但我不知道如何将所需的部分传递给它(回调本身或它们的输入参数,如 response 和 email)。
【问题讨论】:
-
您可以将
*args, **kwargs传递给action_to_perform。 -
如果您假设嵌套在请求函数中的响应处理程序是重要的,即不止几行,您会看到您的预期“路由器”调度成功或失败与嵌套的不可重用处理程序相比,处理程序实际上并没有增加太多。在此基础上重构之前,我会先写更多的代码。
-
@jonrsharpe 谢谢,请参阅下面我对 schwobaseggl 的回复
-
@JLPeyret 我确实写了很多代码。涵盖了所有 API 端点,从而产生了 25 多个具有完全相同架构和响应处理的包装函数。因此,我希望用装饰器重构所有这些。
标签: python decorator python-decorators