scopes in python3: lists as parameters of a function? - python-3.x

I'm studying for a python3 exam and came across this question:
Given this snippet of code:
def my_function(my_list_1):
print("Print #1:", my_list_1)
print("Print #2:", my_list_2)
del my_list_1[0] # Pay attention to this line.
print("Print #3:", my_list_1)
print("Print #4:", my_list_2)
my_list_2 = [2, 3]
my_function(my_list_2)
print("Print #5:", my_list_2)
explain the output #3-#5:
Print #1: [2, 3]
Print #2: [2, 3]
Print #3: [3]
Print #4: [3]
Print #5: [3]
I'm a little perplexed by these questions:
how does python know who 'my_list_2' is, within the 'my_function' def block of code?
Suppose I changed the last 3 lines of code from my_list_2=[2,3] to pluto=[2,3]:
pluto = [2, 3]
my_function(pluto)
print("Print #5:", pluto)
this would generate a NameError which I think makes sense -- so why does it not throw an Error in the initial code?
inside the def block, how does Print #4 for my_list_2 reflect the changes applied to my_list_1?

my_list_2 is a global variable. So it's accessible in your function. It's called LEGB in python which you can read about it here .
And list in python is a mutable variable type. When you pass my_list_2 as param to your function, if you make any changes to that, that would apply all over your code. Because it's mutable changes on it would happen in place.

When you put the list as a parameter, the interpreter puts the link to the object "list."
So, if you call
del my_list_1[0]
You modify the actual list you passed. So, as a result, both my_list_1 and my_list_2 lists are related to the same object, and you see one dropped element there.
An alternative code represent that is:
my_list_1 = [2, 3]
my_list_2 = my_list_1
del my_list_2[0]
print(my_list_1, my_list_2)
# [3] [3]

Related

Recursion for generating subsets of a list

I would like to know for this function, why does after it runs and returns [[],[1]] which is the last line of the function? Then it would run the line smaller = genSubset(L[:-1]) again and again. I visualized the code from pythontutor. However, i do not understand why it works this way. Someone please enlighten me. Thank You.
This function generates all the possible subsets of a given list, so, for example, input list is [1,2,3]. It would return [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]].
def genSubsets(L):
res = []
if len(L) == 0:
return [[]] #list of empty list
smaller = genSubsets(L[:-1]) # all subsets without last element
extra = L[-1:] # create a list of just last element
new = []
for small in smaller:
new.append(small+extra) # for all smaller solutions, add one with last element
return smaller+new # combine those with last element and those without
print(genSubsets([1,2,3]))
This function is recursive, that means - it calls itself. when it finishes one recursion iteration - it returns to where it stopped. This is exactly at the line smaller = getSubsets(L[:-1]) but notice that it wont run it again, but instead continue until it finishes the call. This recursion happens n times (where n is the list's length), and you will see this behavior exactly n times before the function finally returns the subsets.
I hope i understood correctly the question and managed to answer it :)

Why is it that thing += ['x', 1] acts as .extend but thing = thing + ['x', 1] creates a new object? [duplicate]

