创建允许调整基值的属性 class?
Creating an Attribute class that allows adjusting a base value?
我想创建一个 Attribute
class 这样我就可以做类似的事情:
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
这可能吗?
查看属性
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
装饰器的函数)。类似地,当设置健康属性时,调用 setter 函数(标有 health.setter
装饰器的函数)。
c1 = Creature(50)
c1.health = 60
c1.health += 10
print(c1.health)
c1.health = 40
print(c1.health)
产出
70
40
以下是可能满足您需求的四种方法。
描述符
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
这种方法的问题是您没有简单的方法来访问描述符的内部结构。所以在这种情况下,您永远无法检查 adjustments
列表。但是,您可以直接将 c1.health = X
分配给它,就好像它是一个普通属性一样。
注意: 正如 Veedrac 在评论中指出的那样,这些属性是在 class 级别定义的, 将在所有实例之间共享 的 Creature
class。仅出于这个原因,它不是任何解决方案,但在任何情况下都不是一个很好的解决方案。
普通追踪器对象
您可以使用实现“augmented assignment”魔术方法 __iadd__()
和 __isub__()
的 class
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]
这种方法的问题在于您不能在不覆盖属性的情况下直接分配给该属性。换句话说,c1.health = X
将覆盖 health
属性的值使其等于 X——您将失去之前的任何内容。
但是使用这种方法,您 可以 访问 adjustments
列表:print c1.health.adjustments
请注意 c1.health
是 AdjustmentTracker
的实例,而不是您可能期望的数字类型(尝试 print c1.health
)。你有很多方法来 access/extract 数值,在我使用 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]
输出:
Attribute initialized to 50
Attribute initialized to 0
Adjustment added: 10
60
Adjustment added: 10
70
[10, 10]
在这里,描述符不会跟踪基本列表和调整列表本身,而是将它们用作与 AdjustmentTracker
对象交互的代理。有了这个,您可以同时进行直接分配(例如 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]
输出:
Attribute initialized to 50
Attribute initialized to 0
Adjustment added: 10
60
Adjustment added: 10
70
[10, 10]
本例与上例基本相同,只是代码行少了,因为它使用了属性,功能完全相同。
我想创建一个 Attribute
class 这样我就可以做类似的事情:
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
这可能吗?
查看属性 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
装饰器的函数)。类似地,当设置健康属性时,调用 setter 函数(标有 health.setter
装饰器的函数)。
c1 = Creature(50)
c1.health = 60
c1.health += 10
print(c1.health)
c1.health = 40
print(c1.health)
产出
70
40
以下是可能满足您需求的四种方法。
描述符
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
这种方法的问题是您没有简单的方法来访问描述符的内部结构。所以在这种情况下,您永远无法检查 adjustments
列表。但是,您可以直接将 c1.health = X
分配给它,就好像它是一个普通属性一样。
注意: 正如 Veedrac 在评论中指出的那样,这些属性是在 class 级别定义的, 将在所有实例之间共享 的 Creature
class。仅出于这个原因,它不是任何解决方案,但在任何情况下都不是一个很好的解决方案。
普通追踪器对象
您可以使用实现“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]
输出:
Attribute initialized to 50 Attribute initialized to 0 50 Adjustment added: 10 60 [10]
这种方法的问题在于您不能在不覆盖属性的情况下直接分配给该属性。换句话说,c1.health = X
将覆盖 health
属性的值使其等于 X——您将失去之前的任何内容。
但是使用这种方法,您 可以 访问 adjustments
列表:print c1.health.adjustments
请注意 c1.health
是 AdjustmentTracker
的实例,而不是您可能期望的数字类型(尝试 print c1.health
)。你有很多方法来 access/extract 数值,在我使用 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]
输出:
Attribute initialized to 50 Attribute initialized to 0 Adjustment added: 10 60 Adjustment added: 10 70 [10, 10]
在这里,描述符不会跟踪基本列表和调整列表本身,而是将它们用作与 AdjustmentTracker
对象交互的代理。有了这个,您可以同时进行直接分配(例如 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]
输出:
Attribute initialized to 50 Attribute initialized to 0 Adjustment added: 10 60 Adjustment added: 10 70 [10, 10]
本例与上例基本相同,只是代码行少了,因为它使用了属性,功能完全相同。