Multiple-Target Assignments - python

I am reading a book about Python and there is a special part in the book about Multiple-Target Assignments. Now the book explains it like this:
but I dont see use of this. This makes no sense for me. Why would you use more variables?
Is there a reason to do this? What makes this so different from using: a='spam'and then printing out a 3 times?
I can only think of using it for emptying variables in one line.

A very good use for multiple assignment is setting a bunch of variables to the same number.
Below is a demonstration:
>>> vowels = consonants = total = 0
>>> mystr = "abcdefghi"
>>> for char in mystr:
... if char in "aeiou":
... vowels += 1
... elif char in "bcdfghjklmnpqrstvwxyz":
... consonants += 1
... total += 1
...
>>> print "Vowels: {}\nConsonants: {}\nTotal: {}".format(vowels, consonants, total)
Vowels: 3
Consonants: 6
Total: 9
>>>
Without multiple assignment, I'd have to do this:
>>> vowels = 0
>>> consonants = 0
>>> total = 0
As you can see, this is a lot more long-winded.
Summed up, multiple assignment is just Python syntax sugar to make things easier/cleaner.

It's mainly just for convenience. If you want to initialize a bunch of variables, it's more convenient to do them all on one line than several. The book even mentions that at the end of the snippet that you quoted: "for example, when initializing a set of counters to zero".
Besides that, though, the book is actually wrong. The example shown
a = b = c = 'spam'
is NOT equivalent to
c = 'spam'
b = c
a = b
What it REALLY does is basically
tmp = 'spam'
a = tmp
b = tmp
c = tmp
del tmp
Notice the order of the assignments! This makes a difference when some of the targets depend on each other. For example,
>>> x = [3, 5, 7]
>>> a = 1
>>> a = x[a] = 2
>>> a
2
>>> x
[3, 5, 2]
According to the book, x[1] would become 2, but clearly this is not the case.
For further reading, see these previous Stack Overflow questions:
How do chained assignments work?
What is this kind of assignment in Python called? a = b = True
Python - are there advantages/disadvantages to assignment statements with multiple (target list "=") groups?
And probably several others (check out the links on the right sidebar).

You might need to initialize several variables with the same value, but then use them differently.
It could be for something like this:
def fibonacci(n):
a = b = 1
while a < n:
c = a
a = a + b
b = c
return a
(variable swapping with tuple unpacking ommited to avoid confusion as with the downvoted answer)
An important note:
>>> a = b = []
is dangerous. It probably doesn't do what you think it does.
>>> b.append(7)
>>> print(b)
[7]
>>> print(a)
[7] # ???????
This is due to how variables work as names, or labels, in Python, rather than containers of values in other languages. See this answer for a full explanation.

Presumably you go on to do something else with the different variables.
a = do_something_with(a)
b = do_something_else_with(b)
#c is still 'spam'
Trivial example and the initialization step questionably didn't save you any work, but it's a valid way to code. There are certainly places where initializing a significant number of variables is needed, and as long as they're immutable this idiom can save space.
Though as the book pointed out, you can only use this type of grammar for immutable types. For mutable types you need to explicitly create multiple objects:
a,b,c = [mutable_type() for _ in range(3)]
Otherwise you end up with surprising results since you have three references to the same object rather than three objects.

Related

Why does b+=(4,) work and b = b + (4,) doesn't work when b is a list?

