无法调用超类的重写setter属性

时间:2014-06-05 18:56:18

标签: python-2.7 inheritance properties

我试图使用python属性和继承,而且某些东西并不直观。我希望能够利用继承类的属性获取器和设置器,以避免重复代码重复行为。

为了解决我的问题,我创建了以下示例。我有一辆汽车,其行为可以计算乘客,并在给予一定数量的乘客(使用乘客属性吸气剂和安装者)时填充汽车座椅。对于有3排座位的面包车,我只需要定义第3个座位的行为,并按照前2行继承的Car类...

class Car(object):
    def __init__(self):

        #Store vehicle passengers in a dictionary by row
        # - by default we'll have 1 in the front seat and 1 in the back
        self._contents = {'row1': 1,
                          'row2': 1}

    @property
    def occupants(self):
        """
        Number of occupants in the vehicle
        """

        #Get the number of people in row 1
        row1 = self._contents['row1']
        #Get the number of people in row 2
        row2 = self._contents['row2']
        return row1 + row2

    @occupants.setter
    def occupants(self, val):

        #Start with an empty car
        self._contents['row1'] = 0
        self._contents['row2'] = 0

        #Check to see whether there are more than 2 people entering the car
        if val > 2:
            #Put 2 in the front seats
            self._contents['row1'] = 2

            #Put the rest in the back seat - no matter how many there are!
            self._contents['row2'] = val - 2
        else:
            #Since there are 2 or fewer people, let them sit in the front
            self._contents['row1'] = val


class Van(Car):
    def __init__(self):
        super(Van, self).__init__()

        #Van's have an additional 3rd row
        self._contents['row3'] = 1

    @property
    def occupants(self):

        #Number of people in first 2 rows
        first_2_rows = super(Van, self).occupants
        #Number of people in 3rd row
        row3 = self._contents['row3']

        #Total number of people
        return first_2_rows + row3

    @occupants.setter
    def occupants(self, val):

        #Start with an empty van (Car class handles row1 and row2)
        self._contents['row3'] = 0

        #Check if there are more than 4 people entering the van
        if val > 4:
            #Put all but 4 folks in the back row
            self._contents['row3'] = val - 4

            #Load the first 2 rows in the same manner as for a car
            #This causes an AttributeError
            super(Van, self).occupants = 4
        else:
            #This causes an AttributeError
            super(Van, self).occupants = val

if __name__ == '__main__':

    van = Van()

    print "Van has {0} people".format(van.occupants)

    print "Seating 6 people in the van..."
    van.occupants = 6

    print "Van has {0} people".format(van.occupants)

我得到的输出如下:

Van has 3 people
Seating 6 people in the van...

Traceback (most recent call last):
  File "C:/scratch.py", line 74, in <module>
    van.occupants = 6
  File "C:/scratch.py", line 65, in occupants
    super(Van, self).occupants = 4
AttributeError: 'super' object has no attribute 'occupants'

Process finished with exit code 1

我特别感兴趣的是超类的getter工作正常,但是当我尝试使用setter时,我得到了属性错误。我错误地使用super()了吗?有更好的方法吗?

我的实际应用涉及在文本文件格式和类字典数据结构之间进行读/写。文本文件中的一些内容由我的基类解析出来,其他一些特殊参数由子类处理。在子类setter中,我想首先让基类从文本文件中解析它所需的任何内容(填充数据结构),然后让子类解析在继承的数据结构中存储的其他值。

有些研究引导我this,最终Issue 505028将其宣布为&#34;功能&#34;而不是一个错误。那么,除此之外,有没有办法使用属性和继承使上述逻辑工作?我是否必须使用Car.occupants.fset(self, 4)或其他内容?我可以在一分钟内回答自己,但我会继续发布并与人分享。如果它是重复的话,那就太好了。

编辑: 修复了一些额外的错误,例如在设置占用者之前清空所有座位,并且Van占用者设置器中的数字逻辑是错误的和不完整的(只有在修复了属性错误后才会变得明显)。

