【问题标题】:Is there a way to programmatically create very similar class properties?有没有办法以编程方式创建非常相似的类属性?
【发布时间】:2020-11-17 04:49:52
【问题描述】:

我创建了一个简单的类来接收一个表示数组形状的元组,并将名称分配给最后三个维度(深度、高度、宽度)作为类属性。我有兴趣将这些属性设置为属性,以便对实例形状的任何更改都会反映在这些属性中。这会造成我有重复代码的情况,如果我决定将来分配更多维度名称或设置器/删除器,情况会变得更糟。

这是我的(精简)课程:


class Shape():
    """A class to represent 4-dimension names of a shape tuple"""
    def __init__(self, shape):
        """Shape instance is initialized with a shape tuple

        Args:
            shape (tuple): a tuple representing an array shape
        """
        self.shape = shape

    @property
    def shape(self):
        return self._shape

    @shape.setter
    def shape(self, value):
        self._shape = value

    # ndim is needed in case number of elements in shape is not equal to 3
    @property
    def ndim(self):
        return len(self.shape)

    # width, height, depth are similar
    @property
    def width(self):
        """Last dimension of shape"""
        if self.ndim >= 1:
            return self.shape[-1]

    @property
    def height(self):
        """Second last dimension of shape"""
        if self.ndim >= 2:
            return self.shape[-2]

    @property
    def depth(self):
        """Third last dimension of shape"""
        if self.ndim >= 3:
            return self.shape[-3]

实例化类:

x = (4, 5)
shape1 = Shape(x)
print(shape1.shape)
print((shape1.depth, shape1.height, shape1.width))

(4, 5)
(无、4、5)

将形状属性设置为新值:

shape1.shape = (3, 2, 1)
print(shape1.shape)
print((shape1.depth, shape1.height, shape1.width))

(3, 2, 1)
(3, 2, 1)

所以我的课程按预期工作,但是有没有一种更简洁的方法可以在循环中设置多个类似的属性?我试图在 init 的循环中使用 setattr()。这适用于设置常规类属性,但我找不到使用它设置属性的方法,这意味着属性将不再反映对实例形状的更新。

【问题讨论】:

    标签: python properties attributes python-decorators


    【解决方案1】:

    每个单独的属性都可以用更通用的私有方法来实现。

    class Shape:
        def __init__(self, shape):
            self.shape = shape
    
        def _get_dim(self, i):
            try:
                return self.shape[i]
            except IndexError:
                return None
    
    
        @property
        def shape(self):
            return self._shape
    
        @shape.setter
        def shape(self, value):
            self._shape = value
    
        @property
        def ndim(self):
            return len(self.shape)
    
        width = property(lambda self: self._get_dim(0))
        height = property(lambda self: self._get_dim(1))
        depth = property(lambda self: self._get_dim(2))
    

    您可能更喜欢使用 operator.methodcaller 而不是 lambda 表达式,例如width = property(methodcaller("_get_dim", 0))


    您可以更进一步,定义您自己的自定义描述符来代替property。

    class Dimension:
        def __init__(self, n):
            self.n = n
    
        def __get__(self, obj, cls):
            if obj is None:
                return self
            try:
                return obj.shape[n]
            except IndexError:
                return None
    
    
    class Shape:
        width = Dimension(0)
        height = Dimension(1)
        depth = Dimension(2)
    
        def __init__(self, shape):
            self.shape = shape
    
        @property
        def shape(self):
            return self._shape
    
        @shape.setter
        def shape(self, value):
            self._shape = value
    
        @property
        def ndim(self):
            return len(self.shape)
    

    【讨论】:

    • 哇,这些太棒了!我认为我最接近的尝试与您使用 property() 函数的第一个尝试类似,但我无法弄清楚如何将 self 传入。我将不得不更多地阅读类的工作原理。
    • 我也尝试了您的第二个解决方案,但尝试通过以下方式阻止属性的“设置”:def __set__(self, obj, value): raise AttributeError("can't set attribute")
    • 您的问题中没有任何二传手,所以我将它们排除在外。此外,元组是不可变的,因此您必须更改 shape 所指的内容才能修改它(或重建一个新元组)。不过,为了让您开始,您只需定义 __set__(self, obj, value) 以(重新)设置元组,以便 obj.shape[self.n] == value。
    • 感谢您的提示。您的一种解决方案是否比另一种更容易接受/普遍?我觉得第一个更简单一些,因为我们不必创建第二个类。
    猜你喜欢
    • 2010-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    • 2017-04-02
    • 1970-01-01
    相关资源
    最近更新 更多