Using a class instance as a class attribute, descriptors, and properties
I have recently stated trying to use the newer style of classes in Python (those derived from object). As an excersise to familiarise myself with them I am trying to define a class which has a number of class instances as attributes, with each of these class instances describing a different type of data, e.g. 1d lists, 2d array开发者_如何转开发s, scalars etc. Essentially I wish to be able to write
some_class.data_type.some_variable
where data_type
is a class instance describing a collection of variables. Below is my first attempt at implementing this, using just a profiles_1d
instance and rather generic names:
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")
Is the above code roughly an appropriate way of tackling this problem. The examples I have seen of using property
just set the value of some variable. Here I require my set method to initialise an instance of some class. If not, any other suggestions of better ways to implement this would be greatly appreciated.
In addition, is the way I am defining my set method ok? Generally the set method, as far as I understand, defines what to do when the user types, in this example,
collection.profiles1d = ...
The only way I can correctly set the attributes of the profiles_1d
instance with the above code is to type collection.set_profiles([...], y1=[...], ...)
, but I think that I shouldn't be directly calling this method. Ideally I would want to type collection.profiles = ([...], y1=[...], ...)
: is this correct/possible?
Finally, I have seen a decorators mentioned alot with repect to the new style of classes, but this is something I know very little about. Is the use of decorators appropriate here? Is this something I should know more about for this problem?
First, it's good you're learning new-style classes. They've got lots of advantages.
The modern way to make properties in Python is:
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
then set profiles
by doing
collection = Collection()
collection.profiles = (arg1, arg2, arg3), {'kwarg1':val1, 'kwarg2':val2}
Notice all three methods having the same name.
This is not normally done; either have them pass the attributes to collection
s constructor or have them create the profiles_1d
themselves and then do collections.profiles = myprofiles1d
or pass it to the constructor.
When you want the attribute to manage access to itself instead of the class managing access to the attribute, make the attribute a class with a descriptor. Do this if, unlike in the property example above, you actually want the data stored inside the attribute (instead of another, faux-private instance variable). Also, it's good for if you're going to use the same property over and over again -- make it a descriptor and you don't need to write the code multiple times or use a base class.
I actually like the page by @S.Lott -- Building Skills in Python's Attributes, Properties and Descriptors.
When creating property
s (or other descriptors) that need to call other instance methods the naming convention is to prepend an _
to those methods; so your names above would be _get_profiles
, _set_profiles
, and _del_profiles
.
In Python 2.6+ each property is also a decorator, so you don't have to create the (otherwise useless) _name
methods:
@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
It looks like your code is trying to set profiles
on the class instead of instances -- if this is so, properties on the class won't work as collections.profiles
would be overridden with a profiles_1d
object, clobbering the property... if this is really what you want, you'll have to make a metaclass and put the property there instead.
Hopefully you are talking about instances, so the class would look like:
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
and then you would do something like:
collection = Collection()
collection.profiles1d = x, y1, y2, y3
A couple things to note: the setter
method gets called with only two items: self
, and the new value
(which is why you were having to call set_profiles1d
manually); when doing an assignment, keyword naming is not an option (that only works in function calls, which an assignment is not). If it makes sense for you, you can get fancy and do something like:
collection.profiles1d = (x, dict(y1=y1, y2=y2, y3=y3))
and then change the setter
to:
@profiles1d.setter
def profiles1d(self, value):
x, y = value
self._profiles_1d = profiles_1d(x, **y)
which is still fairly readable (although I prefer the x, y1, y2, y3
version myself).
精彩评论