固定只读字段的动态属性或自定义词典

时间:2016-08-25 17:04:46

标签: python python-2.7 python-2.6

我正在创建一个类来检索有关计算机的详细信息,例如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()以上的相同功能。

我是否应该坚持使用动态属性模拟只读字典或呈现类实例?

1 个答案:

答案 0 :(得分:0)

我认为坚持你正在使用的只读字典子类方法很好。但是,通过创建一个通用的只读字典超类来从中导出特定的子类,并使用元类创建keys()方法返回的值,可以稍微改进您的实现。两者都做如下所示。

这意味着您不再需要“滥用”dir()(不存在__dir__属性)。您还可以使用重用通用MetaReadonlyDictReadonlyDict类来创建其他类似类型。

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