How do I assign a property to an instance in Python? - python

Using python, one can set an attribute of a instance via either of the two methods below:
>>> class Foo(object):
pass
>>> a = Foo()
>>> a.x = 1
>>> a.x
1
>>> setattr(a, 'b', 2)
>>> a.b
2
One can also assign properties via the property decorator.
>>> class Bar(object):
#property
def x(self):
return 0
>>> a = Bar()
>>> a.x
0
My question is, how can I assign a property to an instance?
My intuition was to try something like this...
>>> class Doo(object):
pass
>>> a = Doo()
>>> def k():
return 0
>>> a.m = property(k)
>>> a.m
<property object at 0x0380F540>
... but, I get this weird property object. Similar experimentation yielded similar results. My guess is that properties are more closely related to classes than instances in some respect, but I don't know the inner workings well enough to understand what's going on here.

It is possible to dynamically add properties to a class after it's already created:
class Bar(object):
def x(self):
return 0
setattr(Bar, 'x', property(Bar.x))
print Bar.x
# <property object at 0x04D37270>
print Bar().x
# 0
However, you can't set a property on an instance, only on a class. You can use an instance to do it:
class Bar(object):
def x(self):
return 0
bar = Bar()
setattr(bar.__class__, 'x', property(bar.__class__.x))
print Bar.x
# <property object at 0x04D306F0>
print bar.x
# 0
See How to add property to a class dynamically? for more information.

Properties use descriptors which only work on classes and thus
for all instances. But you could use a combination of a descriptor on
a class that would consult a per-instance function.
>>> class Foo(object):
... #property
... def x(self):
... if 'x' in self.__dict__:
... return self.__dict__['x'](self)
...
>>> a = Foo()
>>> def k(self):
... return 0
...
>>> a.__dict__['x'] = k
>>> a.x
0

You can assign the property directly to the class object:
>>> class Foo(object):
pass
>>> a = Foo()
>>> a.__class__
__main__.Foo
>>> setattr(a.__class__, 'm', property(lambda self: 0))
>>> a.m
0
>>> a.m = 24
AttributeError: can't set attribute

Here we have taken #agf's solution and used a lambda function to define the class property.
class A(object):
pass
a = A()
a.__class__.f = property(lambda self: 57)
a.f # 57
The following post provides more context: https://crosscompute.com/n/jAbsB6OIm6oCCJX9PBIbY5FECFKCClyV/_/Assign%20a%20class%20property%20to%20an%20instance

Related

Why can't you set a property on an instance of a class?

setattr can be used on either a class or an instance of the class:
>>> class C(object):
... pass
...
>>> myc = C()
>>> setattr(myc, "val1", 1)
>>> setattr(myc, "val2", 2)
>>> myc.val1
1
>>> myc.val2
2
>>>
However, when I try and set a property I get something unexpected:
>>> setattr(myc, "val3", property(lambda self: 3))
>>> myc.val3
<property object at 0x7f4d2f30c418>
I get the behavior I expected when I set the property to the class instead of the instance:
>>> setattr(C, "val4", property(lambda self: 4))
>>> myc.val4
4
What is happening when I try and set property on an instance of a class?
You can't set a property on an instance because you can't. Well, you can, but as you have discovered, it won't work. Properties only work when set on the class; that's how it's designed. For details on how the underlying feature (descriptors) works, see the Descriptor HowTo.

Confused about the property decorator and its related getter/setter methods

I've been doing some Python, and I realised I Haven't actually know a lot about the property decorator, so I tried making a simple example. This is the code I used:
class foo():
def __init__(self):
self.__test = 0
#property
def test(self):
return self.__test
#test.setter
def test(self, value):
self.__test = value
#test.getter
def test(self):
self.__test += 1
return self.__test
Then I started playing with it in the interactive shell:
>>> bar = foo()
>>> bar.test
1
>>> bar.test
2
So far the object behaved as I expected it to.
Then I tried checking out the setter method
>>> bar.test = 5
>>> bar.test
5
>>> bar.test
5
Weird. For some reason the value of __test wasn't incremented.
>>> bar._foo__test
2
I thought I had set __test to be equal to 5.
What's going on?
The problem is that your foo class is an old style class, descriptors (and as such properties) are only intended to work with new style classes.
From the doc:
Note that descriptors are only invoked for new style objects or classes (a class is new style if it inherits from object or type)
In this case, with an old style class setting bar.test = 5 creates a test attribute in the instance dict, which shadows the property from the class dict:
>>> bar = foo()
>>> foo.__dict__
{'test': <property object at 0x7f302e64c628>, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x7f302e658b18>}
>>> bar.test # test property from class dict is used
1
>>> bar.__dict__
{'_foo__test': 1}
>>> bar.test = 5 # sets test on instance
>>> bar.__dict__
{'test': 5, '_foo__test': 1}
So the solution is simple: make foo a new style class by inheriting from object

