【问题标题】:Python 3.4 - How to create a template property for many classesPython 3.4 - 如何为许多类创建模板属性
【发布时间】:2016-05-08 14:28:52
【问题描述】:

在 Python 3.x 中,是否可以创建一个可以被许多类重用的“模板”属性?

我有大约 200 个类别,对应于商店中销售的产品。每个类都使用特定于产品的属性。每个属性的“get”和“let”函数有时会在设置属性值时对数据执行详细的完整性检查。

有时对许多不同类的许多不同属性执行相同的检查。唯一改变的是属性名称。下面为“Robot”和“Box”类提供了一个简化示例。在这些类中,属性检查它是否被设置为小于 1 的值并更正它。

class Robot():


   def setPrice(self,Price):              # <--                        
                                          # <--
      if (Price < 1): Price = 1           # <--
                                          # <-- Common check
      self.__Price = Price                # <-- to many
                                          # <-- classes
   def getPrice(self):                    # <--  
                                          # <-- 
      return self.__Price                 # <-- 
                                          # <--
   Price = property(getPrice, setPrice)   # <-- 


class Box():


   def setWeight(self,Weight):            # <--                        
                                          # <--
      if (Weight < 1): Weight = 1         # <--
                                          # <-- Common check
      self.__Weight = Weight              # <-- to many
                                          # <-- classes
   def getWeight(self):                   # <--  
                                          # <-- 
      return self.__Weight                # <-- 
                                          # <--
   Weight = property(getWeight, setWeight) # <-- 

是否可以将此属性隔离到某种类的外部函数中,然后我的许多类都可以调用它?换句话说,这样的事情可以实现吗?

class_or_function TemplateProperty():

   ... some code ...


class Robot():    

    Price = TemplateProperty()      
    NumberOfLegs = TemplateProperty()       

class Box():    

    Price = TemplateProperty() 
    Weight = TemplateProperty() 
    Height = TemplateProperty()
    Length = TemplateProperty()      

【问题讨论】:

  • 为什么不把通用属性放在一个基类中呢?
  • @Blckknght:我更新了我的问题。需要模板的属性具有相同的行为,但每个类中的名称不同。可以用基类管理吗?

标签: class python-3.x object properties


【解决方案1】:

如果您的对象永远不会有多个模板属性,那么您可以编写一个简单的函数来构建适当的属性对象并返回它:

def PositiveValuedProperty():
    def getter(obj):
        return obj.__value

    def setter(obj, new_value):
        if new_value < 1:
            new_value = 1
        obj.__value = new_value

    return property(getter, setter)

那么你的课程将是:

class Robot:
    Price = PositiveValuedProperty()

class Box:
    Weight = PositiveValuedProperty()

但是,如果任何类使用多个这些属性,这将不起作用,因为它们都写入相同的属性名称。如果你想让多个类出现在一个类中,你可能需要为工厂函数指定一个属性名称:

def PositiveValuedProperty(name):
    name = "_" + name

    def getter(obj):
        return getattr(obj, name)

    def setter(obj, new_value):
        if new_value < 1:
            new_value = 1
        setattr(obj, name, new_value)

    return property(getter, setter)

现在您可以使用PriceWeight 设置RobotBox

def RobotBox():
    Price = PositiveValuedProperty("Price")
    Weight = PositiveValuedProperty("Weight")

如果您希望内部代码在不经过property 的检查的情况下访问它们,实际值将存储在属性_Price_Weight 中。

请注意,如果您的逻辑比上述复杂得多,您可能会更好地构建自己的描述符类型,而不是像我在上面所做的那样创建闭包并将它们传递给 property 构造函数。描述符只是一个类,其中包含根据descriptor protocol 定义的一些__get____set__ 和/或__delete__ 方法。以下是如何将最新版本的 PositiveValuedProperty 实现为描述符类而不是工厂函数:

class PositiveValuedProperty():
    def __init__(self, name):
        self.name = "_" + name

    def __get__(self, obj, cls=None):
        return getattr(obj, self.name)

    def __set__(self, obj, new_value):
        if new_value < 1:
            new_value = 1
        setattr(obj, self.name, new_value)

如您所见,它与property 代码几乎相同,因为property 是一个描述符,它使用您传递给它的函数来实现其__get____set__ 方法。但是,您可以编写更复杂的描述符。例如,您可以通过检查type(obj) 的属性(或__get__cls 参数)并查找等于self 的属性,让您的描述符推断出它自己的名称。 .不过这有点脆弱,因为描述符实际上可能有多个名称(例如class Foo: x=MyDescritptor(); y=x),并且搜索类的变量可能很慢。

【讨论】:

  • 如果我创建 2 个 Robot 类的对象会怎样? Robot001.Price 会干扰 Robot002.Price 吗?
  • 这应该适用于任一版本,因为装饰器将实际值存储在 obj 上,这是类的实例。
【解决方案2】:

每个类一个描述符的简单解决方案

您可以使用descriptor

class MinValue:

    def __init__(self, limit=1):
        self.limit = limit

    def __get__(self, instance, cls):
        if not hasattr(instance, '_value'):
            return
        return instance._value

    def __set__(self, instance, value):
        if value < self.limit:
            value = self.limit
        instance._value = value

在您的课程中使用它:

class Robot:
    price = MinValue()

class Box:
    weight = MinValue(10)

测试一下:

>>> r = Robot()
>>> r.price = 0.5
>>> r.price
1

>>> b = Box()
>>> b.weight = 8
>>> b.weight
10

为每个类提供多个描述符的更复杂的解决方案

将实例存储在描述符中。为了避免内存泄漏,因为您可能会在普通字典中保留对已删除实例的引用,请使用WeakKeyDictionary

from weakref import WeakKeyDictionary

class MinValue:
    def __init__(self, limit=1):
        self.limit = limit
        self._values = WeakKeyDictionary()

    def __get__(self, instance, cls):
        return self._values.get(instance)

    def __set__(self, instance, value):
        if value < self.limit:
            value = self.limit
        self._values[instance] = value

class Robot:
    price1 = MinValue()
    price2 = MinValue()

class Box:
    height = MinValue()
    width = MinValue()
    weight = MinValue()

测试一下:

>>> robot = Robot()
... box = Box()
...
>>> robot.price1 = 10
... robot.price2 = .5
...
>>> robot.price1
10
>>> robot.price2
1
>>> b.height = 1
... b.width = 20
... b.weight = 0.4
...
>>> box.height = 1
... box.width = 20
... box.weight = 0.4
...
>>> box.height
1
>>> box.width
20
>>> box.weight
1

【讨论】:

  • 当一个类包含多个以下属性时,我遇到了一个问题:weight = MinValue(1)、height = MinValue(1)、length = MinValue(1)。如果我为这些属性中的任何一个设置一个值,其他属性将等于该值。有什么方法可以修改上面的 MinValue 类来解决这个问题?
  • 添加了一个可以满足你需求的版本。
  • 请注意,使用由实例键入的字典要求实例是可散列的。虽然这是自定义类的默认设置(根据其身份进行哈希处理),但并非所有类的所有实例都是如此。
  • @Blckknght 我能想出的唯一解决方法是覆盖__hash__,例如引发NotImplementedError 错误。您是否有其他可能破坏我的解决方案的方法?
  • 如果在给定描述符的类中定义__eq__,它将不起作用。如果自定义对象没有定义__eq__,或者如果他们定义了自己的__hash__,则它们是可散列的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-04
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
相关资源
最近更新 更多