The code looks like:
class A(object):
x = 0
y = 0
z = []
def __init__(self):
super(A, self).__init__()
if not self.y:
self.y = self.x
if not self.z:
self.z.append(self.x)
class B(A):
x = 1
class C(A):
x = 2
print C().y, C().z
print B().y, B().z
The output is
2 [2]
1 [2]
Why is z overwritten but not y? Is it because it's not a immutable type? I looked on python's documentation and didn't find an explanation.
Yes, it's because one is immutable and one isn't. Or rather, it's that you are mutating one and not the other. (What matters isn't whether the object "is mutable", what matters is whether you actually mutate it.) It's also because you're using class variables instead of instance variables (see this question).
In your class definition, you create three class variables, shared among all instances of the class. After creating an instance of your class, if you do self.x on that instance, it will not find x as an attribute on the instance, but will look it up on the class. Likewise self.z will look up on the class and find the one on the class. Note that because you made z a class variable, there is only one list that is shared among all instances of the class (including all instances of all subclasses, unless they override z).
When you do self.y = self.x, you create a new attribute, an instance attribute, on only the instance.
However, when you do self.z.append(...), you do not create a new instance variable. Rather, self.z looks up the list stored on the class, and then append mutates that list. There is no "overwriting". There is only one list, and when you do the append you are changing its contents. (Only one item is appended, because you have if not self.z, so after you append one, that is false and subsequent calls do not append anything more.)
The upshot is that reading the value of an attribute is not the same as assigning to it. When you read the value of self.x, you may be retrieving a value that is stored on the class and shared among all instances. However, if you assign a value to self.x, you are always assigning to an instance attribute; if there is already a class attribute with the same name, your instance attribute will hide that.
The issue is that x and y are immutable, while z is mutable, and you mutate it.
self.z.append() does not replace z, it just adds an item to z.
After you run C() in print C().y, C().z (which is creating two different C objects), self.z no longer evaluates to False because it is no longer empty.
If you reverse your two print lines, you'll find the output is
1 [1]
2 [1]
When Python evaluates the body of class A it instantiates a single list object and assigns it to z. Since the subclasses don't override it, and since list objects are stored by reference in Python, all three classes share the same z list, so the first one you instantiate gets to populate z and then the rest just get whatever was put there. Although you changed the contents of the list, you did not change which list z refers to.
This does not affect y because you're assigning the integer directly into the object's internal dictionary, replacing the previous value.
To fix this, create your array inside the constructor, thus assigning:
class A(object):
x = 0
y = 0
z = None
def __init__(self):
super(A, self).__init__()
if not self.y:
self.y = self.x
if not self.z:
# create a new list
z = [self.x]
class B(A):
x = 1
class C(A):
x = 2
print C().y, C().z
print B().y, B().z
Related
I'm new to Python (coming from C++), and understand that roughly speaking, all variables (names) are references to Python objects. Some of these objects are mutable (lists), while others aren't (tuples, although you can change its elements if they themselves are mutable).
For mutable objects, I can modify them by accessing their modifier functions (such as .append()) through the name(s) they're bound to. For example:
myList = [1,2,3,4]
myList.append(5)
However, I know that simply assigning myList to a second list just instantiates this second list and reassigns myList to it; The original list [1,2,3,4] still exists, until garbage collection cleans it up (or not if another name is assigned to it).
MY QUESTION:
Lets say I have a Point class:
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
p1 = Point(1,1)
p1.x = 2
p1.y = 2
How can I replace p1.x = 2 and p1.y = 2 with a single command that just assigns my Point(1,1) object to a Point(2,2) object? Clearly, p1 = Point(2,2) doesn't work as this just reassigns the p1 name to a new and different Point(2,2) object (which is not what I need!).
Is there a built-in way to do this or do I need to define an additional modifier function in Point:
def changePoint(self, newPoint):
self.x = newPoint.x
self.y = newPoint.y
in order to do this in a single command (i.e. via p1.changePoint(Point(2,2)))? In C++ you can often just use a class' implicitly defined overloaded assignment operator (operator=) and accomplish this in a single command:
SimpleNameClass* objectPtr = new SimpleNameClass("Bob");
//Dereferencing objectPtr and assigning new object:
*objectPtr = SimpleNameClass("Jim");
//Now objectPtr still points to (references) the same address in memory,
//but the underlying object is completely different.
Overall, it seems tedious to have to change every attribute individually when I want to transform my object into a new one, especially if my object contains many attributes!
EDIT:
Adding to Jainil's answer, it turns out I don't even need to change the definition of init at all, I can just use the above version. Then, you can transform a Point object to another one with a single command, like so:
p1.__init__(2,2) #Replaces p1.x = 2, p1.y = 2
It works since the original init takes to 2 args. So a standard, vanilla init method basically already enables changing the underlying object, in addition to instantiating it (at least in this case). Yipee.
one way would be to assign using tuple unpacking:
p1.x, p1.y = 2, 2
or you could implement a setter method in your class:
def set_xy(self, x, y):
self.x, self.y = x, y
but creating a new instance (for a class this simple) may make more sense:
p1 = Point(2, 2)
in python you can not override the assignment operator =.
class Point:
def __init__(self, *args):
if(len(args)==2):
self.x = args[0]
self.y = args[1]
elif(len(args)==1):
self.x=args[0].x
self.y=args[0].y
p1 = Point(1,1)
p1.x = 2
p1.y = 2
p1.__init__(Point(3,3))
print(p1.x," ",p1.y)
it is just what you want , but in python way.
in python = can't be overloaded and it is not an operator in python, it is delimeter in python. see https://docs.python.org/3/reference/lexical_analysis.html#delimiters
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def change_point(self, new_x, new_y):
self.x = new_x
self.y = new_y
I'm not sure this is necessarily encouraged, but you can directly modify the __dict__ attribute of the object to modify it. That leads to a solution like:
def assign_to(obj_one, obj_two) -> None:
fields = obj_one.__dict__ # Grab the field/value dictionary of the object
for field_name, field_value in fields.items():
obj_two.__dict__[field_name] = field_value # Loop over obj_one's fields and assign them to obj_two
Then its use:
p1 = Point(1, 2)
p2 = Point(8, 9)
assign_to(p1, p2)
p2.x, p2.y # To show that they were modified
# Prints (1, 2)
id(p1), id(p2) # To show that they're both still distinct objects
# Prints (69029648, 69029296)
This may have drawbacks though, as honestly I've never played around with __dict__ before. You may want to do further research into it before relying on it too heavily.
I'd honestly just write a custom assigning function as the other answers show. Writing an extra line per field shouldn't be too big of a deal; especially given most classes likely won't need such functionality anyways. You're likely just going to be copying PODs like this.
This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 4 years ago.
I created a class to store some variables and dictionary. Each object will have its own dictionary. However when I created a Class in certain way, it resulted in dictionary getting shared across all objects created.
When I tweaked the init, I was able to achieve what I wanted. I want to know why dictionary got shared across different objects and when and I would that be useful.
Snippet 1: (Where dictionary gets populated and shared across all object instances)
class A(object):
def __init__(self, x, y={}):
self.x = x
self.y = y
def set_y(self,key,value):
self.y[key] = value
Snippet 2:(Where dictionary value is unique and not shared between member instances)
class A(object):
def __init__(self,x):
self.x = x
self.y = {}
def set_y(self,key,value):
self.y[key] = value
Test Driver:
l = "abcdefghijklmnopqrsqtuwxyz"
letter_list = []
node = None
for count, letter in enumerate(l,1):
if node:
letter_list.append(node)
node = A(letter)
node.set_y(letter,count)
I would like to know why dictionary got updated for all instances in first case and not for the second case
The dictionary is updated because of the way you used the default value in the __init__ constructor. In the first case, that empty dict is a single object; it is not a unique constructor for each new object. It gets evaluated when the class is defined, and the same dict object sits there for each new object instantiated. Very simply, the line
def __init__(self, x, y={}):
is executed once, when the function is defined, during the class definition.
In your second case, the initialization self.y = {} is in the body of the function, and so gets executed each time you instantiate a new object. You can see the longer explanation in this canonical posting on the topic.
I have already read many answers here on Stack Exchange like Python - why use "self" in a class?
After reading these answers, I understand that instance variables are unique to each instance of the class while class variables are shared across all instances.
While playing around, I found that this code which gives the output [1]:
class A:
x = []
def add(self):
self.x.append(1)
x = A()
y = A()
x.add()
print "Y's x: ", y.x
However, this code gives 10 as the output, when in my opinion it should be 11:
class A:
x = 10
def add(self):
self.x += 1
x = A()
y = A()
x.add()
print "Y's x: ", y.x
Why A class variable is not updated when I run x.add()? I am not very experienced in programming, so please excuse me.
Class variables are shadowed by instance attribute. This means that when looking up an attribute, Python first looks in the instance, then in the class. Furthermore, setting a variable on an object (e.g. self) always creates an instance variable - it never changes the class variable.
This means that when, in your second example you do:
self.x += 1
which is (in this case, see footnote) equivalent to:
self.x = self.x + 1
what Python does is:
Look up self.x. At that point, self doesn't have the instance attribute x, so the class attribute A.x is found, with the value 10.
The RHS is evaluated, giving the result 11.
This result is assigned to a new instance attribute x of self.
So below that, when you look up x.x, you get this new instance attribute that was created in add(). When looking up y.x, you still get the class attribute. To change the class attribute, you'd have to use A.x += 1 explicitly – the lookup only happens when reading the value of an attribute.
Your first example is a classical gotcha and the reason you shouldn't use class attributes as "default" values for instance attributes. When you call:
self.x.append(1)
there is no assignment to self.x taking place. (Changing the contents of a mutable object, like a list, is not the same as assignment.) Thus, no new instance attribute is added to x that would shadow it, and looking up x.x and y.x later on gives you the same list from the class attribute.
Note: In Python, x += y is not always equivalent to x = x + y. Python allows you to override the in-place operators separately from the normal ones for a type. This mostly makes sense for mutable objects, where the in-place version will directly change the contents without a reassignment of the LHS of the expression. However, immutable objects (such as numbers in your second example) do not override in-place operators. In that case, the statement does get evaluated as a regular addition and a reassignment, explaining the behaviour you see.
(I lifted the above from this SO answer, see there for more details.)
I am developing a simple application which hava a file Constants.py containing all configuration, it is like this
x = y
during execution of program , the value of y changes , I want value of x o get updated too , automatically, this can be reffered as binding, how can I achieve this
In Python variable names point at values. x=y tells Python that the variable name x should point at the value that y is currently pointing at.
When you change y, then the variable name y points at a new value, while the variable name x still points at the old value.
You can not achieve what you want with plain variable names.
I like KennyTM's suggestion to define x as a function since it makes explicit that the value of x requires running some code (the lookup of the value of y).
However, if you want to maintain a uniform syntax (making all the constants accessible in the same way), then you could use a class with properties (attributes which call getter and setter functions):
Constants.py:
class BunchOConstants(object):
def __init__(self, **kwds):
self.__dict__.update(kwds)
#property
def x(self):
return self.y
#x.setter
def x(self,val):
self.y=val
const=BunchOConstants(y=10,z='foo')
Your script.py:
import Constants
const=Constants.const
print(const.y)
# 10
print(const.x)
# 10
Here you change the "constant" y:
const.y='bar'
And the "constant" x is changed too:
print(const.x)
# bar
You can change x also,
const.x='foo'
and y too gets changed:
print(const.y)
# foo
If you change the value (object) itself, then all references to it will be updated:
>>> a = []
>>> b = a # b refers to the same object a is refering right now
>>> a.append('foo')
>>> print b
['foo']
However, if you make the name point to some other object, then other names will still reference whatever they were referencing before:
>>> a = 15
>>> print b
['foo']
That's how python works. Names are just references to objects. You can make a name reference the same object another name is referencing, but you can't make a name reference another name. Name attribution using the = operator (a = 15) changes what a refers to, so it can't affect other names.
if your configuration values are inside a class, you could do something like this:
>>> class A(object):
... a = 4
... #property
... def b(self):
... return self.a
...
then, every time you access b, it will return the value of a.
There is a simple solution you can do. Just define a property and ask for the fget value you defined.
For example:
a = 7
#property
def b():
return a
if you ask for b, you will get something like this <property object at 0x1150418> but if you do b.fget(), you will obtain the value 7
Now try this:
a = 9
b.fget() # this will give you 9. The current value of a
You don't need to have a class with this way, otherwise, I think you will need it.
This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 15 days ago.
I created a class:
class A:
aList = []
now I have function that instantiate this class and add items into the aList.
note: there are 2 items
for item in items:
a = A();
a.aList.append(item);
I find that the first A and the second A object has the same number of items in their aList.
I would expect that the first A object will have the first item in its list and the second A object will have the second item in its aList.
Can anyone explain how this happens ?
PS:
I manage to solve this problem by moving the aList inside a constructor :
def __init__(self):
self.aList = [];
but I am still curious about this behavior
You have defined the list as a class attribute.
Class attributes are shared by all instances of your class.
When you define the list in __init__ as self.aList, then the list is an attribute of your instance (self) and then everything works as you expected.
You are confusing class and object variables.
If you want objects:
class A(object):
def __init__(self):
self.aList = []
in your example aList is a class variable, you can compare it with using the 'static' keyword in other languages. The class variable of course is shared over all instances.
This happened because list is a mutable object, and it is created once only when defining the class, that is why it becomes shared when you create two instances. Eg,
class A:
a = 0 #immutable
b = [0] #mutable
a = A()
a.a = 1
a.b[0] = 1
b = A()
print b.a #print 0
print b.b[0] #print 1, affected by object "a"
Therefore, to solve the problem, we can use constructor like what you have mentioned. When we put the list in constructor, whenever the object is instantiated, the new list will also be created.
In Python, variables declared inside the class definition, instead of inside a method, are class or static variables. You may be interested in taking a look at this answer to another question.