class属性无缘无故地改变值

时间:2014-03-07 19:54:09

标签: python attributes descriptor

我正在写一个我正在编写的程序有问题而且我不能为我的生活弄清楚我做错了什么。 好吧基本上我正在编写一个程序来从XML文档中提取数据并使用数据的类表示进行操作。

现在,我的程序中增加了一些复杂性,因为我试图变得聪明并使用描述符(我正在了解它们并且认为我会尝试将它们集成到我的代码中)

注意:我将问题缩短为自包含的python脚本,可以按原样运行:

#!/usr/bin/env python
import inspect

class Numberise(object):
    def __init__(self, value=0, base=16):
        self.base = base
        self.value = value

    def __get__(self, obj, objtype):
        return self.value
    def __set__(self, obj, val):
        #self.value = self.extract_number(val)
        self.value = val
        print 'set value to:', self.value


class Register(object):
    width = Numberise(base=10)
    def __init__(self, width=16, name='unNamed'):
        super(Register, self).__init__()
        tuple_args = inspect.getargvalues(inspect.currentframe()) #shorthand
        for arg in tuple_args[0]:
            setattr(self, arg, tuple_args[3][arg])

if __name__ == "__main__":

    new_regs = [Register(width=i) for i in range(10)]
    for i,reg in enumerate(new_regs):
        reg.width = i


    for R in new_regs:
        print 'In extract(). Id:%s name:%s, width:%d'%(id(R), R.name, R.width)

当我运行脚本时,我得到以下输出:

C:\Users\gkuhn\Desktop>python test.py
set value to: 0
set value to: 1
set value to: 2
set value to: 3
set value to: 4
set value to: 5
set value to: 6
set value to: 7
set value to: 8
set value to: 9
set value to: 0
set value to: 1
set value to: 2
set value to: 3
set value to: 4
set value to: 5
set value to: 6
set value to: 7
set value to: 8
set value to: 9
In extract(). Id:48851280 name:unNamed, width:9
In extract(). Id:48852080 name:unNamed, width:9
In extract(). Id:48879472 name:unNamed, width:9
In extract(). Id:49285200 name:unNamed, width:9
In extract(). Id:49291504 name:unNamed, width:9
In extract(). Id:49291984 name:unNamed, width:9
In extract(). Id:49292016 name:unNamed, width:9
In extract(). Id:49292048 name:unNamed, width:9
In extract(). Id:49292080 name:unNamed, width:9
In extract(). Id:49292112 name:unNamed, width:9

我想要的是宽度值个人对我所做的每个寄存器对象。看起来它正在被共享。他们不应该是个人吗?!

以下是我原来的问题,你不需要阅读它,但无论如何我都把它留在了这里。实际问题已经讨论过了。

所以在下面的代码片段中,我基本上抓住了我新创建的Register对象并将它们添加到已经创建的列表中。

    self.regs = []
    temps = []

    for register in self.ip_root:
        unrolled_regs = UnrollRegister(register)
        new_regs = unrolled_regs.convert()
        for R in new_regs:
            #print 'In extract(). Id:%s name:%s, width:%d'%(id(R), R.name, R.width)
            if 'extended' in R.name.lower():
                print 'In extract(). Id:%s name:%s, width:%d'%(id(R), R.name, R.width)
                temps.append(R)
                #print 'In extract(). Id:%s name:%s, width:%d'%(id(R), R.name, R.width)
                a = copy.deepcopy(R)
                #print type(R).__dict__
                #print temps

        #self.regs.extend(new_regs)
        self.regs += new_regs
        #self.regs.extend(unrolled_regs.convert())

    for n in temps:
        print '\tIn loop. Id:%s name:%s, width:%d'%(id(n), n.name, n.width)
        #print type(n).__dict__

请原谅印刷品,我一直在试图解决这个问题!

类Register的定义是:

class Register(Base):
    width = Numberise(base=10)
    address = Numberise(base=10)
    def __init__(self, name='unNamed', width=16, description='No description provided', 
                 access='RW', address=0, visibility='Public', reset='async',
                 documentation=''):
        super(Register, self).__init__()
        tuple_args = inspect.getargvalues(inspect.currentframe()) #shorthand
        for arg in tuple_args[0]:
            setattr(self, arg, tuple_args[3][arg])

        self.bitfields = []

如上所述,我正在使用宽度和地址属性的数据描述符。 Numberise描述符的定义是:

class Numberise(Base):
    def __init__(self, value=0, base=16):
        self.base = base
        self.value = self.extract_number(value) 

    def __get__(self, obj, objtype):
        return self.value
    def __set__(self, obj, val):
        self.value = self.extract_number(val)

    def extract_number(self,input):
        "try and get the value being represented"
        if type(input) == int: #its already a number
            return input
        else: #its a string
            RE = re.compile(r"\d?'([hHdDbB])(\w+)") #of the form 'h10 (verilog)
            result = RE.search(input)
            if result is not None:
                radix, string_num = result.groups()
                return int(string_num, {'h':16, 'd':10, 'b':2}[radix.lower()])
            else:
                return int(input, self.base)

Base不包含太多内容,为了清楚起见,我在此处将其包含在内:

class Base(object):
    def __init__(self):
        self._parent_spacer = ''
        self._spacer = '\t'

    @property
    def parent_spacer(self):
        return self._parent_spacer
    @parent_spacer.setter
    def parent_spacer(self, value):
        self._parent_spacer = value
    @property
    def spacer(self):
        return self.parent_spacer+'\t'

