2011-07-22 9 views
5

Más específicamente, quiero ser compatible con lambda: <some_or_other_setter>, pero quiero mantener el código claro y conciso. Debo validar el valor, entonces necesito un setter de algún tipo. Necesito usar lambda porque necesito pasar devoluciones de llamada a los eventos de Tkinter. También necesito poder modificar el valor del atributo fuera de un enlace.¿Cuál sería la forma más pitónica de crear un atributo que se pueda usar en una lambda?

En mis ejemplos siguientes, suponga que un widget de botón llamado spam_button ha sido declarado globalmente. Supongamos también que la clase Eggs tendrá al menos 10 atributos de los que se necesita acceder a todos de la misma manera (me gusta la coherencia).

La primera manera es posible que pudiera hacer esto es usar sólo captadores y definidores:

class Eggs(object): 

    def __init__(self): 
     self._spam = '' 
     self.set_spam('Monster') 
     print self.get_spam() 
     spam_button.bind('<Enter>', lambda: self.set_spam('Ouch')) 

    def set_spam(self, spam): 
     if len(spam) <= 32: 
      self._spam = spam 

    def get_spam(self): 
     return self._spam 

Pero si puedo usar este método y tener un montón de atributos, el código puede ser demasiado larga para administrar.

La segunda manera de que pudiera hacer esto es propiedades de uso, y el uso de la incubadora en la devolución de llamada:

class Eggs(object): 

    def __init__(self): 
     self._spam = '' 
     self.spam = 'Monster' 
     print self.spam 
     spam_button.bind('<Enter>', lambda: self.set_spam('Ouch')) 

    def set_spam(self, spam): 
     if len(spam) <= 32: 
      self._spam = spam 

    def get_spam(self): 
     return self._spam 

    spam = property(get_spam, set_spam) 

De esta manera es un poco más sencillo que el primero cuando se accede al atributo directamente, pero es inconsistente cuando se usa lambda.

La tercera manera de hacer esto sería hacer una clase adicional con métodos get y set:

class Spam(object): 

    def __init__(self): 
     self._value = '' 

    def set(self, value): 
     if len(spam) <= 32: 
      self._value = value 

    def get(self): 
     return self._value 


class Eggs(object): 

    def __init__(self): 
     self._spam = '' 
     self.spam = Spam() 
     self.spam.set('Monster') 
     print self.spam.get() 
     spam_button.bind('<Enter>', lambda: self.spam.set('Ouch')) 

Este método es más organizado que el primero, pero tendrá que hacer una clase para cada tipo de validación

La última forma que podría hacer es utilizar métodos en lugar de propiedades (propiedades fueron el segundo ejemplo):

class Eggs(object): 

    def __init__(self): 
     self._spam = '' 
     self.spam('Monster') 
     print self.spam() 
     spam_button.bind('<Enter>', lambda: self.spam('Ouch')) 

    def spam(self, spam=None): 
     if spam != None: 
      if len(spam) <= 32: 
       self._spam = spam 
     else: 
      return self._spam 

Este método probablemente será la más corta, pero es más difícil de un vistazo para saber si Estoy consiguiendo o poniendo.

¿Cuáles (si alguno) de estos métodos son preferidos?

Respuesta

2

Utilice un cierre para devolver una función para ser utilizado como una devolución de llamada, y definir cada condición como una función (usando lambda o def, su preferencia) en lugar de cada setter como una función.

class Eggs(object): 

    def __init__(self): 
     self.spam = 'Monster' 
     def spam_condition(string = None): 
      return (string is not None) and (len(string) <= 32) 
     self.spam_setter = self.set('spam', 'Ouch', spam_condition) 
     spam_button.bind('<Enter>', self.spam_setter) 
     self.spam_setter(val='Horse') 


    def set(self, name, value, condition): 
     def setter(val): 
      if type(val) is not .... : # fill this in with the TYPE of the event 
       value = val 
      if condition(value): 
       setattr(self, name, value) 
     return setter 

Editar: Ahora puede llamar a la incubadora, que verificará la condición, desde cualquier lugar.

Edición 2: De esta manera, el colocador verificará si obtuvo un evento, y si no, use el valor que ha pasado. También puede utilizar:

if not isinstance(val, ....) : # fill this in with the CLASS of the event 
+0

