I am learning to use #property decorators. I have the following two code snippets:
Code 1
class P2:
def __init__(self, x):
self.x = x
#property
def x(self):
return self.y
#x.setter
def x(self, x):
if x < 0:
self.y = x
elif x > 1000:
self.y = 1000
else:
self.y = x
p1 = P2(7600)
print(p1.y)
Code2
class P2:
def __init__(self, x):
self.x = x
#property
def x(self):
return self.__x
#x.setter
def x(self, x):
if x < 0:
self.__x = x
elif x > 1000:
self.__x = 1000
else:
self.__x = x
p1 = P2(7600)
print(p1.__x)
To obtain the code2 I replace y by __x in code1. But the issue is that the code1 is running perfectly fine but the code2 is giving an error 'P2' object has no attribute '__x'.
My understanding is that y and __x are merely two variables, they should behave in the same way, so in my opinion code2 and code1 are identical and both should give same output.
But, it is not happening. What is wrong in my understanding?
Properties that are prepended with a double underscore are pseudo-private. (There is no notion of completely private variables in Python, unlike Java or C++.)
To access pseudo-private member variables, replace the double underscore with _<name of your class>__ (i.e. an underscore, followed by the class name, followed by two underscores). That being said, if you need to do this, you should consider why the variable is pseudo-private in the first place.
class P2:
def __init__(self, x):
self.x = x
#property
def x(self):
return self.__x
#x.setter
def x(self, x):
if x < 0:
self.__x = x
elif x > 1000:
self.__x = 1000
else:
self.__x = x
p1 = P2(7600)
print(p1._P2__x) # Prints 1000
Related
Create a class called Position that manages the position x and y.
Your constructor should take in the initial position of x and of y and upper limits for x and y and then use properties to manage x and y so that they cannot be set above these limits. Note you will need a property (getter/setter) for both x and y.
If an attempt to assign a value above the limit is made then it should raise a ValueError.
class Position:
def __init__(self,x,y,z,value):
self.x=x
self.y=y
pass
#property
def value(self):
return f"{self._x} and {self._y}"
#value.setter
def name(self,value):
self._x = value.upper()
self._y = value.upper()
if value > 10:
raise ValueError("x cannot be bigger than 10")
self._name = value
if __name__ == "__main__":
p = Position(0,0,10,10) # x=0, y=0,
print(f"x={p.x} and y={p.y}") # prints x=0 and y=0
p.x = 2
print(f"x={p.x} and y={p.y}") # prints x=2 and y=0
p.y += 3
print(f"x={p.x} and y={p.y}") # prints x=2 and y=3
p.x = 11 # raises ValueError: x cannot be bigger than 10
If you want to validate each of x and y, you need to created #property and #_.setter for each one of them.
class Position:
def __init__(self, x, y):
self._x = x # private x
self._y = y # private y
#property
def x(self):
return self._x
#x.setter
def x(self, x):
if x > 10:
raise ValueError("x cannot be bigger than 10")
self._x = x
#property
def y(self):
return self._y
#y.setter
def y(self, y):
if y > 10:
raise ValueError("y cannot be bigger than 10")
self._y = y
p1 = Position(11, 12) # note no validation in __init__ func feel free to add it
p1.x = 30 # raises ValueError("x cannot be bigger than 10")
p1.x = 3 # OK
NB. the property method, the _ in _.setter and the setter method must be named the same.
In the following code snippet, I use a setter on the x attribute (which I'd like to keep private)
class test:
def __init__(self, pos, x):
self._pos = pos
self.x = x # Want to be a private variable, eg. self._x
#property
def x(self):
return self._x
#x.setter
def x(self, x):
# Logic for setting x
if self._pos == 'long':
self._x = -1 * abs(x)
elif self._pos == 'short':
self._x = abs(x)
else:
raise ValueError('$$ pos must be long or short')
The problem is I end up with TWO attributes , self.x AND self._x . Since I want x to be private - I'd like to only have self._x (and discard self.x) . What's missing in the code ?
I have to use a class. I have to make sure that x and y are properties.
If the values provided are not convertible to an integer, raise an AttributeError. If we give a value less than 0 to x or y, it is assigned the value 0.
If we give a value greater than 10 to x or y, it is assigned the value 10.
Here is my code:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def getx(self):
x=int()
return self._x
def gety(self):
y=int()
return self._y
if x>=0:
return 0
else if x<=10:
return 10
I want to obtain this:
p = Point(1,12)
print(p.x, p.y) # output "1 10"
p.x = 25
p.y = -5
print(p.x, p.y) # output "10 0"
What are you looking for is clamp() function, which takes 3 arguments: value, desired minimal value and desired maximal value.
Properties are defined by the #property decorator. For testing if the value assigned to property is number I use numbers module. Here is sample code:
import numbers
def clamp(v, _min, _max):
return max(min(v, _max), _min)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
#property
def x(self):
return self.__x
#x.setter
def x(self, value):
if not isinstance(value, numbers.Number):
raise AttributeError()
self.__x = clamp(int(value), 0, 10)
#property
def y(self):
return self.__y
#y.setter
def y(self, value):
if not isinstance(value, numbers.Number):
raise AttributeError()
self.__y = clamp(int(value), 0, 10)
p = Point(1,12)
print(p.x, p.y) # output "1 10"
p.x = 25
p.y = -5
print(p.x, p.y) # output "10 0"
I used to initialize private attributes in __init__ like below (this way of initializing is also commonly seen),
class Duck():
def __init__(self, input_name):
self.__name = input_name
#property
def name(self):
return self.__name
#name.setter
def name(self, input_name):
self.__name = input_name
# Use private attribute __name internally for other purposes below...
But I just want to make sure if it is actually safer to just use property at the very beginning __init__, for example, in next example, for input greater than 1000 or less than 0, I want to evaluate to 1000 and 0, respectively, rather than the original input value. It seems I can't use self.__x = x,
class P:
def __init__(self,x):
# If self.__x = x, not desirable
self.x = x
#property
def x(self):
return self.__x
#x.setter
def x(self, x):
if x < 0:
self.__x = 0
elif x > 1000:
self.__x = 1000
else:
self.__x = x
I assume you work with python2. Properties are not supported for old-style classes. Just change the first line to
class P(object):
And whether you use self._x or self.__x for the attribute behind does not matter. Just do it consistent, i.e. change the constructor line back to
self.__x = x
Just don't call that self.x as the property.
Edit:
There was a misunderstanding I think. Here the complete code I propose:
class P(object):
def __init__(self,x):
self.__x = x
#property
def x(self):
return self.__x
#x.setter
def x(self, x):
if x < 0:
self.__x = 0
elif x > 1000:
self.__x = 1000
else:
self.__x = x
p = P(3)
p.x = 1001
print p.x
Edit 2 - The actual question:
I apologize, did simply not grasp the heading and actual question here, but was focused on making your class work. My distraction was that if you are in python2 and use old-style classes, the setter would not really get called.
Then like indicated in the comment-conversation below, I don't have a definite answer on whether to initialize the attribute or the property, but I (personally) would say:
a. If the initialisation deserves the same validation as performed in
the setter, then use the property, as else you'd need to copy the
setter code.
b. If however the value to initialise doesn't need validation (for
instance, you set it to an a priori validated constant default
value), then there is no reason to use the property.
I'm having trouble on how to implement property to protect attributes.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def set_x(self, x):
if '_x' in dir(self):
raise NotImplementedError("Cannot change x coordinate")
else:
self._x = x
def get_x(self):
return self._x
#I beleive my mistake is here. I'm not sure if I'm implementing this correctly
x = property(get_x, set_x, None, None)
So I want to prevent any user from changing the x-coordinate. My question is, how do I get python to redirect the user to the set_x() and get_x() methods? I've tried running this code in terminal and whenever I apply the following, the point changes.
p = point(3, 4)
p.x = 5 #x is now 5
You only need this much:
class Point:
def __init__(self, x, y):
self._x = x
self.y = y
def get_x(self):
return self._x
x = property(get_x)
You can set the hidden field self._x in your init, then you don't need a setter for x at all. And have get_x return self._x rather than self.x so it doesn't try and call itself.
You can use the #property decorator to do this even more succinctly.
class Point:
def __init__(self, x, y):
self._x = x
self.y = y
#property
def x(self):
return self._x
The following code works on both python2.x and python3.x:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def set_x(self, x):
if '_x' in dir(self):
raise NotImplementedError("Cannot change x coordinate")
else:
self._x = x
def get_x(self):
return self._x
x = property(get_x, set_x, None, None)
p = Point(2, 3)
print(p.x) # 2
p.x = 6 # NotImplementedError
Pretty much all I did was inherit from object (to get it to work on python2.x) and use the name Point rather than point (which would have been a NameError before).
There are other things you can do to clean it up a bit (e.g. khelwood's suggestion of just writing the getter -- or DSM's suggestion of using hasattr instead of '_x' in dir(self)).
Note, if you really just want a type that takes an x and y arguments that you want to be immutable -- Maybe you should consider using a colledctions.namedtuple
from collections import namedtuple
Point = namedtuple('Point', 'x,y')
p = Point(2, 3)
p.x # 2
p.y # 3
p.x = 6 # AttributeError: can't set attribute