Write python-like code

On Youtube, Raymond Hettinger (python core developper) explains common mistakes and correct writing in python Video I found? v = OSGv2VnC0go), so I will summarize it. In the video, I mainly use python2 as an example (because it is a 2013 video), but here I converted it to python3 as much as possible. Now that it may already be written in an old way, please use it while checking it as appropriate.

Loop

--Use iterator as much as possible

Simple loop

Bad example


for i in [0, 1, 2, 3, 4, 5]:
  print(i**2)

Put the entire list in memory. ↓

Good example


for i in range(6):
  print(i**2)

Since range is generated one by one as an iterator, memory is not wasted.

In python2, range is a list and xrange is an itertor In python3, range is iterator (name of xrange in python2 has changed)

List loop

Bad example


colors = ['red', 'green', 'blue', 'yellow']

for i in range(len(colors)):
  print(colors[i])

Good example


colors = ['red', 'green', 'blue', 'yellow']

for color in colors:
  print(color)

Writing below is faster

Reverse loop

Bad example


colors = ['red', 'green', 'blue', 'yellow']

for i in range(len(colors)-1, -1, -1):
  print(colors[i])

Good example


colors = ['red', 'green', 'blue', 'yellow']

for color in reversed(colors):
  print(color)

I also want to get an index

Bad example


colors = ['red', 'green', 'blue', 'yellow']

for i in range(len(colors)):
  print(i, '--->', colors[i])

Good example


colors = ['red', 'green', 'blue', 'yellow']

for i, color in enumerate(colors):
  print(i, '--->', color)

Loop two lists at the same time

Bad example


names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']

