在 Python 中使用 setter 和 getter 保护 numpy 属性
Posted
技术标签:
【中文标题】在 Python 中使用 setter 和 getter 保护 numpy 属性【英文标题】:Protecting numpy attributes using setters and getters in Python 【发布时间】:2021-12-02 18:53:49 【问题描述】:我在保护我的属性(一个 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)
发生了以下事情:
-
它调用 getter 函数两次,(一次用于
our_object.our_attribute[0]
,一次用于print
)
出现负数时,该属性似乎不受保护。
即使是私有属性似乎也没有受到保护
print(' even the private _our_attribute:', our_object._our_attribute, '\n')
也包括负数!
我的猜测:
-
属性的一部分不受保护,因为我们访问它并没有进入 setter 函数。 Numpy 允许我们直接访问切片并更改内容。 (我们从 getter 函数获得
our_attribute
,现在我们直接作用于这个数组对象,它允许设置一个由 Numpy 处理的切片,而不是我们的类,因为我们正在作用的对象现在只是一个数组。
getter 函数包括return self._our_attribute
,它不是复制操作,现在self.our_attribute
和self._our_attribute
都指向同一个位置。如果您更改其中任何一个,其他更改也会随之更改,因此我们最终会更改我们的私有属性,即使我们并不打算更改它。
现在我的问题:
-
我的推测是正确的,还是我犯了错误。
我假设我可以以不同的方式定义 setter,以确保私有属性与公共属性分开:
@property
def our_attribute(self):
return np.copy(self._our_attribute)
但现在设置切片根本不会改变任何东西。
如何正确保护我的属性,以便我可以更改属性的一部分并仍然保留保护。重要的是,这种保护从外面看不到,以免让我的学生感到困惑。
【问题讨论】:
【参考方案1】:我要感谢@Iguananaut 的明确回答。在这里,我只想在我之前展示的示例中实现她的答案。是的,这仍然没有得到真正的保护,但这应该可以防止出现问题,除非人们开始搞乱内部变量。
我决定采用array.setflags(write=False)
的路线,但尝试以更简单的方式进行。对以下实现进行了以下 3 处更改:
-
在getter函数中复制private属性,保证改变public属性的flag不会改变private属性的flag。
公共属性设置为不可写,以确保人们在尝试设置属性切片时会收到警告。 (没有这个保护工作,但如果有人设置你的公共属性,它不会抛出错误,它根本不会做任何事情)。
确保内部属性不可写。
# 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):
# Set values to the attribute.
self.our_attribute = np.random.random(n)
@property
def our_attribute(self):
out = self._our_attribute.copy() # --> Change 1
out.setflags(write=False) # --> Change 2
return out
@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)
self._our_attribute.setflags(write=False) # --> Change 3
# 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')
更改数组的切片是不可能的,它将通过VallueError: assignment destination is read-only
。
# 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[-1] = -5
print(' our_attribute:', our_object.our_attribute)
现在有人可以尝试“聪明”并更改他们获得的数组的写入标志,但如果他们这样做,他们不会影响私有属性self._our_attribute
,因为他们从中获得了深层副本,并且每次他们调用公共属性时,他们都会再次收到副本,因此以下内容仍然会出现错误:
# 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.setflags(write=True)
our_object.our_attribute[-1] = -5
print(' our_attribute:', our_object.our_attribute)
在不直接接触私有属性的情况下规避此问题的唯一方法是:
# Now modify a slice of the copy of the attribute, at set the full attribute to the changed copy.
print('Modify a slice of the attribute.')
attribute = our_object.our_attribute.copy()
attribute[-1] = -5
our_object.our_attribute = attribute
print(' our_attribute:', our_object.our_attribute)
但这会再次通过我们的 setter 函数,就像我们喜欢的那样。
【讨论】:
【参考方案2】:您的推测基本上是正确的。 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 通过property
访问 Python 对象并没有什么神奇之处,它可以使该对象不可变。如果你真的想要一个不可变的数据类型,你必须使用一个不可变的数据类型。
【讨论】:
感谢您的快速回复,我很害怕这个。然后我将使用不可变类型。但事实上,我的希望寄托在“神奇的 NumPy 功能”上,至于真正的代码,只更改属性的某些部分确实是有意义的。您仍然可以对不可变类型执行此操作,但它会做更多的工作,假设您想要更改不可变类型的属性:1. 复制它和 2. 将副本重新转换为可变类型 3. 更改可变的部分对象 4. 将其重铸为不可变类型, 5. 使用不可变设置属性。以上是关于在 Python 中使用 setter 和 getter 保护 numpy 属性的主要内容,如果未能解决你的问题,请参考以下文章
JS对象属性中get/set与getter/setter是什么
Python get 和 set 方法与 @property 装饰器
使用lombok注解@Getter @Setter方法代码编译成功,但是没有生成get,set方法