【问题标题】:class attribute changing value for no reason类属性无故改变值
【发布时间】:2014-03-07 19:54:09
【问题描述】:

我正在编写的程序有问题,我终其一生都无法弄清楚我做错了什么。 好的,所以基本上我正在编写一个程序来从 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

我想要的是将宽度值 放在我制作的每个寄存器对象中。看起来它正在被共享。他们不应该是个体的吗?!

以下是我最初的问题,您不需要阅读它,但我还是把它留在这里了。实际问题已经讨论过了。

所以在下面的 sn-p 中,我基本上是在抓取我新创建的 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

谁能拯救我的理智并向我解释这种行为?!

【问题讨论】:

  • 您能否详细说明“这种行为”的含义?你期待看到什么?

标签: python attributes descriptor


【解决方案1】:

看完这篇文章好了: http://martyalchin.com/2007/nov/24/python-descriptors-part-2-of-2/ 我已经看到了我的方式的错误。 在上述文章中,提供了以下 sn-p,它说明了在这种情况下使用描述符的正确方法:

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 虽然这个例子没有错,它做了它应该做的事情,但显然它不适用于这种情况,这是我的错误。

【讨论】:

  • 我的答案基本相同,因为我从同一篇文章中获得了灵感。不过,我认为无论如何都值得发布,原因有两个。首先,我想了解自己发生了什么。其次,您的代码在 Numberise 中仍然有 self.value,它不再使用但仍然可能令人困惑。
【解决方案2】:

注意:此答案与 OP 的答案相似,但有一些值得注意的差异。

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

#!/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 的行中添加括号即可。

【讨论】:

  • 很好的答案加萨,我在再次阅读我链接的文章后意识到我的错误,但是当我回来修复它时,你已经打败了我。感谢您抽出宝贵时间进行更详细的解释。
猜你喜欢
  • 2022-11-04
  • 2017-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-24
  • 1970-01-01
  • 2013-05-16
  • 1970-01-01
相关资源
最近更新 更多