Python new style class can not use __dict__ to update property attribute

>>> class foo():
... #property
... def o(self):
... return 'o'
...
>>> f = foo()
>>> f.o
'o'
>>> f.__dict__['o'] = 'f'
>>> f.o
'f'
>>> class foo(object):
... #property
... def o(self):
... return 'o'
...
>>> f = foo()
>>> f.o
'o'
>>> f.__dict__['o'] = 'f'
>>> f.o
'o'
The __dict__ set just does't appear. Any ideas why?
I think maybe Python treat property differently inside, but I am not sure about it, if you write one property class in your own, the set is gonna work, otherwise not.
#property
def o(self):
return 'o'
This is a non-data descriptor, i.e a property with no setter defined. Hence an instance attribute can override this property.
From docs:
If an instance’s dictionary has an entry with the same name as a
non-data descriptor, the dictionary entry takes precedence.
So, to update a property, define its setter method as well.
And don't use instance's __dict__ to access the setter or getter, internally f.o = 'f' for a setter is actually converted to:
type(f).__dict__['o'].__set__(f, 'f')
Demo:
class Foo:
def __init__(self):
self._o = 'o'
#property
def o(self):
return self._o
#o.setter
def o(self, val):
self._o = val
>>> f = Foo()
>>> f.o
'o'
>>> type(f).__dict__['o'].__set__(f, 'f')
>>> f.o
'f'
>>> f.o = 'zzzz' #this is equivalent to the previous call.
>>> f.o
'zzzz'
>>> type(f).__dict__['o'].__set__(f, 'foo')
>>> f.o
'foo'

How do we get an optional class attribute in Python?

For dictionary we can use .get method. What about a class's attribute and provide a default value?
You can use hasattr and getattr.
For example:
hasattr(foo, 'bar')
would return True if foo has an attribute named bar, otherwise False and
getattr(foo, 'bar', 'quux')
would return foo.bar if it exists, otherwise defaults to quux.
It's dead simple: use a keyword argument.
>>> class Foo(object):
... def __init__(self, my_foo=None):
... self.my_foo = my_foo
...
>>> f = Foo()
>>> f.my_foo
>>> repr(f.my_foo)
'None'
>>> f2 = Foo(2)
>>> f2.my_foo
2
If you are looking for an object which does not have an attribute until you set it, Jason's idea is pretty good unless you directly refer to the attribute. You'll have to work around the AttributeError you'll get. Personally, I'm not a fan of creating objects which must be constantly surrounded by try/except blocks just for the sake of not setting an instance attribute.
Python isn't much for getters and setters. However, you can use property() to work around this problem:
>>> class Foo(object):
... def __init__(self):
... pass
... def get_my_foo(self):
... return getattr(self, "_my_foo", "there's no my_foo")
... def set_my_foo(self, foo):
... self._my_foo = foo
... my_foo = property(get_my_foo, set_my_foo)
...
>>> f = Foo()
>>> f.my_foo
"there's no my_foo"
>>> f.my_foo = "foo!"
>>> f.my_foo
'foo!'
It works just as well to call get_my_foo and set_my_foo, if you like. An added benefit is that you can omit the setter to make a read-only attribute, provided someone using your object doesn't change the underlying _my_foo.

How to change Method of Object in python

I write a python file like :
class A(object):
def update(self, str):
pass
def say(self, str):
print "I update: " + str
def fun(obj, str):
obj.say(str)
a = A()
import types
setattr(A, "update", types.MethodType(fun, None, A))
a.update("hello")
b = A()
b.update("world?")
It change behave of class, the object b have been changed. but, I want to only change object a.
How to change Method of Object in python?
Here is a way to do it:
a.update = lambda x: fun(a, x)
You are setting the class method, while you want to set only the method bound to some instance.
>>> class MyClass(object):
... def a(self): pass
...
>>> MyClass.a = lambda x: x
>>> MyClass.a
<unbound method MyClass.<lambda>>
>>> a = MyClass()
>>> a.a
<bound method MyClass.<lambda> of <__main__.MyClass object at 0x1d7fed0>>
Changing the a method at class level changes also the a methods of all instances.
>>> class MyClass(object):
... def a(self): pass
...
>>> b = MyClass()
>>> b.a = lambda x: x
>>> MyClass.a
<unbound method MyClass.a>
>>> b.a
<function <lambda> at 0x1d88938>
>>> c = MyClass()
>>> c.a
<bound method MyClass.a of <__main__.MyClass object at 0x1d8d110>>
Changing the a method of an instance does not change the method of the class or other instances.

Categories