Python Getters, Setters - python

class P:
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
I am trying to learn 'getters' and 'setters' methods.
When I create an instance with these inputs:
p1 = P(1001)
print(p1.x)
p1.x = -12
print(p1.x)
I get as outputs:
1001
-12
I don't know why do I get 1001 and -12 instead of 1000 and 0.

Your real problem is that you're using Python 2.x with old-style classes. Your code as shown will work as you expect on Python 3.x (except for the broken indentation, of course). It will also work on Python 2.x if you make P a new-style class by explicitly inheriting from object (e.g. declare it as class P(object):).
In an old-style class, the problem is that your setter and getter methods are both named x, and both are competing for entries in the same namespace.
When you have code like:
#x.setter
def x(self, x):
...
What happens is that a new function object (confusingly, named x) is created with the code you specify, then x.setter(x_function_object) is called, and whatever x.setter returns (which will be a descriptor object with __get__ and __set__ defined) is assigned to x in the namespace dictionary that is passed to the metaclass's __new__ method and used to build the class's type.
However, in an old-style class, when you write self.x = ... then it will invoke __setattr__ directly rather than using the descriptor protocol (self.__dict__['x'].__set__(x, ...)). Thus the assignment to self.x in your __init__ method will overwrite the setter method instead of calling it.

Related

Access property object (for replacing setter method)

Consider the following code taken from the official documentation
class test:
_x = 10
def getx(self): return self._x
def setx(self, value): self._x = value
x = property(getx, setx)
as already explained in many other questions, this is 100% equivalent to
class test:
_x = 10
#property
def x(self):
return self._x
#x.setter
def x(self, val):
self._x = val
I would like to access the property x (and not the int in _x) in order to change the value of x.setter.
However doing type(test().x) returns int rather than property indicating that what test().x returns is _x and not the property x. Indeed, trying to do access test().x.setter returns a AttributeError: 'int' object has no attribute 'setter'.
I understand that 99.9% of the time, when someone does test().x he wants to access the value associated with the property x. This is exactly what properties are meant for.
However, how can I do in that 0.01% of the times when I want to access the property object rather than the value returned by the getter?
x is a class attribute, whose __get__ method receives a reference to the object when invoked on an instance of the class. You need to get a reference to the class first, then you can get the actual property object without invoking the getter.
>>> t = test()
>>> t.x
10
>>> type(t).x
<property object at ....>

The pythonic way to construct a multimethod setter

