用装饰器注册 Python 函数

注册回调函数应该是开发中很常见的一种行为。这在 Python 中通常通过装饰器来实现,看起来比较漂亮:

flaskr.py
 @app.route("/") def home():     return "It works."

但是这种用法常常带来一种隐藏的“惊讶”,比如说:

admin_menu.py
 @permission(["manager", "developer"]) @app.route("/admin") def admin_menu():     return render_template("admin/menu.html")

这个 @permission 根本不会生效,因为在它装饰 admin_menu 之前,admin_menu 就已经被注册到 app 里了。最终我们执行的是未经过装饰的函数。

类似的陷阱还有可能出现在“猴子补丁”使用的场景。比如下面这段代码将一系列 filter 注册到一个对象中:

filter_manager.py
 class FilterManager(object):     """The filter registry center."""     def __init__(self):         self._filters = {}     def register(self, name):         def decorator(func):             self._filters[name] = func             return func         return decorator     def filter(self, type_name, value):         return self._filters[type_name](value) filter_manager = FilterManager() @filter_manager.register("datetime") def datetime_filter(dt):     dt.strftime("%Y-%m-%d %H:%M")

看起来似乎很漂亮,但是如果我在写单元测试的时候,希望用一个 Stub 来取代 datetime_filter ,就会小小惊讶一下:

test_filter_manager.py
 from datetime import datetime from mock import patch from filter_manager import filter_manager, datetime_filter @patch("filter_manager.datetime_filter") def test_datetime_filter():     filter_manager.filter("datetime", datetime.utcnow())     assert datetime_filter.called

说到底还是用装饰器注册函数,不是 Lazy Binding 的。

这是我个人感觉 Python 的装饰器使用中比较违反直觉的一个地方。虽然装饰器这种看上去很像“声明”的语法很酷,却也很 implicit 。当然因为装饰器的这种用法实在太流行,很多 Python 开发者已经具备了绕过这个陷阱的技能了(Orz,小白和非小白的区别之一)。但是我还是觉得这种东西需要开发者用经验去绕过的,并不符合我比较认同的最小惊讶原则。

所以…… 我可能还是更喜欢用 Qualified Name 来引用要注册的函数,以实现真正的 Lazy Binding:

def import_object(qualname):    try:        from werkzeug.utils import import_string    except ImportError:        from importlib import import_module        module_name, object_name = qualname.rsplit(".", 1)        module = import_module(module_name)        return getattr(module, object_name)    else:        return import_string(qualname)def get_qualname(o):    return getattr(o, "__qualname__", "%s.%s" % (o.__module__, o.__name__))class FilterManager(object):    """The filter registry center."""    def __init__(self):        self._filters_qualname = {}    def register(self, name):        def decorator(func):            self._filters_qualname[name] = get_qualname(func)            return func        return decorator    def filter(self, type_name, value):        qualname = self._filters_qualname[type_name]        func = import_object(qualname)        return func(value)
用装饰器注册 Python 函数

相关文章:

你感兴趣的文章:

标签云: