[英]How to pass arguments to the metaclass from the class definition in Python 3.x?
[英]How to pass callbacks and their arguments from wrapped function to decorator with Python 3.x?
我正在編寫一個圍繞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
因為這些回調是在包裝函數內定義的。
如果我決定將回調移到包裝函數之外,例如與裝飾器本身處於同一級別,那么回調就無法訪問包裝函數內的響應和電子郵件變量。
# 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
)。
您可以將裝飾器鍵轉換為字符串,然后通過f.func_code.co_consts
從傳遞給裝飾器的外部函數中拉出內部函數。 不要這樣做。
import functools, new
from types import CodeType
def decorator(callback_dict=None):
def actual_decorator(f):
code_dict = {c.co_name: c for c in f.func_code.co_consts if type(c) is CodeType}
@functools.wraps(f)
def wrapper(*args, **kwargs):
main_return = f(*args, **kwargs)
if main_return['callback'] in callback_dict:
callback_string = callback_dict[main_return['callback']]
callback = new.function(code_dict[callback_string], {})
return callback(main_return)
return wrapper
return actual_decorator
@decorator({'key_a': 'function_a'})
def main_function(callback):
def function_a(callback_object):
for k, v in callback_object.items():
if k != 'callback':
print '{}: {}'.format(k, v)
return {'callback': callback, 'key_1': 'value_1', 'key_2': 'value_2'}
main_function('key_a')
# key_1: value_1
# key_2: value_2
你能上課嗎? 如果你可以使用一個類,解決方案是立即的。
正如我對其他答案的評論中所提到的,這是一個使用類和裝飾器的答案。 這有點違反直覺,因為get_user_from_email
被聲明為一個類,但在裝飾后最終成為一個函數。 它確實具有所需的語法,所以這是一個加號。 也許這可能是一個更清潔的解決方案的起點。
# dummy response object
from collections import namedtuple
Response = namedtuple('Response', 'data status_code error')
def callback_mapper(callback_map):
def actual_function(cls):
def wrapper(*args, **kwargs):
request = getattr(cls, 'request')
response = request(*args, **kwargs)
callback_name = callback_map.get(response.status_code)
if callback_name is not None:
callback_function = getattr(cls, callback_name)
return callback_function(response)
else:
return response.error
return wrapper
return actual_function
@callback_mapper({'200': 'json', '404': 'does_not_exist'})
class get_user_from_email:
@staticmethod
def json(response):
return 'json response: {}'.format(response.data)
@staticmethod
def does_not_exist(response):
return 'does not exist'
@staticmethod
def request(email):
response = Response('response data', '200', 'exception')
return response
print get_user_from_email('blah')
# json response: response data
您可以將外部函數的函數參數傳遞給處理程序:
def return_as_json(response, email=None): # email param
print('Found user with email [%s].' % email)
return response.json()
@process_response({requests.codes.ok: return_as_json, 404: ...})
def get_user_from_email(email):
return requests.get('...: %s' % email)
# in decorator
# email param will be passed to return_as_json
return action_to_perform(response, *args, **kwargs)
這是一種在類方法上使用函數成員數據的方法,以便將響應函數映射到適當的回調。 這對我來說似乎是最干凈的語法,但仍然有一個類變成一個函數(如果需要可以很容易地避免)。
# dummy response object
from collections import namedtuple
Response = namedtuple('Response', 'data status_code error')
def callback(status_code):
def method(f):
f.status_code = status_code
return staticmethod(f)
return method
def request(f):
f.request = True
return staticmethod(f)
def callback_redirect(cls):
__callback_map = {}
for attribute_name in dir(cls):
attribute = getattr(cls, attribute_name)
status_code = getattr(attribute, 'status_code', '')
if status_code:
__callback_map[status_code] = attribute
if getattr(attribute, 'request', False):
__request = attribute
def call_wrapper(*args, **kwargs):
response = __request(*args, **kwargs)
callback = __callback_map.get(response.status_code)
if callback is not None:
return callback(response)
else:
return response.error
return call_wrapper
@callback_redirect
class get_user_from_email:
@callback('200')
def json(response):
return 'json response: {}'.format(response.data)
@callback('404')
def does_not_exist(response):
return 'does not exist'
@request
def request(email):
response = Response(email, '200', 'exception')
return response
print get_user_from_email('generic@email.com')
# json response: generic@email.com
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.