Avoid class instances to overwrite default values [duplicate] - python

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Python constructor and default value [duplicate]
(4 answers)
Closed 3 years ago.
I am dealing with issues regarding initialization of classes. Here is a reproducing example highlighting the underlying issue (I did not manage to make it much smaller):
Classes
We have two classes A,B. The hierarchy goes
A: First class - instances have two attributes link;name
B: This class has an A-instance as one of its attributes.
In order to faster declare instances of B I considered the following: B(name=name) which behind the curtains does B(a=A(name=name)).
Here is the code
class A(object):
def __init__(self, link='', name=''):
self.name = name
self.link = link
class B(object):
def __init__(self, a=A(), value=0, link='', name=''):
# Two ways to initialize "self.a":
# 1. If "name" or "link" provided
# 2. With "a" provided
if {name, link}.difference({''}):
self.a = A(name=name, link=link)
else:
self.a = a
# Rest
self.value = value
def __str__(self):
return 'Instance:\nname:\t{}\nlink:\t{}\nvalue:\t{}'.format(self.a.name, self.a.link, self.value)
Test
Here are two small tests:
The first one defines two instances b1;b2 using __init__.
The second one defines the instances as empty (B()) and then proceeds to populate/overwrite the fields.
As one can see below, the second option is problematic as it overwrites the classe's default values.
# 1. Set it all inside init
b1 = B(link=link, name=name, value=value)
b2 = B(value=value)
print(b1)
# Instance:
# name: world
# link: hello
# value: 1
print(b2)
# Instance:
# name:
# link:
# value: 1
# NO PROBLEM
# 2. Define empty instances and populate
b1 = B()
b1.a.link = link
b1.a.name = name
b1.value = values[0]
b2 = B()
b2.value = values[1]
print(b1)
# Instance:
# name: world
# link: hello
# value: 1
print(b2)
# Instance:
# name: world
# link: hello
# value: 2
# PROBLEM
How can I guarantee for this problem not to occurr? Should I overwrite the __new__ method, add getters/setter, work with copies (copy.deepcopy), etc. ?

Related

Unexpected behaviour of python list.append() in the context of inheritance

I inherit features from one parent class (__Parent) to two different child classes for constructing a nested data structure. The init() method of ChildTwo initiallizes the parent class using super() thereby setting i_am_from_child to "Two". It then appends an instance of ChildOne to the inherited list var. It is this appending that behaves unexpected when using list.append(). The init() of ChildOne also initializes the parent class in the same way, setting its i_am_from_child to "One", but without appending its inherited list var.
Therfore the list var of the instance of ChildOne stored in ChildTwo's var[0] should have a length of 0 as it is empty. This behaviour is obtained when using numpy append. However pythons list.append() results in an instance of ChildOne beingg strored at that location.
import numpy as np
class __Parent:
var = []
i_am_from_child = None
def __init__(self, which:str=None) -> None:
self.i_am_from_child = which
print(f"Parent initiallized by Child{which}")
def how_long(self) -> int:
return len(self.var)
def from_child(self) -> str:
return self.i_am_from_child
class ChildOne(__Parent):
def __init__(self) -> None:
print("Initiallizing ChildOne")
super().__init__(which="One")
print(f"ChildOne: len(self.var) = {len(self.var)}")
class ChildTwo(__Parent):
def __init__(self) -> None:
print("Initiallizing ChildTwo")
super().__init__(which="Two")
# two options in question
self.var.append(ChildOne()) # this behaves unexpected
#self.var = np.append(self.var, ChildOne()) # this behaves as expected
#####################################################################
X = ChildTwo() # instance of ChildTwo
Y = X.var[0] # copy of ChildOne instance created by ChildTwo constructor
print(f"type(X) = {type(X)} and type(Y) = {type(Y)}")
print(f"X is from Child{X.from_child()} and Y=X.var[0] is from Child{Y.from_child()}")
# something weird with var happens when using diffent append methods
print()
print(f"ChildOne: self.function() = {Y.how_long()} should be 0")
print(f"ChildTwo: self.function() = {X.how_long()} should be 1")
print(f"Type of Y.var[0] is {type(Y.var[0])}")
Using print() I checked the correct sequence of method calles, additionally the types are correct. But Y.var[0] should be empty, i.e. [] and thus should have length zero. Commenting out the python append and uncommenting the numpy append statements in ChildTwo.init() produces the desired behaviour.
It's because you're declaring var as a class variable, which is shared between all instances of that class, rather than as an instance variable. When you type self.var, python first looks in the instance attribute dictionary, and if an attribute with the name var doesn't appear there, it looks in the instance's class dictionary, where it finds the shared var attribute. If you want each instance to treat them separately, you need to assign that variable in the __init__ method.
class Parent:
def __init__(self):
self.var = []

Class variables: "class list" vs "class boolean" [duplicate]

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 6 years ago.
I don't understand the difference in the following example. One time an instance of a class can CHANGE the class variable of another instance and the other time it can't?
Example 1:
class MyClass(object):
mylist = []
def add(self):
self.mylist.append(1)
x = MyClass()
y = MyClass()
x.add()
print "x's mylist: ",x.mylist
print "y's mylist: ",y.mylist
Output:
x's mylist: [1]
y's mylist: [1]
So here an instance x of class A was able to access and modify the class attribute mylist which is also an attribute of the instance y of A.
Example 2:
class MyBank(object):
crisis = False
def bankrupt(self) :
self.crisis = True
bankX = MyBank()
bankY = MyBank()
bankX.bankrupt()
print "bankX's crisis: ",bankX.crisis
print "bankY's crisis: ",bankY.crisis
bankX's crisis: True
bankY's crisis: False
Why does this not work in this example?
In first case there is no assignment in add method:
def add(self):
self.mylist.append(1) # NOT self.mylist = something
In second case there is an assignment:
def bankrupt(self) :
self.crisis = True # self.crisis = something
When an attribute is set on instance, it is always set on particular instance only (it's put to instance's __dict__ atribute). Class __dict__ is unaffected.
In first case there is no assignment, so standard look-up rules apply. Since there is no "mylist" in __dict__ attribute of instance, it falls back to class __dict__.
Operation performed in add mutates value stored in MyClass.__dict__. That's why change is observable in all instances.
Consider following snippet (it may explain your issue better):
class MyClass:
x = []
x1 = MyClass()
x2 = MyClass()
x3 = MyClass()
x1.x.append(1)
print x1.x # [1]
print x2.x # [1]
print x3.x # [1]
assert x1.x is x2.x is x3.x
x3.x = "new" # now x3.x no longer refers to class attribute
print x1.x # [1]
print x2.x # [1]
print x3.x # "new"
assert x1.x is x3.x # no longer True!

