当用户试图更改对象的值时,使用描述符类引发RuntimeError

2024-10-01 16:40:12 发布

您现在位置:Python中文网/ 问答频道 /正文

我用描述符编写了一个Circle类,允许用户设置圆的x、y和r值,并检查x和y的值是否为整数。如果用户输入一个非整数,则会引发TypeError,现在我想创建另一个描述符类,允许用户获取圆的面积和周长值,但不能设置它。我想我的__get__方法工作正常,但是__set__方法没有

class Integer:
    def __init__(self, name):
        self.name = name        # stores name of the managed object's attribute

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        else:
            instance.__dict__[self.name] = value
class Computations(object):
    def __init__(self, name):
        self.name = name   # default value for area, circumference, distance to origin

    def __get__(self, instance, cls):
        if instance is None:
            print('this is the __get__ if statement running')
            return self
        else:
            print('this is the __get__ else statement running')
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if isinstance(value, int):
            raise RuntimeError('Cant set formulas')
        else:
            instance.__dict__[self.name] = value


class Circle:
    x = Integer('_x')   # Use _x and _y as the __dict__ key of a Point
    y = Integer('_y')   # These will be the storage names for a Point
    r = Integer('_r')

    area = Computations('_area')   # class variable of Computations
    circumference = Computations('_circumference')
    distance_to_origin = Computations('_distance_to_origin')

    def __init__(self, x, y, r):
        self.x = x      # invokes Integer.x.__set__
        self.y = y      # invokes Integer.y.__set__
        self.r = r      # for radius/invokes Integer.r.
        self.area = pi * self.r * self.r
        self.circumference = 2 * pi * self.r
        self.distance_to_origin = abs(sqrt((self.x - 0)*(self.x - 0) + (self.y - 0) * (self.y - 0)) - self.r)


# Testing code
if __name__ == '__main__':

    circle = Circle(x=3, y=4, r=5)
    print(circle.x)
    print(circle.y)
    print(circle.r)
    print(circle.area)
   # circle.area = 12
    print(circle.area)
    print(circle.circumference)
    print(circle.distance_to_origin)
    tests = [('circle.x = 12.3', "print('Setting circle.x to non-integer fails')"),
             ('circle.y = 23.4', "print('Setting circle.y to non-integer fails')"),
             ('circle.area = 23.4', "print('Setting circle.area fails')"),
             ('circle.circumference = 23.4', "print('Setting circle.circumference fails')"),
             ('circle.distance_to_origin = 23.4', "print('Setting circle.distance_to_origin fails')"),
             ('circle.z = 5.6', "print('Setting circle.z fails')"),
             ('print(circle.z)', "print('Printing circle.z fails')")]
    for test in tests:
        try:
            exec(test[0])
        except:
            exec(test[1])

该程序运行,但它允许设置面积、周长和距离的原点,这不是我想要它做的。我知道行"if isinstance(value, int): raise RuntimeError('Can't set formulas')"意味着如果用户输入的值不是整数,那么它将引发错误。我只是不知道该用什么代替它,这样它就会抛出一个错误,并且不允许用户更改值。在

测试代码的正确输出如下:

^{pr2}$

Tags: toinstancenameselfifvaluedefarea
2条回答

改进的描述符计算

这将只允许Computations描述符的初始设置:

class Computations(object):
    def __init__(self, name):
        self.name = name   # default value for area, circumference, distance to origin

    def __get__(self, instance, cls):
        if instance is None:
            print('this is the __get__ if statement running')
            return self
        else:
            print('this is the __get__ else statement running')
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if hasattr(instance, self.name + '_is_set'):
            raise ValueError('Cannot set {} again.'.format(self.name[1:]))
        if isinstance(value, int):
            raise RuntimeError('Cant set formulas')
        else:
            instance.__dict__[self.name] = value
            setattr(instance, self.name + '_is_set', True)

在实例上生成一个新的属性:

^{pr2}$

对于实例circle和属性circumference,这意味着:

circle._circumference_is_set = True 

检查此属性是否存在:

^{4}$

同样,对于我们的案例,这意味着:

if hasattr(circle, '_circumference_is_set')

第一次为circumference调用__set__是在Circle类中:

self.circumference = 2 * pi * self.r 

现在_circumference_is_set存在,下一次尝试设置它将导致异常。在

把它们放在一起

您的代码与我的新描述符Computation

from math import pi, sqrt

class Integer(object):
    def __init__(self, name):
        self.name = name        # stores name of the managed object's attribute

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        else:
            instance.__dict__[self.name] = value

class Computations(object):
    def __init__(self, name):
        self.name = name   # default value for area, circumference, distance to origin

    def __get__(self, instance, cls):
        if instance is None:
            print('this is the __get__ if statement running')
            return self
        else:
            print('this is the __get__ else statement running')
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if hasattr(instance, self.name + 'is_set'):
            raise ValueError('Cannot set {} again.'.format(self.name[1:]))
        if isinstance(value, int):
            raise RuntimeError('Cant set formulas')
        else:
            instance.__dict__[self.name] = value
            setattr(instance, self.name + 'is_set', True)


