Свойства класса и __setattr__

В классе Python, когда я использую __setattr__, он имеет приоритет над свойствами, определенными в этом классе (или любых базовых классах). Рассмотрим следующий код:

class Test(object):
    def get_x(self):
        x = self._x
        print "getting x: %s" % x
        return x
    def set_x(self, val):
        print "setting x: %s" % val
        self._x = val
    x = property(get_x, set_x)
    def __getattr__(self, a):
        print "getting attr %s" % a
        return -1
    def __setattr__(self, a, v):
        print "setting attr %s" % a

Когда я создаю класс и пытаюсь установить x, __setattr__ вызывается вместо set_x:

>>> test = Test()
>>> test.x = 2
setting attr x
>>> print test.x
getting attr x_
getting x: -1
-1

Чего я хочу добиться, так это чтобы фактический код в __setattr__ вызывался, только если нет соответствующего свойства, т. е. test.x = 2 должен вызвать set_x. Я знаю, что могу легко добиться этого, вручную проверяя, является ли a « x » __setattr__, однако это приведет к плохому дизайну. Есть ли более умный способ обеспечить правильное поведение в __setattr__ для каждого свойства, определенного в классе, и для всех базовых классов?

14 голосов | спросил Maciek D. 1 PMpMon, 01 Apr 2013 23:42:10 +040042Monday 2013, 23:42:10

4 ответа


0

Порядок поиска, который Python использует для атрибутов, выглядит следующим образом:

  1. __getattribute__ и __setattr__
  2. Дескрипторы данных, такие как property
  3. Переменные экземпляра из объекта __dict__ (при установке атрибута поиск заканчивается здесь)
  4. Дескрипторы, не относящиеся к данным (например, методы) и другие переменные класса
  5. __getattr__ литий>

Поскольку __setattr__ стоит первым в строке, если у вас есть такой, вам нужно сделать его умным, если вы не хотите обрабатывать все настройки атрибутов твой класс. Это может быть разумно любым из двух способов: сделать так, чтобы он обрабатывал только определенный набор атрибутов, или заставить его обрабатывать все, кроме некоторый набор атрибутов. Если вы не хотите, чтобы это обрабатывалось, вызовите super().__setattr__.

Для вашего примера класса обработка «всех атрибутов, кроме« x »», вероятно, самая простая:

def __setattr__(self, name, value):
    if name == "x":
        super(Test, self).__setattr__(name, value)
    else:
        print "setting attr %s" % name
ответил Blckknght 2 AMpTue, 02 Apr 2013 00:21:46 +040021Tuesday 2013, 00:21:46
0

Это не пуленепробиваемое решение, но, как вы и предлагали, вы можете проверить, является ли свойство setattr от пытается получить доступ к объекту property из атрибутов класса (используя getattr на объекте класса).

class Test(object):

    def get_x(self):
        x = self._x
        print "getting x: %s" % x
        return x
    def set_x(self, val):
        print "setting x: %s" % val
        self._x = val
    x = property(get_x, set_x)

    @property  # no fset
    def y(self):
        print "getting y: 99"
        return 99

    def __getattr__(self, a):
        print "getting attr %s" % a
        return -1
    def __setattr__(self, a, v):
        propobj = getattr(self.__class__, a, None)
        if isinstance(propobj, property):
            print "setting attr %s using property's fset" % a
            if propobj.fset is None:
                raise AttributeError("can't set attribute")
            propobj.fset(self, v)
        else:
            print "setting attr %s" % a
            super(Test, self).__setattr__(a, v)


test = Test()
test.x = 2
print test.x
#test.y = 88  # raises AttributeError: can't set attribute
print test.y
test.z = 3
print test.z

РЕДАКТИРОВАТЬ: заменено self.__dict__[a] = v на super(Test, self).__setattr__(a, v) как видно из ответа @ Blckknght

ответил shx2 2 AMpTue, 02 Apr 2013 00:20:26 +040020Tuesday 2013, 00:20:26
0

AFAIK, нет чистого способа сделать это. Проблема здесь возникает из-за асимметрии между __getattr__ и __setattr__. Первый вызывается только в том случае, если атрибут нормальным способом не работает, а второй вызывается безоговорочно. Поскольку не существует общего способа, которым __setattr__ потерпит неудачу, я не знаю, есть ли способ изменить это поведение.

В конечном счете, я считаю, что единственный способ получить желаемое поведение - это вставить действие set_ ваших свойств в ваш __setattr__ функция - И если вы делаете это, вы можете также сложить поведение геттеров в __getattr__, чтобы все это можно было поддерживать из 1 место.

ответил mgilson 1 PMpMon, 01 Apr 2013 23:54:28 +040054Monday 2013, 23:54:28
0

Я сталкивался с этой проблемой несколько раз, мое предпочтительное решение заключается в следующем:

  • Для каждого свойства [property] определите get_[property] и set_[property] работают так же, как вы, но без использования декораторов
  • Измените __getattr__ и __setattr__ так чтобы они проверяли наличие этих функций и использовали их, если они доступны.

Вот минимальный пример:

class SmartSetter(object):

  def __init__(self,name):
    self.set_name(name)

  def get_name(self):
    #return the name property
    return self._name

  def set_name(self,value):
    #set the name property
    self._name = value

  def __getattr__(self,key):
    #first condition is to avoid recursive calling of this function by
    #__setattr__ when setting a previously undefined class attribute.
    if not key.startswith('get_') and hasattr(self,'get_'+key):
      return getattr(self,'get_'+key)()
    #implement your __getattr__ magic here...
    raise AttributeError("no such attribute: %s" % key)

  def __setattr__(self,key,value):
    try:
      return getattr(self,'set_'+key)(value)
    except AttributeError:
      #implement your __setattr__ magic here...
      return super(SmartSetter,self).__setattr__(key,value)

if __name__ == '__main__':
  smart_setter = SmartSetter("Bob the Builder")

  print smart_setter.name
  #Will print "Bob the Builder"

  smart_setter.name = "Spongebob Squarepants"

  print smart_setter.name
  #Will print "Spongebob Squarepants"

Преимущество этого метода в том, что он сохраняет нормальное поведение для всех атрибутов, кроме тех, которые имеют методы "getter" и "setter", и что он не требует каких-либо модификаций __getattr__ и __setattr__ работают при добавлении или удалении свойств.

ответил ThePhysicist 23 MaramSun, 23 Mar 2014 02:56:21 +04002014-03-23T02:56:21+04:0002 2014, 02:56:21

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132