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

Code@Pig Home

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

 
 
 

日志

 
 

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

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

  下载LOFTER 我的照片书  |
8. Production

Item 54: Consider Module-Scoped Code to Configure Deployment Environments

开发环境(development environment) 和 生产环境(production envrionment) 的配置常常是不一样的。
比如数据库配置。

如何让代码兼容两者?看看下面的例子:

# ==== dev_main.py ====
TESTING = True
import db_connection
db = db_connection.Database()

print db

# ==== prod_main.py ====
TESTING = False
import db_connection
db = db_connection.Database()

print db

# ==== db_connection ====
import __main__

class TestingDatabase(object):
  pass

class RealDatabase(object):
  pass

if __main__.TESTING:
  Database = TestingDatabase
else:
  Database = RealDatabase


>>>>
$ python dev_main.py
<db_connection.TestingDatabase object at 0x02D41450>

$ python prod_main.py
<db_connection.RealDatabase object at 0x02B813D0>

简单的差异配置,利用 TESTING 这种全局变量即可解决。
复杂的,建议使用标准库中的 configparser。


Item 55: Use repr Strings for Debugging Output

print 5    # 等价于 print '%s' % 5
print '5'  # 等价于 print '%s' % '5'

>>>
5
5

两者输出一样,你无法分辨实际类型是啥。
repr 是 printable representation of an object。

print repr(5)    # 等价于 print '%r' % 5
print repr('5')  # 等价于 print '%r' % '5'

>>>
5
'5'

给你自己写的 class 加上 def __repr__(),方便 %r print。

class Point(object):
  def __init__(self, x, y):
    self.x = x
    self.y = y

  def __repr__(self):
    return 'Point(%r, %r)' % (self.x, self.y)

p = Point(10, 20)
print p

>>>
Point(10,20)

或者,查看 __dict__ 也可以。

p = Point(4, 5)
print p.__dict__
>>>
{'y': 5, 'x': 4}


Item 56: Test Everything with unittest

# utils.py
def to_str(data):
  if isinstance(data, str):
    return data
  elif isinstance(data, bytes):
    return data.decode('utf-8')
  else:
    raise TypeError('Must supply str or bytes, '
                    'found: %r' % data)

# utils_test.py
from unittest import TestCase, main
from utils import to_str

class UtilsTestCase(TestCase):
  def test_to_str_bytes(self):
    self.assertEqual('hello', to_str(b'hello'))

  def test_to_str_str(self):
    self.assertEqual('hello', to_str('hello'))

  def test_to_str_bad(self):
    self.assertRaises(TypeError, to_str, object())

if __name__ == '__main__':
  main()

继承 TestCase,每个函数名以 "test" 开头。
这类函数会自动跑一遍。

python3 utils_test.py

>>>
...
----------------------------------------------------------------------
Ran 3 tests in 0.005s

OK

有时候需要初始化一些数据,才能开始跑 testcase。请利用 setUp() / tearDown()。

class MyTest(TestCase):
  def setUp(self):
    self.test_dir = TemporaryDirectory()

  def tearDown(self):
    self.test_dir.cleanup()

  # Test methods follow
  # ...

其它的 test framework。
nose, http://nose.readthedocs.org/
pytest, http://pytest.org/


Item 57: Consider Interactive Debugging with pdb

利用 pdb,可以很容易给一个 pure python 程序植入 interactive debugger。
def test3():
  a = 10
  print 'a = ', a

def test2():
  print 'before pdb'
  import pdb; pdb.set_trace()
  print 'after pdb'

def test1():
  test2()
  test3()

if __name__ == '__main__':
  test1()

执行效果:
D:\tmp>python pdbtest.py
before pdb
> d:\tmp\pdbtest.py(8)test2()
-> print 'after pdb'

(Pdb) bt
  d:\tmp\pdbtest.py(15)<module>()
-> test1()
  d:\tmp\pdbtest.py(11)test1()
-> test2()
> d:\tmp\pdbtest.py(8)test2()
-> print 'after pdb'

(Pdb) up
> d:\tmp\pdbtest.py(11)test1()
-> test2()

(Pdb) down
> d:\tmp\pdbtest.py(8)test2()
-> print 'after pdb'

其中
bt, 打印 traceback
up, 沿着调用栈上移一个函数
down, 沿着调用栈下移一个函数

我们继续看看有啥指令可用:
(Pdb) next
after pdb
--Return--
> d:\tmp\pdbtest.py(8)test2()->None
-> print 'after pdb'

(Pdb) next
> d:\tmp\pdbtest.py(12)test1()
-> test3()
(Pdb)

(Pdb) step
--Call--
> d:\tmp\pdbtest.py(1)test3()
-> def test3():

(Pdb) step
> d:\tmp\pdbtest.py(2)test3()
-> a = 10

