2016-06-08 9 views
1

Ich schreibe eine Klasse in Python zu ändern, für einige Einstellungen wich sieht wie folgt aus:Ist es möglich, eine Klasse mit einem Dekorateur

class _CanvasSettings: 
    def __init__(self, **kwargs): 
     super().__init__() 
     self._size_x = _int(kwargs, 'size_x', 320) 
     self._size_y = _int(kwargs, 'size_y', 240) 
     self._lock_ratio = _bool(kwargs'lock_ratio', True) 

    def get_size_x_var(self): 
     return self._size_x 
    def _get_size_x(self): 
     return self._size_x.get() 
    def _set_size_x(self, value): 
     self._size_x.set(value) 
    size_x = property(_get_size_x, _set_size_x) 

    def get_size_y_var(self): 
     return self._size_y 
    def _get_size_y(self): 
     return self._size_y.get() 
    def _set_size_y(self, value): 
     self._size_y.set(value) 
    size_y = property(_get_size_y, _set_size_y) 

    def get_lock_ratio_var(self): 
     return self._lock_ratio 
    def _get_lock_ratio(self): 
     return self._lock_ratio.get() 
    def _set_lock_ratio(self, value): 
     self._lock_ratio.set(value) 
    lock_ratio = property(_get_lock_ratio, _set_lock_ratio) 

wie Sie ich den Block hinzufügen sehen:

def get_something_var(self): 
     return self._something 
    def _get_something(self): 
     return self._something.get() 
    def _set_something(self, value): 
     self._something.set(value) 
    something = property(_get_something, _set_something) 

Für jede einzelne Einstellung.
Ist es möglich, diese Aufgabe mit einem decorator zu automatisieren?

würde Ich mag es, wie diese (Pseudo-Code) tun:

def my_settings_class(cls): 
    result = cls 
    for field in cls: 
     result.add_getter_setter_and_property(field) 
    return result 

@my_settings_class 
class _CanvasSettings: 
    def __init__(self, **kwargs): 
     super().__init__() 
     self._size_x = _int(kwargs, 'size_x', 320) 
     self._size_y = _int(kwargs, 'size_y', 240) 
     self._lock_ratio = _bool(kwargs'lock_ratio', True) 

# Done ! 

Ist das möglich?
Wenn ja, wie?
Wie implementiert man die add_getter_setter_and_property() Methode?


Edit:
Es gibt eine ziemlich ähnliche Frage hier ist: Es Python Class Decorator
aus den Antworten, die ich vermute, dass es möglich ist, etwas zu achive wie ich gebeten haben, aber können Sie mir einen Hinweis geben auf Wie könnte ich die add_getter_setter_and_property() Funktion/Methode implementieren?


Anmerkung:
die _int(), _bool() Funktionen geben nur einen tkinter Int/Bool-var eighter von dem kwargs wenn die Zeichenfolge (fe 'size_x') vorhanden ist oder von dem Standardwert (fe 320) .


Meine Endlösung: Ich glaube, ich eine ziemlich gute Lösung gefunden haben. Ich habe einen Namen Einstellungen nur einmal hinzufügen, was imo genial :-) ist

import tkinter as tk 

def _add_var_getter_property(cls, attr): 
    """ this function is used in the settings_class decorator to add a 
    getter for the tk-stringvar and a read/write property to the class. 
    cls: is the class where the attributes are added. 
    attr: is the name of the property and for the get_XYZ_var() method. 
    """ 
    field = '_' + attr 
    setattr(cls, 'get_{}_var'.format(attr), lambda self: getattr(self, field)) 
    setattr(cls, attr, 
      property(lambda self: getattr(self, field).get(), 
        lambda self, value: getattr(self, field).set(value))) 

def settings_class(cls): 
    """ this is the decorator function for SettingsBase subclasses. 
    it adds getters for the tk-stringvars and properties. it reads the 
    names described in the class-variable _SETTINGS. 
    """ 
    for name in cls._SETTINGS: 
     _add_var_getter_property(cls, name) 
    return cls 


