【发布时间】:2020-10-16 13:13:24
【问题描述】:
多年来,人们曾提出过类似的问题。 Python2 和 Python3 似乎工作方式相同。下面显示的代码可以正常工作并且可以理解(至少对我而言)。但是,有一个无操作让我感到困扰,我想知道是否有更优雅的方式来表达此功能。
关键问题是,当子类在超类中定义的变量中设置属性的新值时,超类应该将新修改的值保存到文件中。下面的代码这样做的方式是让每个子类在 setter 方法中都有这样的一行,这是一个无操作:
self.base_data_property.fset(self, super().data)
我称之为无操作,因为在执行此行时超类的数据已经被修改,而该行代码存在的唯一原因是触发超类的@data.setter 方法的副作用,执行自动保存到文件。
我不喜欢编写这样的副作用代码。有没有更好的办法,除了显而易见的,那就是:
super().save_data() # Called from each subclass setter
上面会调用而不是 no-op。
对下面代码的另一个批评是super()._base_data 显然不是子类lambda_data 的超集。这使得代码难以维护。这导致代码看起来有些神奇,因为更改lambda_data 中的属性实际上是更改super()._base_data 中的属性的别名。
代码
我为此代码创建了GitHub repo。
import logging
class BaseConfig:
def __init__(self, diktionary):
self._base_data = diktionary
logging.info(f"BaseConfig.__init__: set self.base_data = '{self._base_data}'")
def save_data(self):
logging.info(f"BaseConfig: Pretending to save self.base_data='{self._base_data}'")
@property
def data(self) -> dict:
logging.info(f"BaseConfig: self.data getter returning = '{self._base_data}'")
return self._base_data
@data.setter
def data(self, value):
logging.info(f"BaseConfig: self.data setter, new value for self.base_data='{value}'")
self._base_data = value
self.save_data()
class LambdaConfig(BaseConfig):
""" This example subclass is one of several imaginary subclasses, all with similar structures.
Each subclass only works with data within a portion of super().data;
for example, this subclass only looks at and modifies data within super().data['aws_lambda'].
"""
def __init__(self, diktionary):
super().__init__(diktionary)
# See https://stackoverflow.com/a/10810545/553865:
self.base_data_property = super(LambdaConfig, type(self)).data
# This subclass only modifies data contained within self.lambda_data:
self.lambda_data = super().data['aws_lambda']
@property
def lambda_data(self):
return self.base_data_property.fget(self)['aws_lambda']
@lambda_data.setter
def lambda_data(self, new_value):
super().data['aws_lambda'] = new_value
self.base_data_property.fset(self, super().data)
# Properties specific to this class follow
@property
def dir(self):
result = self.data['dir']
logging.info(f"LambdaConfig: Getting dir = '{result}'")
return result
@dir.setter
def dir(self, new_value):
logging.info(f"LambdaConfig: dir setter before setting to {new_value} is '{self.lambda_data['dir']}'")
# Python's call by value means super().data is called, which modifies super().base_data:
self.lambda_data['dir'] = new_value
self.base_data_property.fset(self, super().data) # This no-op merely triggers super().@data.setter
logging.info(f"LambdaConfig.dir setter after set: self.lambda_data['dir'] = '{self.lambda_data['dir']}'")
@property
def name(self): # Comments are as for the dir property
return self.data['name']
@name.setter
def name(self, new_value): # Comments are as for the dir property
self.lambda_data['name'] = new_value
self.base_data_property.fset(self, super().data)
@property
def id(self): # Comments are as for the dir property
return self.data['id']
@id.setter
def id(self, new_value): # Comments are as for the dir property
self.lambda_data['id'] = new_value
self.base_data_property.fset(self, super().data)
if __name__ == "__main__":
logging.basicConfig(
format = '%(levelname)s %(message)s',
level = logging.INFO
)
diktionary = {
"aws_lambda": {
"dir": "old_dir",
"name": "old_name",
"id": "old_id"
},
"more_keys": {
"key1": "old_value1",
"key2": "old_value2"
}
}
logging.info("Superclass data can be changed from the subclass, new value appears everywhere:")
logging.info("main: Creating a new LambdaConfig, which creates a new BaseConfig")
lambda_config = LambdaConfig(diktionary)
aws_lambda_data = lambda_config.data['aws_lambda']
logging.info(f"main: aws_lambda_data = {aws_lambda_data}")
logging.info("")
lambda_config.dir = "new_dir"
logging.info(f"main: after setting lambda_config.dir='new_dir', aws_lambda_data['dir'] = {aws_lambda_data['dir']}")
logging.info(f"main: aws_lambda_data = {aws_lambda_data}")
logging.info(f"main: aws_lambda_data['dir'] = '{aws_lambda_data['dir']}'")
logging.info("")
lambda_config.name = "new_name"
logging.info(f"main: after setting lambda_config.name='new_name', aws_lambda_data['name'] = {aws_lambda_data['name']}")
logging.info(f"main: aws_lambda_data = {aws_lambda_data}")
logging.info(f"main: aws_lambda_data['name'] = '{aws_lambda_data['name']}'")
lambda_config.id = "new_id"
logging.info(f"main: after setting lambda_config.id='new_id', aws_lambda_data['id'] = {aws_lambda_data['id']}")
logging.info(f"main: aws_lambda_data = {aws_lambda_data}")
logging.info(f"main: aws_lambda_data['id'] = '{aws_lambda_data['id']}'")
输出
INFO Superclass data can be changed from the subclass, new value appears everywhere:
INFO main: Creating a new LambdaConfig, which creates a new BaseConfig
INFO BaseConfig.__init__: set self.base_data = '{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data setter, new value for self.base_data='{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: Pretending to save self.base_data='{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO main: aws_lambda_data = {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}
INFO
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO LambdaConfig: dir setter before setting to new_dir is 'old_dir'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'old_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'new_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data setter, new value for self.base_data='{'aws_lambda': {'dir': 'new_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: Pretending to save self.base_data='{'aws_lambda': {'dir': 'new_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'new_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO LambdaConfig.dir setter after set: self.lambda_data['dir'] = 'new_dir'
INFO main: after setting lambda_config.dir='new_dir', aws_lambda_data['dir'] = new_dir
INFO main: aws_lambda_data = {'dir': 'new_dir', 'name': 'old_name', 'id': 'old_id'}
INFO main: aws_lambda_data['dir'] = 'new_dir'
INFO
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'new_dir', 'name': 'old_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'new_dir', 'name': 'new_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data setter, new value for self.base_data='{'aws_lambda': {'dir': 'new_dir', 'name': 'new_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: Pretending to save self.base_data='{'aws_lambda': {'dir': 'new_dir', 'name': 'new_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO main: after setting lambda_config.name='new_name', aws_lambda_data['name'] = new_name
INFO main: aws_lambda_data = {'dir': 'new_dir', 'name': 'new_name', 'id': 'old_id'}
INFO main: aws_lambda_data['name'] = 'new_name'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'new_dir', 'name': 'new_name', 'id': 'old_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data getter returning = '{'aws_lambda': {'dir': 'new_dir', 'name': 'new_name', 'id': 'new_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: self.data setter, new value for self.base_data='{'aws_lambda': {'dir': 'new_dir', 'name': 'new_name', 'id': 'new_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO BaseConfig: Pretending to save self.base_data='{'aws_lambda': {'dir': 'new_dir', 'name': 'new_name', 'id': 'new_id'}, 'more_keys': {'key1': 'old_value1', 'key2': 'old_value2'}}'
INFO main: after setting lambda_config.id='new_id', aws_lambda_data['id'] = new_id
INFO main: aws_lambda_data = {'dir': 'new_dir', 'name': 'new_name', 'id': 'new_id'}
INFO main: aws_lambda_data['id'] = 'new_id'
【问题讨论】:
-
我还没有找到一个继承
property的好方法(尽管我没有非常努力地尝试过)。一种替代方法是定义您自己的类似属性的描述符来代替property。
标签: python