Python, mutable object as default argument, is there any way to solve? - python

class A:
def __init__(self, n=[0]):
self.data = n
a = A()
print a.data[0] #print 0
a.data[0] +=1
b = A()
print a.data[0] #print 1, desired output is 0
In the case above, is there any way to provide a default argument with the mutable object (such as list or class) in __init__() class A, but b is not affected by the operation a?

You could try this:
class A:
def __init__(self, n=None):
if n is None:
n = [0]
self.data = n
Which avoids the biggest problem you're facing here, that is, that's the same list for every single object of your type "A."

One possibility is:
class A:
def __init__(self, n=None):
if n is None:
n = [0]
self.data = n

Also:
class A:
def __init__(self, n=[0]):
print id(n)
self.data = n[:]
print id(self.data)
del n
a = A()
print a.data[0] #prints 0
a.data[0] +=1
print a.data[0] #prints 1
print
b = A()
print b.data[0] #prints desired output 0
The principle is that it creates another list. If a long list is passed as argument, there will be two long list in memory. So the inconvenience is that it creates another list.... That's why I delete n.
Don't think it's better, but it may give you comprehension of what happens

Related

When does a tuple assignment behave like a "swap" of the involved vars, and when does it behave like "individual sequential assignments"?

Below, I illustrate with three examples what I mean with "swapping" and "sequential execution of assignment statements".
Example 1 (Swapping)
Tuple assignment can be very handy in order to swap the contents of variables.
The following example shows how we can swap the contents of two elements in an array in a clear an concise way without the need of temporary variables:
a = [1,2]
# prints [1, 2]
print(a)
a[0], a[1] = a[1], a[0]
# prints: [2,1] and not: [2, 2] as with a sequential assignment!
print(a)
The example shows us how the tuple assignment behaves like a swap, instead of a sequential execution of doing the first assignment, and then doing the third assignment.
Example 2 (Swapping)
Here's another example that swaps three integers:
x, y, z = 1, 2, 3
# prints: 1 2 3
print(x, y, z)
# swap contents in variables:
x, y, z = z, y, x
# prints: 3 2 1 and not: 3 2 3 as with a sequential assignment!
print(x, y, z)
Example 3 (Sequential Assigment Statements)
However, once things get more complicated than simple datatypes, the tuple assignment may also behave like a sequential assignment.
Let's consider the following linked list implementation:
class ListNode:
def __init__(self, data=None, next=None):
self.data = data
self.next = next
def __repr__(self):
is_first = True
current = self
l = []
safety_count = 0
while current and safety_count < 10:
if not is_first:
l.append(' -> ')
is_first = False
l.append(str(current.data))
current = current.next
safety_count += 1
return ''.join(l)
def __str__(self):
return self.__repr__()
This function reverses the order of the linked list (and works perfectly fine):
def reverse_list_working(L):
if not L:
return L
pre_current = None
current = L
while True:
tmp_next = current.next
current.next = pre_current
pre_current = current
current = tmp_next
if not current:
break
return pre_current
Now, we might be tempted to get rid of the tmp_ variable through a tuple assignment, to have a swap of the variable's contents:
def reverse_list_not_working(L):
pre_current = None
current = L
while True:
pre_current, current, current.next = current, current.next, pre_current
if not current:
break
return pre_current
However, this implementations gives an error, once we get to the last element. The problem here is that the tuple assignment behaves like a sequential assignment.
assign: pre_current -> current
assign: current -> current.next (which is None at the end of the list)
assign: current.next -> pre_current : yields error, because current is None!
Here's another answer that summarizes in pseudo-code how the tuple assignment works:
The following tuple-assignment:
a, b, c = e1, e2, e3
Is translated into:
e1_ = eval(e1)
e2_ = eval(e2)
e3_ = eval(e3)
a = e1_
b = e2_
c = e3_
So, note that the evaluation of expression e1 might have effects on the evaluation of the expression e2 if e1 changes some shared state that e2 accesses.
Similarly, an assignment to a might affect the assignment of b, if b is dependant on a (e.g., a = c, b = c.next).
Hence, the tuple-assignment is not just a "swap".
Example:
class Num:
def __init__(self, val):
self.val = val
def __add__(self, other):
return Num(self.val + other)
def __str__(self):
return str(self.val)
class Counter:
def __init__(self):
self.val = Num(1)
def get_and_increment(self):
return_val = self.val
self.val += 1
return return_val
c = Counter()
a = Num(1)
a.val = Num(2)
b = a
a.val, b.val = c.get_and_increment(), c.get_and_increment()
print(str(a)) # -> prints 2
print(str(b)) # -> prints 2
If you take the simple example (three integers) and look at the byte code, you see:
...
LOAD_NAME 2 (z)
LOAD_NAME 1 (y)
LOAD_NAME 0 (x)
ROT_THREE
ROT_TWO
STORE_NAME 0 (x)
STORE_NAME 1 (y)
STORE_NAME 2 (z)
...
I.e. the answer is you first get values for the right hand side onto stack (simple case just get the values referred to by variable, but in complex case, that can have broader impact), then you reorder, and then you assign values from stack to corresponding variable on the left hand side.

Counting the occurence of instances of a certain class in list of lists

I have list of lists in which I want to count the number of B() and C() instances and am looking for a suitable method to do this. Using collections.Counter() and the .count() function have resulted in strange results, and I suspect I do not fully understand how list of lists work in python, or how lists of class instances work in python.
This is the list of lists:
lst = [[B() for w in range(x)] for h in range(y)]
with
class A():
def __init__(self, name):
self.name = name
class B(A):
def __init__(self, name = "B"):
A.__init__(self, name)
def update(self):
if random.random() < 0.05:
return C()
else: return self
class C(A):
def __init__(self, name = "C"):
A.__init__(self, name)
And, I use the below code to randomly change B() instances in lst into C() instances:
for row in range(y):
for column in range(x):
lst[row][column] = lst[row][column].update()
How do I count the number of B() and C() instances in the list?
You can use isinstance()
You can check what class an element is with isinstance().
Here is an example:
>>> a = C()
>>> isinstance(a, C)
True
So if you have your list, you can do:
occurrences_of_B = sum(isinstance(i, B) for r in list for i in r)
occurrences_of_C = sum(isinstance(i, C) for r in list for i in r)
you can get the occurrences of the B() and C() classes.
Essentially, we are using a generator comprehension to apply the isinstance() function to every element in the list. We then use sum on the generator as True evaluates to 1 and False to 0, so we will get the total count.
As a side note, although I said it is not good practice to name a list 'array', it is actually worse to name it exactly 'list' as this prevents you from being able to use the list() function! Better would probably be lst or l. :)

Error while defining the class

I'm new to "class" in python. I created a following class in python. The objective of this class is, if pass a list of numbers, if the sum of 2 numbers is 50, it will return me the position of those number in the list.
from itertools import combinations
class numList(object):
def findComb(self):
a = []
b = []
for comb in combinations(self, 2):
a.append(comb)
for i in range(1, len(a)):
if sum(a[i]) == 50:
b.append(a[i])
return b
c = numList()
c.findComb([10,20,10,40,50,60,70])
But I'm getting the following error, when I'm trying to execute it:
TypeError: findComb() takes 1 positional argument but 2 were given
Please let me know where I'm making the mistake.
Thank you!
By design, the first argument of every class function is always a reference to the current instance of the class (always named self).
You are calling findComb with an additional argument when you defined it to only take one (self).
def findComb(self):
...
should be
def findComb(self, myList):
...
All your references to self in your function implementation will need to be updated accordingly to use myList.
Each method within a class takes as positional input the instance of the class itself, unless you add the #staticmethod decorator.
So you are receiving the error because the function findComb receives as input:
the instance (by default)
the list you passed
This should clarify the error you are receiving.
You can fix it in two ways:
Assigning the input list to an attribute of the class and then use the attribute in the function:
class numList(object):
def __init__(self, inp_list):
self.input = inp_list
def findComb(self):
a = []
b = []
for comb in combinations(self.input, 2):
a.append(comb)
for i in range(1, len(a)):
if sum(a[i]) == 50:
b.append(a[i])
return b
c = numList([10,20,10,40,50,60,70])
c.findComb()
Define findComb as a staticmethod, so that it would only use the argument you are passing (without using the instance as first argument):
class numList(object):
#staticmethod
def findComb(inp_list):
a = []
b = []
for comb in combinations(inp_list, 2):
a.append(comb)
for i in range(1, len(a)):
if sum(a[i]) == 50:
b.append(a[i])
return b
c = numList()
c.findComb([10,20,10,40,50,60,70])

