<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 問題 你已經學過怎樣使用函數參數注解,那么你可能會想利用它來實現基于類型的方法重載。但是你不確定應該怎樣去實現(或者到底行得通不)。 ## 解決方案 本小節的技術是基于一個簡單的技術,那就是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 >>> ## 討論 坦白來講,相對于通常的代碼而已本節使用到了很多的魔法代碼。但是,它卻能讓我們深入理解元類和描述器的底層工作原理,并能加深對這些概念的印象。因此,就算你并不會立即去應用本節的技術,它的一些底層思想卻會影響到其它涉及到元類、描述器和函數注解的編程技術。 本節的實現中的主要思路其實是很簡單的。`MutipleMeta` 元類使用它的 `__prepare__()` 方法來提供一個作為 `MultiDict` 實例的自定義字典。這個跟普通字典不一樣的是,`MultiDict` 會在元素被設置的時候檢查是否已經存在,如果存在的話,重復的元素會在 `MultiMethod`實例中合并。 `MultiMethod` 實例通過構建從類型簽名到函數的映射來收集方法。在這個構建過程中,函數注解被用來收集這些簽名然后構建這個映射。這個過程在 `MultiMethod.register()` 方法中實現。這種映射的一個關鍵特點是對于多個方法,所有參數類型都必須要指定,否則就會報錯。 為了讓 `MultiMethod` 實例模擬一個調用,它的 `__call__()` 方法被實現了。這個方法從所有排除 `slef` 的參數中構建一個類型元組,在內部map中查找這個方法,然后調用相應的方法。為了能讓 `MultiMethod` 實例在類定義時正確操作,`__get__()` 是必須得實現的。它被用來構建正確的綁定方法。比如: >>> b = s.bar >>> b <bound method Spam.bar of <__main__.Spam object at 0x1006a46d0>> >>> b.__self__ <__main__.Spam object at 0x1006a46d0> >>> b.__func__ <__main__.MultiMethod object at 0x1006a4d50> >>> b(2, 3) Bar 1: 2 3 >>> b('hello') Bar 2: hello 0 >>> 不過本節的實現還有一些限制,其中一個是它不能使用關鍵字參數。例如: >>> s.bar(x=2, y=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __call__() got an unexpected keyword argument 'y' >>> s.bar(s='hello') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __call__() got an unexpected keyword argument 's' >>> 也許有其他的方法能添加這種支持,但是它需要一個完全不同的方法映射方式。問題在于關鍵字參數的出現是沒有順序的。當它跟位置參數混合使用時,那你的參數就會變得比較混亂了,這時候你不得不在 `__call__()` 方法中先去做個排序。 同樣對于繼承也是有限制的,例如,類似下面這種代碼就不能正常工作: class A: pass class B(A): pass class C: pass class Spam(metaclass=MultipleMeta): def foo(self, x:A): print('Foo 1:', x) def foo(self, x:C): print('Foo 2:', x) 原因是因為 `x:A` 注解不能成功匹配子類實例(比如B的實例),如下: >>> s = Spam() >>> a = A() >>> s.foo(a) Foo 1: <__main__.A object at 0x1006a5310> >>> c = C() >>> s.foo(c) Foo 2: <__main__.C object at 0x1007a1910> >>> b = B() >>> s.foo(b) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "multiple.py", line 44, in __call__ raise TypeError('No matching method for types {}'.format(types)) TypeError: No matching method for types (<class '__main__.B'>,) >>> 作為使用元類和注解的一種替代方案,可以通過描述器來實現類似的效果。例如: import types class multimethod: def __init__(self, func): self._methods = {} self.__name__ = func.__name__ self._default = func def match(self, *types): def register(func): ndefaults = len(func.__defaults__) if func.__defaults__ else 0 for n in range(ndefaults+1): self._methods[types[:len(types) - n]] = func return self return register def __call__(self, *args): types = tuple(type(arg) for arg in args[1:]) meth = self._methods.get(types, None) if meth: return meth(*args) else: return self._default(*args) def __get__(self, instance, cls): if instance is not None: return types.MethodType(self, instance) else: return self 為了使用描述器版本,你需要像下面這樣寫: class Spam: @multimethod def bar(self, *args): # Default method called if no match raise TypeError('No matching method for bar') @bar.match(int, int) def bar(self, x, y): print('Bar 1:', x, y) @bar.match(str, int) def bar(self, s, n = 0): print('Bar 2:', s, n) 描述器方案同樣也有前面提到的限制(不支持關鍵字參數和繼承)。 所有事物都是平等的,有好有壞,也許最好的辦法就是在普通代碼中避免使用方法重載。不過有些特殊情況下還是有意義的,比如基于模式匹配的方法重載程序中。舉個例子,8.21小節中的訪問者模式可以修改為一個使用方法重載的類。但是,除了這個以外,通常不應該使用方法重載(就簡單的使用不同名稱的方法就行了)。 在Python社區對于實現方法重載的討論已經由來已久。對于引發這個爭論的原因,可以參考下Guido van Rossum的這篇博客:[Five-Minute Multimethods in Python](http://www.artima.com/weblogs/viewpost.jsp?thread=101605)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看