我正在创建一个类来检索有关计算机的详细信息,例如host_name,kernel_version,bios_version等。收集一些细节比其他细节更昂贵所以我有一个get_ *函数来检索它们,但如果再次需要它们,请将结果缓存在对象中。我正在考虑实现它们看起来像一个字典对象,所以可以检索内核版本:
system = System()
kver = system['kernel_version']
这将在内部调用实例方法get_kernel_version(self)
以检索数据。如果从上面实例化的对象第二次检索内核版本,它将从原始调用返回缓存结果get_kernel_version(self)
。请注意,所有这些键/值对都是只读的,根据可用的get_ *方法,它们有固定数量,并且以后不能添加新键,因此它不像常规字典。也不应该调用类似values()
函数的东西,这会导致所有get_ *函数被不必要地命中。此外,语法比我想要的更冗长。对于这个用例,使用system.kernel_version
似乎更自然。
我正在考虑更好的方法是在类实例上使用动态属性。但是,我需要一种自然的方法来检索所有属性的列表,而不是支持它们的内部方法。我可能会使用__dir__
特殊方法返回类似于keys()
字典列表的列表。我想在列表中看到kernel_version和host_name,但不是__class__
或get_kernel_version
。这似乎违反了__dir__
定义的推荐做法,因此我不确定这是否是正确的使用方法。
我可以返回一个代理类实例,当它没有定义适当的属性时,它的唯一作业会调用带有get_ *函数的具体类。
以下是我尝试实施字典方法的版本示例:
class System(dict):
def __getitem__(self, key):
try:
return getattr(self, 'get_'+key)()
except AttributeError as ex:
raise KeyError(ex.message)
def __setitem__(self, key, value):
raise Exception('Read-only')
def __delitem__(self, key, value):
raise Exception('Read-only')
def keys(self):
return [ x[4:] for x in dir(self) if x.startswith('get_') ]
def get_host_name(self):
return 'localhost'
def get_kernel_version(self):
return '4.7.0'
system = System()
print repr(system.keys())
for key in system.keys():
print '{0}: {1}'.format(key, system[key])
try:
system['bios']
except Exception as ex:
print str(ex)
try:
system['kernel_version'] = '5.0'
except Exception as ex:
print str(ex)
产生了以下输出:
['host_name', 'kernel_version']
host_name: localhost
kernel_version: 4.7.0
"'System' object has no attribute 'get_bios'"
Read-only
上面的代码尚未实现值的缓存,但这很容易添加。但是,它的感觉更像是我应该这样做的属性。我不确定如果这样做,我应该滥用__dir__
来模仿我keys()
以上的相同功能。
我是否应该坚持使用动态属性模拟只读字典或呈现类实例?
答案 0 :(得分:0)
我认为坚持你正在使用的只读字典子类方法很好。但是,通过创建一个通用的只读字典超类来从中导出特定的子类,并使用元类创建keys()
方法返回的值,可以稍微改进您的实现。两者都做如下所示。
这意味着您不再需要“滥用”dir()
(不存在__dir__
属性)。您还可以使用重用通用MetaReadonlyDict
和ReadonlyDict
类来创建其他类似类型。
class MetaReadonlyDict(type):
def __new__(mcls, classname, bases, classdict):
classobj = type.__new__(mcls, classname, bases, classdict)
prefix = classdict['prefix']
_keys = set(name[len(prefix):] for name in classdict
if name.startswith(prefix))
setattr(classobj, 'keys', lambda self: list(_keys)) # define keys()
return classobj
class ReadonlyDict(dict):
def __getitem__(self, key):
try:
return getattr(self, self.prefix + key)()
except AttributeError as ex:
raise Exception(
"{} object has no {!r} key".format(self.__class__.__name__, key))
def __setitem__(self, key, value):
verb = "redefined" if key in self else "defined"
raise Exception(
"{} object is read-only: {!r} "
"key can not be {}".format(self.__class__.__name__, key, verb))
def __delitem__(self, key):
raise Exception(
"{} object is read-only: {!r} "
"key can not be deleted".format(self.__class__.__name__, key))
def __contains__(self, key):
return key in self.keys()
class System(ReadonlyDict):
__metaclass__ = MetaReadonlyDict
prefix = '_get_'
def _get_host_name(self):
return 'localhost'
def _get_kernel_version(self):
return '4.7.0'
system = System()
print('system.keys(): {!r}'.format(system.keys()))
print('values associated with system.keys():')
for key in system.keys():
print(' {!r}: {!r}'.format(key, system[key]))
try:
system['bios']
except Exception as ex:
print(str(ex))
try:
system['kernel_version'] = '5.0'
except Exception as ex:
print(str(ex))
try:
del system['host_name']
except Exception as ex:
print(str(ex))
输出:
system.keys(): ['kernel_version', 'host_name']
values associated with system.keys():
'kernel_version': '4.7.0'
'host_name': 'localhost'
System object has no 'bios' key
System object is read-only: 'kernel_version' key can not be redefined
System object is read-only: 'host_name' key can not be deleted