If we take b = [1,2,3] and if we try doing: b+=(4,)
It returns b = [1,2,3,4], but if we try doing b = b + (4,) it doesn't work.
b = [1,2,3]
b+=(4,) # Prints out b = [1,2,3,4]
b = b + (4,) # Gives an error saying you can't add tuples and lists
I expected b+=(4,) to fail as you can't add a list and a tuple, but it worked. So I tried b = b + (4,) expecting to get the same result, but it didn't work.
The problem with "why" questions is that usually they can mean multiple different things. I will try to answer each one I think you might have in mind.
"Why is it possible for it to work differently?" which is answered by e.g. this. Basically, += tries to use different methods of the object: __iadd__ (which is only checked on the left-hand side), vs __add__ and __radd__ ("reverse add", checked on the right-hand side if the left-hand side doesn't have __add__) for +.
"What exactly does each version do?" In short, the list.__iadd__ method does the same thing as list.extend (but because of the language design, there is still an assignment back).
This also means for example that
>>> a = [1,2,3]
>>> b = a
>>> a += [4] # uses the .extend logic, so it is still the same object
>>> b # therefore a and b are still the same list, and b has the `4` added
[1, 2, 3, 4]
>>> b = b + [5] # makes a new list and assigns back to b
>>> a # so now a is a separate list and does not have the `5`
[1, 2, 3, 4]
+, of course, creates a new object, but explicitly requires another list instead of trying to pull elements out of a different sequence.
"Why is it useful for += to do this? It's more efficient; the extend method doesn't have to create a new object. Of course, this has some surprising effects sometimes (like above), and generally Python is not really about efficiency, but these decisions were made a long time ago.
"What is the reason not to allow adding lists and tuples with +?" See here (thanks, #splash58); one idea is that (tuple + list) should produce the same type as (list + tuple), and it's not clear which type the result should be. += doesn't have this problem, because a += b obviously should not change the type of a.
They are not equivalent:
b += (4,)
is shorthand for:
b.extend((4,))
while + concatenates lists, so by:
b = b + (4,)
you're trying to concatenate a tuple to a list
When you do this:
b += (4,)
is converted to this:
b.__iadd__((4,))
Under the hood it calls b.extend((4,)), extend accepts an iterator and this why this also work:
b = [1,2,3]
b += range(2) # prints [1, 2, 3, 0, 1]
but when you do this:
b = b + (4,)
is converted to this:
b = b.__add__((4,))
accept only list object.
From the official docs, for mutable sequence types both:
s += t
s.extend(t)
are defined as:
extends s with the contents of t
Which is different than being defined as:
s = s + t # not equivalent in Python!
This also means any sequence type will work for t, including a tuple like in your example.
But it also works for ranges and generators! For instance, you can also do:
s += range(3)
The "augmented" assignment operators like += were introduced in Python 2.0, which was released in October 2000. The design and rationale are described in PEP 203. One of the declared goals of these operators was the support of in-place operations. Writing
a = [1, 2, 3]
a += [4, 5, 6]
is supposed to update the list a in place. This matters if there are other references to the list a, e.g. when a was received as a function argument.
However, the operation can't always happen in place, since many Python types, including integers and strings, are immutable, so e.g. i += 1 for an integer i can't possibly operate in place.
In summary, augmented assignment operators were supposed to work in place when possible, and create a new object otherwise. To facilitate these design goals, the expression x += y was specified to behave as follows:
If x.__iadd__ is defined, x.__iadd__(y) is evaluated.
Otherwise, if x.__add__ is implemented x.__add__(y) is evaluated.
Otherwise, if y.__radd__ is implemented y.__radd__(x) is evaluated.
Otherwise raise an error.
The first result obtained by this process will be assigned back to x (unless that result is the NotImplemented singleton, in which case the lookup continues with the next step).
This process allows types that support in-place modification to implement __iadd__(). Types that don't support in-place modification don't need to add any new magic methods, since Python will automatically fall back to essentially x = x + y.
So let's finally come to your actual question – why you can add a tuple to a list with an augmented assignment operator. From memory, the history of this was roughly like this: The list.__iadd__() method was implemented to simply call the already existing list.extend() method in Python 2.0. When iterators were introduced in Python 2.1, the list.extend() method was updated to accept arbitrary iterators. The end result of these changes was that my_list += my_tuple worked starting from Python 2.1. The list.__add__() method, however, was never supposed to support arbitrary iterators as the right-hand argument – this was considered inappropriate for a strongly typed language.
I personally think the implementation of augmented operators ended up being a bit too complex in Python. It has many surprising side effects, e.g. this code:
t = ([42], [43])
t[0] += [44]
The second line raises TypeError: 'tuple' object does not support item assignment, but the operation is successfully performed anyway – t will be ([42, 44], [43]) after executing the line that raises the error.
Most people would expect X += Y to be equivalent to X = X + Y. Indeed, the Python Pocket Reference (4th ed) by Mark Lutz says on page 57 "The following two formats are roughly equivalent: X = X + Y , X += Y". However, the people who specified Python did not make them equivalent. Possibly that was a mistake which will result in hours of debugging time by frustrated programmers for as long as Python remains in use, but it's now just the way Python is. If X is a mutable sequence type, X += Y is equivalent to X.extend( Y ) and not to X = X + Y.
As it's explained here, if array doesn't implement __iadd__ method, the b+=(4,) would be just a shorthanded of b = b + (4,) but obviously it's not, so array does implement __iadd__ method. Apparently the implementation of __iadd__ method is something like this:
def __iadd__(self, x):
self.extend(x)
However we know that the above code is not the actual implementation of __iadd__ method but we can assume and accept that there's something like extend method, which accepts tupple inputs.

Python procedure that uses another procedure returns none

I very new with python and I'm doing an online class to learn the basics. While everything is going well, there are still a things that I don't seem to grasp in python..
Even though I found a much simpler way to solve the bellow problem, I'd still like to understand why my procedure is returning "None" .. Is it mistake with my if statements ? a syntax error ?
Here is the problem:
Define a procedure, union, that takes as inputs two lists.
It should modify the first input list to be the set union of the two lists. You may assume the first list is a set, that is, it contains no repeated elements.
The result we are expecting:
a = [1,2,3]
b = [2,4,6]
union(a,b)
print a
#>>> [1,2,3,4,6]
You will note that, in my procedure, I'm using another procedure to find if a list item is in the other list. Could the problem be coming from that ?
Here is my code:
def find_element(a,b):
if b in a:
return a.index(b)
return - 1
def union(a,b):
i = 0
while i < len(b) - 1:
c = find_element(a,b[i])
if c != -1:
i = i + 1
if c == -1:
a = a.append(b[i])
i = i + 1
return a
a = [1,2,3]
b = [2,4,6]
print(union(a,b))
a = a.append(b[i])
Here, a.append(b[i]) appends b[i] to a and returns 'none' which you have assigned to 'a'.
change this to
a.append(b[i])
and you should atleast get an output.
just in case you may need it.
you code could will be easier to read if you have it like this.
but its nice to challenge yourself.
best,
def union(a, b):
for item in b:
if item not in a:
a.append(item)
return a
a = [1, 2, 3]
b = [2, 4, 6]
x = union(a, b)
print(x)

Assigning empty list

I don't really know how I stumbled upon this, and I don't know what to think about it, but apparently [] = [] is a legal operation in python, so is [] = '', but '' = [] is not allowed. It doesn't seem to have any effect though, but I'm wondering: what the hell ?
This is related to Python's multiple assignment (sequence unpacking):
a, b, c = 1, 2, 3
works the same as:
[a, b, c] = 1, 2, 3
Since strings are sequences of characters, you can also do:
a, b, c = "abc" # assign each character to a variable
What you've discovered is the degenerative case: empty sequences on both sides. Syntactically valid because it's a list on the left rather than a tuple. Nice find; never thought to try that before!
Interestingly, if you try that with an empty tuple on the left, Python complains:
() = () # SyntaxError: can't assign to ()
Looks like the Python developers forgot to close a little loophole!
Do some search on packing/unpacking on python and you will find your answer.
This is basically for assigning multiple variables in a single go.
>>> [a,v] = [2,4]
>>> print a
2
>>> print v
4

Reverse a list without using built-in functions

I'm using Python 3.5.
As part of a problem, I'm trying to design a function that takes a list as input and reverts it. So if x = [a, b, c] the function would make x = [c, b, a].
The problem is, I'm not allowed to use any built-in functions, and it has got me stuck. My initial thought was the following loop inside a function:
for revert in range(1, len(x) + 1):
y.append(x[-revert])
And it works. But the problem is I'm using len(x), which I believe is a built-in function, correct?
So I searched around and have made the following very simple code:
y = x[::-1]
Which does exactly what I wanted, but it just seems almost too simple/easy and I'm not sure whether "::" counts as a function.
So I was wondering if anyone had any hints/ideas how to manually design said function? It just seems really hard when you can't use any built-in functions and it has me stuck for quite some time now.
range and len are both built-in functions. Since list methods are accepted, you could do this with insert. It is reeaallyy slow* but it does the job for small lists without using any built-ins:
def rev(l):
r = []
for i in l:
r.insert(0, i)
return r
By continuously inserting at the zero-th position you end up with a reversed version of the input list:
>>> print(rev([1, 2, 3, 4]))
[4, 3, 2, 1]
Doing:
def rev(l):
return l[::-1]
could also be considered a solution. ::-1 (:: has a different result) isn't a function (it's a slice) and [] is, again, a list method. Also, contrasting insert, it is faster and way more readable; just make sure you're able to understand and explain it. A nice explanation of how it works can be found in this S.O answer.
*Reeaaalllyyyy slow, see juanpa.arrivillaga's answer for cool plot and append with pop and take a look at in-place reverse on lists as done in Yoav Glazner's answer.
:: is not a function, it's a python literal. as well as []
How to check if ::, [] are functions or not. Simple,
import dis
a = [1,2]
dis.dis(compile('a[::-1]', '', 'eval'))
1 0 LOAD_NAME 0 (a)
3 LOAD_CONST 0 (None)
6 LOAD_CONST 0 (None)
9 LOAD_CONST 2 (-1)
12 BUILD_SLICE 3
15 BINARY_SUBSCR
16 RETURN_VALUE
If ::,[] were functions, you should find a label CALL_FUNCTION among python instructions executed by a[::-1] statement. So, they aren't.
Look how python instructions looks like when you call a function, lets say list() function
>>> dis.dis(compile('list()', '', 'eval'))
1 0 LOAD_NAME 0 (list)
3 CALL_FUNCTION 0
6 RETURN_VALUE
So, basically
def rev(f):
return f[::-1]
works fine. But, I think you should do something like Jim suggested in his answer if your question is a homework or sent by you teacher. But, you can add this quickest way as a side note.
If you teacher complains about [::-1] notation, show him the example I gave you.
Another way ( just for completeness :) )
def another_reverse(lst):
new_lst = lst.copy() # make a copy if you don't want to ruin lst...
new_lst.reverse() # notice! this will reverse it in place
return new_lst
Here's a solution that doesn't use built-in functions but relies on list methods. It reverse in-place, as implied by your specification:
>>> x = [1,2,3,4]
>>> def reverse(seq):
... temp = []
... while seq:
... temp.append(seq.pop())
... seq[:] = temp
...
>>> reverse(x)
>>> x
[4, 3, 2, 1]
>>>
ETA
Jim, your answer using insert at position 0 was driving me nuts! That solution is quadratic time! You can use append and pop with a temporary list to achieve linear time using simple list methods. See (reverse is in blue, rev is green):
If it feels a little bit like "cheating" using seq[:] = temp, we could always loop over temp and append every item into seq and the time complexity would still be linear but probably slower since it isn't using the C-based internals.
Your example that works:
y = x[::-1]
uses Python slices notation which is not a function in the sense that I assume you're requesting. Essentially :: acts as a separator. A more verbose version of your code would be:
y = x[len(x):None:-1]
or
y = x[start:end:step]
I probably wouldn't be complaining that python makes your life really, really easily.
Edit to be super pedantic. Someone could argue that calling [] at all is using an inbuilt python function because it's really syntactical sugar for the method __getitem__().
x.__getitem__(0) == x[0]
And using :: does make use of the slice() object.
x.__getitem__(slice(len(x), None, -1) == x[::-1]
But... if you were to argue this, anything you write in python would be using inbuilt python functions.
Another way for completeness, range() takes an optional step parameter that will allow you to step backwards through the list:
def reverse_list(l):
return [l[i] for i in range(len(l)-1, -1, -1)]
The most pythonic and efficient way to achieve this is by list slicing. And, since you mentioned you do not need any inbuilt function, it completely suffice your requirement. For example:
>>> def reverse_list(list_obj):
... return list_obj[::-1]
...
>>> reverse_list([1, 3, 5 , 3, 7])
[7, 3, 5, 3, 1]
Just iterate the list from right to left to get the items..
a = [1,2,3,4]
def reverse_the_list(a):
reversed_list = []
for i in range(0, len(a)):
reversed_list.append(a[len(a) - i - 1])
return reversed_list
new_list = reverse_the_list(a)
print new_list

Init method; len() of self object

def __init__(self,emps=str(""),l=[">"]):
self.str=emps
self.bl=l
def fromFile(self,seqfile):
opf=open(seqfile,'r')
s=opf.read()
opf.close()
lisst=s.split(">")
if s[0]==">":
lisst.pop(0)
nlist=[]
for x in lisst:
splitenter=x.split('\n')
splitenter.pop(0)
splitenter.pop()
splitstring="".join(splitenter)
nlist.append(splitstring)
nstr=">".join(nlist)
nstr=nstr.split()
nstr="".join(nstr)
for i in nstr:
self.bl.append(i)
self.str=nstr
return nstr
def getSequence(self):
print self.str
print self.bl
return self.str
def GpCratio(self):
pgenes=[]
nGC=[]
for x in range(len(self.lb)):
if x==">":
pgenes.append(x)
for i in range(len(pgenes)):
if i!=len(pgenes)-1:
c=krebscyclus[pgenes[i]:pgenes[i+1]].count('c')+0.000
g=krebscyclus[pgenes[i]:pgenes[i+1]].count('g')+0.000
ratio=(c+g)/(len(range(pgenes[i]+1,pgenes[i+1])))
nGC.append(ratio)
return nGC
s = Sequence()
s.fromFile('D:\Documents\Bioinformatics\sequenceB.txt')
print 'Sequence:\n', s.getSequence(), '\n'
print "G+C ratio:\n", s.GpCratio(), '\n'
I dont understand why it gives the error:
in GpCratio for x in range(len(self.lb)): AttributeError: Sequence instance has no attribute 'lb'.
When i print the list in def getSequence it prints the correct DNA sequenced list, but i can not use the list for searching for nucleotides. My university only allows me to input 1 file and not making use of other arguments in definitions, but "self"
btw, it is a class, but it refuses me to post it then.. class called Sequence
Looks like a typo. You define self.bl in your __init__() routine, then try to access self.lb.
(Also, emps=str("") is redundant - emps="" works just as well.)
But even if you correct that typo, the loop won't work:
for x in range(len(self.bl)): # This iterates over a list like [0, 1, 2, 3, ...]
if x==">": # This condition will never be True
pgenes.append(x)
You probably need to do something like
pgenes=[]
for x in self.bl:
if x==">": # Shouldn't this be != ?
pgenes.append(x)
which can also be written as a list comprehension:
pgenes = [x for x in self.bl if x==">"]
In Python, you hardly ever need len(x) or for n in range(...); you rather iterate directly over the sequence/iterable.
Since your program is incomplete and lacking sample data, I can't run it here to find all its other deficiencies. Perhaps the following can point you in the right direction. Assuming a string that contains the characters ATCG and >:
>>> gene = ">ATGAATCCGGTAATTGGCATACTGTAG>ATGATAGGAGGCTAG"
>>> pgene = ''.join(x for x in gene if x!=">")
>>> pgene
'ATGAATCCGGTAATTGGCATACTGTAGATGATAGGAGGCTAG'
>>> ratio = float(pgene.count("G") + pgene.count("C")) / (pgene.count("A") + pgene.count("T"))
>>> ratio
0.75
If, however, you don't want to look at the entire string but at separate genes (where > is the separator), use something like this:
>>> gene = ">ATGAATCCGGTAATTGGCATACTGTAG>ATGATAGGAGGCTAG"
>>> genes = [g for g in gene.split(">") if g !=""]
>>> genes
['ATGAATCCGGTAATTGGCATACTGTAG', 'ATGATAGGAGGCTAG']
>>> nGC = [float(g.count("G")+g.count("C"))/(g.count("A")+g.count("T")) for g in genes]
>>> nGC
[0.6875, 0.875]
However, if you want to calculate GC content, then of course you don't want (G+C)/(A+T) but (G+C)/(A+T+G+C) --> nGC = [float(g.count("G")+g.count("C"))/len(g)].

Categories

Resources