python local variables vs self - python

What's the difference between self.x+self.y and x+y in the code below?
class m2:
x, y = 4, 5
def add(self, x, y):
return self.x + self.y
def add2(self, x, y):
return x + y
>>> x = m2()
>>> print "x.add(self.x + self.y = )", x.add(1, 2)
x.add(self.x + self.y = ) 9
>>> print "x.add2(x + y = )", x.add2(1, 2)
x.add2(x + y = ) 3
Why does self.x + self.y return 9 vs x + y returns 3?

In add you are calling the class variables and ignoring the method arguments x and y.
class m2:
# these variables are class variables and are accessed via self.x and self.y
x, y = 4, 5
def add(self, x, y):
return self.x + self.y # refers to 4 and 5
def add2(self, x, y):
return x + y # refers to input arguments x and y, in your case 1 and 2
When defining x and y in the class scope it makes them class variables. They are part of the the class m2 and you don't even need to create an instance of m2 to access them.
print m2.x, m2.y
>> 4, 5
However, you are also able to access them via an instance as if they were instance variables like this:
m = m2()
print m.x, m.y
>> 4, 5
The reason behind this is that the interpreter will look for instance variables with names self.x and self.y, and if they are not found it will default to class variables.
Read more about class attributes in the python documentation.

The difference is that when you use self you refer to the member of the instance of your class
When you use X and Y dirrectly you refer to the parameter that you use in your function
This is a simplification of your class
class m2:
x_member1, y_member2 = 4, 5
def add(self, x_parameter1, y_parameter2 ):
return self.x_member1+ self.y_member2
def add2(self, x_parameter1, y_parameter2 ):
return x_parameter1 + y_parameter2

When a class method is called, the first argument (named self by convention) is set to the class instance. When the method accesses attributes of self, it is accessing those attributes in the class instance, and their values persist in that instance.
On the other hand, if a class method accesses bare variables, those variables are strictly local to those methods and their values do not persist across calls to class methods of that instance.

class m2:
x, y = 4, 5 #This are class attributes
def add(self, x, y ):
return self.x + self.y # This are instance variables
def add2(self, x, y ):
return x + y # This are local variables
Class variables are common to each instance of the class. Instance variables are only avaible to that instance. And local variables are only avaible in the scope of the function.
In add, when you do self.x it's refering to the class variable x cause it's also part of the instance. In add2 it's refering to local variables
The same results could be achieved if those methods were class methods or static methods (With proper adjustments)
Class method:
class m2:
x, y = 4, 5
#classmethod
def add(cls, x, y):
return cls.c + cls.y #Here you're calling class attributes
#classmethod
def add2(cls, x, y):
return x + y
Results:
>>> m.add(1,2)
9
>>> m.add2(1,2)
3
Static method:
class m2:
x, y = 4, 5
#staticmethod
def add(x, y):
return m2.c + m2.y #Here you need to call the class attributes through the class name
#staticmethod
def add2(x, y):
return x + y
Results:
>>> m2.add(1,2)
9
>>> m2.add2(1,2)
3

x and y will be local by default. The self.x and self.y are persisted in that instance, x and y will only be there locally.
class Dog():
def __init__(self):
x = "local"
self.y = "instance"
d = Dog()
print(d.y)
#=> instance
print(d.x)
#=> AttributeError: Dog instance has no attribute 'y'

Related

Do all variables in a class have to start with self. or class.?

If I have intermediate variables that just help with the multiple steps within a method and won't ever be used outside the method, do I still need to prepend self. to the variable? For instance:
class apples:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
self.z = str(self.x + self.y) + " apples"
return self.z
foo = apples(3, 5).add
Does self.z need to start with self. or can it just be defined as z?
No. self. is for variables you'd like to persist on the instance and cls. is for variables you'd like to persist on the class. Variables that should be destroyed when the method ends are just like variables in a function.
In your example, you could just do
def add(self):
z = str(self.x + self.y) + " apples"
return z
But since z is only there to hold the return value, you shouldn't use it at all. This avoids a store followed by a load before return.
class apples:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
return str(self.x + self.y) + " apples"
foo = apples(3, 5).add()

Mock a Python class __init__ partially?

