首页 > 解决方案 > 在 Python 中使用 setter 和 getter 保护 numpy 属性

问题描述

我遇到了保护我的属性 numpy 数组的问题。我想我明白为什么会出现这个问题,但我不确定如何防止它发生。特别是因为我的代码将被相对缺乏经验的程序员使用。

简而言之:,我有一个类,我想确保它的一些属性。为此,我使用私有内部变量以及 getter 和 setter。但是,并非一切都按我想要的方式进行,当设置了一部分属性时,保护不起作用。

详细说明:这是 MWE 的第一部分:

# Importing modules.
import numpy as np


class OurClass(object):
    """
    The class with an attribute that we want to protect.

    Parameters
    ----------
    n : int
        The number of random numbers in the attribute.

    Attributes
    ----------
    our_attribute : array
        The attribute that contains `n` random numbers between 0 and 1, will never be smaller than zero.
    _our_attribute : array
        The protected attributed that ensures that `our_attribute` is never smaller then zero.
        (Normally I don't list this)
    """
    def __init__(self, n):
        self.our_attribute = np.random.random(n)

    @property
    def our_attribute(self):
        return self._our_attribute

    @our_attribute.setter
    def our_attribute(self, value):
        """
        When the attribute is set, every entry needs to be larger then zero.

        Parameters
        ----------
        value : array
            The array that should replace our_attribute.
        """
        print("Setter function is used")
        self._our_attribute = np.clip(value, 0, np.inf)

现在,当我设置并获取our_attribute它时,它应该受到保护,请参见以下示例:

# Lets create our object.
print('Create object:')
num = 5
our_object = OurClass(num)
print('  our_attribute:', our_object.our_attribute, '\n')

# Lets replace the setter function te verify that it works.
print('Change object:')
our_object.our_attribute = np.linspace(-5, 20, num)
print('  our_attribute:', our_object.our_attribute, '\n')

# Now modify the attribute using basic functions.
print('Modify using numpy functionality:')
our_object.our_attribute = our_object.our_attribute - 5
print('  our_attribute:', our_object.our_attribute, '\n')

但是,在我处理属性的切片(视图)的那一刻,奇怪的事情发生了。

# Now modify a slice of the attribute, we can do this because it is a numpy array.
print('Modify a slice of the attribute.')
our_object.our_attribute[0] = -5
print('  our_attribute:', our_object.our_attribute)

发生了以下事情:

  1. 它调用 getter 函数两次,(一次 forour_object.our_attribute[0]和一次 in the print
  2. 当出现负数时,该属性似乎不受保护。
  3. 甚至私有属性似乎也不受保护,因为
print('  even the private _our_attribute:', our_object._our_attribute, '\n')

也包括负数!

我的猜测:

  1. 属性的一部分不受保护,因为我们访问它并没有进入 setter 函数。Numpy 允许我们直接访问切片并更改内容。(我们our_attribute从 getter 函数获得,现在我们直接作用于这个数组对象,它允许设置一个切片,由 Numpy 处理,而不是我们的类,因为我们正在作用的对象现在只是一个数组。
  2. getter 函数包括return self._our_attribute不是复制操作,现在两者都self.our_attribute指向self._our_attribute同一个位置。如果您更改其中任何一个,其他更改也会发生,因此我们最终会更改我们的私有属性,即使我们不打算更改它。

现在我的问题:

  1. 我的猜测是正确的,还是我犯了错误。
  2. 我假设我可以以不同的方式定义 setter,以确保私有属性与公共属性分开:
@property
def our_attribute(self):
    return np.copy(self._our_attribute)

但是现在设置切片根本不会改变任何东西。

如何以可以更改属性的一部分并仍然保留保护的方式正确保护我的属性。重要的是,这种保护从外面看不到,以免混淆我的学生。

标签: pythonnumpyattributesprivate

解决方案


你的推测基本正确。Python 中没有“保护”可变对象不被修改之类的东西,而且 NumPy 数组是可变的。你可以用普通的 Python 列表做同样的事情:

@property
def my_list(self):
    return self._my_list  # = [1, 2, 3, 4]

@my_list.setter
def my_list(self, value):
    self._my_list = [float('inf') if x < 0 else x for x in value]

我不清楚你在这里想要什么,但如果你希望你的属性返回的 numpy 数组是不可变的,你可以array.setflags(write=False)在数组上设置。只要在设置 write=False 标志后创建切片,这也会使数组切片不可变。

但是,如果您想要某种神奇的有界 NumPy 数组,其中对数组的任何操作都将强制执行您设置的边界,则可以使用 ndarray 子类,但并非易事。

尽管如此,这些选项都不会阻止某人将基础数据重新转换为可变数组。

完全保护底层数据的唯一方法是使用只读 mmap 作为底层数组缓冲区。

但是 TL;DR 访问 Python 对象并没有什么神奇之处,property因为它可以使该对象不可变。如果你真的想要一个不可变的数据类型,你必须使用一个不可变的数据类型。


推荐阅读