将类实例用作类属性、描述符和属性
我最近开始尝试在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 个回答
在创建 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
这种写法)。
首先,很高兴你在学习新式的类。这些类有很多好处。
在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中构建技能》的 属性、属性和描述符。