(Pdb) locals()
{}

(Pdb) step
> d:\tmp\pdbtest.py(3)test3()
-> print 'a = ', a

(Pdb) locals()
{'a': 10}

其中
next, 执行下一条指令,如果碰到函数,整个函数执行完,返回 pdb
step,执行下一条指令,如过碰到函数,进入函数,返回 pdb

(Pdb) continue
a = 10

D:\tmp>

continue,继续执行整个程序,不再终端。


Item 58: Profile Before Optimizing

古老的箴言"优化前,先profile一下"。

# ---- test.py ----
def insertion_sort(data):
  result = []
  for value in data:
    insert_value(result, value)
  return result

def insert_value(array, value):
  for i, existing in enumerate(array):
    if existing > value:
      array.insert(i, value)
      return
  array.append(value)


if __name__ == '__main__':
  from random import randint
  max_size = 10**4
  data = [randint(0, max_size) for _ in range(max_size)]
  test = lambda: insertion_sort(data)

  from cProfile import Profile
  profiler = Profile()
  profiler.runcall(test)

  from pstats import Stats
  stats = Stats(profiler)
  stats.strip_dirs()
  stats.sort_stats('cumulative')
  stats.print_stats()
# ---- end test.py ----

> python35.exe test.py
         20003 function calls in 1.633 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.633    1.633 test.py:19(<lambda>)
        1    0.002    0.002    1.633    1.633 test.py:1(insertion_sort)
    10000    1.614    0.000    1.630    0.000 test.py:7(insert_value)
     9991    0.016    0.000    0.016    0.000 {method 'insert' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        9    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects

ncalls, 此函数被调用了几次
tottime, 此函数总耗时。调用其它函数的耗时,不计入
tottime percall, = tottime / ncalls
cumtime, 此函数总耗时。调用其它函数的耗时,计入
cumtime percall, = cumtime / ncalls

从profile结果可以看出,insert_value() 耗时最多,改进下:
from bisect import bisect_left

def insert_value(array, value):
  i = bisect_left(array, value)
  array.insert(i, value)

瞬间性能提升~

> python35.exe test.py
         30003 function calls in 0.021 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.021    0.021 test.py:17(<lambda>)
        1    0.002    0.002    0.021    0.021 test.py:1(insertion_sort)
    10000    0.003    0.000    0.020    0.000 test.py:9(insert_value)
    10000    0.012    0.000    0.012    0.000 {method 'insert' of 'list' objects}
    10000    0.005    0.000    0.005    0.000 {built-in method _bisect.bisect_left}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


Item 59: Use tracemalloc to Understand Memory Usage and Leaks

虽然 Python 有 gc,但代码中漫天飞舞的互相引用,可能导致某个 object
本来没用了得,但不知道不小心被什么地方引用了一下,一直没释放掉。

gc.get_objects() 可用于显示当前 alive 的 object。
waste_memory.run() 会创建一堆 MyObject,测试用。

# using_gc.py
import gc
found_objects = gc.get_objects()
print('%d objects before' % len(found_objects))

import waste_memory
x = waste_memory.run()
found_objects = gc.get_objects()
print('%d objects after' % len(found_objects))
for obj in found_objects[:3]:
  print(repr(obj)[:100])

>>>
4756 objects before
14873 objects after
<waste_memory.MyObject object at 0x1063f6940>
<waste_memory.MyObject object at 0x1063f6978>
<waste_memory.MyObject object at 0x1063f69b0>

下面 treacemalloc 登场啦。(NOTE: Python 3.4+ only)

# top_n.py
import tracemalloc
tracemalloc.start(10)  # Save up to 10 stack frames

time1 = tracemalloc.task_snapshot()
import waste_memory
x = waste_memory.run()
time2 = tracemalloc.task_snapshot()

stats = time2.compare_to(time1, 'lineno')
for stat in stats[:3]:
  print(stat)

>>>
>>>
waste_memory.py:6: size=2235 KiB (+2235 KiB), count=29981 (+29981),average=76 B
waste_memory.py:7: size=869 KiB (+869 KiB), count=10000 (+10000), average=89 B
waste_memory.py:12: size=547 KiB (+547 KiB), count=10000 (+10000), average=56 B


tracemalloc 还可以很方便打印调用栈,看看那个地方的代码导致内存占用过多。

# with_trace.py
# …
stats = time2.compare_to(time1, 'traceback')
top = stats[0]
print('\n'.join(top.traceback.format()))

>>>
File "waste_memory.py", line 6
  self.x = os.urandom(100)
File "waste_memory.py", line 12
  obj = MyObject()
File "waste_memory.py", line 19
  deep_values.append(get_data())
File "with_trace.py", line 10
  x = waste_memory.run()
  评论这张
 
阅读(266)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

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

页脚

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