在 Python 中使用 setter 和 getter 保护 numpy 属性
Protecting numpy attributes using setters and getters in Python
我 运行 遇到了保护我的属性(一个 numpy 数组)的问题。我想我明白为什么会出现这个问题,但我不确定如何防止它发生。特别是因为我的代码将被相对缺乏经验的程序员使用。
简而言之:,我有一个 class 具有我想确保其某些属性的属性。为此,我使用了一个私有内部变量以及 getters 和 setters。然而,并非一切都如我所愿,当设置了一个属性切片时,保护不起作用。
详细信息: 这是 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处理,而不是我们的class作为对象我们正在执行的操作现在只是一个数组。
- getter 函数包括
return self._our_attribute
,这不是复制操作,现在 self.our_attribute
和 self._our_attribute
都指向同一个位置。如果您更改两者中的任何一个,其他更改也会发生,因此即使我们无意更改它,我们最终也会更改我们的私有属性。
现在我的问题:
- 我的猜测是正确的,还是我弄错了。
- 我假设我可以不同地定义 setter 以确保私有属性与 public 分开:
@property
def our_attribute(self):
return np.copy(self._our_attribute)
但是现在设置切片不会改变任何东西。
如何正确保护我的属性,使我可以更改属性的一部分并仍然保留保护。重要的是这种保护从外面是看不见的,以免混淆我的学生。
你的猜测基本上是正确的。 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 子类实现,但并非易事。
不过,none 这些选项会阻止某人将基础数据重新转换为可变数组。
完全保护底层数据的唯一方法是使用只读 mmap 作为底层数组缓冲区。
但是 TL;DR 通过 property
访问 Python 对象并使该对象不可变并没有什么神奇之处。如果你真的想要一个不可变的数据类型,你必须使用一个不可变的数据类型。
感谢@Iguananaut 的明确回答。在这里,我只想在我之前展示的示例中实现她的答案。是的,这仍然没有真正受到保护,但这应该可以防止出现问题,除非人们开始摆弄内部变量。
我决定采用 array.setflags(write=False)
的方法,但尝试以更简单的方式进行。对下面的实现进行了以下 3 处更改:
- 复制getter函数中的私有属性,保证改变public属性的flag不会改变私有属性的flag。
- public 属性设置为不可写,以确保人们在尝试设置属性的一部分时会收到警告。 (保护工作没有这个,但如果有人设置你的 public 属性它不会抛出错误,它不会做任何事情)。
- 确保内部属性不可写。
# 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)
现在有人可以尝试成为 'smart' 并更改他们获得的数组的写入标志,但如果他们这样做,他们不会影响私有属性 self._our_attribute
因为他们获得了从它进行深层复制,每次他们调用 public 属性时,他们都会再次收到副本,因此以下内容仍然会出错:
# 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 函数,就像我们喜欢的那样。
我 运行 遇到了保护我的属性(一个 numpy 数组)的问题。我想我明白为什么会出现这个问题,但我不确定如何防止它发生。特别是因为我的代码将被相对缺乏经验的程序员使用。
简而言之:,我有一个 class 具有我想确保其某些属性的属性。为此,我使用了一个私有内部变量以及 getters 和 setters。然而,并非一切都如我所愿,当设置了一个属性切片时,保护不起作用。
详细信息: 这是 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处理,而不是我们的class作为对象我们正在执行的操作现在只是一个数组。 - getter 函数包括
return self._our_attribute
,这不是复制操作,现在self.our_attribute
和self._our_attribute
都指向同一个位置。如果您更改两者中的任何一个,其他更改也会发生,因此即使我们无意更改它,我们最终也会更改我们的私有属性。
现在我的问题:
- 我的猜测是正确的,还是我弄错了。
- 我假设我可以不同地定义 setter 以确保私有属性与 public 分开:
@property
def our_attribute(self):
return np.copy(self._our_attribute)
但是现在设置切片不会改变任何东西。
如何正确保护我的属性,使我可以更改属性的一部分并仍然保留保护。重要的是这种保护从外面是看不见的,以免混淆我的学生。
你的猜测基本上是正确的。 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 子类实现,但并非易事。
不过,none 这些选项会阻止某人将基础数据重新转换为可变数组。
完全保护底层数据的唯一方法是使用只读 mmap 作为底层数组缓冲区。
但是 TL;DR 通过 property
访问 Python 对象并使该对象不可变并没有什么神奇之处。如果你真的想要一个不可变的数据类型,你必须使用一个不可变的数据类型。
感谢@Iguananaut 的明确回答。在这里,我只想在我之前展示的示例中实现她的答案。是的,这仍然没有真正受到保护,但这应该可以防止出现问题,除非人们开始摆弄内部变量。
我决定采用 array.setflags(write=False)
的方法,但尝试以更简单的方式进行。对下面的实现进行了以下 3 处更改:
- 复制getter函数中的私有属性,保证改变public属性的flag不会改变私有属性的flag。
- public 属性设置为不可写,以确保人们在尝试设置属性的一部分时会收到警告。 (保护工作没有这个,但如果有人设置你的 public 属性它不会抛出错误,它不会做任何事情)。
- 确保内部属性不可写。
# 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)
现在有人可以尝试成为 'smart' 并更改他们获得的数组的写入标志,但如果他们这样做,他们不会影响私有属性 self._our_attribute
因为他们获得了从它进行深层复制,每次他们调用 public 属性时,他们都会再次收到副本,因此以下内容仍然会出错:
# 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 函数,就像我们喜欢的那样。