I have a Python class with complicated initialization. I would like to mock the class initialization to avoid writing too much scaffolding code. I want to test its non-mocked method.
A simple example:
class Person:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def do_work(self):
return self.x + self.y
There's an answer which shows how to get it done and it works - https://stackoverflow.com/a/21771920/3346915.
Here's a passing test:
from unittest.mock import patch
with patch.object(Person, '__init__', lambda self: None):
person = Person()
person.x = 3
person.y = 4
assert person.do_work() == 7
I wonder, however, if it would be possible to pass x and y as part of the Person initialization to avoid assigning the fields after the construction to reduce the amount of code?
I wonder if this would be possible?
from unittest.mock import patch
with patch.object(Person, '__init__', lambda self, x, y: None):
person = Person(x=3, y=4)
assert person.do_work() == 7
This doesn't work of course because the x and y values are not assigned to the person instance.
lambdas do not support assignment, but you do not have to use lambda as third argument - normal (named) function will work too, so you can do:
class Person:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def do_work(self):
return self.x + self.y
from unittest.mock import patch
def newinit(self, x, y):
self.x = x
self.y = y
with patch.object(Person, '__init__', newinit):
person = Person(x=3, y=4)
assert person.do_work() == 7
(tested in Python 3.7.3)

Why does this produce the error message AttributeError: 'A' object has no attribute 'f'

class A:
def __init__(self, x, y):
self.x = x
self.y = y
def method(self):
return A(self.x + 1, self.y + 1)
def method2(self, f):
if self.f().x > 3:
return True
a = A(1, 2)
y = a.method2(a.method())
print(y)
The error occurs on the line with
if self.f().x > 3:
I don't understand why it says it has no attribute and not method.
Let's go through the call to a.method2.
You call a.method(). This returns a new instance of A
This new instance is passed to a.method2 as the local variable f
You then attempt to call a.f (through self), but f is an instance
You aren't calling self.<some_function>, you are trying to say that an instance of A has an attribute that is also an instance of A, which is not the case. It's effectively doing:
a.A(a.x+1, a.y+1).x
Which raises your error. Yes, an instance of A has the attribute x, but no instance of A has an attribute matching this new instance. It's not totally clear what you are trying to accomplish. If you are trying to test the value of the attribute on a new instance, you might just do:
class A:
def __init__(self, x
self.y = y
def method(self):
return A(self.x + 1, self.y + 1)
def method2(self):
# just check the value itself
if self.x > 3:
return True
else:
return False
a = A(1, 2)
# b is a new instance, you shouldn't be testing values of b
# via some function in a, because really this function
# exists in all instances, including b
b = a.method()
# so check b.x through the instance b instead
b.method2()
False
The problem relies just on mixing "self." and "f()". What method() returns and method2() should expect is a class object, so you just have to use it exactly as that.
This way it perfectly works:
class A:
def __init__(self, x, y):
self.x = x
self.y = y
def method(self):
return A(self.x + 1, self.y + 1)
def method2(self, f):
return f.x > 3
a = A(1, 2)
y = a.method2(a.method())
print(y)

How can i use a function inside of another function

I am currently playing around with classes and functions since i am not familiar with python and i would like to know how i can get addy(self, addx) to call addx.
class test:
def __init__(self, x):
self.x = x
def addx(self):
y = self.x + 10
return y
def addy(self, addx):
z = addx() + 10
return z
one = test(1)
print(one.addy())
line 15, in print(one.addy()) TypeError: addy() missing 1
required positional argument: 'addx' Process finished with exit code 1
You need to call self from within a class method.
self.addx()
Also the addx parameter on this line shouldn't be there:
def addy(self, addx):
I think this is what you are going for:
class test:
def __init__(self, x):
self.x = x
def addx(self):
y = self.x + 10
return y
def addy(self):
z = self.addx() + 10
return z
one = test(1)
print(one.addy())
You've overcomplicated things by wrapping it in a class. Take it out and it'll work (mostly) the way you expect.
def add10(x):
return x+10
def add20(x):
return add10(add10(x))
Since you've wrapped it in the class you've complicated the namespace. It's no longer called addx or addy, so using those names throws a NameError. You have to use the qualified name instead.
class FooBar():
def __init__(self):
self.x = 10
def addx(self):
return self.x + 10 # Note the `self.` before the attribute...
def addy(self):
return self.addx() + 10 # ...and also before the method name.
Methods are always passed their owning object as a first argument when called, which is why we've got def addx(self): but then call with self.addx()
If you are attempting to relate addx in the signature of addy to the method addx, you can pass the string name of the method and use getattr:
class Test:
def __init__(self, x):
self.x = x
def addx(self):
y = self.x + 10
return y
def addy(self, func):
z = getattr(self, func)() + 10
return z
s = Test(3)
print(s.addy('addx'))

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