2016-03-30 6 views
4

sagen, dass ich bereits ein Verfahren mit Typenannotationen haben:Wie schreibe ich überladene Annotate-Methoden in eine Unterklasse?

class Shape: 
    def area(self) -> float: 
     raise NotImplementedError 

die ich dann Unterklasse mehrfach:

class Circle: 
    def area(self) -> float: 
     return math.pi * self.radius ** 2 

class Rectangle: 
    def area(self) -> float: 
     return self.height * self.width 

Wie Sie sehen können, habe ich die -> float ziemlich viel bin duplizieren. Angenommen, ich habe 10 verschiedene Formen mit mehreren Methoden wie diese, von denen einige auch Parameter enthalten. Gibt es eine Möglichkeit, die Annotation einfach von der Elternklasse zu "kopieren", ähnlich wie es bei mit Docstrings gemacht wird?

Antwort

4

Dies könnte funktionieren, obwohl ich bin sicher, dass die Grenzfälle, wie zusätzliche Argumente verpassen:

from functools import partial, update_wrapper 


def annotate_from(f): 
    return partial(update_wrapper, 
        wrapped=f, 
        assigned=('__annotations__',), 
        updated=()) 

die im Auge „Wrapper“ Funktion des __annotations__ Attribut aus f.__annotations__ (halten zuweisen, dass es sich nicht um eine Kopieren).

Nach Unterlagen, die Standardeinstellung des update_wrapper Funktion für zugewiesen__annotations__ bereits enthält, aber ich kann sehen, warum Sie würden nicht alle anderen Attribute von gewickelt zugewiesen haben wollen.

Mit diesem können Sie dann definieren Sie Ihre Circle und Rectangle als

class Circle: 
    @annotate_from(Shape.area) 
    def area(self): 
     return math.pi * self.radius ** 2 

class Rectangle: 
    @annotate_from(Shape.area) 
    def area(self): 
     return self.height * self.width 

und das Ergebnis

In [82]: Circle.area.__annotations__ 
Out[82]: {'return': builtins.float} 

In [86]: Rectangle.area.__annotations__ 
Out[86]: {'return': builtins.float} 

Als Nebeneffekt Ihre Methoden wird ein Attribut __wrapped__, haben die zu Shape.area Punkt wird in dieser Fall.


Eine weniger übliche Weise Umgang mit überschriebene Methoden erreicht werden kann, erreichen (wenn Sie die obige Verwendung von update_wrapper Standard nennen kann) eine Klasse Dekorateur mit:

from inspect import getmembers, isfunction, signature 


def override(f): 
    """ 
    Mark method overrides. 
    """ 
    f.__override__ = True 
    return f 


def _is_method_override(m): 
    return isfunction(m) and getattr(m, '__override__', False) 


def annotate_overrides(cls): 
    """ 
    Copy annotations of overridden methods. 
    """ 
    bases = cls.mro()[1:] 
    for name, method in getmembers(cls, _is_method_override): 
     for base in bases: 
      if hasattr(base, name): 
       break 

     else: 
      raise RuntimeError(
        'method {!r} not found in bases of {!r}'.format(
          name, cls)) 

     base_method = getattr(base, name) 
     method.__annotations__ = base_method.__annotations__.copy() 

    return cls 

und dann:

@annotate_overrides 
class Rectangle(Shape): 
    @override 
    def area(self): 
     return self.height * self.width 

Auch dies wird überschreiben Methoden mit zusätzlichen Argumenten nicht behandeln.

+0

Awesome, vielen Dank! :) Ich hatte noch nicht mal an Dekorateur gedacht. –

0

Sie können einen Klassendekorator verwenden, um die Annotationen Ihrer Unterklassenmethoden zu aktualisieren. In Ihrem Decorator müssen Sie Ihre Klassendefinition durchlaufen und dann nur die Methoden aktualisieren, die in Ihrer Oberklasse vorhanden sind. Um auf die Oberklasse zugreifen zu können, müssen Sie die __mro__ verwenden, die nur das Tupel der Klasse, Unterklasse, bis object ist. Hier interessieren wir uns für das zweite Element in diesem Tupel, das den Index 1, also __mro__[1] oder cls.mro()[1] hat. Zuletzt und nicht zuletzt muss der Dekorateur die Klasse zurückgeben.

def wraps_annotations(cls): 
    mro = cls.mro()[1] 
    vars_mro = vars(mro) 
    for name, value in vars(cls).items(): 
     if callable(value) and name in vars_mro: 
      value.__annotations__.update(vars(mro).get(name).__annotations__) 
    return cls 

Demo:

>>> class Shape: 
...  def area(self) -> float: 
...   raise NotImplementedError 
... 
>>> import math 
>>> 
>>> @wraps_annotations 
... class Circle(Shape): 
...  def area(self): 
...   return math.pi * self.radius ** 2 
... 
>>> c = Circle() 
>>> c.area.__annotations__ 
{'return': <class 'float'>} 
>>> @wraps_annotations 
... class Rectangle(Shape): 
...  def area(self): 
...   return self.height * self.width 
... 
>>> r = Rectangle() 
>>> r.area.__annotations__ 
{'return': <class 'float'>} 
+1

Warum wurde diese Antwort abgelehnt? .. Ich kann keine Kommentare sehen? ... –

Verwandte Themen