Related
Is there a magic method that can overload the assignment operator, like __assign__(self, new_value)?
I'd like to forbid a re-bind for an instance:
class Protect():
def __assign__(self, value):
raise Exception("This is an ex-parrot")
var = Protect() # once assigned...
var = 1 # this should raise Exception()
Is it possible? Is it insane? Should I be on medicine?
The way you describe it is absolutely not possible. Assignment to a name is a fundamental feature of Python and no hooks have been provided to change its behavior.
However, assignment to a member in a class instance can be controlled as you want, by overriding .__setattr__().
class MyClass(object):
def __init__(self, x):
self.x = x
self._locked = True
def __setattr__(self, name, value):
if self.__dict__.get("_locked", False) and name == "x":
raise AttributeError("MyClass does not allow assignment to .x member")
self.__dict__[name] = value
>>> m = MyClass(3)
>>> m.x
3
>>> m.x = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __setattr__
AttributeError: MyClass does not allow assignment to .x member
Note that there is a member variable, _locked, that controls whether the assignment is permitted. You can unlock it to update the value.
No, as assignment is a language intrinsic which doesn't have a modification hook.
I don't think it's possible. The way I see it, assignment to a variable doesn't do anything to the object it previously referred to: it's just that the variable "points" to a different object now.
In [3]: class My():
...: def __init__(self, id):
...: self.id=id
...:
In [4]: a = My(1)
In [5]: b = a
In [6]: a = 1
In [7]: b
Out[7]: <__main__.My instance at 0xb689d14c>
In [8]: b.id
Out[8]: 1 # the object is unchanged!
However, you can mimic the desired behavior by creating a wrapper object with __setitem__() or __setattr__() methods that raise an exception, and keep the "unchangeable" stuff inside.
Inside a module, this is absolutely possible, via a bit of dark magic.
import sys
tst = sys.modules['tst']
class Protect():
def __assign__(self, value):
raise Exception("This is an ex-parrot")
var = Protect() # once assigned...
Module = type(tst)
class ProtectedModule(Module):
def __setattr__(self, attr, val):
exists = getattr(self, attr, None)
if exists is not None and hasattr(exists, '__assign__'):
exists.__assign__(val)
super().__setattr__(attr, val)
tst.__class__ = ProtectedModule
The above example assumes the code resides in a module named tst. You can do this in the repl by changing tst to __main__.
If you want to protect access through the local module, make all writes to it through tst.var = newval.
Using the top-level namespace, this is impossible. When you run
var = 1
It stores the key var and the value 1 in the global dictionary. It is roughly equivalent to calling globals().__setitem__('var', 1). The problem is that you cannot replace the global dictionary in a running script (you probably can by messing with the stack, but that is not a good idea). However you can execute code in a secondary namespace, and provide a custom dictionary for its globals.
class myglobals(dict):
def __setitem__(self, key, value):
if key=='val':
raise TypeError()
dict.__setitem__(self, key, value)
myg = myglobals()
dict.__setitem__(myg, 'val', 'protected')
import code
code.InteractiveConsole(locals=myg).interact()
That will fire up a REPL which almost operates normally, but refuses any attempts to set the variable val. You could also use execfile(filename, myg). Note this doesn't protect against malicious code.
I will burn in Python hell, but what's life without a little fun.
Important disclaimers:
I only provide this example for fun
I'm 100% sure I don't understand this well
It might not even be safe to do this, in any sense
I don't think this is practical
I don't think this is a good idea
I don't even want to seriously try to implement this
This doesn't work for jupyter (probably ipython too)*
Maybe you can't overload assignment, but you can (at least with Python ~3.9) achieve what you want even at the top-level namespace. It will be hard doing it "properly" for all cases, but here's a small example by hacking audithooks:
import sys
import ast
import inspect
import dis
import types
def hook(name, tup):
if name == "exec" and tup:
if tup and isinstance(tup[0], types.CodeType):
# Probably only works for my example
code = tup[0]
# We want to parse that code and find if it "stores" a variable.
# The ops for the example code would look something like this:
# ['LOAD_CONST', '<0>', 'STORE_NAME', '<0>',
# 'LOAD_CONST', 'POP_TOP', 'RETURN_VALUE', '<0>']
store_instruction_arg = None
instructions = [dis.opname[op] for op in code.co_code]
# Track the index so we can find the '<NUM>' index into the names
for i, instruction in enumerate(instructions):
# You might need to implement more logic here
# or catch more cases
if instruction == "STORE_NAME":
# store_instruction_arg in our case is 0.
# This might be the wrong way to parse get this value,
# but oh well.
store_instruction_arg = code.co_code[i + 1]
break
if store_instruction_arg is not None:
# code.co_names here is: ('a',)
var_name = code.co_names[store_instruction_arg]
# Check if the variable name has been previously defined.
# Will this work inside a function? a class? another
# module? Well... :D
if var_name in globals():
raise Exception("Cannot re-assign variable")
# Magic
sys.addaudithook(hook)
And here's the example:
>>> a = "123"
>>> a = 123
Traceback (most recent call last):
File "<stdin>", line 21, in hook
Exception: Cannot re-assign variable
>>> a
'123'
*For Jupyter I found another way that looked a tiny bit cleaner because I parsed the AST instead of the code object:
import sys
import ast
def hook(name, tup):
if name == "compile" and tup:
ast_mod = tup[0]
if isinstance(ast_mod, ast.Module):
assign_token = None
for token in ast_mod.body:
if isinstance(token, ast.Assign):
target, value = token.targets[0], token.value
var_name = target.id
if var_name in globals():
raise Exception("Can't re-assign variable")
sys.addaudithook(hook)
No there isn't
Think about it, in your example you are rebinding the name var to a new value.
You aren't actually touching the instance of Protect.
If the name you wish to rebind is in fact a property of some other entity i.e
myobj.var then you can prevent assigning a value to the property/attribute of the entity.
But I assume thats not what you want from your example.
Yes, It's possible, you can handle __assign__ via modify ast.
pip install assign
Test with:
class T():
def __assign__(self, v):
print('called with %s' % v)
b = T()
c = b
You will get
>>> import magic
>>> import test
called with c
The project is at https://github.com/RyanKung/assign
And the simpler gist: https://gist.github.com/RyanKung/4830d6c8474e6bcefa4edd13f122b4df
Generally, the best approach I found is overriding __ilshift__ as a setter and __rlshift__ as a getter, being duplicated by the property decorator.
It is almost the last operator being resolved just (| & ^) and logical are lower.
It is rarely used (__lrshift__ is less, but it can be taken to account).
Within using of PyPi assign package only forward assignment can be controlled, so actual 'strength' of the operator is lower.
PyPi assign package example:
class Test:
def __init__(self, val, name):
self._val = val
self._name = name
self.named = False
def __assign__(self, other):
if hasattr(other, 'val'):
other = other.val
self.set(other)
return self
def __rassign__(self, other):
return self.get()
def set(self, val):
self._val = val
def get(self):
if self.named:
return self._name
return self._val
#property
def val(self):
return self._val
x = Test(1, 'x')
y = Test(2, 'y')
print('x.val =', x.val)
print('y.val =', y.val)
x = y
print('x.val =', x.val)
z: int = None
z = x
print('z =', z)
x = 3
y = x
print('y.val =', y.val)
y.val = 4
output:
x.val = 1
y.val = 2
x.val = 2
z = <__main__.Test object at 0x0000029209DFD978>
Traceback (most recent call last):
File "E:\packages\pyksp\pyksp\compiler2\simple_test2.py", line 44, in <module>
print('y.val =', y.val)
AttributeError: 'int' object has no attribute 'val'
The same with shift:
class Test:
def __init__(self, val, name):
self._val = val
self._name = name
self.named = False
def __ilshift__(self, other):
if hasattr(other, 'val'):
other = other.val
self.set(other)
return self
def __rlshift__(self, other):
return self.get()
def set(self, val):
self._val = val
def get(self):
if self.named:
return self._name
return self._val
#property
def val(self):
return self._val
x = Test(1, 'x')
y = Test(2, 'y')
print('x.val =', x.val)
print('y.val =', y.val)
x <<= y
print('x.val =', x.val)
z: int = None
z <<= x
print('z =', z)
x <<= 3
y <<= x
print('y.val =', y.val)
y.val = 4
output:
x.val = 1
y.val = 2
x.val = 2
z = 2
y.val = 3
Traceback (most recent call last):
File "E:\packages\pyksp\pyksp\compiler2\simple_test.py", line 45, in <module>
y.val = 4
AttributeError: can't set attribute
So <<= operator within getting value at a property is the much more visually clean solution and it is not attempting user to make some reflective mistakes like:
var1.val = 1
var2.val = 2
# if we have to check type of input
var1.val = var2
# but it could be accendently typed worse,
# skipping the type-check:
var1.val = var2.val
# or much more worse:
somevar = var1 + var2
var1 += var2
# sic!
var1 = var2
In the global namespace this is not possible, but you could take advantage of more advanced Python metaprogramming to prevent multiple instances of a the Protect object from being created. The Singleton pattern is good example of this.
In the case of a Singleton you would ensure that once instantiated, even if the original variable referencing the instance is reassigned, that the object would persist. Any subsequent instances would just return a reference to the same object.
Despite this pattern, you would never be able to prevent a global variable name itself from being reassigned.
As mentioned by other people, there is no way to do it directly. It can be overridden for class members though, which is good for many cases.
As Ryan Kung mentioned, the AST of a package can be instrumented so that all assignments can have a side effect if the class assigned implements specific method(s). Building on his work to handle object creation and attribute assignment cases, the modified code and a full description is available here:
https://github.com/patgolez10/assignhooks
The package can be installed as: pip3 install assignhooks
Example <testmod.py>:
class SampleClass():
name = None
def __assignpre__(self, lhs_name, rhs_name, rhs):
print('PRE: assigning %s = %s' % (lhs_name, rhs_name))
# modify rhs if needed before assignment
if rhs.name is None:
rhs.name = lhs_name
return rhs
def __assignpost__(self, lhs_name, rhs_name):
print('POST: lhs', self)
print('POST: assigning %s = %s' % (lhs_name, rhs_name))
def myfunc():
b = SampleClass()
c = b
print('b.name', b.name)
to instrument it, e.g. <test.py>
import assignhooks
assignhooks.instrument.start() # instrument from now on
import testmod
assignhooks.instrument.stop() # stop instrumenting
# ... other imports and code bellow ...
testmod.myfunc()
Will produce:
$ python3 ./test.py
POST: lhs <testmod.SampleClass object at 0x1041dcc70>
POST: assigning b = SampleClass
PRE: assigning c = b
POST: lhs <testmod.SampleClass object at 0x1041dcc70>
POST: assigning c = b
b.name b
Beginning Python 3.8, it is possible to hint that a value is read-only using typing.Final. What this means is that nothing changes at runtime, allowing anyone to change the value, but if you're using any linter that can read type-hints then it's going to warn the user if they attempt to assign it.
from typing import Final
x: Final[int] = 3
x = 5 # Cannot assign to final name "x" (mypy)
This makes for way cleaner code, but it puts full trust in the user to respect it at runtime, making no attempt to stop users from changing values.
Another common pattern is to expose functions instead of module constants, like sys.getrecursionlimit and sys.setrecursionlimit.
def get_x() -> int:
return 3
Although users can do module.get_x = my_get_x, there's an obvious attempt on the user's part to break it, which can't be fixed. In this way we can prevent people from "accidentally" changing values in our module with minimal complexity.
A ugly solution is to reassign on destructor. But it's no real overload assignment.
import copy
global a
class MyClass():
def __init__(self):
a = 1000
# ...
def __del__(self):
a = copy.copy(self)
a = MyClass()
a = 1
How does self.x work if x is not a class bound or instance bound variable? In this case, is there any benefit to use it?
My Analysis:
In general if we execute a.a1 = 5, interpreter will give NameEror: name a is not defined. It means we can't define variable using letter ., as it is used to call attribute of an object.
If I write code as
a = 5
a.a1 = 10
It gives AttributeError: int object has no attribute x. It was expected.
Now look at my below code
class Example:
p = 1
def __init__(self):
self.q = 2
def my_function(self):
self.x = 5
return self.x
obj = Example()
print(obj.my_function())
print(dir(obj), obj.__dict__)
As x is neither a class bound variable nor an instance bound variable. So I assume x is not an attribute of self but it executes successfully without any error. So I am wondering, how does it work. Please explain it, also let me know the use because as per explanation we can use it.
Doing self.x = 5 you simply create a new attribute which you can later modify, just like an ordinary variable ; except you access it through the instance.
At that moment you are declaring and instantiating a new field for the object of type Example. When in other function, there would be reference to self.x, no error would be thrown. When no declaration was made - runtime error would be raised, for example:
class Example:
p = 1
def __init__(self):
self.q = 2
def my_function(self):
self.x = 5
return self.x
def other(self):
return self.x
obj = Example()
print(obj.other())
print(obj.my_function())
This results in AttributeError: 'Example' object has no attribute 'x'.
One needs to bear in mind execution order, so that the field is declared and instantiated before referenced.
This is standard way of defining attribute. Here you can find more about classes and attributes: https://docs.python.org/2/tutorial/classes.html
I'm doing it like:
def set_property(property,value):
def get_property(property):
or
object.property = value
value = object.property
What's the pythonic way to use getters and setters?
Try this: Python Property
The sample code is:
class C(object):
def __init__(self):
self._x = None
#property
def x(self):
"""I'm the 'x' property."""
print("getter of x called")
return self._x
#x.setter
def x(self, value):
print("setter of x called")
self._x = value
#x.deleter
def x(self):
print("deleter of x called")
del self._x
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
What's the pythonic way to use getters and setters?
The "Pythonic" way is not to use "getters" and "setters", but to use plain attributes, like the question demonstrates, and del for deleting (but the names are changed to protect the innocent... builtins):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
If later, you want to modify the setting and getting, you can do so without having to alter user code, by using the property decorator:
class Obj:
"""property demo"""
#
#property # first decorate the getter method
def attribute(self): # This getter method name is *the* name
return self._attribute
#
#attribute.setter # the property decorates with `.setter` now
def attribute(self, value): # name, e.g. "attribute", is the same
self._attribute = value # the "value" name isn't special
#
#attribute.deleter # decorate with `.deleter`
def attribute(self): # again, the method name is the same
del self._attribute
(Each decorator usage copies and updates the prior property object, so note that you should use the same name for each set, get, and delete function/method.)
After defining the above, the original setting, getting, and deleting code is the same:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
You should avoid this:
def set_property(property,value):
def get_property(property):
Firstly, the above doesn't work, because you don't provide an argument for the instance that the property would be set to (usually self), which would be:
class Obj:
def set_property(self, property, value): # don't do this
...
def get_property(self, property): # don't do this either
...
Secondly, this duplicates the purpose of two special methods, __setattr__ and __getattr__.
Thirdly, we also have the setattr and getattr builtin functions.
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
The #property decorator is for creating getters and setters.
For example, we could modify the setting behavior to place restrictions the value being set:
class Protective(object):
#property
def protected_value(self):
return self._protected_value
#protected_value.setter
def protected_value(self, value):
if acceptable(value): # e.g. type or range check
self._protected_value = value
In general, we want to avoid using property and just use direct attributes.
This is what is expected by users of Python. Following the rule of least-surprise, you should try to give your users what they expect unless you have a very compelling reason to the contrary.
Demonstration
For example, say we needed our object's protected attribute to be an integer between 0 and 100 inclusive, and prevent its deletion, with appropriate messages to inform the user of its proper usage:
class Protective(object):
"""protected property demo"""
#
def __init__(self, start_protected_value=0):
self.protected_value = start_protected_value
#
#property
def protected_value(self):
return self._protected_value
#
#protected_value.setter
def protected_value(self, value):
if value != int(value):
raise TypeError("protected_value must be an integer")
if 0 <= value <= 100:
self._protected_value = int(value)
else:
raise ValueError("protected_value must be " +
"between 0 and 100 inclusive")
#
#protected_value.deleter
def protected_value(self):
raise AttributeError("do not delete, protected_value can be set to 0")
(Note that __init__ refers to self.protected_value but the property methods refer to self._protected_value. This is so that __init__ uses the property through the public API, ensuring it is "protected".)
And usage:
>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0
Do the names matter?
Yes they do. .setter and .deleter make copies of the original property. This allows subclasses to properly modify behavior without altering the behavior in the parent.
class Obj:
"""property demo"""
#
#property
def get_only(self):
return self._attribute
#
#get_only.setter
def get_or_set(self, value):
self._attribute = value
#
#get_or_set.deleter
def get_set_or_delete(self):
del self._attribute
Now for this to work, you have to use the respective names:
obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error
I'm not sure where this would be useful, but the use-case is if you want a get, set, and/or delete-only property. Probably best to stick to semantically same property having the same name.
Conclusion
Start with simple attributes.
If you later need functionality around the setting, getting, and deleting, you can add it with the property decorator.
Avoid functions named set_... and get_... - that's what properties are for.
In [1]: class test(object):
def __init__(self):
self.pants = 'pants'
#property
def p(self):
return self.pants
#p.setter
def p(self, value):
self.pants = value * 2
....:
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20
Using #property and #attribute.setter helps you to not only use the "pythonic" way but also to check the validity of attributes both while creating the object and when altering it.
class Person(object):
def __init__(self, p_name=None):
self.name = p_name
#property
def name(self):
return self._name
#name.setter
def name(self, new_name):
if type(new_name) == str: #type checking for name property
self._name = new_name
else:
raise Exception("Invalid value for name")
By this, you actually 'hide' _name attribute from client developers and also perform checks on name property type. Note that by following this approach even during the initiation the setter gets called. So:
p = Person(12)
Will lead to:
Exception: Invalid value for name
But:
>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
This is an old question but the topic is very important and always current. In case anyone wants to go beyond simple getters/setters i have wrote an article about superpowered properties in python with support for slots, observability and reduced boilerplate code.
from objects import properties, self_properties
class Car:
with properties(locals(), 'meta') as meta:
#meta.prop(read_only=True)
def brand(self) -> str:
"""Brand"""
#meta.prop(read_only=True)
def max_speed(self) -> float:
"""Maximum car speed"""
#meta.prop(listener='_on_acceleration')
def speed(self) -> float:
"""Speed of the car"""
return 0 # Default stopped
#meta.prop(listener='_on_off_listener')
def on(self) -> bool:
"""Engine state"""
return False
def __init__(self, brand: str, max_speed: float = 200):
self_properties(self, locals())
def _on_off_listener(self, prop, old, on):
if on:
print(f"{self.brand} Turned on, Runnnnnn")
else:
self._speed = 0
print(f"{self.brand} Turned off.")
def _on_acceleration(self, prop, old, speed):
if self.on:
if speed > self.max_speed:
print(f"{self.brand} {speed}km/h Bang! Engine exploded!")
self.on = False
else:
print(f"{self.brand} New speed: {speed}km/h")
else:
print(f"{self.brand} Car is off, no speed change")
This class can be used like this:
mycar = Car('Ford')
# Car is turned off
for speed in range(0, 300, 50):
mycar.speed = speed
# Car is turned on
mycar.on = True
for speed in range(0, 350, 50):
mycar.speed = speed
This code will produce the following output:
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Turned on, Runnnnnn
Ford New speed: 0km/h
Ford New speed: 50km/h
Ford New speed: 100km/h
Ford New speed: 150km/h
Ford New speed: 200km/h
Ford 250km/h Bang! Engine exploded!
Ford Turned off.
Ford Car is off, no speed change
More info about how and why here: https://mnesarco.github.io/blog/2020/07/23/python-metaprogramming-properties-on-steroids
Properties are pretty useful since you can use them with assignment but then can include validation as well. You can see this code where you use the decorator #property and also #<property_name>.setter to create the methods:
# Python program displaying the use of #property
class AgeSet:
def __init__(self):
self._age = 0
# using property decorator a getter function
#property
def age(self):
print("getter method called")
return self._age
# a setter function
#age.setter
def age(self, a):
if(a < 18):
raise ValueError("Sorry your age is below eligibility criteria")
print("setter method called")
self._age = a
pkj = AgeSet()
pkj.age = int(input("set the age using setter: "))
print(pkj.age)
There are more details in this post I wrote about this as well: https://pythonhowtoprogram.com/how-to-create-getter-setter-class-properties-in-python-3/
You can use accessors/mutators (i.e. #attr.setter and #property) or not, but the most important thing is to be consistent!
If you're using #property to simply access an attribute, e.g.
class myClass:
def __init__(a):
self._a = a
#property
def a(self):
return self._a
use it to access every* attribute! It would be a bad practice to access some attributes using #property and leave some other properties public (i.e. name without an underscore) without an accessor, e.g. do not do
class myClass:
def __init__(a, b):
self.a = a
self.b = b
#property
def a(self):
return self.a
Note that self.b does not have an explicit accessor here even though it's public.
Similarly with setters (or mutators), feel free to use #attribute.setter but be consistent! When you do e.g.
class myClass:
def __init__(a, b):
self.a = a
self.b = b
#a.setter
def a(self, value):
return self.a = value
It's hard for me to guess your intention. On one hand you're saying that both a and b are public (no leading underscore in their names) so I should theoretically be allowed to access/mutate (get/set) both. But then you specify an explicit mutator only for a, which tells me that maybe I should not be able to set b. Since you've provided an explicit mutator I am not sure if the lack of explicit accessor (#property) means I should not be able to access either of those variables or you were simply being frugal in using #property.
*The exception is when you explicitly want to make some variables accessible or mutable but not both or you want to perform some additional logic when accessing or mutating an attribute. This is when I am personally using #property and #attribute.setter (otherwise no explicit acessors/mutators for public attributes).
Lastly, PEP8 and Google Style Guide suggestions:
PEP8, Designing for Inheritance says:
For simple public data attributes, it is best to expose just the attribute name, without complicated accessor/mutator methods. Keep in mind that Python provides an easy path to future enhancement, should you find that a simple data attribute needs to grow functional behavior. In that case, use properties to hide functional implementation behind simple data attribute access syntax.
On the other hand, according to Google Style Guide Python Language Rules/Properties the recommendation is to:
Use properties in new code to access or set data where you would normally have used simple, lightweight accessor or setter methods. Properties should be created with the #property decorator.
The pros of this approach:
Readability is increased by eliminating explicit get and set method calls for simple attribute access. Allows calculations to be lazy. Considered the Pythonic way to maintain the interface of a class. In terms of performance, allowing properties bypasses needing trivial accessor methods when a direct variable access is reasonable. This also allows accessor methods to be added in the future without breaking the interface.
and cons:
Must inherit from object in Python 2. Can hide side-effects much like operator overloading. Can be confusing for subclasses.
You can use the magic methods __getattribute__ and __setattr__.
class MyClass:
def __init__(self, attrvalue):
self.myattr = attrvalue
def __getattribute__(self, attr):
if attr == "myattr":
#Getter for myattr
def __setattr__(self, attr):
if attr == "myattr":
#Setter for myattr
Be aware that __getattr__ and __getattribute__ are not the same. __getattr__ is only invoked when the attribute is not found.
when I learn 'property' of python, To my surprise, the output is not as same as expected.The code illustrated below:
class HideX(object):
def __init__(self,x):
self.x = x
def get_x(self):
return ~self.__x
def set_x(self,x):
assert isinstance(x,int),\
'"x" must be an integer!'
self.__x = ~x
x = property(get_x, set_x)
inst = HideX(20)
#inst.x = 20#
when it executes inst = HideX(20). I think it will call __init__(self,x) so the instruction self.x = xwill be executed. The problem occurs. I think it will not call x = property(get_x, set_x)because self.x is in the body of class (it is in the top of the class).I've always thought
only in the outside of class (as show in #..#)can we access x = property(get_x, set_x) am I wrong? can you understand what I mean?
sovled:
After repeated tests, I found amazedly that when we executeinst = HideX(20), the code x = property(get_x, set_x)
will be called in the first place ,not the 'init(self,x)'.Totally beyond my expectation!!!(In the java ,when we create an instance,the init() of the class will be first called i think ,maybe i am wrong)
(1)Can you give me an explanation of the intrinsic mechanism? I am a green hand,Thanks for your patience.
the code below is the Segment I copy from :
class HideXX(object):
def __init__(self, x):
self.x = x
#property
def x():
def fget(self):
return ~self.__x
def fset(self,x):
assert isinstance(x,int),\
'"x" must be an integer!'
self.__x = ~x
return locals()
#x = property(**x())
inst = HideXX(1)
But it can not run correctly
the error code is :
File "<string>", line 21, in <module>
File "<string>", line 4, in __init__
AttributeError: can't set attribute
(2)Is the book wrong ?? When I removed #property and add the code 'x = property(**x())' It works!!!
can you explain the reason for me ? thanks very much
For your first question , the answer is simple, x is an attribute of the class (not the object/instance of the class) , it would be evaluated when the class gets defined (not when its object is created).
An Example to show this -
>>> class CA:
... y = print("Hello")
... def __init__(self):
... print("Blah")
...
Hello
>>> c = CA()
Blah
As you can see the value of y gets calculated when the class is defined, its the same with all functions in the class, they get defined when the class gets defined, but they are evaluated only when the function gets called.
Also, using the #property is not same as property(**x()) , when you do the later , **x() resolves to -
{'fget': <function HideXX.x.<locals>.fget at 0x00943B28>, 'fset': <function HideXX.x.<locals>.fset at 0x00943CD8>}
And then these positional arguments are used for setting the getter and setter for the property x , whereas the #property annotation is used to define the getter for property x.
Im trying to understand how the #property decorator works.
Here I have used method y as a property for field x,
After the attribute-self.x has a property, does it mean that we can't set the value explicitly..
I thought the last statement--> c.x = 2 will not work once you have the property method set on a variable?
class C(object):
def __init__(self):
self.x = 0
self.list = [1,2,3,4,10]
#property
def y(self):
print 'getting'
self.x = sum(self.list)
return self.x
#y.setter
def y(self, value):
print 'setting'
self.x = value
if __name__ == '__main__':
c = C()
print 'Value of c.y=',c.y
print '-'*80
c.y = 50
print '-'*80
print c.y
print '-'*80
if c.y >5:
print 'Hi'
You can always set x explicitly.
class Foo(object):
def __init__(self):
self.x = 1
self.lst = [1,2,3]
#property
def y(self):
self.x = sum(self.lst)
return self.x
#y.setter
def y(self,value):
self.x = value
f = Foo()
print f.y #6
print f.x #6
f.x = 3
print f.x #3
print f.y #6
print f.x #6
The problem is that in this example, calling the getter (y) also sets the value of the x attribute, so you'll never see the change of x if you're doing all of the changing via y because the act of looking at y changes the value of x.
One way that you might try to get around that limitation is:
class Foo(object):
def __init__(self):
self.x = None
self.lst = [1,2,3]
#property
def y(self):
return sum(self.lst) if self.x is None else self.x
#y.setter
def y(self,value):
self.x = value
Now if you explicitly set a value for x (or y), that value will stick until you set it back to None which you could even do in another function decorated with #y.deleter if you really wanted.
There is limited support for private instance variables in Python via name-mangling
to avoid exposing x, you need two leading underscores, i.e. __x
You cant prohibit to change attribute directly using property decorator but You can do such a trick I think
class A(object):
def __init__(self):
self.x = 0
#property
def x(self):
return self.__dict__['x']
#x.setter
def x(self, value):
self.__dict__['x']=value
this will allow You to implement behavior like You have described
Python does not provide any capability for preventing callers from accessing variables. In other words, there is no "private" in Python. By convention, a variable or method prefixed with an underscore is not intended for external use. E.g.,
class C(object):
def __init__(self):
self._x = 0
self.list = [1,2,3,4,10]
.
.
.
I can still access _x if I really want to, and nothing prevents me from setting it.
>>> c = C()
>>> c._x
10
>>> c._x = 20
>>> c._x
20
However, by convention, the underscore tells me I'm doing something dangerous and ill advised. It's up to me, the programmer, to determine if I broke anything by doing it.
This is a conscious design decision made when creating Python. The idea is that whoever uses your class is responsible for what they do with it; if they misuse it and it breaks, that's their fault. You warned them with the underscore. I think the notion that a clever programmer can get around your attempts to lock them out anyway may have played a role in the decision (such as reflection libraries or interacting with the compiled bytecode directly), but don't hold me to that.
On a mildly related note, the underscore does actually do something if the variable (including other imported modules, functions, etc.) is a member of a module. Members beginning with an underscore are not imported by import *. E.g.,
a.py
_a = 10
b = 50
Command prompt:
>>> from a import *
>>> b
50
>>> _a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_a' is not defined
In your particular example, x and its setter are relatively useless since you're overriding its value any time the getter is called.
class Foo(object):
def init(self):
self.x = None
self.lst = [1,2,3]
#property
def y(self):
return sum(self.lst) if self.x is None else self.x
#y.setter
def y(self,value):
self.x = value