开发者社区> 问答> 正文

利用函数注解实现方法重载

你已经学过怎样使用函数参数注解,那么你可能会想利用它来实现基于类型的方法重载。 但是你不确定应该怎样去实现(或者到底行得通不)。

展开
收起
哦哦喔 2020-04-17 16:21:51 1039 0
2 条回答
写回答
取消 提交回答
  • 有点尴尬唉 你要寻找的东西已经被吃掉啦!

    坦白来讲,相对于通常的代码而已本节使用到了很多的魔法代码。 但是,它却能让我们深入理解元类和描述器的底层工作原理, 并能加深对这些概念的印象。因此,就算你并不会立即去应用本节的技术, 它的一些底层思想却会影响到其它涉及到元类、描述器和函数注解的编程技术。

    本节的实现中的主要思路其实是很简单的。MutipleMeta 元类使用它的 prepare() 方法 来提供一个作为 MultiDict 实例的自定义字典。这个跟普通字典不一样的是, MultiDict 会在元素被设置的时候检查是否已经存在,如果存在的话,重复的元素会在 MultiMethod 实例中合并。

    MultiMethod 实例通过构建从类型签名到函数的映射来收集方法。 在这个构建过程中,函数注解被用来收集这些签名然后构建这个映射。 这个过程在 MultiMethod.register() 方法中实现。 这种映射的一个关键特点是对于多个方法,所有参数类型都必须要指定,否则就会报错。

    为了让 MultiMethod 实例模拟一个调用,它的 call() 方法被实现了。 这个方法从所有排除 slef 的参数中构建一个类型元组,在内部map中查找这个方法, 然后调用相应的方法。为了能让 MultiMethod 实例在类定义时正确操作,get() 是必须得实现的。 它被用来构建正确的绑定方法。

    2020-04-17 17:36:46
    赞同 展开评论 打赏
  • 本小节的技术是基于一个简单的技术,那就是Python允许参数注解,代码可以像下面这样写:
    
    class Spam:
        def bar(self, x:int, y:int):
            print('Bar 1:', x, y)
    
        def bar(self, s:str, n:int = 0):
            print('Bar 2:', s, n)
    
    s = Spam()
    s.bar(2, 3) # Prints Bar 1: 2 3
    s.bar('hello') # Prints Bar 2: hello 0
    下面是我们第一步的尝试,使用到了一个元类和描述器:
    
    # multiple.py
    import inspect
    import types
    
    class MultiMethod:
        '''
        Represents a single multimethod.
        '''
        def __init__(self, name):
            self._methods = {}
            self.__name__ = name
    
        def register(self, meth):
            '''
            Register a new method as a multimethod
            '''
            sig = inspect.signature(meth)
    
            # Build a type signature from the method's annotations
            types = []
            for name, parm in sig.parameters.items():
                if name == 'self':
                    continue
                if parm.annotation is inspect.Parameter.empty:
                    raise TypeError(
                        'Argument {} must be annotated with a type'.format(name)
                    )
                if not isinstance(parm.annotation, type):
                    raise TypeError(
                        'Argument {} annotation must be a type'.format(name)
                    )
                if parm.default is not inspect.Parameter.empty:
                    self._methods[tuple(types)] = meth
                types.append(parm.annotation)
    
            self._methods[tuple(types)] = meth
    
        def __call__(self, *args):
            '''
            Call a method based on type signature of the arguments
            '''
            types = tuple(type(arg) for arg in args[1:])
            meth = self._methods.get(types, None)
            if meth:
                return meth(*args)
            else:
                raise TypeError('No matching method for types {}'.format(types))
    
        def __get__(self, instance, cls):
            '''
            Descriptor method needed to make calls work in a class
            '''
            if instance is not None:
                return types.MethodType(self, instance)
            else:
                return self
    
    class MultiDict(dict):
        '''
        Special dictionary to build multimethods in a metaclass
        '''
        def __setitem__(self, key, value):
            if key in self:
                # If key already exists, it must be a multimethod or callable
                current_value = self[key]
                if isinstance(current_value, MultiMethod):
                    current_value.register(value)
                else:
                    mvalue = MultiMethod(key)
                    mvalue.register(current_value)
                    mvalue.register(value)
                    super().__setitem__(key, mvalue)
            else:
                super().__setitem__(key, value)
    
    class MultipleMeta(type):
        '''
        Metaclass that allows multiple dispatch of methods
        '''
        def __new__(cls, clsname, bases, clsdict):
            return type.__new__(cls, clsname, bases, dict(clsdict))
    
        @classmethod
        def __prepare__(cls, clsname, bases):
            return MultiDict()
    为了使用这个类,你可以像下面这样写:
    
    class Spam(metaclass=MultipleMeta):
        def bar(self, x:int, y:int):
            print('Bar 1:', x, y)
    
        def bar(self, s:str, n:int = 0):
            print('Bar 2:', s, n)
    
    # Example: overloaded __init__
    import time
    
    class Date(metaclass=MultipleMeta):
        def __init__(self, year: int, month:int, day:int):
            self.year = year
            self.month = month
            self.day = day
    
        def __init__(self):
            t = time.localtime()
            self.__init__(t.tm_year, t.tm_mon, t.tm_mday)
    下面是一个交互示例来验证它能正确的工作:
    
    >>> s = Spam()
    >>> s.bar(2, 3)
    Bar 1: 2 3
    >>> s.bar('hello')
    Bar 2: hello 0
    >>> s.bar('hello', 5)
    Bar 2: hello 5
    >>> s.bar(2, 'hello')
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "multiple.py", line 42, in __call__
            raise TypeError('No matching method for types {}'.format(types))
    TypeError: No matching method for types (<class 'int'>, <class 'str'>)
    >>> # Overloaded __init__
    >>> d = Date(2012, 12, 21)
    >>> # Get today's date
    >>> e = Date()
    >>> e.year
    2012
    >>> e.month
    12
    >>> e.day
    3
    >>>
    
    2020-04-17 16:22:01
    赞同 展开评论 打赏
问答地址:
问答排行榜
最热
最新

相关电子书

更多
建立联系方法之一 立即下载
低代码开发师(初级)实战教程 立即下载
阿里巴巴DevOps 最佳实践手册 立即下载