python - 创建一个允许调整基值的属性类?

标签 python python-3.x get attributes set

我想创建一个 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

这可能吗?

最佳答案

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


描述符

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')

输出:

Attribute initialized to 50
Attribute initialized to 0
Adjustment added: 10
60
Adjustment added: 10
70

The problem with this approach is that you have no easy way to access the internals of the descriptor. So in this case, you can't ever inspect the adjustments list. You can, however, directly assign c1.health = X to it as if it were a normal attribute.

Note: As noted by Veedrac in the comments, these attributes are defined at the class level and will be shared between all instances of the Creature class. For this reason alone it's not a solution whatsoever, but it wasn't a great solution in any case.

Normal Tracker Objects

You can use a class that implements the "augmented assignment" magic methods __iadd__() and __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]

输出:

Attribute initialized to 50
Attribute initialized to 0
50
Adjustment added: 10
60
[10]

The problem with this approach is that you can't directly assign to the attribute without overriding it. In other words, c1.health = X will override the value of the health attribute to be equal to X -- you lose whatever was there previously.

But with this approach, you can access the adjustments list: print c1.health.adjustments

Note that c1.health is an instance of AdjustmentTracker, not a numeric type like you may expect (try print c1.health). You have a number of ways to access/extract the numeric value, in the example I use the int(c1.health) type conversion (possible since I implemented __int__).

Descriptor + Tracker Object

Using a combination of the two above methods, you can use all of the syntax you listed.

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]

输出:

Attribute initialized to 50
Attribute initialized to 0
Adjustment added: 10
60
Adjustment added: 10
70
[10, 10]

Here, the descriptors aren't keeping track of the base and adjustment list themselves, rather you're using them as a proxy to interface with AdjustmentTracker obejcts. With this you can do both direct assignment (e.g. c1.health = 60) and access the underlying initial base / adjustments (e.g. c1._health.adjustments).

Properties + Tracker Objects

Just as in the previous example, we use AdjustmentTracker objects to hold the state of the attributes. But in this example, instead of using an explicit descriptors, you can use properties to shield the attribute.

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]

输出:

Attribute initialized to 50
Attribute initialized to 0
Adjustment added: 10
60
Adjustment added: 10
70
[10, 10]

这个示例与上一个示例基本相同,只是代码行数较少,因为它使用了属性,并且功能完全相同。

关于python - 创建一个允许调整基值的属性类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29000020/

相关文章:

python - 图中的 K 阶邻居 - Python networkx

python - 在没有 root 权限的情况下安装 matplotlib 及其依赖项

python - 同名的相对和绝对导入导致 "AttributeError: ' 模块'对象没有属性...”

javascript - 循环遍历 JSON 对象的条目和 react 状态,得到 'state undefined'

java - java中的HTTP GET请求

python - 使用 nltk.corpus.gutenberg.fileids() 解码路径中的错误

python - Lambda Python : How to check if get_item returned a value

python - 如何在 PyCharm 中使用元编程支持动态类型提示?

python - 导入错误 : No module named 'html.parser' ; 'html' is not a package (python3)

json - 在 Perl 中获取 JSON