About python class __init__ and Decorators - python

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.

Related

Is there a way to access an attribute outside a class?

I want to access an attribute outside a class, but even after googling I don't see any similar solution for this particular problem.
class test():
def __init__(self) -> None:
pass
def testpy(self):
self.x = 'Hello world'
value = test().testpy.x
print(value)
>> AttributeError: 'function' object has no attribute 'x'
I think you are confused with what an attribute is.
In your code X is a local variable in the member function testpy. X exists only in that function, and is deleted upon function exit.
If you want a member, write self.x = 'Hello world'. This will create and store a data member called x. Additionally, when you write test().testpy().x you are calling the member function testpy() and are then attempting to call .x on the return result, which will obviously break.
I think you're confusing local scope, with class attributes, and global scope.
You can achieve your result in one of three ways:
Creating a class attribute to access
Using the return keyword to return local values
Using the global keyworld to create a globally scoped variable. Highly not recommended
class test:
def __init__(self) -> None:
self.x = "Hello world"
pass
def testpy(self):
self.x = "Hello world"
x = "Hello world"
global Y
Y = "Hello world - global"
return x
object = test()
value_via_objects_attribute = object.x
value_via_objects_method = object.testpy()
value_via_global = Y
print(value_via_objects_attribute)
print(value_via_objects_method)
print(value_via_global)
In your code, x is a local variable inside a function, not a class attribute. You can return that variable:
class test():
def __init__(self) -> None:
pass
def testpy(self):
x = 'Hello world'
return x
value = test().testpy()
print(value)
Please go through python class tutorial once more.
testpy is a function of class Test, it returns None in the above code. x is an attibute of class Test not function 'testpy'
class Test():
def __init__(self):
pass
def testpy(self):
self.x = 'Hello world'
t = Test()
t.testpy()
value = t.x
print(value)
I think you are confusing your testpy() function with your constructor - the init method. In python, you can initialize a new object by calling the class as if it were a function - ex, "value = test()". When you call this function, the init method gets called, and returns a new object (self). So to declare an attribute on self, in the init function (your constructor), you simply write "self.x = "Hello World".
The full solution to your problem is below.
class test():
def __init__(self):
self.x = 'Hello World'
value = test()
print(value.x)

Can some one explain how x pass into function f

def f(obj):
print('attr =', obj.attr)
class Foo:
attr = 100
attr_val = f
x = Foo()
print(x.attr)
x.attr_val()
Output:
100
attr = 100
I got this code from real python but I don't understand how x is pass into function f.
Can someone explain that to me, thanks.
x is a class object when you are doing x.attr_val() it automatically takes itself and provides it as a first argument to the function (often arguments like this are named self).
attr_val is what is called an instance method. When your Foo class calls it, it passes the object as first argument automatically, effectively running: f(x)
If you were using a custom __init__ method, the standard practice would be to pass the self variable to indicate this self-reference.
Thus, a more verbose variant would be:
def f(obj):
print('attr =', obj.attr)
class Foo:
def __init__(self):
self.attr = 100
def attr_val(self):
f(self) # or "return f(self)"
x = Foo()
x.attr_val()
# attr = 100

Python Variables across Class functions - how to call them?