The += operator in python seems to be operating unexpectedly on lists. Can anyone tell me what is going on here?
class foo:
bar = []
def __init__(self,x):
self.bar += [x]
class foo2:
bar = []
def __init__(self,x):
self.bar = self.bar + [x]
f = foo(1)
g = foo(2)
print f.bar
print g.bar
f.bar += [3]
print f.bar
print g.bar
f.bar = f.bar + [4]
print f.bar
print g.bar
f = foo2(1)
g = foo2(2)
print f.bar
print g.bar
OUTPUT
[1, 2]
[1, 2]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3]
[1]
[2]
foo += bar seems to affect every instance of the class, whereas foo = foo + bar seems to behave in the way I would expect things to behave.
The += operator is called a "compound assignment operator".
The general answer is that += tries to call the __iadd__ special method, and if that isn't available it tries to use __add__ instead. So the issue is with the difference between these special methods.
The __iadd__ special method is for an in-place addition, that is it mutates the object that it acts on. The __add__ special method returns a new object and is also used for the standard + operator.
So when the += operator is used on an object which has an __iadd__ defined the object is modified in place. Otherwise it will instead try to use the plain __add__ and return a new object.
That is why for mutable types like lists += changes the object's value, whereas for immutable types like tuples, strings and integers a new object is returned instead (a += b becomes equivalent to a = a + b).
For types that support both __iadd__ and __add__ you therefore have to be careful which one you use. a += b will call __iadd__ and mutate a, whereas a = a + b will create a new object and assign it to a. They are not the same operation!
>>> a1 = a2 = [1, 2]
>>> b1 = b2 = [1, 2]
>>> a1 += [3] # Uses __iadd__, modifies a1 in-place
>>> b1 = b1 + [3] # Uses __add__, creates new list, assigns it to b1
>>> a2
[1, 2, 3] # a1 and a2 are still the same list
>>> b2
[1, 2] # whereas only b1 was changed
For immutable types (where you don't have an __iadd__) a += b and a = a + b are equivalent. This is what lets you use += on immutable types, which might seem a strange design decision until you consider that otherwise you couldn't use += on immutable types like numbers!
For the general case, see Scott Griffith's answer. When dealing with lists like you are, though, the += operator is a shorthand for someListObject.extend(iterableObject). See the documentation of extend().
The extend function will append all elements of the parameter to the list.
When doing foo += something you're modifying the list foo in place, thus you don't change the reference that the name foo points to, but you're changing the list object directly. With foo = foo + something, you're actually creating a new list.
This example code will explain it:
>>> l = []
>>> id(l)
13043192
>>> l += [3]
>>> id(l)
13043192
>>> l = l + [3]
>>> id(l)
13059216
Note how the reference changes when you reassign the new list to l.
As bar is a class variable instead of an instance variable, modifying in place will affect all instances of that class. But when redefining self.bar, the instance will have a separate instance variable self.bar without affecting the other class instances.
The problem here is, bar is defined as a class attribute, not an instance variable.
In foo, the class attribute is modified in the init method, that's why all instances are affected.
In foo2, an instance variable is defined using the (empty) class attribute, and every instance gets its own bar.
The "correct" implementation would be:
class foo:
def __init__(self, x):
self.bar = [x]
Of course, class attributes are completely legal. In fact, you can access and modify them without creating an instance of the class like this:
class foo:
bar = []
foo.bar = [x]
There are two things involved here:
1. class attributes and instance attributes
2. difference between the operators + and += for lists
+ operator calls the __add__ method on a list. It takes all the elements from its operands and makes a new list containing those elements maintaining their order.
+= operator calls __iadd__ method on the list. It takes an iterable and appends all the elements of the iterable to the list in place. It does not create a new list object.
In class foo the statement self.bar += [x] is not an assignment statement but actually translates to
self.bar.__iadd__([x]) # modifies the class attribute
which modifies the list in place and acts like the list method extend.
In class foo2, on the contrary, the assignment statement in the init method
self.bar = self.bar + [x]
can be deconstructed as:
The instance has no attribute bar (there is a class attribute of the same name, though) so it accesses the class attribute bar and creates a new list by appending x to it. The statement translates to:
self.bar = self.bar.__add__([x]) # bar on the lhs is the class attribute
Then it creates an instance attribute bar and assigns the newly created list to it. Note that bar on the rhs of the assignment is different from the bar on the lhs.
For instances of class foo, bar is a class attribute and not instance attribute. Hence any change to the class attribute bar will be reflected for all instances.
On the contrary, each instance of the class foo2 has its own instance attribute bar which is different from the class attribute of the same name bar.
f = foo2(4)
print f.bar # accessing the instance attribute. prints [4]
print f.__class__.bar # accessing the class attribute. prints []
Hope this clears things.
Although much time has passed and many correct things were said, there is no answer which bundles both effects.
You have 2 effects:
a "special", maybe unnoticed behaviour of lists with += (as stated by Scott Griffiths)
the fact that class attributes as well as instance attributes are involved (as stated by Can Berk Büder)
In class foo, the __init__ method modifies the class attribute. It is because self.bar += [x] translates to self.bar = self.bar.__iadd__([x]). __iadd__() is for inplace modification, so it modifies the list and returns a reference to it.
Note that the instance dict is modified although this would normally not be necessary as the class dict already contains the same assignment. So this detail goes almost unnoticed - except if you do a foo.bar = [] afterwards. Here the instances's bar stays the same thanks to the said fact.
In class foo2, however, the class's bar is used, but not touched. Instead, a [x] is added to it, forming a new object, as self.bar.__add__([x]) is called here, which doesn't modify the object. The result is put into the instance dict then, giving the instance the new list as a dict, while the class's attribute stays modified.
The distinction between ... = ... + ... and ... += ... affects as well the assignments afterwards:
f = foo(1) # adds 1 to the class's bar and assigns f.bar to this as well.
g = foo(2) # adds 2 to the class's bar and assigns g.bar to this as well.
# Here, foo.bar, f.bar and g.bar refer to the same object.
print f.bar # [1, 2]
print g.bar # [1, 2]
f.bar += [3] # adds 3 to this object
print f.bar # As these still refer to the same object,
print g.bar # the output is the same.
f.bar = f.bar + [4] # Construct a new list with the values of the old ones, 4 appended.
print f.bar # Print the new one
print g.bar # Print the old one.
f = foo2(1) # Here a new list is created on every call.
g = foo2(2)
print f.bar # So these all obly have one element.
print g.bar
You can verify the identity of the objects with print id(foo), id(f), id(g) (don't forget the additional ()s if you are on Python3).
BTW: The += operator is called "augmented assignment" and generally is intended to do inplace modifications as far as possible.
The other answers would seem to pretty much have it covered, though it seems worth quoting and referring to the Augmented Assignments PEP 203:
They [the augmented assignment operators] implement the same operator
as their normal binary form, except that the operation is done
`in-place' when the left-hand side object supports it, and that the
left-hand side is only evaluated once.
...
The idea behind augmented
assignment in Python is that it isn't just an easier way to write the
common practice of storing the result of a binary operation in its
left-hand operand, but also a way for the left-hand operand in
question to know that it should operate `on itself', rather than
creating a modified copy of itself.
>>> elements=[[1],[2],[3]]
>>> subset=[]
>>> subset+=elements[0:1]
>>> subset
[[1]]
>>> elements
[[1], [2], [3]]
>>> subset[0][0]='change'
>>> elements
[['change'], [2], [3]]
>>> a=[1,2,3,4]
>>> b=a
>>> a+=[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])
>>> a=[1,2,3,4]
>>> b=a
>>> a=a+[5]
>>> a,b
([1, 2, 3, 4, 5], [1, 2, 3, 4])
>>> a = 89
>>> id(a)
4434330504
>>> a = 89 + 1
>>> print(a)
90
>>> id(a)
4430689552 # this is different from before!
>>> test = [1, 2, 3]
>>> id(test)
48638344L
>>> test2 = test
>>> id(test)
48638344L
>>> test2 += [4]
>>> id(test)
48638344L
>>> print(test, test2) # [1, 2, 3, 4] [1, 2, 3, 4]```
([1, 2, 3, 4], [1, 2, 3, 4])
>>> id(test2)
48638344L # ID is different here
We see that when we attempt to modify an immutable object (integer in this case), Python simply gives us a different object instead. On the other hand, we are able to make changes to an mutable object (a list) and have it remain the same object throughout.
ref : https://medium.com/#tyastropheus/tricky-python-i-memory-management-for-mutable-immutable-objects-21507d1e5b95
Also refer below url to understand the shallowcopy and deepcopy
https://www.geeksforgeeks.org/copy-python-deep-copy-shallow-copy/
listname.extend() works great for this purpose :)

