python - 当用户尝试更改对象的值时使用描述符类引发 RuntimeError

标签 python descriptor

我使用描述符编写了一个 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')" 意味着如果用户输入的值不是整数,那么它会引发一个错误。我只是不确定用什么来代替它,这样它就会抛出错误并且不允许用户更改值。

测试代码正确输出如下:

78.53981633974483
31.41592653589793
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

最佳答案

改进的描述符计算

这将只允许对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)

诀窍是在实例上生成一个新属性:

setattr(instance, self.name + '_is_set', True) 

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

circle._circumference_is_set = True 

检查这个属性是否存在:

if hasattr(instance, self.name + '_is_set')

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

if hasattr(circle, '_circumference_is_set')

第一次 __set__circumference 调用是在 Circle 类中:

self.circumference = 2 * pi * self.r 

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

综合考虑

你的代码和我的新描述符计算:

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

关于python - 当用户尝试更改对象的值时使用描述符类引发 RuntimeError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34478011/

相关文章:

Python分别从子进程stdout和stderr读取,同时保留顺序

python 脚本不以 etc/rc.local 开头

python - Django 和限制 RAM 使用 : Recommended syntax, 约定和策略?

python - 使用描述符实现绑定(bind)方法

c - 如何使用 C 在 Linux 上的线程中修复 "Error reading from socket - bad file descriptor"?

用于调试的 Python 数组可视化工具

python - 两组虚拟变量的频率/列联表

python __get__ 方法

python - 为什么类方法/静态方法的 cls.__dict__[meth] 与 getattr(cls, meth) 不同?

Java 高级成像 GradientMagnitudeDescriptor