从数组创建python属性,共享getter和setter吗?

时间:2018-07-16 03:26:52

标签: properties python-3.6

我一直在研究python脚本,其中每次更改某些值时,也应该修改并行值。例如:

self.empire.gold = 345
self.empire.update_times.gold = time.time()

每次执行此操作都变得很痛苦,因此我一直在研究如何自动执行此操作。这是我想出的:

import time


class boring_object:
    pass


class Object:
    def __init__(self, gold):
        self._gold = gold
        self._updates = boring_object()

    @property
    def gold(self):
        return self._gold

    @gold.setter
    def gold(self, value):
        self._gold = value
        self._updates.gold = time.time()

使用此功能,我可以自动更新时间。

但是,我也想将类似的设置方法应用于其他资源。

我可以将该代码重复五遍以覆盖goldgrainpotatoes等,但是我只想将一组资源名称传递给构造函数,并让他们共享通用的getter和setters 。有可能吗?

在执行任何此类操作之前,我使用的是更接近boring_object()的内容:

class Object:
    def __init__(self, array):
        for each in array:
            setattr(self, each, 0)

除了缺少属性功能外,print()也不是很好。该解决方案的优点是可以同时使用通用的getter和setter并在没有太多额外代码的情况下很好地打印。

写完这篇文章后,我发现this question涉及吸气剂和吸气剂。一个答案建议使用using __getattr__ and __setattr__,这将使我更接近目标,但我不想存储更新时间并更改最终在该对象上设置的所有属性(仅当初始化它时命名的属性) 。其他属性应该表现得好像没有自定义的getter / setter逻辑一样。

2 个答案:

答案 0 :(得分:2)

__getattr__等是不错的神奇方法,可让您变得聪明。但是,我建议你不要这样做。它们使您的代码难以理解和遵循。想像一下其他人(或一年后的你)试图理解what you did there and why。如果有一种明确且易于理解的方法可以完成工作,那么我将始终倾向于这样做。

如果您想限制对属性的访问或向其中添加一些小的逻辑(例如在此处存储更新时间),则Python @property是很好的选择。但是,属性实际上并不适合您的情况,因为您需要动态的东西,并且可以在运行时进行配置。那么为什么不使用显而易见的呢?仅仅存储帝国资源的一些“私有”字典(请参阅下面的_possessions_updates)怎么样?

因此,这是我对如何解决您的问题的建议:

import time


class Empire(object):

    def __init__(self, resources):
        self._possessions = {r: 0 for r in resources}
        self._updates = {r: None for r in resources}

    def update(self, resource, value):
        if resource not in self._possessions:
            raise ValueError('Your mighty Empire has no clue what {}'
                             ' really is and how to handle'
                             ' it.'.format(resource))
        self._possessions[resource] = value
        self._updates[resource] = time.time()

    def get(self, resource):
        if resource not in self._possessions:
            raise ValueError('Your poor empire has no {}.'.format(resource))
        return self._possessions[resource]


# Some use cases:
my_little_kingdom = Empire(('gold', 'celebrities', 'vanilla_ice'))

print(my_little_kingdom._updates['gold'])
my_little_kingdom.update('gold', 10)

print(my_little_kingdom.get('gold'))
print(my_little_kingdom._updates['gold'])

try:
    my_little_kingdom.update('bitcoin', 42)
except ValueError as e:
    print(e) 

如果您坚持要进行属性访问(第一次编辑)

如果真的要使用奇特的__getattr____setattr__方法,可以轻松地通过以下方式扩展类:

def __getattr__(self, item):
    try:
        return self.get(item)
    except ValueError as e:
        # We are trying to get an attribute, so better
        # raise the corresponding error here.
        raise AttributeError(str(e))

def __setattr__(self, key, value):
    if key in ('_possessions', '_updates'):
        # We need to do this to avoid infinite loops.
        # You do see how quickly this gets really complicated!?
        return super().__setattr__(key, value)
    try:
        self.update(key, value)
    except ValueError as e:
        raise AttributeError(str(e))

__setattr__的问题在这里变得很明显。我们需要检查 real 属性,以避免无限循环。这样的东西使您的代码非常复杂。无论如何,如果您将这两种魔术方法添加到Empire中,则可以立即执行以下操作:

my_little_kingdom.gold = 123

print(my_little_kingdom.gold)
print(my_little_kingdom._updates['gold'])

try:
    my_little_kingdom.balloons = 99
except AttributeError as e:
    print(e)

奖励积分:可打印的帝国(第二次编辑)

当然,要创建一个漂亮的可打印帝国,只需添加以下方法:

def __str__(self):
    possessions = ', '.join('{value} {key}'.format(value=p[1],
                                                   key=p[0])
                            for p in self._possessions.items())
    return ('A glorious and filthy rich empire ' 
            'owning {}.'.format(possessions))

现在您可以通过以下方式打印王国

print(my_little_kingdom)

看到这个:

A glorious and filthy rich empire owning 123 gold, 0 celebrities, 0 vanilla_ice.

答案 1 :(得分:0)

只要您在运行时不需要添加新资源(例如黄金,白银等),我就同意SmCaterpillar,我会避免使用__(g|s)etattr__魔术,而应使用建议的{ {1}}方法。