注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Code@Pig Home

喜欢背着一袋Code傻笑的Pig .. 忧美.欢笑.记忆.忘却 .之. 角落

 
 
 

日志

 
 

[轻书快读] Effective Python - 59 Specific Ways to Write Better Python (2)  

2016-03-21 09:43:29|  分类: lang_python |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Item 11: Use zip to Process Iterators in Parallel

names = ['Cecilia', 'Lise', 'Marie']
letters = [len(n) for n in names]

longest_name = None
max_letters = 0

# ugly
for i in range(len(names)):
  count = letters[i]
  if count > max_letters:
    longest_name = names[i]
    max_letters = count

# still ugly
for i, name in enumerate(names):
  count = letters[i]
  if count > max_letters:
    longest_name = name
    max_letters = count

# use zip, yeah~
for name, count in zip(names, letters):
  if count > max_letters:
    longest_name = name
    max_letters = count

# NOTE: python 2 中 zip() 不是 generator,需要用 itertools.izip()
from itertools import izip
for x, y in izip(a, b):
  print x, y


# 如过两个list长度不一致,以短的那个为准
a = [1,2,3]
b = [4,5]
for x, y in zip(a, b):
  print x, y

>>>
1 4
2 5

# 如果要以长的那个list为准,可以用 izip_longest。(python 3 名为 itertools.zip_longest)
from itertools import izip_longest
for x, y in izip_longest(a, b):
  print x, y

>>>
1 4
2 5
3 None


Item 12: Avoid else Blocks After for and while Loops

和正常的 else 思维习惯不符合,别用~ 别用就对了~

随便举几个例子:
for i in range(3):
  print('Loop %d' % i)
else:
  print('Else block!')

>>>
Loop 0
Loop 1
Loop 2
Else block!

----

for i in range(3):
  print('Loop %d' % i)
  if i == 1:
    break
else:
  print('Else block!')

>>>
Loop 0
Loop 1

----

for x in []:
  print('Never runs')
else:
  print('For Else block!')

>>>
For Else block!


Item 13: Take Advantage of Each Block in try/except/else/finally

该用 else 的时候用 else,该用 finally 的时候,用 finally。下面给个 all in one 的例子:
UNDEFINED = object()

def divide_json(path):
  handle = open(path, 'r+')       # May raise IOError
  try:
    data = handle.read()          # May raise UnicodeDecodeError
    op = json.loads(data)         # May raise ValueError
    value = (
      op['numerator'] /
      op['denominator'])          # May raise ZeroDivisionError
  except ZeroDivisionError as e:
    return UNDEFINED
  else:
    op['result'] = value
    result = json.dumps(op)
    handle.seek(0)
    handle.write(result)          # May raise IOError
    return value
  finally:
    handle.close()                # Always runs


Item 14: Prefer Exceptions to Returning None

def divide(a, b):
  try:
    return a / b
  except ZeroDivisionError:
    return None

# 正确用法
result = divide(x, y)
if result is None:
  print 'Invalid inputs'

# 但很容易误用
x, y = 0, 5
result = divide(x, y)
if not result:
  print 'Invalid inputs'

对于这种情况,还是推荐不要返回 None 比较好
def divide(a, b):
  try:
    return a / b
  except ZeroDivisionError as e:
    raise ValueError('Invalid inputs') from e

x, y = 5, 2
try:
  result = divide(x, y)
except ValueError:
  print 'Invalid inputs'
else:
  print 'Result is %.1f'%result


Item 15: Know How Closures Interact with Variable Scope

理解 closure 中变量的作用域
def sort_priority(values, group):
  def helper(x):
    if x in group:
      return (0, x)
    return (1, x)
  values.sort(key=helper)

numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
sort_priority(numbers, group)
print numbers

>>>
[2, 3, 5, 7, 1, 4, 6, 8]

这里有个很容易出错的例子:
def sort_priority2(numbers, group):
  found = False
  def helper(x):
    if x in group:
      found = True
      return (0, x)
    return (1, x)
  numbers.sort(key=helper)
  return found

found = sort_priority(numbers, group)
print 'Found:', found
print numbers

>>>
Found: False
[2, 3, 5, 7, 1, 4, 6, 8]

获取一个变量的值
1. The current function's scope
2. Any enclosing scopes (like other containing functions)
3. The scope of the module that contains the code (also called the global scope)
4. The built-in scope (that contains functions like len and str)
If none of these places have a defined variable with the referenced name, then a NameError exception is raised.

给一个变量赋值
1. If the variable is already defined in the current scope, then it will just take on the new value
2. If the variable doesn't exists in the current scope, then Python treats the assignment as a variable definition

# nonlocal - python 3 keyword
def sort_priority2(numbers, group):
  found = False
  def helper(x):
    nonlocal found
    if x in group:
      found = True
      return (0, x)
    return (1, x)
  numbers.sort(key=helper)
  return found

# python 2
def sort_priority3(numbers, group):
  found = [False]
  def helper(x):
    nonlocal found
    if x in group:
      found[0] = True
      return (0, x)
    return (1, x)
  numbers.sort(key=helper)
  return found[0]


Item 16: Consider Generators Instead of Returning Lists