class Circle(object):
    x = Integer('_x')   # Use _x and _y as the __dict__ key of a Point
    y = Integer('_y')   # These will be the storage names for a Point
    r = Integer('_r')

    area = Computations('_area')   # class variable of Computations
    circumference = Computations('_circumference')
    distance_to_origin = Computations('_distance_to_origin')

    def __init__(self, x, y, r):
        self.x = x      # invokes Integer.x.__set__
        self.y = y      # invokes Integer.y.__set__
        self.r = r      # for radius/invokes Integer.r.
        self.area = pi * self.r * self.r
        self.circumference = 2 * pi * self.r
        self.distance_to_origin = abs(sqrt(self.x * self.x + self.y * self.y) - self.r)

测试

现在尝试设置circle.circumference会引发一个异常:

# Testing code
if __name__ == '__main__':

    circle = Circle(x=3, y=4, r=5)
    print('circumference', circle.circumference)

    print('try setting circumference')
    circle.circumference = 12.5

输出:

this is the __get__ else statement running
circumference 31.41592653589793
try setting circumference

                                     -
ValueError                                Traceback (most recent call last)
<ipython-input-227-316199bab738> in <module>()
     64 
     65     print('try setting circumference')
 -> 66     circle.circumference = 12.5

<ipython-input-227-316199bab738> in __set__(self, instance, value)
     31     def __set__(self, instance, value):
     32         if hasattr(instance, self.name + 'is_set'):
 -> 33             raise ValueError('Cannot set {} again.'.format(self.name[1:]))
     34         if isinstance(value, int):
     35             raise RuntimeError('Cant set formulas')

ValueError: Cannot set circumference again.

您的测试:

if __name__ == '__main__':

    circle = Circle(x=3, y=4, r=5)
    print(circle.x)
    print(circle.y)
    print(circle.r)
    print(circle.area)
   # circle.area = 12
    print(circle.area)
    print(circle.circumference)
    print(circle.distance_to_origin)
    tests = [('circle.x = 12.3', "print('Setting circle.x to non-integer fails')"),
             ('circle.y = 23.4', "print('Setting circle.y to non-integer fails')"),
             ('circle.area = 23.4', "print('Setting circle.area fails')"),
             ('circle.circumference = 23.4', "print('Setting circle.circumference fails')"),
             ('circle.distance_to_origin = 23.4', "print('Setting circle.distance_to_origin fails')"),
             ('circle.z = 5.6', "print('Setting circle.z fails')"),
             ('print(circle.z)', "print('Printing circle.z fails')")]
    for test in tests:
        try:
            exec(test[0])
        except:
            exec(test[1])

生成此输出:

3
4
5
this is the __get__ else statement running
78.53981633974483
this is the __get__ else statement running
78.53981633974483
this is the __get__ else statement running
31.41592653589793
this is the __get__ else statement running
0.0
Setting circle.x to non-integer fails
Setting circle.y to non-integer fails
Setting circle.area fails
Setting circle.circumference fails
Setting circle.distance_to_origin fails
5.6

不能正确使用浮动

您的__set__函数当前允许设置一个值,只要它不是整数:

def __set__(self, instance, value):
    if isinstance(value, int):
        raise RuntimeError('Cant set formulas')
    else:
        instance.__dict__[self.name] = value

然后,在类本身和测试中将circumference设置为一个浮点值,以便它通过:

^{pr2}$

以及:

circle.circumference = 23.4

但是,设置整数值将失败:

^{4}$

修复__set__

要解决该问题,可以使__set__始终失败:

def __set__(self, instance, value):
    raise RuntimeError('Cant set formulas')

您仍然希望圆周在类中是可写的,但是为此,您可以使用private_circumference

self._area = pi * self.r * self.r
self._circumference = 2 * pi * self.r
self._distance_to_origin = abs(sqrt((self.x - 0)*(self.x - 0) + (self.y - 0) * (self.y - 0)) - self.r)

更简单的解决方案

上面的修正只是理论上的,说明它是可以修正的。使用property来提供定制的getter和setter通常要简单得多:

def __init__(self, ...):
    ...
    self._circumference = 2 * pi * self.r

@property
def circumference(self):
    return self._circumference

# if you wanted to allow setting (but you don't), you would add this:

#@circumference.setter
#def circumference(self, value):
#    self._circumference = value

Computations类根本不需要。在

更好的设计

如果您要为一个计算值拥有自己的getter和setter,那么您可以根据需要计算它,而不必记住该值:

@property
def circumference(self):
    return 2 * pi * self.r

# there is no self._circumference!

这样做的好处是当r发生变化时,圆周仍然是正确的(对于原始代码,circumference始终具有来自__init__的原始值)。在

注意:这是python3!在

以前没有明确说过,所以我在这里说。这段代码在Python3中工作。在

如果使用Python2运行,由于旧样式的类定义,描述符将被忽略。要使代码与Python2一起工作,只需确保所有类都继承自object,例如

class Circle(object):
    ...

注2:型式检查

在Python中实现类型检查之前要三思而后行。检查类型通常会使代码更好。在您的例子中,您将值限制为整数,这意味着:float不起作用,Decimal不起作用,long不起作用(在python2中),MySpecialNumericType不工作。尽管如果代码没有明确禁止它,它还是可以工作的。在

相关问题 更多 >

    热门问题