Last active
November 9, 2022 15:04
-
-
Save grafuls/b8468cc374a258f83572 to your computer and use it in GitHub Desktop.
Python Best practices
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # substracted from https://www.youtube.com/watch?v=OSGv2VnC0go among others | |
| # Raymond Hettinger | |
| # Learn to take better advantage of Python's best features and improve existing code through a series of code transformations, | |
| # "When you see this, do that instead." | |
| from itertools import izip | |
| from functools import partial | |
| from functools import wraps | |
| from collections import defaultdict | |
| from collections import deque | |
| from decimal import localcontext | |
| import random | |
| #Python 3.3 | |
| #from collections import ChainMap | |
| import argparse | |
| """looping over a collection""" | |
| names = ['raymond', 'rachel', 'mathew'] | |
| colors = ['red', 'green', 'blue', 'yellow'] | |
| #NO | |
| #for i in range(len(colors)): | |
| #print colors[i] | |
| for color in colors: | |
| print color | |
| """looping backwards""" | |
| #NO | |
| #for i in range(len(colors)-1, -1, -1): | |
| #print colors[i] | |
| for color in reversed(colors): | |
| print color | |
| """looping over collection and indicies""" | |
| #NO | |
| #for i in range(len(colors)): | |
| #print i, '-->', colors[i] | |
| for i, color in enumerate(colors): | |
| print i, '-->', colors[i] | |
| """looping over 2 collections""" | |
| #NO | |
| #n = min(len(names), len(colors)) | |
| #for i in range(n): | |
| #print names[i], '-->', colors[i] | |
| #NO | |
| #for name, color in zip(names, colors): | |
| #print name, '-->', color | |
| for name, color in izip(names, colors): | |
| print name, '-->', color | |
| """looping in sorted order""" | |
| for color in sorted(colors): | |
| print color | |
| for color in sorted(colors, reverse=True): | |
| print color | |
| """custom sort order""" | |
| #NO | |
| #def compare_length(c1,c2): | |
| #if len(c1) < len(c2): return -1 | |
| #if len(c1) > len(c2): return 1 | |
| #return 0 | |
| #print sorted(colors, cmp=compare_length) | |
| print sorted(colors, key=len) | |
| """call a function until a sentinel value""" | |
| blocks = [] | |
| #NO | |
| #while True: | |
| #block = f.read(32) | |
| #if block == '': | |
| #break | |
| #blocks.append(block) | |
| #YES | |
| #for block in iter(partial(f.read, 32), ''): | |
| #blocks.append(block) | |
| """distinguishing multiple exit points in loops""" | |
| #NO | |
| #def find(seq, target): | |
| #found = False | |
| #for i, value in enumerate(seq): | |
| #if value == target: | |
| #found = True | |
| #break | |
| #if not found: | |
| #return -1 | |
| #return i | |
| def find(seq, target): | |
| for i, value in enumerate(seq): | |
| if value == target: | |
| break | |
| else: | |
| return -1 | |
| return i | |
| """looping over dictionary keys""" | |
| #if you mutate a dictionary while you are iterating through it you are living in a state of sin | |
| d = {'mathew': 'blue', 'rachel': 'green', 'raymond': 'red'} | |
| for k in d: | |
| print k | |
| for k in d.keys(): | |
| if k.startswith('r'): | |
| del d[k] | |
| d = {k : d[k] for k in d if not k.startswith('r')} | |
| """looping over a dictionary keys and values""" | |
| #for k in d: | |
| #print k, '-->', d[k] | |
| #this will create a big list | |
| #for k, v in d.items(): | |
| #print k, '-->', v | |
| for k, v in d.iteritems(): | |
| print k, '-->', v | |
| """construct a dictionary from pairs""" | |
| d = dict(izip(names, colors)) | |
| #{'mathew': 'blue', 'rachel': 'green', 'raymond': 'red'} | |
| """counting with dictionaries""" | |
| #d = {} | |
| #for color in colors: | |
| #if color not in d: | |
| #d[color] = 0 | |
| #d[color] += 1 | |
| d = {} | |
| for color in colors: | |
| d[color] = d.get(color, 0) + 1 | |
| #TODO: distinctions between regular dictionary and default dict | |
| #TODO: factory functions | |
| d = defaultdict(int) | |
| for color in colors: | |
| d[color] += 1 | |
| """grouping with dicts""" | |
| names = ['raymond', 'rachel', 'matthew', 'roger', 'betty', 'melissa', 'judith', 'chaarlie'] | |
| d = {} | |
| for name in names: | |
| key = len(name) | |
| if key not in d: | |
| d[key] = [] | |
| d[key].append(name) | |
| d = {} | |
| for name in names: | |
| key = len(name) | |
| d.setdefault(key, []).append(name) | |
| d = defaultdict(list) | |
| for name in names: | |
| key = len(name) | |
| d[key].append(name) | |
| """is dict popitem() atomic?""" | |
| d = {'mathew': 'blue', 'rachel': 'green', 'raymond': 'red'} | |
| while d: | |
| key, value = d.popitem() | |
| print key, '-->', value | |
| """linking dictionaries""" | |
| defaults = {'color': 'red', 'user': 'guest'} | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('-u', '--user') | |
| parser.add_argument('-c', '--color') | |
| namespace = parser.parse_args([]) | |
| command_line_args = {k:v for k, v in vars(namespace).items() if v} | |
| #NO | |
| #if you want your code to be fast don't copy so much | |
| #d = defaults.copy() | |
| #d.update(os.environ) | |
| #d.update(command_line_args) | |
| #Python 3.3 | |
| #YES!!! | |
| print 'use chainmap!!!' | |
| #d = ChainMap(command_line_args, os.environ, defaults) | |
| """improving clarity""" | |
| """clarity funtion calls with keyword arguments""" | |
| #twitter_search('@obama', False, 20, True) | |
| #YES!!! | |
| print 'use function calls with keyword arguments' | |
| #twitter_search('@obama', retweets=False, numtweets=20, popular=True) | |
| """clarify multiple return valures with named tuples""" | |
| # | |
| #doctest.testmod() | |
| #(0, 4) | |
| #named tuples are a sub class of tuple | |
| #YES!!! | |
| print 'use named tuples!' | |
| #doctest.testmod() | |
| #TestResults(failed=0, attempted=4) | |
| #how? | |
| #TestResults = namedtuple('TestRestls', ['failed', 'attempted']) | |
| """unpacking sequences""" | |
| p = 'Raymond', 'Hettinger', 0x30, '[email protected]' | |
| #fname = p[0] | |
| #lname = p[1] | |
| #age = p[2] | |
| #email = p[3] | |
| fname, lname, age, email = p | |
| """updating multiple state variables""" | |
| #NO | |
| #def fibonacci(n): | |
| #x = 0 | |
| #y = 1 | |
| #for i in range(n): | |
| #print x | |
| #t = y | |
| #y = x + y | |
| #x = t | |
| #q: always xrange? | |
| #a: http://stackoverflow.com/questions/135041/should-you-always-favor-xrange-over-range | |
| def fibonacci(n): | |
| x, y = 0, 1 | |
| for i in range(n): | |
| print x | |
| x, y = y, x + y | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| """concatenating strings""" | |
| names = ['raymond', 'rachel', 'matthew', 'roger', 'betty', 'melissa', 'judith', 'chaarlie'] | |
| #NO | |
| #s = names[0] | |
| #for name in names[1:]: | |
| #s += ', ' + name | |
| #print s | |
| print ', '.join(names) | |
| """updating sequences""" | |
| #del names[0] | |
| #names.pop(0) | |
| #names.insert(0, 'mark') | |
| names = deque(['raymond', 'rachel', 'matthew', 'roger', 'betty', 'melissa', 'judith', 'chaarlie']) | |
| del names[0] | |
| names.popleft() | |
| names.appendleft('mark') | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| """decorators and context managers""" | |
| """using decorators to factor-out administrative logic""" | |
| #def web_lookup(url, saved={}): | |
| #if url in saved: | |
| #return saved[url] | |
| #page = urllib.urlopen(url).read() | |
| #saved[url] = page | |
| #return page | |
| def cache(func): | |
| saved = {} | |
| @wraps(func) | |
| def newfunc(*args): | |
| if args in saved: | |
| return newfunc(*args) | |
| result = func(*args) | |
| saved[args] = result | |
| return result | |
| return newfunc | |
| @cache | |
| def web_lookup(url): | |
| return urllib.urlopen(url).read() | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| """factor-out temporary contexts""" | |
| #old_context = getcontext().copy() | |
| #getcontext().prec = 50 | |
| #print Decimal(355) / Decimal(113) | |
| #setcontext(old_context) | |
| #context manager | |
| print 'use context manager!!!' | |
| #with localcontext(Context(prec=50)): | |
| #print Decimal(355) / Decimal(113) | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| """how to open and close files""" | |
| #f = open('data.txt') | |
| #try: | |
| #data = f.read() | |
| #finally: | |
| #f.close() | |
| with open('data.txt') as f: | |
| data = f.read() | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| """how to use locks""" | |
| # Make a lock | |
| # TODO: threading.lock() ???? | |
| lock = threading.Lock() | |
| #Old way to use a lock | |
| #lock.acquire() | |
| #try: | |
| #print 'Critical section 1' | |
| #print 'Critical section 2' | |
| #finally: | |
| #lock.release() | |
| #new awesome way to use it | |
| with lock: | |
| print 'Critical section 1' | |
| print 'Critical section 2' | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| #NO | |
| #try: | |
| #os.remove('somefile.txt') | |
| #except OSError: | |
| #pass | |
| with ignored(OSError): | |
| os.remove('somefile.txt') | |
| #add this to your utils directory | |
| @contextmanager | |
| def ignored(*exceptions): | |
| try: | |
| yield | |
| except exceptions: | |
| pass | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| #with open('help.txt', 'w') as f: | |
| #oldstdout = sys.stdout | |
| #sys.stdout = f | |
| #try: | |
| #help(pow) | |
| #finally: | |
| #sys.stdout = oldstdout | |
| #TODO: stdout? | |
| with open('help.txt', 'w') as f: | |
| with redirect_stdout(f): | |
| help(pow) | |
| @contextmanager | |
| def redirect_stdout(fileobj): | |
| oldstdout = sys.stdout | |
| sys.stdout = fileobj | |
| try: | |
| yield fileobj | |
| finally: | |
| sys.stdout = oldstdout | |
| """list comprehensions and generator expressions""" | |
| #result = [] | |
| #for i in range(10): | |
| #s = i ** 2 | |
| #result.append(s) | |
| #print sum(result) | |
| #single unit of thought | |
| #print sum([i ** 2 for i in xrange(10)]) | |
| #better way | |
| print sum(i ** 2 for i in xrange(10)) | |
| #TODO: metaclasess? | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| #string concatanation | |
| foo = 'foo' | |
| bar = 'bar' | |
| foobar = '%s%s' % (foo, bar) # It is OK | |
| foobar = '{0}{1}'.format(foo, bar) # It is better | |
| foobar = '{foo}{bar}'.format(foo=foo, bar=bar) # It is best | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| #Select a random item from a list/tuple/data stucture in Python | |
| items = ['here', 'are', 'some', 'strings', 'of', 'which', 'we', 'will', 'select', 'one'] | |
| #NO | |
| #rand_item = items[random.randrange(len(items))] | |
| #NEITHER | |
| #rand_items = [items[random.randrange(len(items))] for item in range(4)] | |
| #YES | |
| rand_item = random.choice(items) | |
| #Why not? | |
| rand_items = random.sample(items, n) | |
| #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- | |
| #NO | |
| #if 'xyz' in self.attr: | |
| #self.attr.remove('xyz') | |
| #YES | |
| try: | |
| self.attr.remove('xyz') | |
| except ValueError: | |
| pass | |
| #Second way is a bit more pythonic as it follows the "It's easier to ask for forgiveness than for permission" principle. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment