Reputation: 325
Getting up to speed on learning classes. I have been reading that the constructor (def init in Python) should only set assigned variables, that calculated instance attributes should be set via a property. Also, that using @property is preferred to a Java-style getter/setter.
OK, but every example I have ever seen on this sets only one property. Let's say I have an object with three complicated attributes that need to be calculated, queried etc. How do you represent multiple @property getters, setters, deleters? Here is an example from another post:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
So if I had three instance variables that were calculated values based on some other attributes, would it look like this
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
@property
def y(self):
"""I'm the 'y' property."""
return self._y
@y.setter
def y(self, value):
self._y = value
@y.deleter
def y(self):
del self._y
@property
def z(self):
"""I'm the 'z' property."""
return self._z
@z.setter
def z(self, value):
self._z = value
@z.deleter
def z(self):
del self._z
Or is that fact that I only ever see one @property
statement mean that having a class with more than one @property
is a bad idea?
Upvotes: 12
Views: 12749
Reputation: 702
You can use the methods __setattr__
and __getattr__
:
class Properties:
def __init__(self, **kwargs):
self._attrs = kwargs
def __getattr__(self, name):
if name in self._attrs:
return self._attrs[name]
raise AttributeError(f"'{type(self).__name__}' object has no property '{name}'")
def __setattr__(self, name, value):
if name != "_attrs" and "_attrs" in self.__dict__ and name in self._attrs:
self._attrs[name] = value
else:
super().__setattr__(name, value)
# Init
obj = Properties(field1=88, field2=99)
# Print attributes
print(obj.field1)
print(obj.field2)
# Change attributes
obj.field1 = 888
obj.field2 = 333
print(obj.field1)
print(obj.field2)
# Error:
print(obj.field3)
Upvotes: 0
Reputation: 5256
To bounce on another answer,
there is no shortcut to the somewhat ugly looking pattern
Not in python stdlib indeed, but there is now a library named pyfields
to do this in a more concise way, without sacrificing speed when you do not need validators not converters.
from pyfields import field, init_fields
class C(object):
x = field(default=None, doc="the optional 'x' property")
y = field(doc="the mandatory 'y' property")
z = field(doc="the mandatory 'z' property")
@init_fields
def __init__(self):
pass
c = C(y=1, z=2)
print(vars(c))
yields
{'z': 2, 'y': 1, 'x': None}
I'm the author of this lib, I wrote it because none of the existing alternatives were satisfying to me.
See documentation for details - Do not hesitate to provide feedback !
Upvotes: 2
Reputation: 938
yes it does but @property decorator make it more neat and tidy to define multiple property in a class. Property is Different with attribute. The reason property used in encapsulation because python don't have built-in access modifier. In fact, when we call @property decorator above a method we instantiate a property object. Property object is an object have get,set, and del method. Property can be defined in a class without the need to define it in init. init method invoked when an object instantiated. If the private attribute need to set when the object created define it in init but when we don't need initial value we have no need to define the attributes. Here's the example of property without attribute.
class C(object):
#X Property
@property
def x(self):
"""I'm the 'x' property."""
print('Get The X Property')
return self._x
@x.setter
def x(self, value):
print('X Property Setted to {}'.format(value))
self._x = value
@x.deleter
def x(self):
print('X is Killed !')
del self._x
#Y Property
@property
def y(self):
"""I'm the 'y' property."""
print('Get The Y Property')
return self._y
@y.setter
def y(self, value):
print('Y Property Setted to {} '.format(value))
self._y = value
@y.deleter
def y(self):
print('Y is Killed !')
del self._y
When we need to define x and y as an attribute to the class C. Just set the the attribute in init. so, x and y listed as class C attribute and have initial value setted when the object instantiated.
class C(object):
def __init__(self):
self._x = 0
self._y = 0
#X Property
@property
def x(self):
"""I'm the 'x' property."""
print('Get The X Property')
return self._x
@x.setter
def x(self, value):
print('X Property Setted to {}'.format(value))
self._x = value
@x.deleter
def x(self):
print('X is Killed !')
del self._x
#Y Property
@property
def y(self):
"""I'm the 'y' property."""
print('Get The Y Property')
return self._y
@y.setter
def y(self, value):
print('Y Property Setted to {} '.format(value))
self._y = value
@y.deleter
def y(self):
print('Y is Killed !')
del self._y
The Difference is simple. Without any initial value the class C have no x and y attributes. so, if we trying to get the attribute an exception will raised. With initial value the class C already have X and Y attribute from the beginning.
Upvotes: 0
Reputation: 12343
it's not a bad idea. it's a regular property pattern.
another pattern is doing like:
class A(object):
def _get_a(self):
return self._a
def _set_a(self, v)
self._a = v
def _del_a(self)
del self._a
a = property(_get_a, _set_a, _del_a)
the result is the same that in the first example, and you can use it as much as you wish. initializing self._a in the constructor is a good idea, since otherwise, accesing it BEFORE by calling self.a would raise AttributeError
Upvotes: 3
Reputation: 1125398
No, you can use multiple @property
decorators to your heart's content. There is no limit here, other than that of example writers imaginations, apparently.
The Python standard library is full of @property
use if you want examples:
numbers
defines ABCs for the numbers classes in Python.
tempfile
implements temporary file objects
threading
provding higher-level thread support
urlparse
for handling URLs and query strings.
etc.
You had it spot on; multiple properties would look exactly like what you posted.
Upvotes: 12
Reputation: 11748
It may be ugly, but that is the way it works. In general, the pythonic recommendation is not to create simple setter/getters that directly access the native variable (unless you know the interface is likely to change). Assuming that the reason for the properties is 'complex calculations' as you said, there is no shortcut to the somewhat ugly looking pattern. See this link http://tomayko.com/writings/getters-setters-fuxors
Upvotes: 0