【问题标题】:How to perform input validation for read-only instance attributes?如何对只读实例属性执行输入验证?
【发布时间】:2022-01-18 21:08:11
【问题描述】:

here 发布了一个非常相似的问题,但没有可接受的答案,没有代码示例,而且我不太喜欢那里提供的唯一答案所建议的使用外部库的想法。

以下代码允许定义只读实例属性:

class Point:
    def __init__(self, x, y):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

但我也想验证用户输入。我想验证xy 的类型是否正确。

这样做最pythonic/优雅的方式是什么?如果我提供设置器,则属性不再是只读的。

在构造函数中执行输入验证是唯一的方法吗?

【问题讨论】:

    标签: python validation oop properties


    【解决方案1】:

    这是一种使用工厂函数创建属性的优雅和 Pythonic 方式:

    class ReadOnlyError(Exception):
        """Attempt made to assign a new value to something that can't be changed."""
    
    
    # Based on recipe in book "Python Cookbook 3rd Edition" - section 9.21 -
    # titled "Avoiding Repetitive Property Methods".
    def readonly_typed_property(name, expected_type):
        storage_name = '_' + name
    
        @property
        def prop(self):
            return getattr(self, storage_name)
    
        @prop.setter
        def prop(self, value):
            if hasattr(self, storage_name):
                raise ReadOnlyError('{!r} is read-only!'.format(name))
            if not isinstance(value, expected_type):
                raise TypeError('{!r} must be a {!r}'.format(name, expected_type.__name__))
            setattr(self, storage_name, value)
    
        return prop
    
    
    class Point:
        x = readonly_typed_property('x', int)
        y = readonly_typed_property('y', int)
    
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
    
    if __name__ == '__main__':
        try:
            p1 = Point(1, 2)
        except Exception as e:
            print('ERROR: No exception should have been raised for case 1.')
            print(e)
        else:
            print('As expected, NO exception raised for case 1.')
    
        print()
        try:
            p2 = Point('1', 2)
        except TypeError as e:
            print(e)
            print(f'As expected, {type(e).__name__} exception raised for case 2.')
        else:
            print('ERROR: expected TypeError exception not raised for case 2')
    
        print()
        try:
            p1.x = 42
        except Exception as e:
            print(e)
            print(f'As expected, {type(e).__name__} exception raised for case 3.')
        else:
            print('ERROR: expected ReadOnlyError exception not raised for case 3')
    

    【讨论】:

    • 谢谢!但是,属性不再是只读的。确实:instance = Point(4, 6) 然后instance.x = 10 我没有错误。是否可以恢复只读属性?谢谢!
    • 糟糕,对遗漏感到抱歉,请参阅更新的答案。
    • 很遗憾,我无法投票,非常感谢!
    • 当然,不用担心。请注意,在实现时,这些属性将更准确地称为“一次写入”,而不仅仅是“只读”(或者可能是“一次写入多次读取”;¬)
    • 哇,老实说我没有意识到,谢谢,这很有意义。现在我正在了解更多关于 here 的信息。
    猜你喜欢
    • 2018-06-04
    • 1970-01-01
    • 2014-09-26
    • 2014-04-25
    • 2012-06-02
    • 1970-01-01
    • 1970-01-01
    • 2011-04-24
    • 1970-01-01
    相关资源
    最近更新 更多