2 个答案:

答案 0 :(得分:1)

正如您所说,Guido van Rossum says

  

...超级语义......仅适用于       代码属性,而不是数据属性。

因此,解决方法是调用代码属性,而不是数据属性。这意味着,在这种情况下,您需要保持对 setter方法的引用;我们称之为set_occupants。而不是

@occupants.setter
def occupants(self, val):

使用

def set_occupants(self, val):
    ...
occupants = property(get_occupants, set_occupants)

而不是super(...).occupants = 4,你可以调用super的方法:

super(Van, self).set_occupants(4)

class Car(object):
    def __init__(self):

        #Store vehicle passengers in a dictionary by row
        # - by default we'll have 1 in the front seat and 1 in the back
        self._contents = {'row1': 1,
                          'row2': 1}

    def get_occupants(self):
        """
        Number of occupants in the vehicle
        """

        #Get the number of people in row 1
        row1 = self._contents['row1']
        #Get the number of people in row 2
        row2 = self._contents['row2']
        return row1 + row2

    def set_occupants(self, val):

        #Check to see whether there are more than 2 people entering the car
        if val > 2:
            #Put 2 in the front seats
            self._contents['row1'] = 2

            #Put the rest in the back seat - no matter how many there are!
            self._contents['row2'] = val - 2
        else:
            #Since there are 2 or fewer people, let them sit in the front
            self._contents['row1'] = val
    occupants = property(get_occupants, set_occupants)


class Van(Car):
    def __init__(self):
        super(Van, self).__init__()

        #Van's have an additional 3rd row
        self._contents['row3'] = 1

    def get_occupants(self):

        #Number of people in first 2 rows
        first_2_rows = super(Van, self).occupants
        #Number of people in 3rd row
        row3 = self._contents['row3']

        #Total number of people
        return first_2_rows + row3

    def set_occupants(self, val):

        #Check if there are more than 4 people entering the van
        if val > 4:
            #Put all but 4 folks in the back row
            self._contents['row3'] = val - 4

        #Load the first 2 rows in the same manner as for a car
        super(Van, self).set_occupants(4)

    occupants = property(get_occupants, set_occupants)

if __name__ == '__main__':

    van = Van()

    print "Van has {0} people".format(van.occupants)

    print "Seating 6 people in the van..."
    van.occupants = 6

    print "Van has {0} people".format(van.occupants)

产量

Van has 3 people
Seating 6 people in the van...
Van has 6 people

要继续使用@property装饰器,但仍然可以从super调用setter,而无需手动添加大量额外属性,您可以使用元类为你做的工作。类装饰器也是可能的,但是元类的优点是你只需要将它定义为Car的元类,然后元类及其行为由Car的所有子类继承。而,必须手动将类装饰器应用于每个子类。


class MetaCar(type):
    def __init__(cls, name, bases, clsdict):
        super(MetaCar, cls).__init__(name, bases, clsdict)
        for name, val in clsdict.items():
            if isinstance(val, property):
                setattr(cls, 'get_{}'.format(name), val.fget)
                setattr(cls, 'set_{}'.format(name), val.fset)
                setattr(cls, 'del_{}'.format(name), val.fdel) 

class Car(object):
    __metaclass__ = MetaCar

    def __init__(self):

        #Store vehicle passengers in a dictionary by row
        # - by default we'll have 1 in the front seat and 1 in the back
        self._contents = {'row1': 1,
                          'row2': 1}
    @property
    def occupants(self):
        """
        Number of occupants in the vehicle
        """

        #Get the number of people in row 1
        row1 = self._contents['row1']
        #Get the number of people in row 2
        row2 = self._contents['row2']
        return row1 + row2

    @occupants.setter
    def occupants(self, val):

        #Check to see whether there are more than 2 people entering the car
        if val > 2:
            #Put 2 in the front seats
            self._contents['row1'] = 2

            #Put the rest in the back seat - no matter how many there are!
            self._contents['row2'] = val - 2
        else:
            #Since there are 2 or fewer people, let them sit in the front
            self._contents['row1'] = val


