如何在__init__方法中定义属性

100 阅读3分钟

在编写Python程序时,有时需要在类(class)的初始方法(__init__方法)中定义属性(property)。通过这种方式,可以在创建类实例时直接为属性指定值。但是,在某些情况下,可能会遇到无法在__init__方法中定义属性的问题。

例如,下面是一个测试代码,试图在__init__方法中使用setattr()方法来为属性p指定值:

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...> 

在上面的代码中,我们希望在__init__方法中动态地为类Basket定义两个属性Apple和Pear,这两个属性都是只读的属性。但是,当我们运行这段代码时,会发现Apple和Pear属性的输出结果并不是我们预期的字符串值,而是两个property对象。这表明属性Apple和Pear并没有被正确地定义。

2、解决方案

问题的原因在于,在__init__方法中,我们使用了setattr()方法将属性p的值设置为property(lambda : p)。这种做法是错误的,因为property()函数只能在类定义中使用,而不能在__init__方法中使用。

为了解决这个问题,我们需要将属性Apple和Pear的定义从__init__方法中移到类定义中。这样,我们就可以在类定义中使用property()函数来正确地定义这些属性。

下面是一个修改后的代码:

class Basket(object):

  # define the properties
  Apple = property(lambda s : 'Apple')
  Pear = property(lambda s : 'Pear')

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

  def __init__(self):
    pass

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

在这个修改后的代码中,我们将在类定义中直接使用property()函数来定义Apple和Pear属性,并在__init__方法中不再定义这些属性。这样,我们就可以正确地定义这些属性,并且在创建类实例时,这些属性的值也可以被正确地访问到。

除了上述方法之外,还可以使用metaclass来定义属性。metaclass是一个特殊的类,它可以用来创建其他类。通过使用metaclass,我们可以在创建类的时候动态地为类添加属性。

下面是一个使用metaclass来定义属性的例子:

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

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

在这个例子中,我们创建了一个叫做WithProperties的metaclass。这个metaclass有一个__new__()方法,它会在创建类的时候被调用。在__new__()方法中,我们会将类属性__props__中的值添加到类中。然后,我们会创建一个叫做make_prop()的函数,这个函数会创建一个property对象。最后,我们会将make_prop()函数生成的property对象添加到类中。

通过使用metaclass,我们可以在创建类的时候动态地为类添加属性。这种方法非常灵活,可以用来定义各种各样的属性。