【问题标题】:Creating an Attribute class that allows adjusting a base value?创建一个允许调整基值的属性类?
【发布时间】:2015-03-12 00:40:06
【问题描述】:

我想创建一个Attribute 类,这样我就可以执行以下操作:

class Creature:
    def __init__(self, health=100, armor=0):
        self.health = Attribute(health)
        self.armor = Attribute(armor)

现在当我这样做时

c1 = Creature()
c1.health += 10

它实际上并没有改变实际值,但它保持healthbase值为100,同时给它一个+10的调整。

这是我尝试过的,但它使用 healt.get()health.set() 是不需要的:

class Attribute:
    def __init__(self, base=0, adjustments=None):
        self._base = base
        self._adjustments = adjustments or []

    def set(self, value):
        difference = value - self._base
        if difference:
            self._adjustments.append(difference)

    def get(self):
        return self._base + sum(self._adjustments)

c1 = Creature(50)
c1.health.set(60)  # Adds +10 adjustment

但我希望能够做到这一点:

c1 = Creature(50)
c1.health = 60
# Or just:
c1.health += 10

这可能吗?

【问题讨论】:

  • 我想你想要properties__iadd__()的组合
  • @Alex 的回答是正确的,也是最简单的。但是,如果您必须使用类作为属性,则可以使用元类。这就是 Django 模型的工作方式。例如country = models.TextField(max_length=5, default='US'). 你可以看到你可以添加额外的元信息并将通常的验证抽象到另一个类中。您现在可以像使用普通属性一样使用该属性:obj.country = 'ENG'. 当我重新开始工作时,我将发布示例代码来说明如何做到这一点。
  • 这很可能是个坏主意。假装是一个整数,但完全像其他东西一样行事是非常令人困惑的。

标签: python python-3.x get attributes set


【解决方案1】:

以下是可能满足您需求的四种方法。


描述符

Descriptors 允许您在隐藏底层实现的同时提供直接属性访问。

class AttributeDescriptor(object):
    def __init__(self):
        self.initialized = False
        self.base = 0
        self.adjustments = []

    def compute(self):
        return self.base + sum(self.adjustments)

    def __set__(self, inst, value):
        if not self.initialized:
            self.base = value
            self.initialized = True
            print("Attribute initialized to %s" % value)
        else:
            # Calculate delta
            delta = (value - self.compute())
            self.adjustments.append(delta)
            print("Adjustment added: %s" % delta)

    def __get__(self, inst, owner):
        return self.compute()

class Creature(object):
    health = AttributeDescriptor()
    armor  = AttributeDescriptor()

    def __init__(self, health=100, armor=0):
        self.health = health
        self.armor  = armor


c1 = Creature(50)
c1.health = 60      # Adds a +10 adjustment
print c1.health     # 60
c1.health += 10     # Add a +10 adjustment
print c1.health     # 70
#print c1.health.adjustments     # This won't work ('int' object has no attribute 'adjustments')

输出:

属性初始化为 50 属性初始化为 0 添加调整:10 60 添加调整:10 70

这种方法的问题是您没有简单的方法来访问描述符的内部。所以在这种情况下,您永远无法检查adjustments 列表。但是,您可以直接将 c1.health = X 分配给它,就好像它是一个普通属性一样。

注意:正如 Veedrac 在 cmets 中所指出的,这些属性是在类级别定义的,将在Creature 类的所有实例之间共享。仅出于这个原因,它不是一个解决方案,但无论如何它都不是一个很好的解决方案。

普通跟踪器对象

您可以使用实现“augmented assignment”魔术方法__iadd__()__isub__() 的类

class AttributeObject(object):
    def __init__(self, base):
        self.base = base
        self.adjustments = []
        print("Attribute initialized to %s" % base)

    def __compute(self):
        return self.base + sum(self.adjustments)

    def __int__(self):
        return self.__compute()

    def __iadd__(self, delta):
        print("Adjustment added: %s" % delta)
        self.adjustments.append(delta)
        return self

    def __isub__(self, delta):
        print("Adjustment added: %s" % -delta)
        self.adjustments.append(-delta)
        return self

class Creature(object):
    def __init__(self, health=100, armor=0):
        self.health = AttributeObject(health)
        self.armor =  AttributeObject(armor)


c1 = Creature(50)
#c1.health = 60         # Can't do this, because it will override the AttributeObject
print int(c1.health)    # 60
c1.health += 10         # Add a +10 adjustment
print int(c1.health)    # 70
print c1.health.adjustments  # [10]

输出:

属性初始化为 50 属性初始化为 0 50 添加调整:10 60 [10]

