local variable optimization in python method - python

In the following code, I would like to know if the grade_to_score dictionary will be created every time the method is called?
def get_score(grade):
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0} # step 1
return grade_to_score.get(grade, -1)
also, what is the way to confirm that? I am working with Python 2.7

Yes it will. To get around it, you can pass it as a default argument so that it will only be evaluated once:
def get_score(grade, grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0}):
return grade_to_score.get(grade, -1)
or the better approach:
def get_score(grade, grade_to_score = None):
if grade_to_score == None:
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0}
return grade_to_score.get(grade, -1)

To answer your question "what is the way to confirm that?", you can check whether the same object is being used each time:
def get_score(grade):
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0} # step 1
print(id(grade_to_score)) # check object ID
return grade_to_score.get(grade, -1)
Now you can call it:
>>> a = get_score("")
50252080
>>> b = get_score("")
50249920
>>> c = get_score("")
50249776
A different id means a different object, so grade_to_score clearly is being created anew on each call. Interestingly, this doesn't happen if you call in a for loop:
>>> for _ in range(3):
a = get_score("")
50249920
50249920
50249920
>>> scores = [get_score(grade) for grade in "ABC"]
53737032
53737032
53737032

Yes, the dictionary is created a-new every time the function is called.
You can make it a global instead, or make it a function default:
grade_to_score = {'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0} # step 1
def get_score(grade):
return grade_to_score.get(grade, -1)
or
def get_score(grade, grade_to_score={'A': 10, 'B': 8, 'C': 6, 'D': 4, 'F': 0}):
return grade_to_score.get(grade, -1)
In the second casegrade_to_score is passed into the function as a local, so lookups are (marginally) faster.
In both cases the dictionary literal is executed once, on module import. Note that in both cases, because grade_to_score is a mutable dictionary, so any changes you make to it are global, not local to the get_score() call.

Related

set size is changing even though it shouldn't python

class Test:
TheFlag = True
StartNodeQuery = {1, 2, 3, 4, 5}
def parsa(self):
while self.TheFlag:
SNQ = self.StartNodeQuery
self.iterator(SNQ)
def iterator(self, CurrentNodeQuery):
#it prints {1, 2, 3, 4, 5}
print(CurrentNodeQuery)
if len(CurrentNodeQuery) < 100:
b = len(CurrentNodeQuery) * 2
c = len(CurrentNodeQuery) * 3
self.StartNodeQuery.update({b, c})
# it prints {1, 2, 3, 4, 5, 10, 15}
print(CurrentNodeQuery)
else:
self.TheFlag = False
assert 0
obj = Test()
obj.parsa()
as you can see I deliberately ended the program with assert 0.
The Main issue is:
Before the function is finished the parameters that is passed to it gets changed!
as you can see
StartNodeQuery = {1, 2, 3, 4, 5} and
SNQ = self.StartNodeQuery
so why when I change the size of the self.StartNodeQuery inside the function before it's finished , CurrentNodeQuery which is a different variable with the same values as self.StartNodeQuery (or SNQ) gets changed as well, even though we didn't pass the new self.StartNodeQuery to CurrentNodeQuery yet?
I hope you understand my problem, if you have the solution, please help a guy out
Some issues and suggestions in your code
Don't mix class and instance variables. You are using class variables TheFlag and StartNodeQuery as instance variables, so make them one
Use a constructor to instantiate instance variables
Perhaps use exit() to break the function
You need to explicitly copy the set to make a new instance via copy.copy. Assignment SNQ = self.StartNodeQuery just makes a new reference to the variable
So the fixed code might look like
from copy import copy
class Test:
def __init__(self):
# Made instance variables as class variables
self.TheFlag = True
self.StartNodeQuery = {1, 2, 3, 4, 5}
def parsa(self):
while self.TheFlag:
SNQ = self.StartNodeQuery.copy()
self.iterator(SNQ)
def iterator(self, CurrentNodeQuery):
#it prints {1, 2, 3, 4, 5}
print(CurrentNodeQuery)
if len(CurrentNodeQuery) < 100:
b = len(CurrentNodeQuery) * 2
c = len(CurrentNodeQuery) * 3
self.StartNodeQuery.update({b, c})
# it prints {1, 2, 3, 4, 5, 10, 15}
print(CurrentNodeQuery)
else:
self.TheFlag = False
#Use exit to break the function
exit()
obj = Test()
obj.parsa()
And the output will be
{1, 2, 3, 4, 5}
{1, 2, 3, 4, 5}

recursive multi variable lambda function in python

I have seen an elegant solution for recursive functions using lambda in Python. As a matter of fact, I am very interested at applying this concept in multi-variable function, two variables — x and y — for instance. How can it be done in python?
The function itself is not important, but if it helps, here it comes the description: I have dictionaries containing one-layer dictionaries inside, as:
dicts = [{'A': {'a': 1}, 'B': {'a': 10}}, {'A': {'b':3}}, {'B': {'c': 31}}, {'A': {'j': 4, 't': 9}}, {'B': {'y': 400, 'r': 160}}]
And the keys in the inner dictionary does not repeat for the same given outer dictionary key. I want then to be merged, in a way that they result in:
result = {'A': {'a': 1, 'b': 3, 'j': 4, 't': 9}, 'B': {'a': 10, 'c': 31, 'r': 160, 'y': 400}}
The challenge of using lambda function to solve this kept my attention and focus. So if someone could give hints or help me solving this, I do appreciate!
Python lambda functions can only consist of a single expression. So when you want to do something complex with a lambda, what you are looking for is a one-liner.
from functools import reduce # Only needed in Python3
f = lambda dicts: reduce(lambda acc, el: {**acc, **{k: {**acc.get(k, {}), **v} for k, v in el.items()}}, dicts, {})
f(dicts) # {'A': {'a': 1, 'b': 3, 'j': 4, 't': 9}, 'B': {'a': 10, 'c': 31, 'r': 160, 'y': 400}}
Note that there is an implicit recursion with the reduce function. This provides you with an example of a multi-variable lambda.

Merging two Python dictionaries but remaining the order [duplicate]

I'm just starting to play around with Python (VBA background). Why does this dictionary get created out of order? Shouldn't it be a:1, b:2...etc.?
class Card:
def county(self):
c = 0
l = 0
groupL = {} # groupL for Loop
for n in range(0,13):
c += 1
l = chr(n+97)
groupL.setdefault(l,c)
return groupL
pick_card = Card()
group = pick_card.county()
print group
here's the output:
{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4, 'g': 7, 'f': 6, 'i': 9, 'h': 8, 'k': 11, 'j': 10, 'm': 13, 'l': 12}
or, does it just get printed out of order?
Dictionaries have no order in python. In other words, when you iterate over a dictionary, the order that the keys/items are "yielded" is not the order that you put them into the dictionary. (Try your code on a different version of python and you're likely to get differently ordered output). If you want a dictionary that is ordered, you need a collections.OrderedDict which wasn't introduced until python 2.7. You can find equivalent recipes on ActiveState if you're using an older version of python. However, often it's good enough to just sort the items (e.g. sorted(mydict.items()).
EDIT as requested, an OrderedDict example:
from collections import OrderedDict
groupL = OrderedDict() # groupL for Loop
c = 0
for n in range(0,13):
c += 1
l = chr(n+97)
groupL.setdefault(l,c)
print (groupL)

How to remove the least frequent element from a Counter in Python the fastest way?

I'd like to implement a Counter which drops the least frequent element when the counter's size going beyond some threshold. For that I need to remove the least frequent element.
What is the fastest way to do that in Python?
I know counter.most_common()[-1], but it creates a whole list and seems slow when done extensively? Is there a better command (or maybe a different data structure)?
You may implement least_common by borrowing implementation of most_common and performing necessary changes.
Refer to collections source in Py2.7:
def most_common(self, n=None):
'''List the n most common elements and their counts from the most
common to the least. If n is None, then list all element counts.
>>> Counter('abcdeabcdabcaba').most_common(3)
[('a', 5), ('b', 4), ('c', 3)]
'''
# Emulate Bag.sortedByCount from Smalltalk
if n is None:
return sorted(self.iteritems(), key=_itemgetter(1), reverse=True)
return _heapq.nlargest(n, self.iteritems(), key=_itemgetter(1))
To change it in order to retrieve least common we need just a few adjustments.
import collections
from operator import itemgetter as _itemgetter
import heapq as _heapq
class MyCounter(collections.Counter):
def least_common(self, n=None):
if n is None:
return sorted(self.iteritems(), key=_itemgetter(1), reverse=False) # was: reverse=True
return _heapq.nsmallest(n, self.iteritems(), key=_itemgetter(1)) # was _heapq.nlargest
Tests:
c = MyCounter("abbcccddddeeeee")
assert c.most_common() == c.least_common()[::-1]
assert c.most_common()[-1:] == c.least_common(1)
Since your stated goal is to remove items in the counter below a threshold, just reverse the counter (so the values becomes a list of keys with that value) and then remove the keys in the counter below the threshold.
Example:
>>> c=Counter("aaaabccadddefeghizkdxxx")
>>> c
Counter({'a': 5, 'd': 4, 'x': 3, 'c': 2, 'e': 2, 'b': 1, 'g': 1, 'f': 1, 'i': 1, 'h': 1, 'k': 1, 'z': 1})
counts={}
for k, v in c.items():
counts.setdefault(v, []).append(k)
tol=2
for k, v in counts.items():
if k<=tol:
c=c-Counter({}.fromkeys(v, k))
>>> c
Counter({'a': 5, 'd': 4, 'x': 3})
In this example, all counts less than or equal to 2 are removed.
Or, just recreate the counter with a comparison to your threshold value:
>>> c
Counter({'a': 5, 'd': 4, 'x': 3, 'c': 2, 'e': 2, 'b': 1, 'g': 1, 'f': 1, 'i': 1, 'h': 1, 'k': 1, 'z': 1})
>>> Counter({k:v for k,v in c.items() if v>tol})
Counter({'a': 5, 'd': 4, 'x': 3})
If you only want to get the least common value, then the most efficient way to handle this is to simply get the minimum value from the counter (dictionary).
Since you can only say whether a value is the lowest, you actually need to look at all items, so a time complexity of O(n) is really the lowest we can get. However, we do not need to have a linear space complexity, as we only need to remember the lowest value, and not all of them. So a solution that works like most_common() in reverse is too much for us.
In this case, we can simply use min() with a custom key function here:
>>> c = Counter('foobarbazbar')
>>> c
Counter({'a': 3, 'b': 3, 'o': 2, 'r': 2, 'f': 1, 'z': 1})
>>> k = min(c, key=lambda x: c[x])
>>> del c[k]
>>> c
Counter({'a': 3, 'b': 3, 'o': 2, 'r': 2, 'z': 1})
Of course, since dictionaries are unordered, you do not get any influence on which of the lowest values is removed that way in case there are multiple with the same lowest occurrence.

Why does this python dictionary get created out of order using setdefault()?

I'm just starting to play around with Python (VBA background). Why does this dictionary get created out of order? Shouldn't it be a:1, b:2...etc.?
class Card:
def county(self):
c = 0
l = 0
groupL = {} # groupL for Loop
for n in range(0,13):
c += 1
l = chr(n+97)
groupL.setdefault(l,c)
return groupL
pick_card = Card()
group = pick_card.county()
print group
here's the output:
{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4, 'g': 7, 'f': 6, 'i': 9, 'h': 8, 'k': 11, 'j': 10, 'm': 13, 'l': 12}
or, does it just get printed out of order?
Dictionaries have no order in python. In other words, when you iterate over a dictionary, the order that the keys/items are "yielded" is not the order that you put them into the dictionary. (Try your code on a different version of python and you're likely to get differently ordered output). If you want a dictionary that is ordered, you need a collections.OrderedDict which wasn't introduced until python 2.7. You can find equivalent recipes on ActiveState if you're using an older version of python. However, often it's good enough to just sort the items (e.g. sorted(mydict.items()).
EDIT as requested, an OrderedDict example:
from collections import OrderedDict
groupL = OrderedDict() # groupL for Loop
c = 0
for n in range(0,13):
c += 1
l = chr(n+97)
groupL.setdefault(l,c)
print (groupL)

Categories

Resources