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

8

我最近开始尝试在Python中使用新款类的风格(那些派生自object的类)。为了熟悉它们,我正试图定义一个类,其中有许多类实例作为属性,每个类实例描述不同类型的数据,例如1维列表,2维数组,标量等。基本上,我希望能够编写以下内容:

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=[...], ...):这样做正确/可行吗?
最后,我已经看到很多关于新类风格中装饰器的提及,但这是我很少了解的。在这里使用装饰器是否合适?这是我为这个问题应该更多了解的东西吗?

1
为什么要使用getter和setter以及private属性?只需将profiles_1d设置为公共属性,让设置它的代码传递一个profiles_1d对象而不是构造函数的参数即可。 - Wooble
作为用户,只处理主类(在这种情况下是“collection”)肯定会更容易。但你说得对,这将是一个简单的解决方案。 - Chris
你的 collection 已经向使用它的代码返回了 profiles_1d 类型的对象;对于一个 setter 来说,传递与 getter 返回值不同的内容违反了最小惊讶原则(principal of least surprise),更不用说 getter 和 setter 在 Python 中已经被淘汰了。 - Wooble
@Wooble 谢谢 - 我会记住的。 - Chris
2个回答

10

首先,你学习新式类很好。它们有很多优点。

在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}

请注意这三个方法的名称相同。

通常情况下不这样做;要么将属性传递给集合(collection)的构造函数,要么让它们自己创建profiles_1d,然后执行collections.profiles = myprofiles1d或将其传递给构造函数。

当您希望属性“管理对自身的访问”而不是类管理对属性的访问时,请将属性定义为描述符类。如果您实际上想要存储在属性内部的数据(而不是伪私有实例变量中的数据),那么可以使用此方法。另外,如果您打算多次使用相同的属性,请将其定义为描述符类,这样您就不需要重复编写代码或使用基类。

我实际上很喜欢@S.Lott的页面 -- Building Skills in Python Attributes, Properties and Descriptors


谢谢您的回答,非常有帮助。不过,假设参数x不应该在这一行中 self._profiles_1d = profiles_1d(x, *args, **kwargs) 出现? - Chris

1

在创建需要调用其他实例方法的property(或其他描述符)时,命名约定是在这些方法前面添加_;因此,您上面的名称将是_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的版本)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接