Adding up instance variables of a class object

I want to sum up an instance variable of a class object in a list of that class object.
Class A(object):
def __init__(self):
self.a = 20
B = []
for i in range(10):
B.append(A())
# Can this be made more pythonic?
sum = 0
for i in B:
sum += i.a
I was thinking along the lines of using a map function or something? Don't know if that's pushing it too much though. Just curious.
class A(object):
def __init__(self):
self.a = 20
B = []
for i in range(10):
B.append(A())
s = sum(i.a for i in B)
print s
works.
You can use reduce
reduce(lambda acc, c: acc + c, [i.a for i in B])
or sum() with comprehension
sum([i.a for i in B])

Class variable Vs. Instance variable in python for int value

I am new to python and i am not sure how this is working. Code is as below:
class test():
d=0
def __init__(self):
self.d=self.d+1;
D=test()
print D.d
D1=test()
print D1.d
D2=test()
print D2.d
Output is
1,1,1 # This should not be
Now using this :
class test():
d=[]
def __init__(self):
self.d.apend("1");
D=test()
print D.d
D1=test()
print D1.d
D2=test()
print D2.d
Result is (This should be)
['1']
['1', '1']
['1', '1', '1']
So i am not sure why integer value is not being treated as class variable while list is being treated.
In the first example,
self.d = self.d + 1
rebinds self.d, making it independent of test.d.
In the second example,
self.d.append("1")
modifies test.d.
To see that for yourself, print id(self.d) at the end of both constructors.
If you modified the second example to match the first:
self.d = self.d + ["1"]
you'd see that the behaviour would also change to match.
If you want to modify a class variable, do:
class test(object):
d=0
def __init__(self):
type(self).d=self.d+1;
D=test()
print D.d
D1=test()
print D1.d
D2=test()
print D2.d
You don't need the type on the right hand side of the assignment, because this way you never create an instance variable d. Note that new-style classes are necessary to this.
type is a function (actually a callable - it is also a class; but don't worry about that for now) which returns the class of its argument. So, type(self) returns the class of self. Classes are first class objects in Python.
Demo here: http://ideone.com/JdNpiV
Update: An alternative would be to use a classmethod.
To address a class variable use class_name.variable_name, giving :
class test(object):
d=0
def __init__(self):
test.d = test.d + 1;
NPE's answer tells you what is going wrong with your code. However, I'm not sure that it really tells you how to solve the issue properly.
Here's what I think you want, if each test instance should have a different d value in an instance variable:
class test(object): # new style class, since we inherit from "object"
_d = 0 # this is a class variable, which I've named _d to avoid confusion
def __init__(self):
self.d = test._d # assign current value of class variable to an instance variable
test._d += 1 # increment the class variable
Now, you can create multiple instances and each one will get a unique value for d:
>>> D0 = test()
>>> D1 = test()
>>> D2 = test()
>>> print D0.d
0
>>> print D1.d
1
>>> print D2.d
2

Categories