Adding element to dictionary inside object inside dictionary [duplicate]

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 6 years ago.
I have a dictionary with 100 Cluster objects, the clusters have several Member objects and I need to add them to the Cluster they belong, my problem is that every Member es being added to every Cluster, and I can't find out why. Here's the code
self.clusters = {}
with open('/tmp/numpy_dumps/kmeansInput.txt.cluster_centres') as f:
for line in f:
cluster = Cluster(line)
self.clusters[cluster.id] = cluster
with open('/tmp/numpy_dumps/kmeansInput.txt.membership') as f:
for line in f:
member = Member(line, self.reps)
self.clusters[member.clusterId].members[member.imageId] = member
for id, cluster in self.clusters.items():
print(cluster)
print(cluster.members)
print('cluster {} has {} members'.format(id, len(cluster.members)))
The output tells me that every cluster has all the members
The problem is very certainly in the Cluster class, that you did'nt post in your snippet. It's a bit of a wild guess but this behaviour is typical of shared attributes, either class attributes or mutable default arguments. If your Cluster class looks like one of the snippets below then looks no further:
# class attributes:
class Cluster(object):
members = {} # this will be shared by all instances
# solution:
class Cluster(object):
def __init__(self):
self.members = {} # this will be per instance
# default mutable argument:
class Cluster(object):
def __init__(self, members={}):
# this one is well known gotcha:
# the default for the `members` arg is eval'd only once
# so all instances created without an explicit
# `members` arg will share the same `members` dict
self.members = members
# solution:
class Cluster(object):
def __init__(self, members=None):
if members is None:
members = {}
self.members = members

Python Static Class attributes [duplicate]

This question already has answers here:
Class (static) variables and methods
(27 answers)
Closed 8 years ago.
Is it possible in Python make a static attribute of class which will be same for all instances (objects) of that class, they all will use same reference to that attribute without creating their own attributes.
For example :
class myClass:
__a = 0
def __init__(self, b, c):
self.b = b
self.c = c
def increase_A(self):
self.__a += 1
return
def get_A(self):
return self.__a
So if I have
myObject1 = myClass(1,2)
myObject2 = myClass(3,4)
myObject2.increase_A()
print myObject1.get_A()
will show one and not zero, couse they share the same variable ?
To make your code work as it appears you intend, use myClass.__a to access the variable, instead of self.__a.
def increase_A(self):
myClass.__a += 1
return
def get_A(self):
return myClass.__a
They start off as the same variable. However, when you do
self.__a += 1
this rebinds the object's __a to a new object whose value is 1.
It does not change any other object's __a so the code will print out 0.

Python referencing instance list variable

I just started to learn Python and I"m struggling a little with instance variables. So I create an instance variable in a method that's of a list type. Later on, I want to call and display that variable's contents. However, I'm having issues doing that. I read some online, but I still can't get it to work. I was thinking of something along the following (this is a simplified version):
What would the proper way of doing this be?
class A:
def _init_(self):
self.listVar = [B("1","2","3"), B("1","2","3")]
def setListVal():
#Is this needed? Likewise a "get" method"?
def randomMethod():
A.listVar[0] #something like that to call/display it right? Or would a for
#for loop style command be needed?
Class B:
def _init_(self):
self.a = ""
self.b = ""
self.c = ""
Is the list something you'll be passing to the instance when you create it (i.e. will it be different each time)?
If so, try this:
class A:
def __init__(self, list):
self.listVar = list
Now, when you instantiate (read: create an instance) of a class, you can pass a list to it and it will be saved as the listVar attribute for that instance.
Example:
>>> first_list = [B("1","2","3"), B("1","2","3")]
>>> second_list = [C("1","2","3"), C("1","2","3")]
>>> first_instance = A(first_list) # Create your first instance and pass it your first_list. Assign it to variable first_instance
>>> first_instance.listVar # Ask for the listVar attribute of your first_instance
[B("1","2","3"), B("1","2","3")] # Receive the list you passed
>>> second_instance = A(second_list) # Create your second instance and pass it your second_list. Assign it to variable second_instance
>>> second_instance.listVar # Ask for the listVar attribute of your second_instance
[C("1","2","3"), C("1","2","3")] # Receive the list you passed second instance
Feel free to ask if anything is not clear.
class A:
def __init__(self):
self.listVar = [B("1","2","3"), B("1","2","3")]
def setListVal(self, val):
self.listVar[0] = val # example of changing the first entry
def randomMethod(self):
print self.listVar[0].a # prints 'a' from the first entry in the list
class B:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
I made several changes. You need to use self as the first argument to all the methods. That argument is the way that you reference all the instance variables. The initialization function is __init__ note that is 2 underscores before and after. You are passing three arguments to initialize B, so you need to have 3 arguments in addition to self.

Categories

Resources