{"id":3311,"date":"2018-05-29T17:48:52","date_gmt":"2018-05-29T09:48:52","guid":{"rendered":"http:\/\/www.yewen.us\/blog\/?p=3311"},"modified":"2018-06-06T10:54:21","modified_gmt":"2018-06-06T02:54:21","slug":"get-args-of-origin-function-in-python-decorator-chain","status":"publish","type":"post","link":"https:\/\/www.yewen.us\/blog\/2018\/05\/get-args-of-origin-function-in-python-decorator-chain\/","title":{"rendered":"Python \u591a\u5c42 decorator \u5185\u83b7\u53d6\u539f\u59cb\u51fd\u6570\u53c2\u6570\u5b57\u5178"},"content":{"rendered":"<h3>0. \u5728 decorator \u91cc\u83b7\u53d6\u539f\u59cb\u51fd\u6570\u7684\u53c2\u6570\u503c<\/h3>\n<p>\u9879\u76ee\u91cc\u505a\u4e86\u4e00\u4e2a\u901a\u7528\u9501\uff0c\u4f7f\u7528 decorator \u6765\u65b9\u4fbf\u7684\u5305\u4f4f\u67d0\u4e9b\u9700\u8981\u9650\u5236\u5e76\u53d1\u7684\u51fd\u6570\u3002\u56e0\u4e3a\u5e76\u53d1\u4e0d\u662f\u51fd\u6570\u7ea7\u522b\u7684\uff0c\u800c\u662f\u6839\u636e\u53c2\u6570\u6765\u9650\u5236\uff0c\u6240\u4ee5\u9700\u8981\u628a\u53c2\u6570\u4f20\u5230\u901a\u7528\u9501\u7684 decorator \u91cc\uff0c\u4ee3\u7801\u5927\u81f4\u5982\u4e0b<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef lock_decorator(key=None):\r\n    def _lock_func(func):\r\n        @functools.wraps(func)\r\n        def wrapper(*args, **kwargs):\r\n            # TODO: get lock_key\r\n            lock_key = kwargs.get(key, '')\r\n            with LockContext(key=lock_key):\r\n                return func(*args, **kwargs)\r\n        return wrapper\r\n    return _lock_func\r\n\r\n@lock_decorator(key='uid')\r\ndef apply_recharge(uid, amount):\r\n    # ...\r\n<\/pre>\n<p>\u8003\u8651\u5230\u51fd\u6570\u8c03\u7528\u4e0d\u4e00\u5b9a\u90fd\u662f\u5e26\u7740\u53c2\u6570\u540d\u7684\uff0c\u5c31\u662f\u8bf4\u8c03\u7528\u65f6\u4e0d\u4e00\u5b9a\u6240\u6709\u53c2\u6570\u90fd\u4f1a\u8fdb <code>**kwargs<\/code>\uff0c\u90a3\u5c31\u9700\u8981\u4ece <code>**args<\/code> \u91cc\u9762\u6309\u53c2\u6570\u540d\u635e\u53c2\u6570<\/p>\n<p>\u600e\u4e48\u80fd\u77e5\u9053\u539f\u51fd\u6570\u7684\u53c2\u6570\u540d\u5217\u8868\uff0c\u7ffb\u5404\u79cd\u624b\u518c\u7684\u5f97\u77e5\u53ef\u4ee5\u7528 <code>inspect.getargspec(func)<\/code> \u6765\u641e\u5230\uff0c\u90a3\u4e48\u4e0a\u9762\u7684 <code>TODO<\/code> \u90e8\u5206\u5c31\u53ef\u4ee5\u6539\u5199\u5982\u4e0b<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n            args_name = inspect.getargspec(func)&#x5B;0]\r\n            key_index = args_name.index(key)\r\n            if len(args) &gt; key_index:\r\n                lock_key = args&#x5B;key_index]\r\n            else:\r\n                lock_key = kwargs.get(key, '')\r\n<\/pre>\n<p>\u81ea\u6b64\uff0c\u4e00\u5207\u90fd\u5f88\u7f8e\u597d<\/p>\n<h3>1. \u5728 decorator \u91cc\u83b7\u53d6\u539f\u59cb\u51fd\u6570\u7684\u8c03\u7528\u53c2\u6570\u5b57\u5178<\/h3>\n<p>\u9879\u76ee\u91cc\u53c8\u505a\u4e86\u4e2a\u901a\u7528\u7684 <code>Logger<\/code>\uff0c\u4e5f\u505a\u6210 decorator \u5f80\u76ee\u6807\u51fd\u6570\u4e00\u5957\uff0c\u5c31\u53ef\u4ee5\u6253\u5370\u51fa\u8c03\u7528\u65f6\u7684\u5165\u53c2\u548c\u7ed3\u679c\uff0c\u5927\u81f4\u5982\u4e0b<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef log_decorator():\r\n    def _log_func(func):\r\n        @functools.wraps(func)\r\n        def wrapper(*args, **kwargs):\r\n            # TODO: get full args\r\n            print('call func &#x5B;{}] with args &#x5B;{}] and kwargs &#x5B;{}]'.format(func.__name__, args, kwargs))\r\n            ret = func(*args, **kwargs)\r\n            print('func &#x5B;{}] return &#x5B;{}]'.format(func.__name__, ret))\r\n            return ret\r\n        return wrapper\r\n    return _log_func\r\n\r\n@log_decorator()\r\ndef apply_recharge(uid, amount):\r\n    # ...\r\n<\/pre>\n<p>\u770b\u8d77\u6765\u4e5f\u8fd8\u597d\uff0c\u4e0d\u8fc7\u56e0\u4e3a\u51fd\u6570\u53ef\u80fd\u5e26\u9ed8\u8ba4\u53c2\u6570\uff0c\u800c\u4e14\u4e5f\u5e0c\u671b\u770b\u5230 <code>**args<\/code> \u5230\u5e95\u4f20\u5230\u54ea\u4e2a\u53c2\u6570\u4e0a\uff0c\u8fd8\u662f\u5e0c\u671b\u628a\u6240\u6709\u53c2\u6570\u6309 Key-Value \u7684\u5f62\u5f0f\u6253\u5370\u51fa\u6765\uff0c\u8ddf\u5904\u7406\u901a\u7528\u9501\u4e00\u6837\uff0c\u7528 <code>inspect.getargspec(func)<\/code> \u628a\u53c2\u6570\u540d\u548c\u9ed8\u8ba4\u503c\u90fd\u6478\u51fa\u6765\uff0c\u518d\u8003\u8651\u4e00\u4e0b\u53ef\u53d8\u53c2\u6570\u7684\u60c5\u51b5\uff0c\u5bf9\u4e0a\u9762\u7684 <code>TODO<\/code> \u90e8\u5206\u6539\u5199\u5982\u4e0b<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n            args_name, _, _, func_defaults = inspect.getargspec(func)\r\n            parsed_kwargs = dict()\r\n            # default args\r\n            default_args = dict()\r\n            default_start = len(args_name, func_defaults)\r\n            for idx, d in enumerate(func_defaults):\r\n                default_args&#x5B;args_name&#x5B;default_start + idx]] = d\r\n            parsed_kwargs.update(default_args)\r\n            # args with name\r\n            varargs_start = len(args_name)\r\n            for idx, a in enumerate(args&#x5B;:varargs_start]):\r\n                parsed_kwargs&#x5B;args_name&#x5B;idx]] = a\r\n            # varargs\r\n            if len(args) &gt; varargs_start:\r\n                parsed_kwargs&#x5B;'varargs'] = args&#x5B;varargs_start:]\r\n            # kwargs\r\n            parsed_kwargs.update(kwargs)\r\n            print('call func &#x5B;{}] with args &#x5B;{}]'.format(func.__name__, parsed_kwargs))\r\n<\/pre>\n<p>\u5230\u8fd9\u91cc\uff0c\u8fd8\u662f\u5f88\u7f8e\u597d<\/p>\n<h3>2. \u591a\u5c42 decorator \u600e\u4e48\u62ff\u5230\u6700\u539f\u59cb\u51fd\u6570\u7684\u53c2\u6570\u8868<\/h3>\n<p>\u6ce8\u610f\u5230\u4e0a\u9762\u4e24\u4e2a\u4f8b\u5b50\u91cc\uff0c<code>apply_recharge<\/code> \u90fd\u53ea\u5957\u4e86\u4e00\u4e2a decorator\uff0c\u5982\u679c\u4e24\u4e2a\u4e00\u8d77\u7528\u4f1a\u53d1\u751f\u4ec0\u4e48\uff1f<\/p>\n<p>\u6839\u636e <a href=\"https:\/\/www.python.org\/dev\/peps\/pep-0318\/#current-syntax\">PEP318<\/a> \u91cc\u5bf9 decorator \u7684\u5b9a\u4e49<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n@dec2\r\n@dec1\r\ndef func(arg1, arg2, ...):\r\n    pass\r\n<\/pre>\n<p>\u7b49\u4ef7\u4e8e<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef func(arg1, arg2, ...):\r\n    pass\r\nfunc = dec2(dec1(func))\r\n<\/pre>\n<p>\u8fd9\u91cc\u5c31\u51fa\u95ee\u9898\u4e86\uff0c<code>dec2<\/code> \u62ff\u5230\u7684\u4f20\u5165\u51fd\u6570\u5176\u5b9e\u662f <code>dec1<\/code> \u800c\u4e0d\u662f <code>func<\/code>\u3002\u4e0d\u8fc7\u5728\u628a <code>lock_decorator<\/code> \u548c <code>log_decorator<\/code> \u6df7\u7528\u65f6\uff0c\u4e0d\u7ba1\u8c01\u5199\u524d\u9762\uff0c<code>func.__name__<\/code> \u90fd\u662f\u539f\u59cb\u7684\u51fd\u6570\u540d\uff0c\u8bf4\u660e\u4e5f\u8fd8\u662f\u6709\u795e\u5668\u7684\u5730\u65b9\u505a\u4e86\u7a7f\u900f\uff0c\u4f46\u662f <code>inspect.getargspec<\/code> \u53c8\u62ff\u4e0d\u5230\u6700\u5e95\u5c42\u51fd\u6570\u7684\u53c2\u6570\u8868\uff0c\u5bfc\u81f4\u4e0d\u7ba1\u8c01\u524d\u8c01\u540e\uff0c\u90fd\u6709\u95ee\u9898<\/p>\n<p>\u6ce8\u610f\u5230\u6bcf\u4e2a decorator \u6784\u5efa\u7684\u65f6\u5019\u90fd\u53c8\u5c01\u4e86\u4e00\u4e2a <code>@functools.wraps(func)<\/code>\uff0c\u8fd9\u4e2a\u662f\u5e72\u561b\u7684\u5462\uff1f\u4ee5\u524d\u90fd\u662f\u65e0\u8111\u7528\uff0c\u4e5f\u6ca1\u60f3\u8fc7\u4e3a\u5565\u8981\u5305\u4e00\u5c42\u8fd9\u4e2a\uff0c\u53bb\u6389\u4f1a\u600e\u6837\uff1f<\/p>\n<p>\u53bb\u6389\u8fd9\u4e2a <code>@functools.wraps(func)<\/code> \u540e\uff0c<code>inspect.getargspec<\/code> \u8fd8\u662f\u4e00\u6837\u7684\u53ea\u80fd\u62ff\u5230\u6700\u8fd1\u4e00\u5c42\u7684\u4fe1\u606f\uff0c\u800c\u4e4b\u524d\u672c\u6765\u53ef\u4ee5\u62ff\u5230\u5e95\u5c42\u7684 <code>func.__name__<\/code> \u4e5f\u53d8\u6210\u6700\u8fd1\u4e00\u5c42\u7684\u51fd\u6570\u540d\u4e86\uff0c\u8bf4\u660e\u8fd9\u91cc\u505a\u4e86\u7a7f\u900f\u3002\u90a3\u4e48\u53bb\u770b\u770b\u4ee3\u7801\u5427<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n# functools.py\r\n\r\nfrom _functools import partial, reduce\r\n\r\n# update_wrapper() and wraps() are tools to help write\r\n# wrapper functions that can handle naive introspection\r\n\r\nWRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')\r\nWRAPPER_UPDATES = ('__dict__',)\r\ndef update_wrapper(wrapper,\r\n                   wrapped,\r\n                   assigned = WRAPPER_ASSIGNMENTS,\r\n                   updated = WRAPPER_UPDATES):\r\n    &quot;&quot;&quot;Update a wrapper function to look like the wrapped function\r\n\r\n       wrapper is the function to be updated\r\n       wrapped is the original function\r\n       assigned is a tuple naming the attributes assigned directly\r\n       from the wrapped function to the wrapper function (defaults to\r\n       functools.WRAPPER_ASSIGNMENTS)\r\n       updated is a tuple naming the attributes of the wrapper that\r\n       are updated with the corresponding attribute from the wrapped\r\n       function (defaults to functools.WRAPPER_UPDATES)\r\n    &quot;&quot;&quot;\r\n    for attr in assigned:\r\n        setattr(wrapper, attr, getattr(wrapped, attr))\r\n    for attr in updated:\r\n        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))\r\n    # Return the wrapper so this can be used as a decorator via partial()\r\n    return wrapper\r\n\r\ndef wraps(wrapped,\r\n          assigned = WRAPPER_ASSIGNMENTS,\r\n          updated = WRAPPER_UPDATES):\r\n    &quot;&quot;&quot;Decorator factory to apply update_wrapper() to a wrapper function\r\n\r\n       Returns a decorator that invokes update_wrapper() with the decorated\r\n       function as the wrapper argument and the arguments to wraps() as the\r\n       remaining arguments. Default arguments are as for update_wrapper().\r\n       This is a convenience function to simplify applying partial() to\r\n       update_wrapper().\r\n    &quot;&quot;&quot;\r\n    return partial(update_wrapper, wrapped=wrapped,\r\n                   assigned=assigned, updated=updated)\r\n<\/pre>\n<p>\u539f\u6765\u5c31\u662f\u8fd9\u91cc\u800d\u82b1\u6837\u4e86\uff0c\u628a\u5e95\u5c42\u51fd\u6570\u7684 <code>('__module__', '__name__', '__doc__')<\/code> \u90fd\u8d4b\u7ed9\u4e86 decorator \u5c01\u8d77\u6765\u7684\u8fd9\u4e00\u5c42\uff0c\u6b3a\u9a97\u66f4\u4e0a\u5c42\u7528 <code>__name__<\/code> \u53bb\u5224\u65ad\u65f6\u5c31\u5f53\u6211\u662f\u5e95\u5c42<\/p>\n<p>\u90a3\u6211\u4e5f\u5b66\u8fd9\u4e2a\uff0c\u628a <code>inspect.getargspec<\/code> \u7684\u5730\u65b9\u4e5f\u5904\u7406\u4e0b\u4e0d\u5c31\u5b8c\u4e86\uff0c\u53bb\u770b\u770b\u8fd9\u4e2a\u5730\u65b9\u662f\u600e\u4e48\u62ff\u53c2\u6570\u8868\u7684<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\n# inspect.py\r\n\r\ndef getargspec(func):\r\n    &quot;&quot;&quot;Get the names and default values of a function's arguments.\r\n\r\n    A tuple of four things is returned: (args, varargs, varkw, defaults).\r\n    'args' is a list of the argument names (it may contain nested lists).\r\n    'varargs' and 'varkw' are the names of the * and ** arguments or None.\r\n    'defaults' is an n-tuple of the default values of the last n arguments.\r\n    &quot;&quot;&quot;\r\n\r\n    if ismethod(func):\r\n        func = func.im_func\r\n    if not isfunction(func):\r\n        raise TypeError('{!r} is not a Python function'.format(func))\r\n    args, varargs, varkw = getargs(func.func_code)\r\n    return ArgSpec(args, varargs, varkw, func.func_defaults)\r\n<\/pre>\n<p>\u770b\u4e86\u4e0b\u7528\u5230\u4e86 <code>func.func_code<\/code> \u548c <code>func.func_defaults<\/code>\uff0c\u6309 Python \u5b98\u65b9\u6587\u6863 <a href=\"https:\/\/docs.python.org\/2\/library\/inspect.html\">https:\/\/docs.python.org\/2\/library\/inspect.html<\/a> \u7684\u89e3\u91ca\uff0c<code>func_code<\/code> \u662f\u8fd0\u884c\u65f6\u7684\u5b57\u8282\u7801\uff0c\u4ece\u8fd9\u91cc\u9762\u635e\u53c2\u6570\u8868\u679c\u7136\u53ef\u884c\uff0c\u90a3\u662f\u4e0d\u662f\u6211\u628a\u8fd9\u4e24\u4e2a\u5c5e\u6027\u4e5f\u4f20\u9012\u4e0a\u53bb\u5c31\u884c\u4e86\u5462\uff1f\u6539\u7528\u81ea\u5df1\u7684 <code>wraps<\/code> \u5982\u4e0b<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nWRAPPER_ASSIGNMENTS = functools.WRAPPER_ASSGNMENTS + ('func_code', 'func_defaults')\r\ndef my_wraps(wrapped,\r\n             assigned = WRAPPER_ASSIGNMENTS,\r\n             updated = WRAPPER_UPDATES):\r\n    return _functool.partial(functools.update_wrapper, wrapped=wrapped,\r\n                             assigned=assigned, updated=updated)\r\n<\/pre>\n<p>\u8fd0\u884c\u65f6\u62a5\u9519\uff0c\u770b\u4e86\u4e0b\u9519\u8bef\u63d0\u793a\uff0c<code>func_code<\/code> \u4e0d\u53ef\u8986\u76d6\uff0c\u8fd9\u4e5f\u5bf9\uff0c\u90fd\u662f\u8fd0\u884c\u65f6\u7684\u5b57\u8282\u7801\u4e86\uff0c\u8fd9\u4e2a\u8986\u76d6\u6389\u90a3\u5305\u7684\u8fd9\u5c42 decorator \u5230\u5e95\u8fd8\u6709\u6ca1\u6709\u81ea\u5df1\u7684\u903b\u8f91\u90e8\u5206<\/p>\n<p>\u8fd8\u662f\u81ea\u5df1\u52a8\u624b\u4e30\u8863\u8db3\u98df\uff0c\u65e2\u7136 <code>func_code<\/code> \u4e0d\u53ef\u8986\u76d6\uff0c\u6211\u81ea\u5df1\u53e6\u5916\u5f04\u4e00\u4e2a\u603b\u53ef\u4ee5\u4e86\u5427\uff0c\u800c\u4e14\u5f53\u524d\u9700\u6c42\u662f\u62ff\u5230\u53c2\u6570\u8868\u548c\u9ed8\u8ba4\u53c2\u6570\uff0c\u90a3\u5c31\u76f4\u63a5\u89e3\u51fa\u6765\u7a7f\u900f\uff0c\u4e5f\u61d2\u5f97\u6700\u540e\u518d\u89e3\u4e00\u6b21\u3002\u4fee\u6539 <code>my_wraps<\/code> \u5982\u4e0b<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nWRAPPER_ASSIGNMENTS = functools.WRAPPER_ASSIGNMENTS + ('__func_args_name__', '__func_default_args__')\r\n\r\ndef my_wraps(wrapped,\r\n             assigned = WRAPPER_ASSIGNMENTS,\r\n             updated = functools.WRAPPER_UPDATES):\r\n    if getattr(wrapped, '__func_args_name__', None) is None:\r\n        setattr(wrapped, '__func_args_name__', inspect.getargs(wrapped.func_code)&#x5B;0])\r\n        func_defaults = getattr(wrapped, 'func_defaults') or ()\r\n        default_args = dict()\r\n        default_start = len(wrapped.__func_args_name__) - len(func_defaults)\r\n        for idx, d in enumerate(func_defaults):\r\n            default_args&#x5B;wrapped.__func_args_name__&#x5B;default_start + idx]] = d\r\n        setattr(wrapped, '__func_default_args__', default_args)\r\n    return _functools.partial(functools.update_wrapper, wrapped=wrapped,\r\n                              assigned=assigned, updated=updated)\r\n<\/pre>\n<p>\u540c\u65f6\u5728\u8fd0\u884c\u65f6\u89e3\u53c2\u6570\u8868\uff0c\u4e5f\u7528\u4e00\u4e2a\u901a\u7528\u51fd\u6570\u6765\u5b9e\u73b0<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef parse_func(func, *args, **kwargs):\r\n    parsed_kwargs = dict()\r\n    # default args\r\n    parsed_kwargs.update(func.__func_default_args__)\r\n    # args with name\r\n    varargs_start = len(func.__func_args_name__)\r\n    for idx, a in enumerate(args&#x5B;:varargs_start]):\r\n        parsed_kwargs&#x5B;func.__func_args_name__&#x5B;idx]] = a\r\n    # varargs\r\n    if len(args) &gt; varargs_start:\r\n        parsed_kwargs&#x5B;'varargs'] = args&#x5B;varargs_start:]\r\n    # kwargs\r\n    parsed_kwargs.update(kwargs)\r\n\r\n    return parsed_kwargs\r\n<\/pre>\n<p>\u8fd9\u6837\u5728 <code>lock_decorator<\/code> \u548c <code>log_decorator<\/code> \u91cc\uff0c\u7528 <code>my_wraps<\/code> \u6765\u5c01\u88c5\u5904\u7406\uff0c\u540c\u65f6\u5728\u91cc\u9762\u7528 <code>parse_func<\/code> \u6765\u89e3\u6790\u53c2\u6570\uff0c\u5c31\u80fd\u62ff\u5230\u5b8c\u6574\u7684\u53c2\u6570\u8868\u4e86<\/p>\n<p>\u5b8c\u6574\u7684\u6d4b\u8bd5\u4ee3\u7801\u89c1 <a href=\"https:\/\/gist.github.com\/whusnoopy\/9081544f7eaf4e9ceeaa9eba46ff28da\">https:\/\/gist.github.com\/whusnoopy\/9081544f7eaf4e9ceeaa9eba46ff28da<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>0. \u5728 decorator \u91cc\u83b7\u53d6\u539f\u59cb\u51fd\u6570\u7684\u53c2\u6570\u503c \u9879\u76ee\u91cc\u505a\u4e86\u4e00\u4e2a\u901a\u7528\u9501\uff0c\u4f7f\u7528 decorator \u6765\u65b9\u4fbf\u7684 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[7],"tags":[665,667,666,127],"class_list":["post-3311","post","type-post","status-publish","format-standard","hentry","category-tech-notes","tag-decorator","tag-functools","tag-inspect","tag-python"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p4aR5e-Rp","_links":{"self":[{"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/posts\/3311","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/comments?post=3311"}],"version-history":[{"count":3,"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/posts\/3311\/revisions"}],"predecessor-version":[{"id":3319,"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/posts\/3311\/revisions\/3319"}],"wp:attachment":[{"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/media?parent=3311"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/categories?post=3311"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.yewen.us\/blog\/wp-json\/wp\/v2\/tags?post=3311"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}