class Van(Car):
    def __init__(self):
        super(Van, self).__init__()

        #Van's have an additional 3rd row
        self._contents['row3'] = 1

    @property
    def occupants(self):

        #Number of people in first 2 rows
        first_2_rows = super(Van, self).occupants
        #Number of people in 3rd row
        row3 = self._contents['row3']

        #Total number of people
        return first_2_rows + row3

    @occupants.setter
    def occupants(self, val):

        #Check if there are more than 4 people entering the van
        if val > 4:
            #Put all but 4 folks in the back row
            self._contents['row3'] = val - 4

        #Load the first 2 rows in the same manner as for a car
        super(Van, self).set_occupants(4)


if __name__ == '__main__':
    van = Van()

    print "Van has {0} people".format(van.occupants)

    print "Seating 6 people in the van..."
    van.occupants = 6

    print "Van has {0} people".format(van.occupants)

答案 1 :(得分:0)

环顾网络,我发现SomeClass.property.fset(self, value)可以用来调用属性的setter。在这种情况下,SomeClass是Car(Van的超级类),self是当前的Van实例,汽车占用者设置器操作以填充面包车的前两行(就像汽车一样)。

class Car(object):
    def __init__(self):

        #Store vehicle passengers in a dictionary by row
        # - by default we'll have 1 in the front seat and 1 in the back
        self._contents = {'row1': 1,
                          'row2': 1}

    @property
    def occupants(self):
        """
        Number of occupants in the vehicle
        """

        #Get the number of people in row 1
        row1 = self._contents['row1']
        #Get the number of people in row 2
        row2 = self._contents['row2']
        return row1 + row2

    @occupants.setter
    def occupants(self, val):

        #Start with an empty car
        self._contents['row1'] = 0
        self._contents['row2'] = 0

        #Check to see whether there are more than 2 people entering the car
        if val > 2:
            #Put 2 in the front seats
            self._contents['row1'] = 2

            #Put the rest in the back seat - no matter how many there are!
            self._contents['row2'] = val - 2
        else:
            #Since there are 2 or fewer people, let them sit in the front
            self._contents['row1'] = val


class Van(Car):
    def __init__(self):
        super(Van, self).__init__()

        #Van's have an additional 3rd row
        self._contents['row3'] = 1

    @property
    def occupants(self):

        #Number of people in first 2 rows
        first_2_rows = super(Van, self).occupants
        #Number of people in 3rd row
        row3 = self._contents['row3']

        #Total number of people
        return first_2_rows + row3

    @occupants.setter
    def occupants(self, val):

        #Start with an empty van (first 2 rows handled by Car class)
        self._contents['row3'] = 0

        #Check if there are more than 4 people entering the van
        if val > 4:
            #Put all but 4 folks in the back row
            self._contents['row3'] = val - 4

            #Load the first 2 rows in the same manner as for a car
            Car.occupants.fset(self, 4)
        else:
            #Load the first 2 rows in the same manner as for a car
            Car.occupants.fset(self, val)


if __name__ == '__main__':

    van1 = Van()
    van2 = Van()

    print "Van has {0} people".format(van1.occupants)

    print "Seating 6 people in van1"
    van1.occupants = 6

    print "Seating 2 people in van2"
    van2.occupants = 2

    print "van1 has {0} people".format(van1.occupants)
    print "van2 has {0} people".format(van2.occupants)

输出结果为:

Van has 3 people
Seating 6 people in van1
Seating 2 people in van2
van1 has 6 people
van2 has 2 people

道具也向unutbu进行了大量工作,并证明这也可以通过属性函数或元类来解决。我还不确定哪种方法更优雅,因为每种方法都有亲和的方法。

如果在这种情况下回答我自己的问题是不好的形式,请打电话给我,我很乐意做任何必要的事情来遵循社区协议。