# 直接返回 list,代码看起来有点冗余
def index_words(text):
  result = []
  if text:
    result.append(0)
  for index, letter in enumerate(text):
    if letter == ' ':
      result.append(index + 1)
  return result

address = 'Four score and seven years ago...'
result = index_words(address)
print result[:3]

>>>
[0, 5, 11]

# 改成generator似乎更好
def index_words_iter(text):
  if text:
    yield 0
  for index, letter in enumerate(text):
    if letter == ' ':
      yield index + 1

# 且随时可以通过 list(xxx) 来获得 list result
result = list(index_words_iter(address))


Item 17: Be Defensive When Iterating Over Arguments

def normalize(numbers):
  total = sum(numbers)
  result = []
  for value in numbers:
    percent = 100 * value / total
    result.append(percent)
  return result

visits = [15, 35, 80]
percentages = normalize(visits)
print percentages

>>>
[11.5, 26.9, 61.5]

但如果把一个 generator 丢入 normalize(),无法得到期望的结果,因为 generator 只能遍历一次。
def read_visits(data_path):
  with open(data_path) as f:
    for line in f:
      yield int(line)

it = read_visits('/tmp/my_numbers.txt')
percentages = normalize(it)
print percentages

>>>
[]

来看看 generator 的本质:实现了 iterator protocol 的任何 class,都可以遍历。
 1. for x in foo: 时,会调用 iter(foo),实际调用了 foo.__iter__
 2. foo.__iter__ 返回一个 iterator object (只要其实现了 __next__ 接口)
 3. 然后 for 不停地 next(iterator object),得到返回值,直到碰到 StopIteration。
下面的例子中 ReadVisits.__iter__() 会自动返回一个新的 iterator object。
class ReadVisits(object):
  def __init__(self, data_path):
    self.data_path = data_path

  def __iter__(self):
    with open(self.data_path) as f:
      for line in f:
        yield int(line)

visits = ReadVisits(path)
percentages = normalize(visits)
print percentages

>>>
[11.5, 26.9, 61.5]

最后来看看 defensive 的写法
def normalize_defensive(numbers):
  if iter(numbers) is iter(numbers):  # An iterator - bad!
    raise TypeError('Must supply a container')

  total = sum(numbers)
  result = []
  for value in numbers:
    percent = 100 * value / total
    result.append(percent)
  return result


Item 18: Reduce Visual Noise with Variable Positional Arguments

教你 *args 怎么用,以及有哪些缺点。

看一个例子。第二个参数如果是个空 list,调用时看起来不那么优雅。
def log(message, values):
  if not values:
    print message
  else:
    values_str = ', '.join(str(x) for x in values)
    print '%s: %s' % (message, values_str)

log('My numbers are', [1, 2])
log('Hi there', [])            # <-- ugly

>>>
My numbers are: 1, 2
Hi there

我们可以改用 *args,让代码看起来优美一些。
def log(message, *values):  # The only difference
  if not values:
    print message
  else:
    values_str = ', '.join(str(x) for x in values)
    print '%s: %s' % (message, values_str)

log('My numbers are', 1, 2)
log('Hi there')              # Much better

>>>
My numbers are: 1, 2
Hi there

当然,缺点就是如果你新增了参数,那老代码就得对应修改了
def log(sequence, message, *values):
  if not values:
    print '%s: %s' % (sequence, message)
  else:
    values_str = ', '.join(str(x) for x in values)
    print '%s: %s: %s' % (sequence, message, values_str)

log(1, 'Favorites', 7, 33)     # New usage is OK
log('Favorite numbers', 7, 33) # Old usage breaks

>>>
1: Favorites: 7, 33
Favorite numbers: 7: 33


Item 19: Provide Optional Behavior with Keyword Arguments

好像,没啥可说的。就是介绍 **kwargs 怎么用。


Item 20: Use None and Docstrings to Specify Dynamic Default Arguments

第一次使用 default argument,python 的行为一定会让你感到意外的。
因为 default argument 只在 module 加载时计算一次,因此 when 是固定值。
def log(message, when=datetime.now()):
  print '%s: %s' % (when, message)

log('Hi there!')
sleep(0.1)
log('Hi again!')

>>>
2014-11-15 21:10:10.371432: Hi there!
2014-11-15 21:10:10.371432: Hi again!

需要 dynamic default argument,可以这样
def log(message, when=None):
  """Log a message with a timestamp.

  Args:
    message: Message to print.
    when: datetime of when the message occurred.
      Defaults to the present time.
  """
  when = datetime.now() if when is None else when
  print '%s: %s' % (when, message)

再看另一个例子
def decode(data, default={}):
  try:
    return json.loads(data)
  except ValueError:
    return default

foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print 'Foo:', foo
print 'Bar:', bar

>>>
Foo: {'stuff': 5, 'meep': 1}
Bar: {'stuff': 5, 'meep': 1}

一不小心的悲剧了。正确写法是:
def decode(data, default=None):
  """Load JSON data from a string.

  Args:
    data: JSON data to decode.
    default: Value to return if decoding fails.
      Defaults to an empty dictionary.
  """
  if default is None:
    default = {}
  try:
    return json.loads(data)
  except ValueError:
    return default
  评论这张
 
阅读(201)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017