python技巧 - 函数、方法的动态调用

2023-02-27 20:16:06 浏览数 (1)

今天逛github的时候看到这样一个项目,其中在RPC远程调用接口中实现一个功能,并用add_method进行装饰,于是我把它从项目中摘出来。

并在此基础上,我额外增加了add_missing_method方法,用于包装一个自定义方法,处理拦截未找到方法的情况。

以下代码演示了如何动态调用函数、方法。

代码语言:python代码运行次数:0复制
# -*- coding: utf-8 -*-
import functools
try:
    from collections.abc import MutableMapping
except ImportError:
    from collections import MutableMapping
class Dispatcher(MutableMapping):
    """ Dictionary like object which maps method_name to method."""
    def __init__(self, prototype=None):
        """ Build method dispatcher.
        Parameters
        ----------
        prototype : object or dict, optional
            Initial method mapping.
        Examples
        --------
        Init object with method dictionary.
        >>> Dispatcher({"sum": lambda a, b: a   b})
        None
        """
        self.missing_method_name = '__missing__'
        self.method_map = dict()
        if prototype is not None:
            self.build_method_map(prototype)
    def __getitem__(self, key):
        if key in self.method_map:
            return self.method_map[key]
        if self.missing_method_name in self.method_map:
            return self.method_map[self.missing_method_name]
        raise Exception('missing method <'   key   '> if you want to close the Exception, you can use '
                                                   'add_missing_method.')
    def __setitem__(self, key, value):
        self.method_map[key] = value
    def __delitem__(self, key):
        del self.method_map[key]
    def __len__(self):
        return len(self.method_map)
    def __iter__(self):
        return iter(self.method_map)
    def __repr__(self):
        return repr(self.method_map)
    def add_class(self, cls):
        prefix = cls.__name__.lower()   '.'
        self.build_method_map(cls(), prefix)
    def add_object(self, obj):
        prefix = obj.__class__.__name__.lower()   '.'
        self.build_method_map(obj, prefix)
    def add_dict(self, dict, prefix=''):
        if prefix:
            prefix  = '.'
        self.build_method_map(dict, prefix)
    def add_method(self, f=None, name=None):
        """ Add a method to the dispatcher.
        Parameters
        ----------
        f : callable
            Callable to be added.
        name : str, optional
            Name to register (the default is function **f** name)
        Notes
        -----
        When used as a decorator keeps callable object unmodified.
        Examples
        --------
        Use as method
        >>> d = Dispatcher()
        >>> d.add_method(lambda a, b: a   b, name="sum")
        <function __main__.<lambda>>
        Or use as decorator
        >>> d = Dispatcher()
        >>> @d.add_method
            def mymethod(*args, **kwargs):
                print(args, kwargs)
        Or use as a decorator with a different function name
        >>> d = Dispatcher()
        >>> @d.add_method(name="my.method")
            def mymethod(*args, **kwargs):
                print(args, kwargs)
        """
        if name and not f:
            return functools.partial(self.add_method, name=name)
        self.method_map[name or f.__name__] = f
        return f
    def add_missing_method(self, f=None, name=None):
        """ Add missing method to the dispatcher"""
        self.missing_method_name = name
        return self.add_method(f, name)
    def build_method_map(self, prototype, prefix=''):
        """ Add prototype methods to the dispatcher.
        Parameters
        ----------
        prototype : object or dict
            Initial method mapping.
            If given prototype is a dictionary then all callable objects will
            be added to dispatcher.
            If given prototype is an object then all public methods will
            be used.
        prefix: string, optional
            Prefix of methods
        """
        if not isinstance(prototype, dict):
            prototype = dict((method, getattr(prototype, method))
                             for method in dir(prototype)
                             if not method.startswith('_'))
        for attr, method in prototype.items():
            if callable(method):
                self[prefix   attr] = method
if __name__ == '__main__':
    d = Dispatcher()
    d.add_method(lambda a, b: a   b, name="sum")
    d.add_method(lambda a, b: a - b, name="sub")
    d.add_method(lambda a, b: a * b, name="mul")
    @d.add_method(name="miclon")
    def method(*args, **kwargs):
        print(args, kwargs)
        return "miclon"
    @d.add_class
    class MyClass:
        def __init__(self):
            self.a = 1
        def method(self, b):
            return self.a   b
    
    @d.add_missing_method(name='__miss__')
    def missing_method(*args, **kwargs):
        print("未找到接收调用的方法", args, kwargs)
    print(d['sum'](1, 2))
    # 3
    print(d['miclon']('a', {'b': 'c'}, 'd'))
    # ('a', {'b': 'c'}, 'd') {}
    print(d['myclass.method'](2))
    # 3
    print(d['qqqqq'](2))
    # 未找到接收调用的方法
    

Dispatcher是一个类似字典的对象,它负责存储方法,并且提供一个字典存储方法的名称和方法的映射。

实际调用端可以通过方法名称来动态的调用方法,也可以通过方法名称来获取方法。

它没有任何限制,你要做的就是暴露公共的实例化Dispatcher类。

然后通过:add_method方法添加方法,add_class方法添加类,add_object方法添加对象,add_dict方法添加字典(字典中也是方法的名称和方法的映射),add_missing_method方法添加当引用一个不存在方法的时候的默认方法。


0 人点赞