Какой был бы самый питонический способ сделать атрибут, который можно использовать в лямбде? - PullRequest
5 голосов
/ 22 июля 2011

В частности, я хочу иметь возможность поддерживать lambda: <some_or_other_setter>, но я хочу, чтобы код был ясным и лаконичным.Я должен проверить значение, поэтому мне нужен какой-то сеттер.Мне нужно использовать лямбду, потому что мне нужно передавать обратные вызовы событиям Tkinter.Мне также нужно иметь возможность изменять значение атрибута вне привязки.

В моих следующих примерах предположим, что виджет кнопки с именем spam_button был объявлен глобально.Также предположим, что класс Eggs будет иметь как минимум 10 атрибутов, к которым необходимо обращаться одинаково (мне нравится согласованность).

Первый возможный способ сделать это - использовать только методы получения и установки:

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

Но если я использую этот метод и у меня много атрибутов, код может оказаться слишком длинным для управления.

Второй способ, которым я мог бы это сделать, - это использовать свойства и использоватьsetter в обратном вызове:

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)

Этот способ немного проще первого при прямом доступе к атрибуту, но несовместим при использовании лямбды.

Третий способ сделать эточтобы создать дополнительный класс с методами get и 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'))

Этот метод более организован, чем первый, но мне нужно будет создать класс для каждого типа проверки.

последний способ сделать это - использовать методы вместо свойств (свойства были вторым примером):

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

Этот метод, вероятно, будет самым коротким, но на первый взгляд сложнее сказатьЯ получаю или настраиваю.

Какой (если таковой имеется) из этих методов предпочтительнее?

Ответы [ 4 ]

2 голосов
/ 22 июля 2011

Используйте замыкание для возврата функции, которая будет использоваться в качестве обратного вызова, и определите каждое условие как функцию (используя lambda или def, ваше предпочтение) вместо каждого установщика как функция.

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

Редактировать: Теперь вы можете вызывать установщик, который будет проверять состояние, из любого места.

Редактировать 2: Таким образом, установщик будет проверятьесли оно получило событие, а если нет, используйте значение, которое оно передало.Вы также можете использовать:

if not isinstance(val, ....) :  # fill this in with the CLASS of the event
1 голос
/ 22 июля 2011

Я все еще предлагаю использовать диктовку и называть ее днем. Разбейте его на подклассы и переопределите __setitem__ для проверки данных, затем не беспокойтесь о получателях:

#!/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 голосов
/ 22 июля 2011

Мне нравится классовый подход.Вероятно, у вас будет ограниченный набор проверок (например, максимальная длина строки, допустимый диапазон для числа и т. Д.), Поэтому у вас будет ограниченное количество классов.

Например:

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'))

Если у вас действительно есть уникальные проверки, которые необходимо выполнить для различных атрибутов, вы не сможете избежать написания большого количества кода.Но, как всегда в программировании, обобщайте, когда начинаете повторяться.


Обновление после предложения TokenMacGuy: Альтернатива, использующая довольно неизвестную функцию Python "дескрипторы":

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')

Я нашел довольно хорошее введение в дескрипторы .

0 голосов
/ 22 июля 2011

Проблема проверки может быть решена с помощью property:

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

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

Очевидно, что вы все еще можете использовать self._spam = 'spam '*10+'baked beans and spam' безнаказанно.

Использование встроенного setattr:

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

Если вы возражаете против ..."spam"... и предпочитаете просто ...spam..., вы можете использовать методы property:

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

или, поскольку property является дескриптором:

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

Но первая версия предпочтительнее, я надеюсь, по понятным причинам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...