使用类实例作为类属性、描述符和属性

Posted

技术标签:

【中文标题】使用类实例作为类属性、描述符和属性【英文标题】:Using a class instance as a class attribute, descriptors, and properties 【发布时间】:2011-10-24 21:24:34 【问题描述】:

我最近说过尝试在 Python 中使用更新的类样式(那些从对象派生的)。作为熟悉它们的练习,我试图定义一个具有许多类实例作为属性的类,每个类实例描述不同类型的数据,例如一维列表、二维数组、标量等。基本上我希望能够编写

some_class.data_type.some_variable

其中data_type 是描述变量集合的类实例。下面是我第一次尝试实现这一点,只使用 profiles_1d 实例和相当通用的名称:

class profiles_1d(object):
    def __init__(self, x, y1=None, y2=None, y3=None):
        self.x = x
        self.y1 = y1
        self.y2 = y2
        self.y3 = y3

class collection(object):
    def __init__(self):
        self._profiles_1d = None

    def get_profiles(self):
        return self._profiles_1d

    def set_profiles(self, x, *args, **kwargs):
        self._profiles_1d = profiles_1d(x, *args, **kwargs)

    def del_profiles(self):
        self._profiles_1d = None

    profiles1d = property(fget=get_profiles, fset=set_profiles, fdel=del_profiles,
        doc="One dimensional profiles")

上面的代码大致是解决这个问题的合适方法吗?我见过的使用property 的例子只是设置了一些变量的值。在这里,我需要我的 set 方法来初始化某个类的实例。如果没有,任何其他更好的实现方法的建议将不胜感激。

另外,我定义 set 方法的方式好吗?据我了解,通常 set 方法定义了用户键入时要执行的操作,在此示例中,

collection.profiles1d = ...

我可以用上面的代码正确设置profiles_1d实例的属性的唯一方法是输入collection.set_profiles([...], y1=[...], ...),但我认为我不应该直接调用这个方法。理想情况下,我想输入collection.profiles = ([...], y1=[...], ...):这是正确的/可能的吗?

最后,我看到一个装饰器提到了很多关于新样式的类,但我对此知之甚少。这里使用装饰器合适吗?对于这个问题,这是我应该了解的更多信息吗?

【问题讨论】:

为什么要使用 getter 和 getter 以及私有属性?只需将profiles_1d 设为公共属性并让设置它的代码传递profiles_1d 对象而不是构造函数的参数。 作为用户,处理主类肯定会更容易(在这种情况下为collection)。但你是对的,这将是一个简单的解决方案。 您的collection 已经将profiles_1d 类型的对象返回给使用它的代码;它违反了 setter 传递与 getter 返回的不同的东西的最不意外原则(更不用说 getter 和 setter 完全不符合 Python)。 @Wooble 谢谢 - 我会记住这一点的。 【参考方案1】:

首先,很高兴您正在学习新式课程。他们有很多优势。

在 Python 中创建属性的现代方法是:

class Collection(object):
    def __init__(self):
        self._profiles_1d = None

    @property
    def profiles(self):
        """One dimensional profiles"""
        return self._profiles_1d

    @profiles.setter
    def profiles(self, argtuple):
        args, kwargs = argtuple
        self._profiles_1d = profiles_1d(*args, **kwargs)

    @profiles.deleter
    def profiles(self):
        self._profiles_1d = None

然后通过做设置profiles

collection = Collection()
collection.profiles = (arg1, arg2, arg3), 'kwarg1':val1, 'kwarg2':val2

注意所有三个具有相同名称的方法。

通常不会这样做;要么让他们将属性传递给collections 构造函数,要么让他们自己创建profiles_1d,然后执行collections.profiles = myprofiles1d 或将其传递给构造函数。

当您希望属性管理对自身的访问而不是管理对属性的访问的类时,请使属性成为具有描述符的类。如果与上面的属性示例不同,您实际上希望将数据存储在属性中(而不是另一个假私有实例变量),请执行此操作。此外,如果您要一遍又一遍地使用相同的属性,这也很有用——将其设为描述符,您无需多次编写代码或使用基类。

我真的很喜欢 @S.Lott 的页面——在 Python 的 Attributes, Properties and Descriptors 中构建技能。

【讨论】:

感谢您的回答,非常有帮助。但是,大概x 的论点不应该在self._profiles_1d = profiles_1d(x, *args, **kwargs) 行中?【参考方案2】:

在创建需要调用其他实例方法的propertys(或其他描述符)时,命名约定是在这些方法前面加上_;所以你上面的名字是_get_profiles_set_profiles_del_profiles

在 Python 2.6+ 中,每个属性也是一个装饰器,因此您不必创建(否则无用)_name 方法:

@property
def test(self):
    return self._test

@test.setter
def test(self, newvalue):
    # validate newvalue if necessary
    self._test = newvalue

@test.deleter
def test(self):
    del self._test

看起来您的代码正试图在类而不是实例上设置profiles——如果是这样,类上的属性将不起作用,因为collections.profiles 将被profiles_1d 对象覆盖,破坏属性...如果这确实是您想要的,则必须创建一个元类并将属性放在那里。

希望你在谈论实例,所以这个类看起来像:

class Collection(object):  # notice the capital C in Collection
    def __init__(self):
        self._profiles_1d = None

    @property
    def profiles1d(self):
        "One dimensional profiles"
        return self._profiles_1d

    @profiles1d.setter
    def profiles1d(self, value):
        self._profiles_1d = profiles_1d(*value)

    @profiles1d.deleter
    def profiles1d(self):
        del self._profiles_1d

然后你会做类似的事情:

collection = Collection()
collection.profiles1d = x, y1, y2, y3

需要注意的几件事:setter 方法仅使用两个项目调用:self 和新的value(这就是您必须手动调用set_profiles1d 的原因);在进行赋值时,关键字命名不是一个选项(仅适用于函数调用,而赋值不是)。如果它对你有意义,你可以花点心思做一些类似的事情:

collection.profiles1d = (x, dict(y1=y1, y2=y2, y3=y3))

然后将setter更改为:

    @profiles1d.setter
    def profiles1d(self, value):
        x, y = value
        self._profiles_1d = profiles_1d(x, **y)

这仍然是相当可读的(虽然我自己更喜欢x, y1, y2, y3 版本)。

【讨论】:

以上是关于使用类实例作为类属性、描述符和属性的主要内容,如果未能解决你的问题,请参考以下文章

类属性实例属性

python 类属性 实例属性,可变数据结构作为类属性需要注意的地方

基础知识回顾:属性

如何通过图访问作为图节点的类实例的属性?

Swift 属性

内部类和外部类的实例变量可以共存