Instead of using a global variable, I'm trying to make an instance of a variable in a class, as it seems to be best practice. How do I reference this variable across other functions in the class? I would have thought that Test.running_sum would work or at least running_sum in test_function, but I'm not having any luck with either. Thanks very much!
class Test:
def __init__(self):
self.root = None
running_sum = 0
def test_function(self):
print(Test.running_sum)
return
x = Test()
x.test_function()
Error:
Traceback (most recent call last):
File "so.py", line 1, in <module>
class Test:
File "so.py", line 10, in Test
x = Test()
NameError: name 'Test' is not defined
Use self parameter provided in the method signature.
Note that what you wrote is not a method, but an external function using class Test. To write a method of Test, the def should be at one level of indentation inside class Test as following:
class Test:
def __init__(self):
self.running_sum = 0
def test_function(self):
print(self.running_sum)
There are several things to add if you want an explanation behind this "best practice".
Assuming you write the following code:
class Test:
numbers = []
def add(self, value):
self.numbers.append(value)
The Test.numbers list is instantiated once and shared accross all instances of Test. Therefore, if 2 different instances add to the list, both act on the same list:
a = Test()
b = Test()
a.add(5)
b.add(10)
assert a.numbers == b.numbers == Test.numbers
When creating instance variables in the __init__ function, __init__ will be run at each instantiation, and therefore, the list will no longer be shared because they will be created for each individual instances.
class Test:
def __init__(self):
self.numbers = []
def add(self, number):
self.numbers.append(number)
a = Test()
b = Test()
a.add(5)
b.add(10)
assert a != b
As an object attribute: each object gets its own.
Test is the class; self is the Test object that invoked the method.
class Test:
def __init__(self):
self.root = None
self.running_sum = 0
def test_function(self):
self.running_sum += 1
print(self.running_sum)
return
x = Test()
y = Test()
x.test_function()
y.test_function()
Output:
1
1
As a class attribute: all objects share the same variable.
self.__class__ is the class of the invoking object (i.e. Test).
class Test:
running_sum = 0
def __init__(self):
self.root = None
def test_function(self):
self.__class__.running_sum += 1
print(self.__class__.running_sum)
return
x = Test()
y = Test()
x.test_function()
y.test_function()
Output:
1
2
how do I reference this variable across other functions in the class
Several things I see wrong here. First of all, you are calling running_sum on the class itself which doesn't make sense since you are declaring running_sum as an attribute of an instance of Test. Second, from the way you formatted your question, it seems that test_function is outside of the class Test which doesn't make sense since you are passing self to it, implying it is an instance method. To resolve you could do this:
class Test:
def __init__(self):
self.running_sum = 0
def test_function(self):
print(self.running_sum)
Then again this also is weird... Why would you need a "test_function" when you can simply test the value of running_sum by simply doing:
x = Test()
x.running_sum
In your __init__ function, you've created a local variable. That variable will no longer exist after the function has completed.
If you want to create a variable specific to the object x then you should create a self.running_sum variable
class Test:
def __init__(self):
self.root = None
self.running_sum = 0
def test_function(self):
print(self.running_sum)
If you want to create a variable specific to the class Test then you should create a Test.running_sum variable.
class Test:
running_sum = 0
def __init__(self):
self.root = None
def test_function(self):
print(Test.running_sum)

Wanted to know real use of property() in python

I am new and trying property() in python. I saw an example and tried to execute that but I am receiving an error. I tried the code below:
class proper(object):
def __init__(self):
self.x = 4
def setx(self,val):
self.x = val
def getx(self):
return self.x
def delx(self):
del self.x
p = property(setx,getx,delx,'i am doc')
pr = proper()
pr.setx(7)
print pr.getx()
error:
Traceback (most recent call last):
File "./test3.py", line 148, in <module>
pr.p=5
TypeError: getx() takes exactly 1 argument (2 given)
I have no idea what line 148 is - but it doesn't refer to the code you posted.
The whole point of using property is that you can define methods which make a data item look like an attribute.
With your class definition as posted you can now do :
pr = proper()
pr.x = 7 # No need to call pr.setx
print pr.x # no need to call pr.sety
This is a very basic and redundant use of properties.
But say you define setx as :
def setx( self, val):
if val < 0:
raise ValueError("'x' cannot be negative")
self.x = val
You now have a special attribute x which can never be negative. Note I have not been able to test this, as I don't have access to a Python implementation.
The error you are getting is because you have created your p property with its arguments in the wrong order.
Try:
p = property(getx, setx, delx, 'i am doc') # getter before setter!
The error was happening because getx was being called when setx should have been. Because they take different numbers of arguments, this was breaking things.
Note that a more elegant way of setting up a property is to use decorators as you define the accessor methods:
#property
def p(self): # formerly getx
'i am doc'
return self.x
#p.setter
def p(self, value): # formerly setx
self.x = value
#p.deleter
def p(self): # formerly delx
del self.x
You are misunderstanding how properties are meant to be accessed.
Adapted from the Python documentation for property:
If then pr is an instance of proper, pr.x will invoke getx, pr.x = setx will invoke the setter and del pr.x the deleter, delx.
Therefore, you aren't supposed to call it as pr.setx(7).

Using python property and still able to set the values explicitliy

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

Categories

Resources