这种方法的问题是你不能直接分配给属性而不覆盖它。换句话说,c1.health = X 将覆盖 health 属性的值,使其等于 X——你会丢失之前的任何内容。

但是通过这种方法,您可以访问adjustments 列表:print c1.health.adjustments

注意c1.healthAdjustmentTracker 的一个实例,而不是您可能期望的数字类型(试试print c1.health)。您有多种访问/提取数值的方法,在示例中我使用了int(c1.health) 类型转换(可能因为我实现了__int__)。

描述符 + 跟踪器对象

结合使用上述两种方法,您可以使用您列出的所有语法。

class AttributeDescriptor(object):
    def __init__(self, attr):
        self.attr = attr

    def __set__(self, inst, value):
        getattr(inst, self.attr).update(value)

    def __get__(self, inst, owner):
        return getattr(inst, self.attr).compute()


class AdjustmentTracker(object):
    def __init__(self, base):
        print("Attribute initialized to %s" % base)
        self.base = base
        self.adjustments = []

    def compute(self):
        return self.base + sum(self.adjustments)

    def update(self, value):
        delta = (value - self.compute())
        print("Adjustment added: %s" % delta)
        self.adjustments.append(delta)


class Creature(object):
    health = AttributeDescriptor('_health')
    armor  = AttributeDescriptor('_armor')

    def __init__(self, health=100, armor=0):
        self._health = AdjustmentTracker(health)
        self._armor =  AdjustmentTracker(armor)


c1 = Creature(50)
c1.health = 60      # Adds a +10 adjustment
print c1.health     # 60
c1.health += 10     # Add a +10 adjustment
print c1.health     # 70
print c1._health.adjustments     # [10, 10]

输出:

属性初始化为 50 属性初始化为 0 添加调整:10 60 添加调整:10 70 [10, 10]

在这里,描述符本身并没有跟踪基本列表和调整列表,而是将它们用作与AdjustmentTracker obejcts 接口的代理。有了这个,您可以直接分配(例如c1.health = 60访问底层的初始基础/调整(例如c1._health.adjustments)。

属性 + 跟踪器对象

就像前面的例子一样,我们使用AdjustmentTracker 对象来保存属性的状态。但在本例中,您可以使用properties 来屏蔽属性,而不是使用显式描述符。

class AdjustmentTracker(object):
    def __init__(self, base):
        print("Attribute initialized to %s" % base)
        self.base = base
        self.adjustments = []

    def compute(self):
        return self.base + sum(self.adjustments)

    def update(self, value):
        delta = (value - self.compute())
        print("Adjustment added: %s" % delta)
        self.adjustments.append(delta)


class Creature(object):
    @property
    def health(self):         return self._health.compute()
    @health.setter
    def health(self, value):  self._health.update(value)

    @property
    def armor(self):          return self._armor.compute()
    @armor.setter
    def armor(self, value):   self._armor.update(value)

    def __init__(self, health=100, armor=0):
        self._health = AdjustmentTracker(health)
        self._armor =  AdjustmentTracker(armor)


c1 = Creature(50)
c1.health = 60      # Adds a +10 adjustment
print c1.health     # 60
c1.health += 10     # Add a +10 adjustment
print c1.health     # 70
print c1._health.adjustments     # [10, 10]

输出:

属性初始化为 50 属性初始化为 0 添加调整:10 60 添加调整:10 70 [10, 10]

这个例子和上一个基本相同,只是代码行数更少,因为它使用了属性,功能完全一样。

【讨论】:

  • 第一个示例将值存储在类级别,因此所有Creatures 共享相同的生命值和护甲。
  • @Veedrac,你是对的——让我看看我能不能解决这个问题。
  • @Veedrac,我“修复”了它——多么丑陋的解决方案。
  • 我很确定那会内存泄漏。
  • 它不应该让Creature 元素保持活动状态,因为它只是一个哈希,但它永远不会释放有关收集的实例的信息。我回滚了编辑并注意到您的担忧。如果我能找到一个很好的方法来处理这个问题,我会编辑它。
【解决方案2】:

查看属性 https://docs.python.org/3/library/functions.html#property

在下面的示例中,属性被用作装饰器。

class Creature:
    def __init__(self, health):
        self._base_health = health
        self._modifications = []
    @property
    def health(self):
        return self._base_health + sum(self._modifications)
    @health.setter
    def health(self, value):
        self._modifications.append(value - self._base_health - sum(self._modifications))

每次检索健康属性时,都会调用 getter 函数(标有 property 装饰器的函数)。同样,当设置了 health 属性时,setter 函数(标有health.setter 装饰器的函数)会被调用。

c1 = Creature(50)
c1.health = 60
c1.health += 10
print(c1.health)
c1.health = 40
print(c1.health)

输出

70
40

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多