n = min(len(names), len(colors))
for i in range(n):
  print(names[i], '--->'. colors[i]

Good example


names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']

for name, color in zip(names, colors):
  print(name, '--->', color)

When iterators of different lengths are entered in the zip, they will be aligned to the shorter one. ʻItertools.zip_longest` to align to the longer one

In python2, zip produces a list (ʻizipis an iterator), In python3zip` generates iterator

Custom sort

Bad example


colors = ['red', 'green', 'blue', 'yellow']

def compare_length(c1, c2):
  if len(c1) < len(c2):
    return -1
  elif len(c1) > len(c2):
    return 1
  else:
    return 0

print(sorted(colors, cmp=compare_length)

Good example


colors = ['red', 'green', 'blue', 'yellow']

print(sorted(colors, key=len))

** Is sorting by key sufficient? ** ** In some cases it is not enough, but in most cases it is okay. (SQL does a lot of sorting, but sorts by key)

Stop the loop with sentinel value

Bad example


blocks = []
while True:
  block = f.read(32)
  if block == '':
    break
  blocks.append(block)

Good example


blocks = []

for block in iter(functool.partial(f.read, 32), ''):
  blocks.append(block)

partial is unpleasant, but the merit of being able to handle it as an iterator is great It is better to avoid sentinel value

Get out of the loop depending on the condition

Bad example


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

Example when you have to use a flag (found) ↓

Good example


def find(seq, target):
  for i, value in enumerate(seq):
    if value == target:
      break
  else:
    return -1
  return i

If there is no break in for, the else statement is executed. He regrets that he should have named it nobreak instead of else.

Dictionary

Loop the dictionary key

d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k in d:
  print(k)

↑ Something strange happens when you make changes to the dictionary

d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k in d.keys():
  if k.startswith('r'):
    del d[k]

d.keys () makes a copy of the list in advance, so you can change the dictionary

Loop key and value

Bad example


d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k in d:
  print(k, '--->', d[k])

Good example


d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k, v in d.items():
  print(k, '--->', v)

Create a dictionary from a list

names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue']

d = dict(zip(names, colors))

Count the number of occurrences in the list

Inefficient method


colors = ['red', 'green', 'red', 'blue', 'green', 'red']

d = {}
for color in colors:
  if color in d:
    d[color] = 0
  d[color] += 1

The right way


d = {}
for color in colors:
  d[color] = d.get(color, 0) + 1

Recent method


d = defaultdict(int)
for color in colors:
  d[color] += 1

Group the list

Bad example


names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']

d = {}
for name in names:
  key = len(name)
  if key not in d:
    d[key] = []
  d[key].append(name)

The right way


d = {}
for name in names:
  key = len(name)
  d.setdefault(key, []).append(name)

Recent method


d = defaultdict(list)
for name in names:
  key = len(name)
  d[key].append(name)

get does not assign to the dictionary. setdefault substitutes

Stick multiple dictionaries together

Bad example


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}

d = defaults.copy()
d.update(os.environ)
d.update(command_line_args)

A large number of dictionaries are copied ↓

Good example


d = collections.ChainMap(command_line_args, os.environ, defaults)

Keep the original dictionary as it is without copying

Improved code readability

Function keyword argument

Bad example


twitter_search('@obama', False, 20, True)

I don't understand the meaning of the argument ↓

Good example


twitter_search('@obama', retweets=False, numtweets=20, popular=True)

NamedTuple

Bad example


> doctest.testmod()
(0, 4)

I don't understand the meaning of 0,4 ↓

Good example


> doctest.testmod()
TestResults(failed=0, attempted=4)

TestResults is

TestResults = namedtuple('TestResults', ['failed', 'attempted'])

Can be made with

tuple unpacking

Bad example


p = 'Raymond', 'Hettinger', 0x30, '[email protected]'

fname = p[0]
lname = p[1]
age = p[2]
email = p[3]

Good example


fname, lname, age, email = p

Update multiple states at the same time

Bad example


def fibonacci(n):
  x = 0
  y = 1
  for i in range(n):
    print(x)
    t = y
    y = x + y
    x = t

There is a moment when the state collapses during execution. Easy to get the line order wrong ↓

Good example


def fibonacci(n):
  x, y = 0, 1
  for i in range(n):
    print(x)
    x, y = y, x+y

This is closer to human thinking.

Efficiency (high speed, memory saving)

String concatenation

Bad example


names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']

s = names[0]
for name in names[1:]:
  s += ', ' + name

Good example


', '.join(names)

Update list

Bad example


names = ['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie']

del names[0]
names.pop(0)
names.insert(0, 'mark')

slow ↓

Good example


names = deque(['raymond', 'raychel', 'matthew', 'roger', 'betty', 'melisa', 'judith', 'charlie'])

del names[0]
names.popleft()
names.appendleft('mark')

fast

decorator and context manager

--Separate business logic and administrative logic --The code is clean ――If you don't name it correctly, it will be a mess.

cache

Bad example


def web_lookup(url, saved={}):
  if url in saved:
    return saved[url]
  page = urlib.urlopen(url).read()
  saved[url] = page
  return page

Good example


@lru_cache()
def web_lookup(url):
  return urllib.urlopen(url).read()

Business logic and administrative logic are separated

Temporary context

Bad example


oldcontext = getcontext().copy()
getcontext().prec = 50
print(Decimal(355) / Decimal(113))
setcontext(oldcontext)

Good example


with localcontext(Context(prec=50)):
  print(Decimal(355) / Decimal(113))

Opening and closing files

Bad example


f = open('data.txt')
try:
  data = f.read()
finally:
  f.close()

Good example


with open('data.txt') as f:
  data = f.read()

Thread lock

Bad example


lock = threading.Lock()
lock.acquire()
try:
  print('Critical section 1')
  print('Critical section 2')
finally:
  lock.release()

Good example


lock = threading.Lock()
with lock:
  print('Critical section 1')
  print('Critical section 2')

Ignore error

Bad example


try:
  os.remove('somefile.tmp')
except OSError:
  pass

Good example


with ignored(OSError):
  os.remove('somefile.tmp')

Temporarily replace standard output

Bad example


with open('help.txt', 'w') as f:
  oldstdout = sys.stdout
  sys.stdout = f
  try:
    help(pow)
  finally:
    sys.stdout = oldstdout

Good example


with open('help.txt', 'w') as f:
  with redirect_stdout(f):
    help(pow)

List comprehension

Bad example


result = []
for i in range(10):
  s = i**2
  result.append(a)
print(sum(result))

Good example


print(sum([i**2 for i in range(10)])

Good example


print(sum(i**2 for i in range(10)))

Recommended Posts

Write python-like code
Write standard input in code
Write Spigot in VS Code
Write & compile & run code at codeanywhere.com
Write selenium test code in python
A note for writing Python-like code
Write Ethereum contract code using Serpent
Qiita (1) How to write a code name
Write test-driven FizzBuzz code using Python doctest.
Isn't it okay to write test code?
Character code
Bad code