Why two different simple list variables behave exactly like each other? [duplicate]

This question already has answers here:
Variable assignment and modification (in python) [duplicate]
(6 answers)
Closed 4 years ago.
Something very strange is happening to me. when i write this code down :
a = [3,2,4]
b = a
a.sort()
print(a)
print(b)
The variable "b" must be [3,2,4] and "a" must be [2,3,4].
But this result came out :
[2, 3, 4]
[2, 3, 4]
Why did it sort both of them?
I think it only happens to lists,because I tried to write the code below :
dots = dotsDetecter(param).getDots()
wholeDots = dots
The variable "dots" is gonna be a list but after that whatever I do to the "dots" list, wholeDots variable changes exactly like dots.
Does anybody now why it is happening?
b = a does not instantiate a new list, b is just an alias of a. So every operation on a will also affect b. You should do something like this:
def main():
a = [3, 2, 4]
b = list(a) # create new list initialized with a values
a.sort()
print(a)
print(b)

Get name of elements of a OrderedDict in pandas [duplicate]

With Python 2.7, I can get dictionary keys, values, or items as a list:
>>> newdict = {1:0, 2:0, 3:0}
>>> newdict.keys()
[1, 2, 3]
With Python >= 3.3, I get:
>>> newdict.keys()
dict_keys([1, 2, 3])
How do I get a plain list of keys with Python 3?
This will convert the dict_keys object to a list:
list(newdict.keys())
On the other hand, you should ask yourself whether or not it matters. It is Pythonic to assume duck typing -- if it looks like a duck and it quacks like a duck, it is a duck. The dict_keys object can be iterated over just like a list. For instance:
for key in newdict.keys():
print(key)
Note that dict_keys doesn't support insertion newdict[k] = v, though you may not need it.
Python >= 3.5 alternative: unpack into a list literal [*newdict]
New unpacking generalizations (PEP 448) were introduced with Python 3.5 allowing you to now easily do:
>>> newdict = {1:0, 2:0, 3:0}
>>> [*newdict]
[1, 2, 3]
Unpacking with * works with any object that is iterable and, since dictionaries return their keys when iterated through, you can easily create a list by using it within a list literal.
Adding .keys() i.e [*newdict.keys()] might help in making your intent a bit more explicit though it will cost you a function look-up and invocation. (which, in all honesty, isn't something you should really be worried about).
The *iterable syntax is similar to doing list(iterable) and its behaviour was initially documented in the Calls section of the Python Reference manual. With PEP 448 the restriction on where *iterable could appear was loosened allowing it to also be placed in list, set and tuple literals, the reference manual on Expression lists was also updated to state this.
Though equivalent to list(newdict) with the difference that it's faster (at least for small dictionaries) because no function call is actually performed:
%timeit [*newdict]
1000000 loops, best of 3: 249 ns per loop
%timeit list(newdict)
1000000 loops, best of 3: 508 ns per loop
%timeit [k for k in newdict]
1000000 loops, best of 3: 574 ns per loop
with larger dictionaries the speed is pretty much the same (the overhead of iterating through a large collection trumps the small cost of a function call).
In a similar fashion, you can create tuples and sets of dictionary keys:
>>> *newdict,
(1, 2, 3)
>>> {*newdict}
{1, 2, 3}
beware of the trailing comma in the tuple case!
list(newdict) works in both Python 2 and Python 3, providing a simple list of the keys in newdict. keys() isn't necessary.
You can also use a list comprehension:
>>> newdict = {1:0, 2:0, 3:0}
>>> [k for k in newdict.keys()]
[1, 2, 3]
Or, shorter,
>>> [k for k in newdict]
[1, 2, 3]
Note: Order is not guaranteed on versions under 3.7 (ordering is still only an implementation detail with CPython 3.6).
A bit off on the "duck typing" definition -- dict.keys() returns an iterable object, not a list-like object. It will work anywhere an iterable will work -- not any place a list will. a list is also an iterable, but an iterable is NOT a list (or sequence...)
In real use-cases, the most common thing to do with the keys in a dict is to iterate through them, so this makes sense. And if you do need them as a list you can call list().
Very similarly for zip() -- in the vast majority of cases, it is iterated through -- why create an entire new list of tuples just to iterate through it and then throw it away again?
This is part of a large trend in python to use more iterators (and generators), rather than copies of lists all over the place.
dict.keys() should work with comprehensions, though -- check carefully for typos or something... it works fine for me:
>>> d = dict(zip(['Sounder V Depth, F', 'Vessel Latitude, Degrees-Minutes'], [None, None]))
>>> [key.split(", ") for key in d.keys()]
[['Sounder V Depth', 'F'], ['Vessel Latitude', 'Degrees-Minutes']]
If you need to store the keys separately, here's a solution that requires less typing than every other solution presented thus far, using Extended Iterable Unpacking (Python3.x+):
newdict = {1: 0, 2: 0, 3: 0}
*k, = newdict
k
# [1, 2, 3]
Operation
no. Of characters
k = list(d)
9 characters (excluding whitespace)
k = [*d]
6 characters
*k, = d
5 characters
Converting to a list without using the keys method makes it more readable:
list(newdict)
and, when looping through dictionaries, there's no need for keys():
for key in newdict:
print key
unless you are modifying it within the loop which would require a list of keys created beforehand:
for key in list(newdict):
del newdict[key]
On Python 2 there is a marginal performance gain using keys().
Yes, There is a better and simplest way to do this in python3.X
use inbuild list() function
#Devil
newdict = {1:0, 2:0, 3:0}
key_list = list(newdict)
print(key_list)
#[1, 2, 3]
I can think of 2 ways in which we can extract the keys from the dictionary.
Method 1: -
To get the keys using .keys() method and then convert it to list.
some_dict = {1: 'one', 2: 'two', 3: 'three'}
list_of_keys = list(some_dict.keys())
print(list_of_keys)
-->[1,2,3]
Method 2: -
To create an empty list and then append keys to the list via a loop.
You can get the values with this loop as well (use .keys() for just keys and .items() for both keys and values extraction)
list_of_keys = []
list_of_values = []
for key,val in some_dict.items():
list_of_keys.append(key)
list_of_values.append(val)
print(list_of_keys)
-->[1,2,3]
print(list_of_values)
-->['one','two','three']
Beyond the classic (and probably more correct) way to do this (some_dict.keys()) there is also a more "cool" and surely more interesting way to do this:
some_dict = { "foo": "bar", "cool": "python!" }
print( [*some_dict] == ["foo", "cool"] ) # True
Note: this solution shouldn't be used in a develop environment; I showed it here just because I thought it was quite interesting from the *-operator-over-dictionary side of view. Also, I'm not sure whether this is a documented feature or not, and its behaviour may change in later versions :)
You can you use simple method like below
keys = newdict.keys()
print(keys)
This is the best way to get key List in one line of code
dict_variable = {1:"a",2:"b",3:"c"}
[key_val for key_val in dict_variable.keys()]

