簡體   English   中英

制作可在 lambda 中使用的屬性的最 Pythonic 方法是什么?

[英]What would be the most pythonic way to make an attribute that can be used in a lambda?

更具體地說,我希望能夠支持lambda: <some_or_other_setter> ,但我想保持代碼簡潔明了。 我必須驗證這個值,所以我需要某種 setter。 我需要使用 lambda 因為我需要將回調傳遞給 Tkinter 事件。 我還需要能夠修改綁定之外的屬性值。

在下面的示例中,假設已全局聲明了一個名為spam_button的按鈕小部件。 還假設 class Eggs至少有 10 個屬性,而不是需要以相同的方式訪問(我喜歡一致性)。

我可以做到這一點的第一種可能的方法是只使用 getter 和 setter:

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)

這種方式在直接訪問屬性時比第一種方式簡單一點,但是在使用lambda時就不一致了。

第三種方法是使用 get 和 set 方法制作一個額外的 class :

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。

我可能做的最后一種方法是使用方法而不是屬性(屬性是第二個例子):

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

這種方法可能是最短的,但很難一目了然地判斷我是在獲取還是設置。

這些方法中的哪一種(如果有的話)是首選?

Use a closure to return a function to be used as a callback, and define each condition as a function (using lambda or def , your preference) instead of each setter as a function.

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:這樣,setter 將檢查它是否收到事件,如果沒有,則使用它傳遞的值。 您還可以使用:

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

我仍然建議使用字典並稱之為一天。 將其子類化並覆蓋__setitem__以進行數據驗證,然后不必擔心 getter:

#!/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)

可以使用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')

如果您 object 到..."spam"...並且更喜歡...spam... ,您可以使用property方法:

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

或者,因為property是一個描述符:

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

但首選第一個版本,我希望有明顯的原因。

我喜歡基於類的方法。 您可能需要進行一組有限的驗證(例如字符串的最大長度、數字的有效范圍等),因此您將擁有有限數量的類。

例如:

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

我發現了一個很好的描述符介紹

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM