将一些对象属性重置为初始值
Reset some object attributes to initial values
要求:
我有一个 class,其中许多字段在 __init__
方法中初始化。 这些字段中的一些 应该可以通过 reset()
方法重置为初始值。
我想为这些属性提供输入信息,并让 Flake、MyPy、PyCharm(和我)对解决方案感到满意。
可能的解决方案:
重复初始值
在此解决方案中,所有工具(MyPy、Flake、PyCharm)都很满意,但我不满意。我在两个地方(__init__
和 reset
)有初始值,我需要让它们保持同步。有可能以后要修改一个初始值,那我两处都不改了。
class Test:
def __init__(self) -> None:
self.persistentCounter: int = 0
self.resetableCounter: int = 1 # the same value is in reset method. Keep them in sync!!!
# seven more attributes of different types and initial values
def reset(self) -> None:
self.resetableCounter = 1 # the same value is in __init__ method. Keep them in sync!!!
# reset remaining seven attributes to the same values as in __init__()
仅在一种方法中保留初始值
修复似乎很简单:仅在 reset
方法中保留初始值并从 __init__
.
调用 reset
class Test:
def __init__(self) -> None:
self.persistentCounter: int = 0
self.reset()
def reset(self) -> None:
self.resetableCounter: int = 1
# seven more attributes of different types and initial values
我很高兴(Flake8 和 MyPy 也一样)有这样的解决方案,但 PyCharm 抱怨
Instance attribute resetableCounter defined outside __init__
可以关闭此警告,但有时它很有用 - 当我忘记在 __init__
和 reset
方法中定义某些属性时。
定义属性为None
所以我们可以改进第二种解决方案-在__init__
中定义属性,但将其设置为None
,然后调用reset
方法。
from typing import Optional
class Test:
def __init__(self) -> None:
self.persistentCounter: int = 0
self.resetableCounter: Optional[int] = None
# seven more attributes of different types - all defined as Optional
self.reset()
def reset(self) -> None:
self.resetableCounter = 1
# seven more attributes with different initial values
def foo(self) -> bool:
return self.resetableCounter > 10
这个解决方案的缺点是该属性被定义为 Optional[int]
,而不是 int
,当我在 foo
方法中使用它时,mypy 会抱怨
error: Unsupported operand types for < ("int" and "None")
note: Left operand is of type "Optional[int]"
当我在 foo
方法中添加一个额外的断言时,这个问题可以得到解决:
def foo(self) -> bool:
assert self.resetableCounter is not None
return self.resetableCounter > 10
它让所有工具都满意,但我不满意 - 我不想用许多“不必要的”断言填充源代码。
问题:
如何满足上述要求并减轻所提出解决方案的缺点?
Instance attribute attribute_name defined outside __init__ 描述了一个类似的问题,但没有答案符合我的要求(见上文)。
您可以编写一个自定义描述符来存储默认值并处理重置。
from typing import Type, Any, TypeVar, Generic, Optional
T = TypeVar('T')
class ResettableAttribute(Generic[T]):
def __init__(self, default_value: T) -> None:
# the attribute stores the default value
self.default_value = default_value
# and its name (will be filled later)
self.name = ''
def __set_name__(self, owner: Type[Any], name: str) -> None:
# this method requires python 3.6+
# it is called when we instantiate a descriptor inside a class
# prepend an underscore to prevent name collisions
self.name = f'_{name}'
def __get__(self, instance: Optional[Any], owner: Type[Any]) -> T:
# this is called when we access the descriptor from a class (or instance)
assert instance is not None # you could handle the case ClassName.attribute
# return the stored value or the default if it was never set
return getattr(instance, self.name, self.default_value)
def __set__(self, instance: Any, value: T) -> None:
# this is called when we assign to the descriptor from an instance
setattr(instance, self.name, value)
def __delete__(self, instance: Any) -> None:
# this is called when you write del instance.attribute
# we can use this to reset the value to the default
setattr(instance, self.name, self.default_value)
注意:这仅适用于不可变的默认值,因为 default_values 对于所有实例都是相同的。
您可以像这样使用属性:
class Test:
x = ResettableAttribute(1)
y = ResettableAttribute("default")
def __init__(self) -> None:
self.persistent = 0
def reset(self) -> None:
del self.x
del self.y
a = Test()
b = Test()
a.x = 123
print(a.x, b.x) # 123 1
b.x = 234
print(a.x, b.x) # 123 234
a.reset()
print(a.x, b.x) # 1 234
现在默认值只提到一次。 Flake、Mypy 和 Pycharm 不要抱怨。
如果您仍然不想在 类 中重复所有可重置的属性,您可以这样做:
def reset_all_resettable_attributes(instance: Any) -> None:
for attribute in vars(type(instance)).values():
if isinstance(attribute, ResettableAttribute):
attribute.__delete__(instance)
那么reset()
方法就变成了这样:
def reset(self) -> None:
reset_all_resettable_attributes(self)
如果您想支持可变默认值,请执行以下操作:让 ResettableAttribute
接受构造默认值的工厂函数并在构造函数中调用该工厂。
受到@Wombatz 回复的启发,我以这个解决方案结束:
class Test:
resetableCounter: int = 1
# seven more attributes of different types
def __init__(self) -> None:
self.persistentCounter: int = 0
self.reset()
def reset(self) -> None:
if 'resetableCounter' in self.__dict__:
del self.resetableCounter
# del also rest seven attributes
所有工具都很满意,代码干净、简洁且可读。
非常感谢所有贡献。
更新
我必须将 if 'resetableCounter' in self.__dict__:
行添加到上面的代码中。否则,reset
方法在从 __init__
调用时失败,或者当它在行中被调用两次而第一次和第二次调用之间没有变量修改时。
现在的代码不是很好(尤其是需要手动将变量名写成字符串)但我认为它仍然是最好的(我知道的)解决方案。
要求:
我有一个 class,其中许多字段在 __init__
方法中初始化。 这些字段中的一些 应该可以通过 reset()
方法重置为初始值。
我想为这些属性提供输入信息,并让 Flake、MyPy、PyCharm(和我)对解决方案感到满意。
可能的解决方案:
重复初始值
在此解决方案中,所有工具(MyPy、Flake、PyCharm)都很满意,但我不满意。我在两个地方(
__init__
和reset
)有初始值,我需要让它们保持同步。有可能以后要修改一个初始值,那我两处都不改了。class Test: def __init__(self) -> None: self.persistentCounter: int = 0 self.resetableCounter: int = 1 # the same value is in reset method. Keep them in sync!!! # seven more attributes of different types and initial values def reset(self) -> None: self.resetableCounter = 1 # the same value is in __init__ method. Keep them in sync!!! # reset remaining seven attributes to the same values as in __init__()
仅在一种方法中保留初始值
修复似乎很简单:仅在
调用reset
方法中保留初始值并从__init__
.reset
class Test: def __init__(self) -> None: self.persistentCounter: int = 0 self.reset() def reset(self) -> None: self.resetableCounter: int = 1 # seven more attributes of different types and initial values
我很高兴(Flake8 和 MyPy 也一样)有这样的解决方案,但 PyCharm 抱怨
Instance attribute resetableCounter defined outside __init__
可以关闭此警告,但有时它很有用 - 当我忘记在
__init__
和reset
方法中定义某些属性时。定义属性为
None
所以我们可以改进第二种解决方案-在
__init__
中定义属性,但将其设置为None
,然后调用reset
方法。from typing import Optional class Test: def __init__(self) -> None: self.persistentCounter: int = 0 self.resetableCounter: Optional[int] = None # seven more attributes of different types - all defined as Optional self.reset() def reset(self) -> None: self.resetableCounter = 1 # seven more attributes with different initial values def foo(self) -> bool: return self.resetableCounter > 10
这个解决方案的缺点是该属性被定义为
Optional[int]
,而不是int
,当我在foo
方法中使用它时,mypy 会抱怨error: Unsupported operand types for < ("int" and "None") note: Left operand is of type "Optional[int]"
当我在
foo
方法中添加一个额外的断言时,这个问题可以得到解决:def foo(self) -> bool: assert self.resetableCounter is not None return self.resetableCounter > 10
它让所有工具都满意,但我不满意 - 我不想用许多“不必要的”断言填充源代码。
问题:
如何满足上述要求并减轻所提出解决方案的缺点?
Instance attribute attribute_name defined outside __init__ 描述了一个类似的问题,但没有答案符合我的要求(见上文)。
您可以编写一个自定义描述符来存储默认值并处理重置。
from typing import Type, Any, TypeVar, Generic, Optional
T = TypeVar('T')
class ResettableAttribute(Generic[T]):
def __init__(self, default_value: T) -> None:
# the attribute stores the default value
self.default_value = default_value
# and its name (will be filled later)
self.name = ''
def __set_name__(self, owner: Type[Any], name: str) -> None:
# this method requires python 3.6+
# it is called when we instantiate a descriptor inside a class
# prepend an underscore to prevent name collisions
self.name = f'_{name}'
def __get__(self, instance: Optional[Any], owner: Type[Any]) -> T:
# this is called when we access the descriptor from a class (or instance)
assert instance is not None # you could handle the case ClassName.attribute
# return the stored value or the default if it was never set
return getattr(instance, self.name, self.default_value)
def __set__(self, instance: Any, value: T) -> None:
# this is called when we assign to the descriptor from an instance
setattr(instance, self.name, value)
def __delete__(self, instance: Any) -> None:
# this is called when you write del instance.attribute
# we can use this to reset the value to the default
setattr(instance, self.name, self.default_value)
注意:这仅适用于不可变的默认值,因为 default_values 对于所有实例都是相同的。
您可以像这样使用属性:
class Test:
x = ResettableAttribute(1)
y = ResettableAttribute("default")
def __init__(self) -> None:
self.persistent = 0
def reset(self) -> None:
del self.x
del self.y
a = Test()
b = Test()
a.x = 123
print(a.x, b.x) # 123 1
b.x = 234
print(a.x, b.x) # 123 234
a.reset()
print(a.x, b.x) # 1 234
现在默认值只提到一次。 Flake、Mypy 和 Pycharm 不要抱怨。
如果您仍然不想在 类 中重复所有可重置的属性,您可以这样做:
def reset_all_resettable_attributes(instance: Any) -> None:
for attribute in vars(type(instance)).values():
if isinstance(attribute, ResettableAttribute):
attribute.__delete__(instance)
那么reset()
方法就变成了这样:
def reset(self) -> None:
reset_all_resettable_attributes(self)
如果您想支持可变默认值,请执行以下操作:让 ResettableAttribute
接受构造默认值的工厂函数并在构造函数中调用该工厂。
受到@Wombatz 回复的启发,我以这个解决方案结束:
class Test:
resetableCounter: int = 1
# seven more attributes of different types
def __init__(self) -> None:
self.persistentCounter: int = 0
self.reset()
def reset(self) -> None:
if 'resetableCounter' in self.__dict__:
del self.resetableCounter
# del also rest seven attributes
所有工具都很满意,代码干净、简洁且可读。
非常感谢所有贡献。
更新
我必须将 if 'resetableCounter' in self.__dict__:
行添加到上面的代码中。否则,reset
方法在从 __init__
调用时失败,或者当它在行中被调用两次而第一次和第二次调用之间没有变量修改时。
现在的代码不是很好(尤其是需要手动将变量名写成字符串)但我认为它仍然是最好的(我知道的)解决方案。