Gracias por la idea, pero realmente no quiero usar cadenas para acceder a los atributos (también puedo usar un diccionario), y esto no proporciona una manera de establecer y validar el valor fuera de un enlace (a menos que haga 'self.set ('spam', 'Ouch', spam_condition)()' cada vez que quiera establecer el valor, que es realmente feo). Además, deseará agregar un parámetro 'event' a la función' setter', o bien Tkinter no puede pasar el evento (y obtiene un 'ValueError'). –

+0

Ahora el setter se puede usar desde cualquier lugar. – agf

+0

Si utilizo 'lambda e: self.spam_setter()' como devolución de llamada y elimino el parámetro 'event' de' setter', no necesito especificar 'val =' cada vez que establezco el valor. ¿Es posible deshacerse de la 'e' de' lambda e: 'en la devolución de llamada? –

1

Todavía sugiero usar un dict y llamarlo un día.Subclase y anular __setitem__ hacer su validación de datos, entonces no se preocupe por captadores:

#!/usr/bin/env python 

class Egg(dict): 
    def __init__(self, *args, **kwargs): 
     super(Egg, self).__init__(*args, **kwargs) 
     self['spam'] = 'foo' 
     spam_button.bind('<Enter>', lambda: self.__setitem__('spam', 'Ouch')) 

    def __setitem__(self, key, value): 
     if key == 'spam': 
      if len(value) > 32: 
       raise ValueError('"%s" is longer than 32 characters') 
      return super(Egg, self).__setitem__(key, value) 
     raise KeyError(key) 
0

El problema de validación puede ser manejado usando un property:

class Egg(object): 
    @property 
    def spam(self): 
     return self._spam 

    @spam.setter 
    def spam(self, value):  
     if len(value) <= 32: 
      self._spam = value 

Obviamente, todavía se puede utilizar self._spam = 'spam '*10+'baked beans and spam' con impunidad.

Usar la orden interna setattr:

lambda: setattr(self, 'spam', 'Ouch') 

Si usted se opone a ..."spam"... y prefieren simplemente ...spam..., puede utilizar métodos de property:

lambda: self.__class__.spam.fset(self, 'Ouch') 

o, ya property es un descriptor:

lambda: type(self).spam.__set__(self, 'Ouch') 

Pero la primera versión es preferida, espero por razones obvias.

+0

Él quiere verificar automáticamente la condición cada vez que establece correo no deseado, por lo que simplemente usar 'setattr' no ayuda. – agf

0

Me gusta el enfoque basado en la clase. Probablemente tenga un conjunto limitado de validación que quiera hacer (por ejemplo, longitud máxima de una cadena, rango válido para un número, etc.), por lo que tendrá un número limitado de clases.

Por ejemplo:

class LimitedLengthString(object): 
    def __init__(self, max_length): 
     self.max_length = max_length 
    def set(self, value): 
     if len(value) <= self.max_length: 
      self.value = value 
    def get(self): 
     return value 

class Eggs(object): 
    def __init__(self): 
     self.spam = LimitedLengthString(32) 
     self.spam.set('Monster') 
     print self.spam.get() 
     spam_button.bind('<Enter>', lambda: self.spam.set('Ouch')) 

Si realmente tiene validaciones únicos que necesitan ser hechas a los diferentes atributos, no se puede escapar de escribir mucho código. Pero como siempre en la programación, generaliza cuando empieces a repetirte.


Update después de sugerencia de TokenMacGuy: Alternativa utilizando la función de Python bastante desconocida "descriptores":

class LimitedLengthString(object): 
    def __init__(self, name, max_length): 
     self.name = name 
     self.max_length = max_length 

    def __set__(self, instance, value): 
     if len(value) <= self.max_length: 
      instance.__dict__[self.name] = value 

    def __get__(self, instance, owner): 
     return instance.__dict__[self.name] 

class Eggs(object): 
    spam = LimitedLengthString('spam', 32) 
    def __init__(self): 
     self.spam = 'Monster' 
     print self.spam # prints 'Monster' 
     self.spam = 'x' * 40 
     print self.spam # still 'Monster' 
     spam_button.bind('<Enter>', lambda: self.spam = 'Ouch') 

me encontré con un pretty good introduction to descriptors.

+0

¿por qué no implementar el protocolo descriptor? (http://docs.python.org/reference/datamodel.html#descriptors) – SingleNegationElimination

+0

@TokenMacGuy: Buen punto. Actualizado mi respuesta. –

Cuestiones relacionadas