class SettingsBase: 
    """ this is the base class for a settings class. it automatically 
    adds fields to the class described in the class variable _SETTINGS. 
    when you subclass SettingsBase you should overwrite _SETTINGS. 
    a minimal example could look like this: 

     @settings_class 
     class MySettings(SettingsBase): 
      _SETTINGS = { 
       'x': 42, 
       'y': 23} 

    this would result in a class with a _x tk-intvar and a _y tk-doublevar 
    field with the getters get_x_var() and get_y_var() and the properties 
    x and y. 
    """ 

    _SETTINGS = {} 

    def __init__(self, **kwargs): 
     """ creates the fields described in _SETTINGS and initialize 
     eighter from the kwargs or from the default values 
     """ 
     super().__init__() 
     fields = self._SETTINGS.copy() 
     if kwargs: 
      for key in kwargs: 
       if key in fields: 
        typ = type(fields[key]) 
        fields[key] = typ(kwargs[key]) 
       else: 
        raise KeyError(key) 
     for key in fields: 
      value = fields[key] 
      typ = type(value) 
      name = '_' + key 
      if typ is int: 
       var = tk.IntVar() 
      elif typ is str: 
       var = tk.StringVar() 
      elif typ is bool: 
       var = tk.BooleanVar() 
      elif typ is float: 
       var = tk.DoubleVar() 
      else: 
       raise TypeError(typ) 
      var.set(value) 
      setattr(self, name, var) 

nach, dass meine Einstellungen Klassen einfach aussehen:

@settings_class 
class _CanvasSettings(SettingsBase): 

    _SETTINGS = { 
     'size_x': 320, 
     'size_y': 240, 
     'lock_ratio': True 
     } 
+1