We can use a #property to construct a getter and setter. This is a short example how we can do this:
class A:
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 > 100:
self.__x = 100
else:
self.__x = x
My case seems to be more complicated.
class A:
def __init__(self, x):
self.__x = x
self.x1()
self.x2()
self.x3()
def x1(self):
self.__x1 = self.__x + 1
return self.__x1
def x2(self):
self.__x2 = self.__x1 + 2
return self.__x2
def x3(self):
self.__x3 = self.__x2 + 3
return self.__x3
if __name__ == "__main__":
a = A(3)
print(a.x3)
Methods x1, x2 and x3 are oversimplified. The self.__x3 variable is set only once, when the __init__ method is called. Now, I need a getter method to get self.__x3 by calling a.x3. How to achieve that in the pythonic way?
Attempting an answer based on the assumption that you want the __x# variables modified only during __init__, and never again, but also want the accessors to follow the same code path (possibly because the read is also programmatically complex):
In this case, you can have the implementing function take an additional, defaulted argument. When accessed in attribute form, it will receive the defaulted argument, but if the fget member of the property is explicitly accessed, it can be called with the non-default argument. A simple example addressing x1 only:
class A:
def __init__(self, x):
self.__x = x
# Access the property itself off the class, bypassing execution,
# then call it directly with the non-default argument
type(self).x1.fget(self, True)
#property
def x1(self, doset=False):
if doset:
self.__x1 = self.__x + 1
return self.__x1
Alternatively, to simplify the usage in __init__, you can use a separate name for the underlying function vs. the property to achieve the same effect:
class A:
def __init__(self, x):
self.__x = x
# Call the implementing function directly with the non-default argument
self._x1(True)
# Implementing function named with single underscore prefix to indicate it's
# for internal/protected use only
def _x1(self, doset=False):
if doset:
self.__x1 = self.__x + 1
return self.__x1
# Define property x1 based on x1 for outside use
x1 = property(_x1)
Of course, if you don't have a complicated getter path, then the real solution is to separate _x1 from x1 completely, where _x1 is pure setter helper function for __init__, and x1 is pure getter:
class A:
def __init__(self, x):
self.__x = x
# Call the init helper
self._init_x1()
# Implementing function named with single underscore prefix to indicate it's
# for internal/protected use only
def _init_x1(self):
self.__x1 = self.__x + 1
#property:
def x1(self):
return self.__x1
To be clear, only the last of these is "Pythonic" in any meaningful sense. The second option has some limited use cases (where you have a function that demands existence, and is highly configurable, but has a reasonable set of defaults that a property could use), but in that case, it's usually a function that has public utility just like the property. Option #1 is the least Pythonic, as it's inconvenient to use (needing to elevate to the class type, extract the fget member, and explicitly pass self), and makes it quite clear that there is no expected use case outside of __init__ (because it's such a pain to use that no one would bother).

Python class attribute validation on __init__

I'm trying to validate one attribute of my class using setter in the code below. The attribute I want to validate is called '__x' and is set to parameter passed on 'init' method. When I change 'self__x' to 'self.x', it's working as I expect. What I want to is how it's working with 'self.x', while I don't return 'x' attribute anywhere in getter and setter methods and why it's not working with 'self.__x'?
class P:
def __init__(self, x):
self.__x = x # not working
# self.x = x # working
#property
def x(self):
return self.__x
#x.setter
def x(self, x):
if x < 0:
self.__x = 0
else:
self.__x = x
p = P(-5)
print(p.x) # prints -5
It's like this. Imagine there's a school bully, let's call him Dan, who targets you. There's also Beth, who you like very much. Normally, you want to avoid Dan and meet Beth, but Dan doesn't care and will bop you on the head if he sees you.
Now you also make friends with Joe. He's a gentle giant. Very nice guy. He says to come to his place and he'll make sure he doesn't let Dan in. It all works great: when Dan comes to Joe's door, he's turned away; when Beth comes, Joe lets her in.
The key point is this: it only works as long as Dan is opening the door. If you hear the doorbell and you go out yourself, it doesn't work any more.
So here, if you do self.x = -5, Joe checks the number, sees it's Dan, and sends him packing with a zero. But if you do self.__x = -5, Joe never sees Dan. You get a bop on the head.
self.__x is just a variable, it can't do any checking on its own. self.x is a function though (two of them really, one for reading and one for writing), and it can do whatever it wants - set self.__x or refuse to.
Let's begin with the "#decorator" syntax. It's actually only syntactic sugar, so
#decorate
def myfunc():
pass
is just a shorthand for
def myfunc():
pass
myfunc = decorate(myfunc)
Note that python functions are objects too (as well as classes and modules FWIW) so you can pass functions as arguments to other functions, return functions from functions, store functions as variables or attributes etc.
Now with the property class (yes, it's a class): it's only a generic implementation of the descriptor protocol, which is the python mechanism to support computed attributes.
A naive python implementation of property would mostly look something like (I ignore the fdel and __del__ parts):
class propertytype(type):
# this is what will allow you
# to use `property` as decorator,
# it will return a new `property` instance
# with `func` as setter
def __call__(cls, func):
return cls(func)
class property(metaclass=propertytype):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
# this is the getter
def __get__(self, instance, cls=None):
if instance is None:
return self
return self.fget(instance)
# this is the setter (if there's one)
def __set__(self, instance, value):
if not self.fset:
raise AttributeError("Attribute is read-only")
self.fset(instance, value)
# and this allows you to use`#myprop.setter`
# in your class definition
def setter(self, func):
self.fset = func
return self
And finally: while it's good practice to create all instance attributes of an object in the initializer (the __init__ method), you can actually set existing or new attributes just wherever and whenever you want. Except for a few types that (mainly for implementation reasons) use a totally different way to store attributes (you can look for slots if you want to learn more about this), ordinary Python objects are, mainly, dicts in disguise, so myobj.foo = 'bar' will usually just store 'bar' in self.__dict__['foo']. Well, if you don't use computed attributes, of course ;)
Ok, now we have the building blocks, let analyze what's going on with your class:
class P:
# let's ignore the initializer for now
#property
def x(self):
return self.__x
#x.setter
def x(self, x):
if x < 0:
self.__x = 0
else:
self.__x = x
This could be rewritten as
class P:
# let's ignore the initializer for now
def _getx(self):
return self.__x
def _setx(self):
if x < 0:
self.__x = 0
else:
self.__x = x
x = property(_getx, setx)
So now with
p = P()
when we do:
p.x = 5
the attribute resolution rules (implemented in object.__setattr__(self, name, value)) will actually lookup "x" on "P", find our "x" property, and since it's a binding descriptor (it has a __set__ method), call x.__set__(p, 5), which in turn will call self.fset(p, 5) (cf property.__set__() definition), which will call p._setx(5).
And if we had back the initializer:
class P:
def __init__(self, x):
self.x = x
# getter / setter / property definition here
then the very exact thing happens (except the P instance is named self instead of p ) - it actually ends up calling P._setx(self, x).
The only difference with your original implementation is that using the property has a decorator, the getter and setter functions do not become methods of the class, they only live as the fget and fset attributes of the x property object.

Is it possible to pass a #property to a predefined function such that the property's underlying method is invoked when the function uses it?

I'm curious about the #property annotation in Python 3.
I know I can manipulate state between calls to a property like this ...
class Obj:
_x = 0
#property
def x(self):
self._x += 1
return self._x
obj = Obj()
print(obj.x)
print(obj.x)
... which prints:
1
2
However, is it possible keep this mechanism after it has been passed to a function?
Take the following function:
def f(x):
print(x)
print(x)
Say we cannot change it, meaning we can't simply pass the object to the function and use the properties directly. Is it possible to pass it only the property, or something to that effect, such that each time x is "used" in the function x increments? Is there maybe a way to do this by manipulating and passing the class or object itself? Something similar to __call__(self, ...), or adding #property to the class itself or the __init__(self, ...) of the class?
I've tried the following naive approaches ...
obj = Obj()
f(obj.x)
... and ...
obj = Obj()
f(getattr(obj, 'x'))
Both print ...
1
1
... which makes sense seeing as it is evaluated before it is passed to f. I also tried to extend Obj:
class Obj:
_x = 0
#property
def x(self):
self._x += 1
return self._x
def y(self):
return x
... and then go ...
obj = Obj()
f(obj.y)
... but it produces ...
<bound method Obj.y of <__main__.Obj object at 0x000002379E015748>>
<bound method Obj.y of <__main__.Obj object at 0x000002379E015748>>
This also makes sense seeing as we're simply getting the method itself and passing it to the f function.
Just a note: This isn't a real world problem/example. I'm simply trying to understand the principles and limits of python itself.
The property() wrapper is a descriptor, meaning that it is only invoked by dotted access, like a.x, and not by plain variable access.
To trigger a method invocation or function call with just a variable reference, I think it would be necessary to exec() code in a custom locals namespace:
>>> def add_ten(x):
return x + 10
>>> class NS(dict):
def __getitem__(self, key):
value = dict.__getitem__(self, key)
if key == 'x':
return add_ten(value)
return value
>>> exec('print(x + 2)', globals(), NS(x=5))
17
In the above example, just a reference to the variable x is enough to trigger a call to add_ten().
This is likely more trouble than its worth, but as you say, you just wanted to see what is possible :-)

