is it OK to use property setter on private variables in python? - python

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 ?

Related

how do i raise a value error in this. i am trying to do so but it wont just output

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.

Why is `self.__x` not working but `self.y` is?

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

class property conversions

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"

Setting Values Of Inherited Properties

I want to be able to create a concrete instance of a class that inherits from another concrete class, which in turn inherits from an abstract class.
The basic pattern is:
from abc import ABCMeta, abstractproperty
class Foo(object):
__metaclass__ = ABCMeta
#abstractproperty
def x(self):
pass
#abstractproperty
def y(self):
pass
class Bar(Foo):
x = None
y = None
def __init__(self, x, y):
self.x = x
self.y = y
#property
def x(self):
return self.x
#x.setter
def x(self, value):
self.x = value
#property
def y(self):
return self.y
#y.setter
def y(self, value):
self.y = value
class Baz(Bar):
def __init__(self):
super().__init__(x=2, y=6)
a = Baz()
When I try to create the instance of Baz I get a RecursionError: maximum recursion depth exceeded error. (As well as a pylint warning telling me that the signatures of the setter methods don't match the signatures of the base class)
However, if I remove the setters, I get an error self.x = x AttributeError: can't set attribute
What's the correct pattern to do this?
You need to change names for your x() / y() methods or for your x / y properties, for example rename
class Bar(Foo):
x = None
y = None
To:
class Bar(Foo):
x_val = None
y_val = None
And rename the references to x / y as well.
What you did is basically:
def x():
return x()
It happened because your def x overridden the x = None, so x is a function(property) that is calling itself. Avoid this by using another attribute(named differently) for storing the actual value of x.
Example from python docs (https://docs.python.org/3.5/library/functions.html#property):
class C:
def __init__(self):
self._x = None
#property
def x(self):
return self._x
#x.setter
def x(self, value):
self._x = value
Note: attribute names starting with underscore should be considered "private" and should not be directly accessed outside of the class. But it's only a convention for programmers, technically they are just normal attributes and you can do whatever you want, but it's nice to follow some convention, isn't it?

How to use 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

Categories

Resources