这个描述符背后的想法是确保无论我们初始化宽度和地址属性是什么,保存的值将始终是整数而不是字符串。

现在运行代码后的所有重要输出:

In extract(). Id:239825680 name:ASIC_ADC_RESULTS_EXTENDED_READ, width:64
In extract(). Id:239779088 name:ASIC_HART_EXTENDED_RECEIVE_BUFFER, width:64
        In loop. Id:239825680 name:ASIC_ADC_RESULTS_EXTENDED_READ, width:16
        In loop. Id:239779088 name:ASIC_HART_EXTENDED_RECEIVE_BUFFER, width:16

有人能保存我的理智并向我解释这种行为吗?!

2 个答案:

答案 0 :(得分:1)

看完这篇文章后好了: http://martyalchin.com/2007/nov/24/python-descriptors-part-2-of-2/ 我看到了我的方式的错误。 在上述文章中,提供了以下代码段,其中说明了在此方案中使用描述符的正确方法:

class SimpleDescriptor(object):
   def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        if self.name not in instance.__dict__:
            raise AttributeError, self.name
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value

所以我的描述符应该是这样的:

class Numberise(object):
    def __init__(self, value=0, base=16):
        self.base = base
        self.value = value

    def __get__(self, obj, objtype):
        return obj.value
    def __set__(self, obj, val):
        #self.value = self.extract_number(val)
        obj.value = val
        print 'set value to:', self.value

我通过使用以下类作为参考来犯了错误:

class RevealAccess(object):
    """A data descriptor that sets and returns values
       normally and prints a message logging their access.
    """

    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        print 'Retrieving', self.name
        return self.val

    def __set__(self, obj, val):
        print 'Updating', self.name
        self.val = val

>>> class MyClass(object):
    x = RevealAccess(10, 'var "x"')
    y = 5

>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5

上述课程取自文档:http://docs.python.org/2/howto/descriptor.html 虽然这个例子没有错,而且它做了它本应该做的事情,但在这种情况下它并不适用于我的错误。

答案 1 :(得分:0)

注意:这个答案类似于OP的答案,但有一些值得注意的差异。

在阅读article链接的another relevant SO question后,我会看到以下代码:

#!/usr/bin/env python
import inspect

class Numberise(object):
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        if self.name not in instance.__dict__:
            raise (AttributeError, self.name)
        return '%o'%(instance.__dict__[self.name])

    def __set__(self, instance, value):
        print ('setting value to: %d'%value)
        instance.__dict__[self.name] = value

class Register(object):
    width = Numberise("truewidth")
    def __init__(self, width=16, name='unNamed'):
        super(Register, self).__init__()
        tuple_args = inspect.getargvalues(inspect.currentframe()) #shorthand
        for arg in tuple_args[0]:
            setattr(self, arg, tuple_args[3][arg])

if __name__ == "__main__":

    new_regs = [Register(width=i) for i in range(10)]
    for i,reg in enumerate(new_regs):
        reg.width = i

    for R in new_regs:
        print ('In extract(). Id:%s name:%s, width:%s, truewidth:%d'%(id(R), R.name, R.width, R.truewidth))

这个程序产生的输出我觉得是你想要的:

setting value to: 0
setting value to: 1
setting value to: 2
setting value to: 3
setting value to: 4
setting value to: 5
setting value to: 6
setting value to: 7
setting value to: 8
setting value to: 9
setting value to: 0
setting value to: 1
setting value to: 2
setting value to: 3
setting value to: 4
setting value to: 5
setting value to: 6
setting value to: 7
setting value to: 8
setting value to: 9
In extract(). Id:35542384 name:unNamed, width:0, truewidth:0
In extract(). Id:35543152 name:unNamed, width:1, truewidth:1
In extract(). Id:35537776 name:unNamed, width:2, truewidth:2
In extract(). Id:36072560 name:unNamed, width:3, truewidth:3
In extract(). Id:36070384 name:unNamed, width:4, truewidth:4
In extract(). Id:36073040 name:unNamed, width:5, truewidth:5
In extract(). Id:36073072 name:unNamed, width:6, truewidth:6
In extract(). Id:36073104 name:unNamed, width:7, truewidth:7
In extract(). Id:36073136 name:unNamed, width:10, truewidth:8
In extract(). Id:36073168 name:unNamed, width:11, truewidth:9

以下是对所发生情况的解释。在Register类的行width = Numberise("truewidth")中,我们引入了描述符。它是每个类一个,而不是每个实例一个,因此Numberise本身没有存储任何值:我们必须将实际值存储在实例中。定义的描述符允许我们访问Register类的实例的成员变量self.truewidth。出于说明的目的,__get__方法不返回truewidth(将是return instance.__dict__[self.name]),而是将其字符串表示为八进制数。打印R.width正在通过描述符访问它。打印R.truewidth正在直接访问它。

我们可以调用成员变量width,与描述符相同,并且不存在命名冲突:描述符是类命名空间的一部分,成员变量是每个实例的一部分命名空间。因此,truewidth仅用于清晰,以更好地区分这两个实体。在实际代码中,或许将其命名为width更好,以便实际数据隐藏在描述符后面,并且您无法偶然访问它。

此外,该程序对Python2和Python3都很友好,只需在raiseprint的行中添加括号即可。