Can you change a python descriptor's __get__ method at runtime?

All,
As the title asks, is it possible to change the __get__ method of a Descriptor at run time. I'm in a situation where I've got a function that is being decorated and undecorated on the the fly at run time. I'd like the result of this function to be available as a attribute, similar to what the #property does. I researched that and found it's a descriptor, but it seems descriptors' __get__ method is read only.
class Test( object ):
def __init__( self ):
self._x = 10
def get_x( self ):
return self._x
#property
def x( self ):
return self.get_x()
The above code does what I want, roughly, in that
The value is set in the constructor
I can decorate the get_x method to my heart's content
instance.x returns the correct value
My issue is that I'd rather not have to create the get_x method since it's basically unnecessary. I just haven't been able to decorate the __get__ method of x as it is read-only.
Background
I'm writing a turn based strategy game, and I'm using decorators to implement persistent conditions. I'm able to implement these decorators effectively when I use test cases, but the issue is that to get the computed value then, you must use a function call, not an attribute access. This seems like an bad idea because getting values describing a unit would inconsistently use functions or attributes. I'd like to standardize on attributes if I can.
You can override default "read-only" characteristic of property's __get__ attribute using simple inheritance :
class MyProperty( property ): pass
class Test( object ):
def __init__( self ):
self._x = 10
def get_x( self ):
return self._x
#MyProperty
def x( self ):
return self.get_x()
test = Test()
The problem now that even if you redefine __get__ attribure of your Text.x property, on test.x request python runtime will call MyProperty.__get__(Test.x, test, Test)
So you could rewrite it only there like
MyProperty.__get__ = lambda self,instance,owner: ""the x attribute"
So good option here is to delegate call to some redifineable attribute like
MyProperty.__get__ = lambda self,instance,owner: self.get(instance,owner)
From now on get attribute of your property in your full control.
Also there is bad option to generate separate type for each property-like object.
So in good case you could do something like:
class MyProperty( property ):
def __get__(self,instance,owner) :
if not instance: return self
else: return self.get(instance,owner)
class Test( object ):
def __init__( self ):
self._x = 10
def get_x( self ):
return self._x
#MyProperty
def x( self ): pass
#MyProperty
def y(self): pass
x.get = lambda self,clazz: self.get_x()
y.get = lambda self,clazz: "the y property of " + clazz.__name__ + " object"
>>> test = Test()
>>> test.x
10
>>> test.y
'the y property of Test object'

Categories

Resources