Как определить свойства в __init__ - PullRequest
5 голосов
/ 21 сентября 2009

Я хочу определить свойства в классе из функции-члена. Ниже приведен тестовый код, показывающий, как я хотел бы, чтобы это работало. Однако я не получаю ожидаемого поведения.

class Basket(object):

  def __init__(self):
    # add all the properties
    for p in self.PropNames():
      setattr(self, p, property(lambda : p) )

  def PropNames(self):
    # The names of all the properties
    return ['Apple', 'Pear']

  # normal property
  Air = property(lambda s : "Air")

if __name__ == "__main__":
  b = Basket()
  print b.Air # outputs: "Air"
  print b.Apple # outputs: <property object at 0x...> 
  print b.Pear # outputs: <property object at 0x...> 

Как я мог заставить это работать?

Ответы [ 3 ]

13 голосов
/ 21 сентября 2009

Вам необходимо установить свойства для класса (то есть: self.__class__), а не для объекта (т.е.: self). Например:

class Basket(object):

  def __init__(self):
    # add all the properties
    setattr(self.__class__, 'Apple', property(lambda s : 'Apple') )
    setattr(self.__class__, 'Pear', property(lambda s : 'Pear') )

  # normal property
  Air = property(lambda s : "Air")

if __name__ == "__main__":
  b = Basket()
  print b.Air # outputs: "Air"
  print b.Apple # outputs: "Apple"
  print b.Pear # outputs: "Pear"

Как бы то ни было, использование p при создании lamdas в цикле не дает ожидаемого поведения. Поскольку значение p изменяется при прохождении цикла, оба свойства, установленные в цикле, возвращают одно и то же значение: последнее значение p.

3 голосов
/ 21 сентября 2009

Это делает то, что вы хотели:

class Basket(object):
  def __init__(self):
    # add all the properties

    def make_prop( name ):
        def getter( self ):
            return "I'm a " + name
        return property(getter)

    for p in self.PropNames():
        setattr(Basket, p, make_prop(p) )

  def PropNames(self):
    # The names of all the properties
    return ['Apple', 'Pear', 'Bread']

  # normal property
  Air = property(lambda s : "I'm Air")

if __name__ == "__main__":
  b = Basket()
  print b.Air 
  print b.Apple 
  print b.Pear 

Другим способом сделать это был бы метакласс ... но они смущают многих людей ^^.

Потому что мне скучно:

class WithProperties(type):
    """ Converts `__props__` names to actual properties """
    def __new__(cls, name, bases, attrs):
        props = set( attrs.get('__props__', () ) )
        for base in bases:
            props |= set( getattr( base, '__props__', () ) )

        def make_prop( name ):
            def getter( self ):
                return "I'm a " + name
            return property( getter )

        for prop in props:
            attrs[ prop ] = make_prop( prop )

        return super(WithProperties, cls).__new__(cls, name, bases, attrs)       

class Basket(object):
    __metaclass__ = WithProperties
    __props__ = ['Apple', 'Pear']

    Air = property(lambda s : "I'm Air")

class OtherBasket(Basket):
    __props__ = ['Fish', 'Bread']

if __name__ == "__main__":
    b = Basket()
    print b.Air 
    print b.Apple 
    print b.Pear 

    c = OtherBasket()
    print c.Air 
    print c.Apple 
    print c.Pear
    print c.Fish 
    print c.Bread 
0 голосов
/ 21 сентября 2009

Почему вы определяете свойства в __init__ время? Это запутанно и умно, так что вам лучше иметь действительно вескую причину. Проблема цикла, на которую указал Стеф, является лишь одним из примеров того, почему этого следует избегать.

Если вам нужно переопределить свойства подкласса, вы можете просто сделать del self.<property name> в методе подкласса __init__ или определить новые свойства в подклассе.

Кроме того, некоторые щипцы стиля:

  • Отступ до 4 пробелов, а не 2
  • Не смешивайте типы цитат без необходимости
  • Используйте имена подчеркивания вместо верблюда. PropNames -> prop_names
  • PropNames не обязательно должен быть метод
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...