List inheritance: "extend" vs "+" vs "+=" [duplicate] - python

This question already has answers here:
Why does += behave unexpectedly on lists?
(9 answers)
Different behaviour for list.__iadd__ and list.__add__
(3 answers)
Closed 3 years ago.
I would like to understand why the following three, extremely simple, python codes give (apparently) inconsistent answers. It's just a toy exercise to understand what's happening, not a real-world problem. The class myList(list) inherits from list and defines a method "add" that should mimic the well-known "extend" method. I've tried to do this in three ways, with the direct use of extend, with the + operator and with +=. However, with great surprise, I get inconsistent answers. Notice that, among the three codes, a single line (inside the add method) changes.
CODE 1: self.extend(item)
class myList(list):
def add(self,item):
self.extend(item)
l = myList([1,2])
l.add([3,4])
print(l)
Prints [1, 2, 3, 4]
CODE 2: self = self + item
class myList(list):
def add(self,item):
self = self + item
l = myList([1,2])
l.add([3,4])
print(l)
Prints [1, 2]
CODE 3: self += item
class myList(list):
def add(self,item):
self += item
l = myList([1,2])
l.add([3,4])
print(l)
Prints [1, 2, 3, 4]
I am a bit confused... what's happening?
Thank you in advance.

self = self + item
does not modify the object, only the local variable inside the method.
By using + the interpreter is calling __add__ in list, which creates a new list, and you're then making the self variable point to it within the add method, and when the method exits its value is discarded.
On the other hand, the += version calls the __iadd__ method of the list class, which updates its contents as expected.

Related

How to use a copy of a variable in a closure [duplicate]

This question already has answers here:
Python Argument Binders
(7 answers)
Closed 2 years ago.
In python I can use the nonlocal keyword to make sure that I use the variable from the outer scope, but how do I copy the variable from the outer scope into the closure?
I have code like this:
my_functions_with_parameters = []
for fun_name in my_function_names:
fun = my_functions[fun_name]
def fun_with_parameters():
return fun(1,2,3)
my_functions_with_parameters.append(fun_with_parameter)
My problem is, that each instance of fun_with_parameters calls the last function, i.e., my_functions[my_function_names[-1]], because the variable fun is a reference.
I tried to use fun = copy.deepcopy(my_functions[fun_name]) but it did not change anything and is probably not the best or fastest way.
Try this instead:
my_functions_with_parameters = []
for fun_name in my_function_names:
fun = my_functions[fun_name]
my_functions_with_parameters.append(fun)
Now you can provide the parameters to each fun in my_functions_with_parameters when you loop through them:
for my_fun in my_functions_with_parameters:
my_fun(1,2,3)
Or you can store the parameters with the function in a tuple:
my_functions_with_parameters = []
for fun_name in my_function_names:
fun = my_functions[fun_name]
my_functions_with_parameters.append((fun, 1, 2, 3))
And call via:
for tup in my_functions_with_parameters:
tup[0](tup[1], tup[2], tup[3])

Is list pass by value or by reference? [duplicate]

This question already has answers here:
How do I pass a variable by reference?
(39 answers)
Closed 4 years ago.
Consider the following code, at first glance it does the same thing, but the result is different, sometimes it seems list is pass by value, sometimes list seems pass by reference:
lst = [1, 2]
def f(lst):
# lst = lst + [3] # seems pass by value
# lst += [3] # strange! same as above but seems pass by reference
lst = lst.append(3) # seems pass by reference
return lst
f(lst)
print(lst)
can anyone tell me what is going on?
It’s passed by value of reference. So modifications to the object can be seen outside the function, but assigning the variable to a new object does not change anything outside the function.
It’s essentially the same as passing a pointer in C, or a reference type in Java.
The result of the += case is because that operator actually modifies the list in place, so the effect is visible outside the function. lst.append() is also an in-place operation, which explains your last case.

Python creating new object of class contains values of older object [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 7 years ago.
I have got a class with a list-variable and a function to append items to this list. I cannot append items directly, because I have to validate them:
class Foo:
def __init__(self, elements=[]):
self.elements = elements
def append(self, element):
self.elements.append(element)
If I instantiate an object of this class, add items and then create another new object, this object contains the items of the first object.
foo = Foo()
print foo.elements # []
foo.append(element=4)
print foo.elements # [4]
foo.append(element=7)
print foo.elements # [4, 7]
bar = Foo()
print bar.elements # [4, 7]
Can someone explain my, why this happens?
A possible solution for me could be this, but I don't like it...
class Foo:
def __init__(self, elements=None):
if elements is None:
self.elements = []
else:
self.elements = elements
def append(self, element):
self.elements.append(element)
Thanks for all answers!
You've got your answer yourself already. Initializing with a mutable object ist never a good idea. It will be initialized once the class gets defined and reused for every instance.

Why does Python treat arrays in classes as (somehow) "static"? [duplicate]

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 8 years ago.
I do not understand why the following piece of code produces the output it does:
class A:
x = [0]
y = 0
def __init__(self):
self.x.append(1)
self.y += 1
a = A()
print a.x, a.y
a = A()
print a.x, a.y
The output is:
[0, 1] 1
[0, 1, 1] 1
What I would expect is:
[0, 1] 1
[0, 1] 1
Using Python 2.7.6.
The class itself is an object. You can modify the properties of a class. Every instance derived afterwards inherits these modifications, or to be more correct (see comment of jsbueno) every instance-"variable"/identifier referencing the original object hast instantly the same value.
x and y are not instance-variables but class-variables. So ever invocation/instantiation of the Class modifies them in __init__.
To get real instance-variables you need them to define inside of the __init__ method.
The slightly different behaviour of x and y are results of the differet mutability of those variables. While the list identified by x is a mutable object, the number 0 isn't! Therefore while with append you also change the state of the object referenced by the class-variable this doesn't happen for y, because y is immutable. Instead you assign a new object to the (now) instance-identifier y.
That's also the reason, why it's not very accurate to speak of variables in python, but better of "identifier". You practically put a tag/stamp/name-shield on an object. But the coupling between identifier and referenced object is quite loose. You can reattach the tag as you want, without loosing the referenced object, if it's otherwise referenced.

Sort a list of Class Instances Python [duplicate]

This question already has answers here:
How to sort a list of objects based on an attribute of the objects in descending order?
(9 answers)
Closed 7 years ago.
I have a list of class instances -
x = [<iteminstance1>,...]
among other attributes the class has score attribute. How can I sort the items in ascending order based on this parameter?
EDIT: The list in python has something called sort. Could I use this here? How do I direct this function to use my score attribute?
In addition to the solution you accepted, you could also implement the special __lt__() ("less than") method on the class. The sort() method (and the sorted() function) will then be able to compare the objects, and thereby sort them. This works best when you will only ever sort them on this attribute, however.
class Foo(object):
def __init__(self, score):
self.score = score
def __lt__(self, other):
return self.score < other.score
l = [Foo(3), Foo(1), Foo(2)]
l.sort()
import operator
sorted_x = sorted(x, key=operator.attrgetter('score'))
if you want to sort x in-place, you can also:
x.sort(key=operator.attrgetter('score'))

Categories

Resources