Can you call 2 args from a function into another function? [duplicate]

So, Python functions can return multiple values. It struck me that it would be convenient (though a bit less readable) if the following were possible.
a = [[1,2],[3,4]]
def cord():
return 1, 1
def printa(y,x):
print a[y][x]
printa(cord())
...but it's not. I'm aware that you can do the same thing by dumping both return values into temporary variables, but it doesn't seem as elegant. I could also rewrite the last line as "printa(cord()[0], cord()[1])", but that would execute cord() twice.
Is there an elegant, efficient way to do this? Or should I just see that quote about premature optimization and forget about this?
printa(*cord())
The * here is an argument expansion operator... well I forget what it's technically called, but in this context it takes a list or tuple and expands it out so the function sees each list/tuple element as a separate argument.
It's basically the reverse of the * you might use to capture all non-keyword arguments in a function definition:
def fn(*args):
# args is now a tuple of the non-keyworded arguments
print args
fn(1, 2, 3, 4, 5)
prints (1, 2, 3, 4, 5)
fn(*[1, 2, 3, 4, 5])
does the same.
Try this:
>>> def cord():
... return (1, 1)
...
>>> def printa(y, x):
... print a[y][x]
...
>>> a=[[1,2],[3,4]]
>>> printa(*cord())
4
The star basically says "use the elements of this collection as positional arguments." You can do the same with a dict for keyword arguments using two stars:
>>> a = {'a' : 2, 'b' : 3}
>>> def foo(a, b):
... print a, b
...
>>> foo(**a)
2 3
Actually, Python doesn't really return multiple values, it returns one value which can be multiple values packed into a tuple. Which means that you need to "unpack" the returned value in order to have multiples.
A statement like
x,y = cord()
does that, but directly using the return value as you did in
printa(cord())
doesn't, that's why you need to use the asterisk. Perhaps a nice term for it might be "implicit tuple unpacking" or "tuple unpacking without assignment".

Resources