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

8 投票
2 回答
516 浏览
提问于 2025-04-16 23:22

我最近开始尝试在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的例子通常只是设置某个变量的值。而在这里,我需要我的设置方法来初始化某个类的实例。如果这样不行,您有什么更好的建议吗?

另外,我定义的设置方法这样可以吗?一般来说,设置方法的作用是定义当用户输入时应该做什么,比如在这个例子中:

collection.profiles1d = ...

我唯一能正确设置profiles_1d实例属性的方法是输入collection.set_profiles([...], y1=[...], ...),但我觉得我不应该直接调用这个方法。理想情况下,我希望能输入collection.profiles = ([...], y1=[...], ...):这样做是正确的吗?可能吗?

最后,我看到很多人提到装饰器和新风格的类,但我对此了解不多。在这里使用装饰器合适吗?这是我需要更深入了解的内容吗?

2 个回答

1

在创建 property(或者其他描述符)时,如果这些属性需要调用其他实例方法,命名的习惯是给这些方法前面加一个 _。所以你上面提到的方法应该叫 _get_profiles_set_profiles_del_profiles

在 Python 2.6 及以上版本中,每个 property 也是一个装饰器,所以你不需要再创建那些(本来没用的) _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 这种写法)。

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 的那一页——《在Python中构建技能》的 属性、属性和描述符

撰写回答