2017-02-24 3 views
2

Haftungsausschluss:Wie kann man zwischen Funktionen wechseln, abhängig vom Attributzugriffstyp (mit Klasse oder Instanz)?

Dieser Artikel ist ein Rezept als eine Frage, aber ich fand sehr interessant das Thema, mit fast keine Referenzen im Web.

Wenn es einen besseren Ort auf StackOverflow gibt, um diese Art von Artikeln zu veröffentlichen, lassen Sie es mich bitte wissen.

Betreff:

Wie kann ich Python zwingen andere Funktion aufzurufen auf dem Attributtyp Zugriff abhängig (mit Klasse oder Instanz) - z.B. Erzwingen Python, unterschiedliche Methode für MyClass.my_method() und MyClass().my_method() aufzurufen?

Usecase:

Sagen wir mal, wir individuelle Enum Implementierung haben (basierend auf Python36 Enum, aber mit etwas Anpassung). Als Benutzer dieser Enum möchten wir ein CustomEnum erstellen, das nicht nur von Enum, sondern auch von str abgeleitet wird: class MyEnum(str, Enum). Wir möchten auch die Encoding- und Decoding-Funktion hinzufügen. Unsere Idee ist, MyEnum.encode zu verwenden, um irgendein Objekt zu kodieren, das unsere enum-Mitglieder enthält, aber das ursprüngliche str.encode für Instanzen unserer enum-Klasse in Kraft zu lassen. Kurz gesagt: MyEnum.encode rufen Sie unsere benutzerdefinierte Encoding-Funktion, und haben von diesem Standpunkt aus perfektes Gefühl. MyEnum() ist eine Zeichenfolge, daher sollte MyEnum().encode die von der str-Klasse geerbte encode-Funktion aufrufen.

Lösung:

einen Descriptor schreiben, die als Schalter arbeiten. Volle Antwort in meinem ersten Beitrag.

+0

Bitte geben Sie die Lösung Teil als Antwort posten. – Gabriel

+1

Sie können es im Dokumentationsabschnitt von SO (http://stackoverflow.com/documentation) veröffentlichen. Oder Sie können Ihre Frage nur auf den Problembereich beschränken und selbst mit dem Lösungsabschnitt antworten –

+0

@utsav_deep - Wie kann ich meine Antwort nur auf ein Problem beschränken? Gibt es dafür eine Option? Sorry für noob Frage, das sind meine ersten Schritte auf StackOverflow. –

Antwort

3

Lösung:

Soweit ich weiß, Deskriptoren sind die einzigen Objekte, die unterscheiden kann, wenn sie für die Klasse oder Instanz sind berufen, weil der __get__ Funktion Unterschrift: __get__(self, instance, instance_type). Diese Eigenschaft ermöglicht es uns, einen Schalter darüber zu bauen.

class boundmethod(object):  
    def __init__(self, cls_method=None, instance_method=None, doc=None): 
     self._cls_method = cls_method 
     self._instance_method = instance_method 
     if cls_method: 
      self._method_name = cls_method.__name__ 
     elif instance_method: 
      self._method_name = instance_method.__name__ 

     if doc is None and cls_method is not None: 
      doc = cls_method.__doc__ 
     self.__doc__ = doc 

     self._method = None 
     self._object = None 

    def _find_method(self, instance, instance_type, method_name): 
     for base in instance_type.mro()[1:]: 
      method = getattr(base, method_name, None) 
      if _is_descriptor(method): 
       method = method.__get__(instance, base) 
      if method and method is not self: 
       try: 
        return method.__func__ 
       except AttributeError: 
        return method 

    def __get__(self, instance, instance_type): 
     if instance is None: 
      self._method = self._cls_method or self._find_method(instance, instance_type, self._method_name) 
      self._object = instance_type 
     else: 
      self._method = self._instance_method or self._find_method(instance, instance_type, self._method_name) 
      self._object = instance 
     return self 

    @staticmethod 
    def cls_method(obj=None): 
     def constructor(cls_method): 
      if obj is None: 
       return boundmethod(cls_method, None, cls_method.__doc__) 
      else: 
       return type(obj)(cls_method, obj._instance_method, obj.__doc__) 

     if isinstance(obj, FunctionType): 
      return boundmethod(obj, None, obj.__doc__) 
     else: 
      return constructor 

    @staticmethod 
    def instance_method(obj=None): 
     def constructor(instance_method): 
      if obj is None: 
       return boundmethod(None, instance_method, instance_method.__doc__) 
      else: 
       return type(obj)(obj._cls_method, instance_method, obj.__doc__) 

     if isinstance(obj, FunctionType): 
      return boundmethod(None, obj, obj.__doc__) 
     else: 
      return constructor 

    def __call__(self, *args, **kwargs): 
     if self._method: 
      try: 
       return self._method(self._object, *args, **kwargs) 
      except TypeError: 
       return self._method(*args, **kwargs) 
     return None 

Beispiel:

>>> class Walkmen(object): 
...  @boundmethod.cls_method 
...  def start(self): 
...   return 'Walkmen start class bound method' 
...  @boundmethod.instance_method(start) 
...  def start(self): 
...   return 'Walkmen start instance bound method' 
>>> print Walkmen.start() 
Walkmen start class bound method 
>>> print Walkmen().start() 
Walkmen start instance bound method 

Ich hoffe, es wird helfen, einige o euch.

Am besten.

0

Ich habe eigentlich nur diese Frage gestellt (Python descriptors and inheritance Ich hatte diese Frage nicht gesehen). verwendet Deskriptoren und eine Metaklasse für die Vererbung.

von my answer:

class dynamicmethod: 
    ''' 
     Descriptor to allow dynamic dispatch on calls to class.Method vs obj.Method 

     fragile when used with inheritence, to inherit and then overwrite or extend 
     a dynamicmethod class must have dynamicmethod_meta as its metaclass 
    ''' 
    def __init__(self, f=None, m=None): 
     self.f = f 
     self.m = m 

    def __get__(self, obj, objtype=None): 
     if obj is not None and self.f is not None: 
      return types.MethodType(self.f, obj) 
     elif objtype is not None and self.m is not None: 
      return types.MethodType(self.m, objtype) 
     else: 
      raise AttributeError('No associated method') 

    def method(self, f): 
     return type(self)(f, self.m) 

    def classmethod(self, m): 
     return type(self)(self.f, m) 

def make_dynamicmethod_meta(meta): 
    class _dynamicmethod_meta(meta): 
     def __prepare__(name, bases, **kwargs): 
      d = meta.__prepare__(name, bases, **kwargs) 
      for base in bases: 
       for k,v in base.__dict__.items(): 
        if isinstance(v, dynamicmethod): 
         if k in d: 
          raise ValueError('Multiple base classes define the same dynamicmethod') 
         d[k] = v 
      return d 

    return _dynamicmethod_meta 

dynamicmethod_meta=make_dynamicmethod_meta(type) 

class A(metaclass=dynamicmethod_meta): 
    @dynamicmethod 
    def a(self): 
     print('Called from obj {} defined in A'.format(self)) 

    @a.classmethod 
    def a(cls) 
     print('Called from class {} defined in A'.format(cls)) 

class B(A): 
    @a.method 
    def a(self): 
     print('Called from obj {} defined in B'.format(self)) 

A.a() 
A().a() 
B.a() 
B().a() 

Ergebnisse in:

Called from class <class 'A'> defined in A 
Called from obj <A object at ...> defined in A 
Called from class <class 'B'> defined in A 
Called from obj <B object at ...> defined in B 
Verwandte Themen