如何在__init__中定义属性

5 投票
3 回答
4693 浏览
提问于 2025-04-15 14:28

我想在一个类里面通过一个成员函数来定义属性。下面是一些测试代码,展示了我希望它能如何工作。不过,我得到的结果并不是我预期的那样。

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 个回答

0

你为什么要在__init__的时候定义属性呢?这样做会让人感到困惑,而且显得很聪明,所以你最好有个很好的理由。Stef提到的循环问题就是一个例子,说明了为什么应该避免这样做。

如果你需要重新定义一个子类的属性,可以在子类的__init__方法里用del self.<属性名>来删除,或者在子类里定义新的属性。

另外,还有一些风格上的小建议:

  • 缩进用4个空格,不要用2个
  • 不要随便混用不同类型的引号
  • 方法名用下划线,而不是驼峰命名法。比如PropNames应该改成prop_names
  • PropNames其实不需要定义成一个方法
3

这段代码实现了你想要的功能:

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 
13

你需要在类上设置属性(也就是 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"

顺便提一下,你在循环中创建 lambda 函数时使用的 p,并不会产生你想要的效果。因为在循环过程中,p 的值会不断变化,所以在循环中设置的两个属性最终都会返回相同的值:就是 p 的最后一个值。

撰写回答