Mögliche Duplikate von [Python Class Decorator] (http://stackoverflow.com/questions/681953/python-class-decorator) – ppperry

+0

@ppperry Teile meiner Frage ist ziemlich ähnlich (danke für den Link!), Aber tun Sie (oder jemand anders) könnte mir einen Hinweis geben, wie ich die 'add_gett_setter_and_property' Methode/Funktion implementieren könnte? – linluk

+0

Verwenden Sie die Funktion 'dir '. – ppperry

Antwort

1

Es ist sicherlich möglich zu tun, was Sie wollen, setattr mit den Funktionen und Eigenschaft als Attribute zu binden, von das Klassenobjekt:

def add_getter_setter_property(cls, attrib_name): 
    escaped_name = "_" + attrib_name 
    setattr(cls, "get_{}_var".format(attrib_name), 
      lambda self: getattr(self, escaped_name)) 
    setattr(cls, attrib_name, 
      property(lambda self: getattr(self, escaped_name).get() 
        lambda self, value: getattr(self, escaped_name).set(value))) 

Hier überspringe ich Namen mit den getter und setter Methoden, die von der property verwendet werden. Du könntest sie der Klasse hinzufügen, wenn du das wirklich willst, aber ich denke, dass es wahrscheinlich unnötig ist.

Das knifflige Bit kann tatsächlich herausfinden, auf welche Attributnamen Sie dieses anwenden müssen. Anders als in Ihrem Beispiel können Sie nicht über ein Klassenobjekt iterieren, um dessen Attribute zu erhalten.

Die einfachste Lösung (aus der Umsetzung Standpunkt aus) wäre es, die Klasse zu verlangen, die Namen in einer Klassenvariablen zu spezifizieren:

def my_settings_class(cls): 
    for field in cls._settings_vars: 
     add_getter_setter_and_property(cls, field) 
    return cls 

@my_settings_class 
class _CanvasSettings: 
    _settings_vars = ["size_x", "size_y", "lock_ratio"] 
    def __init__(self, **kwargs): 
     super().__init__() 
     self._size_x = _int(kwargs, 'size_x', 320) 
     self._size_y = _int(kwargs, 'size_y', 240) 
     self._lock_ratio = _bool(kwargs, 'lock_ratio', True) 

Ein benutzerfreundlicher Ansatz könnte dir oder vars verwenden, um die Klassen zu untersuchen Variablen und wählen Sie diejenigen aus, die automatisch umbrochen werden müssen. Sie können isinstance verwenden, um zu überprüfen, ob der Wert einen bestimmten Typ hat, oder nach einem bestimmten Muster im Attributnamen suchen. Ich weiß nicht, was für Ihre spezifische Verwendung am besten ist, also überlasse ich Ihnen das.

+0

Danke! Es hat für mich funktioniert. Ich muss nur 'lambda self ändern: return self.getattr (escaped_name)' in 'lambda self: getattr (self, escaped_name)'. Wäre toll, wenn Sie es für den nächsten, der das liest, beheben können. – linluk

+1

Ah, das ist ein ziemlich dummer Fehler, den ich gemacht habe. Ich habe es bearbeitet, um es zu beheben. – Blckknght

1

Ausstatter für die Klasse.

def add_get_set(cls): 
    for prop in cls.PROPERTIES: 
     # Note cannot be "lambda self: getattr(self, prop)" because of scope prop changes to be the last item in PROPERTIES 
     setattr(cls, "get"+prop, lambda self, attr=prop: getattr(self, attr)) 

    return cls 

@add_get_set 
class _CanvasSettings: 
    PROPERTIES = ["_size_x", "_size_y", "_lock_ratio"] 

    def __init__(self, **kwargs): 
     super().__init__() 

     for prop in self.PROPERTIES: 
      setattr(self, prop, 0) 

c = _CanvasSettings() 
print(c.get_size_y()) 

könnten Sie setzen nur die Funktionen als Variablen

class _CanvasSettings: 
    def __init__(self, **kwargs): 
     super().__init__() 
     self._size_x = _int(kwargs, 'size_x', 320) 
     self._size_y = _int(kwargs, 'size_y', 240) 
     self._lock_ratio = _bool(kwargs'lock_ratio', True) 

     for variable in ["_size_x", "_size_y", "_lock_ratio"]: 
      setattr(self, "get"+variable, lambda: getattr(self, variable)) 
      # bind the method (Not sure if binding the method gets you anything) 
      #setattr(self, "get"+variable, (lambda self: getattr(self, variable)).__get__(self, self.__class__)) 

Alternate

class _CanvasSettings: 
    def __init__(self, **kwargs): 
     super().__init__() 
     self._size_x = _int(kwargs, 'size_x', 320) 
     self._size_y = _int(kwargs, 'size_y', 240) 
     self._lock_ratio = _bool(kwargs'lock_ratio', True) 

     for variable in dir(self): 
      if variable.startswith("_") and not variable.startswith("__"): 
       self.__dict__["get"+variable] = lambda: getattr(self, variable) 
+1

Während es möglich ist, "vorgebundene" Funktionen auf diese Weise als Instanzvariablen hinzuzufügen, können Sie das nicht mit 'property'-Instanzen tun. Da sie das Deskriptorprotokoll verwenden, müssen sie wirklich der Klasse und nicht einer Instanz zugewiesen werden. – Blckknght

1

Als Alternative zur Herstellung von Eigenschaften automatisieren Sie __getattr__ und __setattr__ zu erkennen, wenn ein privater Bereich mit einem entsprechenden Getter oder Setter-Methode zur Verfügung steht überlasten könnten:

class Field: # so I could test it. 
    def __init__(self,args,name,default): 
     self.name = name 
     self.value = default 
    def get(self): 
     return self.value 
    def set(self,value): 
     self.value = value 

class CanvasSettings: 
    def __init__(self, **kwargs): 
     super().__init__() 
     self._size_x = Field(kwargs, 'size_x', 320) 
     self._size_y = Field(kwargs, 'size_y', 240) 
     self._lock_ratio = Field(kwargs, 'lock_ratio', True) 

    def __getattr__(self, attr): 
     private_name = "_" + attr 
     field = object.__getattribute__(self, private_name) #this will fail for non-special attributes 
     getter = getattr(field,"get",None) 
     if getter is None: 
      raise AttributeError("Private member did not have getter") #may want to change the error handling 
     else: 
      return getter() 

    def __setattr__(self,attr, value): 
     private_name = "_" + attr 
     try: 
      field = getattr(self,private_name) 
     except AttributeError: 
      # if there is no private field or there is but no setter 
      # resort back to defaualt behaviour. 
      return super().__setattr__(attr,value) 
     else: 
      setter = getattr(field, "set", None) 
      if setter is None: 
       raise AttributeError("private field does not have a setter") 
      setter(value) 

Dann, wann immer Sie versuchen oder thing.size_x gesetzt zu bekommen es wird zunächst ein thing._size_x sucht und für ein geeignetes Verfahren zu überprüfen, hier ist eine Demo:

>>> thing = CanvasSettings() 
>>> thing._size_x.value 
320 
>>> thing.size_x 
320 
>>> thing.size_x = 5 
>>> 5 == thing.size_x == thing._size_x.value 
True 

für ein vorhandenes Feld Überprüfung jedes Mal, wenn die attrib abrufen Ute kann Strafen für die Leistung haben, aber ich wollte dies nur als Alternative anbieten, wenn Sie viele Klassen mit privaten Feldern haben, die zu diesem Modell passen.

Verwandte Themen