我想创建一个 Attribute
类,这样我就可以执行以下操作:
class Creature:
def __init__(self, health=100, armor=0):
self.health = Attribute(health)
self.armor = Attribute(armor)
现在当我这样做的时候
c1 = Creature()
c1.health += 10
它实际上并没有改变实际值,但它将 health
的 base 值保持在 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/