设为首页 | 加入收藏

欢迎访问彩票大赢家双色走势图-彩票大赢家走势图旧版-彩票大赢家双色球软件

彩票大赢家双色球软件 >> 彩票大赢家双色走势图-Python 从业十年是种什么体会?老程序员的一篇万字经历共享

花下猫语:我“触摸” Python 已有十年了,最初咱们要做一个网站,有个学弟只花两天,就用 Django 开发好了后台。那是我第一次感触到了 Python 的强壮魅力。不过,我正式学习和运用它,才仅有两年时刻……

标题中说的“从业十年”,并不是指我,而是下面文章的作者。像他具有这么长的 Python 阅历的程序员并不多见,写成文章同享出来的就更少见了。所以这篇文章还挺有价值的,内容很丰厚,特同享给咱们,主张保藏。


作者:laisky(根据 CC BY 4.0 授权答应)

原题:Python之路(内容略有调整)

来历:https://laisky.com/p/python-road

一、概述

本文来源于我在 Twitter 上发布的关于 Python 阅历的一系列论题。

出于某些原因,想记载一下我曩昔数年运用 Python 的阅历和一些感悟。 究竟算是一门把我带入互联网职业的言语,而我近期现已简直不再写 Py 代码, 做一个记载,或许会对他人起到些微的协助,也算是留念与感恩了。

二、摘录

推文地址:https://twitter.com/ppcelery/status/1159620182089728000


最早触摸 py 是 2010 年左右,那之前首要是运用 c、fortran 和 matlab 做数值运算。其时在做一些文件文本处理时觉得很费事,后来看到 NASA 说要用 py 替代 matlab,就去触摸了 py。

python 那极为简练与美丽的语法给了其时的我极大的震慑,时至今日,写 py 代码对我而言仍然是一种带有艺术意味的享用。


首要开宗明义的说一句:python 并不慢,至少不行慢。拿一个 web 后端来说,一台废物 4 核虚机,跑 4 个同步堵塞的 django,假定 django 上合理运用线程分管了堵塞操作,假定每节点每秒能够处理 50 个恳求(超轻视),在白日的 10 小时内就能够处理 720 万恳求。而这种机器跑一天仅需求 20 块钱。


在学习 Python 从前需求着重的是:根底语法十分重要。尽管咱们都不推重过多的死记硬背,可是少数必要的死背是今后全部杂乱思想活动的根底,就像五十音关于日语,通假字和常用动名词关于文言文,你不会便是不可。

一般以为,这包含数据类型(值/引证)、效果域(scope)、keyword、builtin 函数等


关于 Python 版别的挑选,许多公司老项目仍然在用 2.6、2.7,新项意图话主张至少挑选 3.6(具有安稳的 asyncio)。

  • 从 2.7 到 3.4 https://blog.laisky.com/p/whats-new-in-python3-4/
  • 从 3.4 到 3.5 https://blog.laisky.com/p/whats-new-in-python3-5/
  • 从 3.5 到 3.6 https://blog.laisky.com/p/whats-new-in-python3-6/
  • 从 3.6 到 3.7 https://docs.python.org/zh-cn/3/whatsnew/3.7.html

关于版别终究在说几点,主张在本地和服务器上都经过 pyenv 来办理版别,而不要去动体系自带的 python(防止引起额定的费事) https://blog.laisky.com/p/pyenv/

别的一点便是,假如你想写一个兼容 2、3 的东西包,你能够考虑运用 future http://python-future.org/compatible_idioms.html

终究提示一下,2to3 这个脚本是有或许犯错的。


学完根底就能够开端动手写代码了,这时分应该谨记恪守一些“通行标准”,几年前给公司内同享时做过一个摘要:

  • 风格指引 https://laisky.github.io/style-guide-cn/style-guides/source-code-style-guides/
  • 一些留意事项 https://laisky.github.io/style-guide-cn/style-guides/consensuses/

有了必定的实践阅历后,你应该学习更多的包来进步自己的代码水平。

  • 值得学习的内建包 https://pymotw.com/3/
  • 值得了解的第三方包 https://github.com/vinta/awesome-python

由于 py 的哲学(import this)主张应该有且仅有一个完美的办法做一件事,所以主张优先选用且完善既有项目而不主张过多的造轮子。


一个小插曲,写这段的 Tim Peters 便是创造 timsort 的那位。

https://en.wikipedia.org/wiki/Tim_Peters_(software_engineer)


有空时分,主张尽或许的完好读教材和文档,树立体系性的常识体系,这能够极大的进步你的视野和思想能力。我自己读过且觉得值得引荐的针对 py 的书本有:

  • https://docs.python.org/3/
  • learning python
  • 中心编程
  • 改善Python的91个主张
  • Python高手之路
  • Python源码剖析
  • 数据结构与算法:Python言语描绘

假如你真的很喜爱 Python 的话,那我觉得你应该也会喜爱阅览 PEP,记住几年前我只需有空就会去翻阅 PEP,这适当所以 Py 的 RFC,里边记载了简直每一项语法的规划理念与意图。我特别喜爱的 PEP 有:

  • 8
  • 3148
  • 380
  • 484 & 3107
  • 492: async
  • 440
  • 3132
  • 495 你甚至能学到历史常识


从前听他人讲过一个比方,静态言语是吃冒菜,一次性烫好。而动态言语是涮火锅,吃一点涮一点。

那么我觉得,GIL 便是仅有一双筷子的火锅,即便你菜许多,一次也只能涮一个。

可是,关于 I/O bound 的操作,你不用一向夹着菜,而是能够夹一些扔到锅里,这样就能够一起涮许多,进步并行功率。


GIL 在一个进程内,解说器仅能一起解说履行一条句子,这为 py 供给了天然的句子级线程安全,从许多含义上说,这都极大的简化了并行编程的难度。关于 I/O 型运用,多线程并不会遭到多大影响。关于 CPU 型运用,编写一个根据 Queue 的多进程 worker 其实也便是几行的事。

(修订:应为伪指令级的线程安全)

 
from time import sleep
from concurrent.futures import ProcessPoolExecutor, wait
from multiprocessing import Manager, Queue


N_PARALLEL = 5


def worker(i: int, q: Queue) -> None:
print(f'worker {i} start')
while 1:
data = q.get()
if data is None: # 选用毒丸(poison pill)办法来结束进程池
q.put(data)
print(f'worker {i} exit')
return

print(f'dealing with data {data}...')
sleep(1)




def main():
executor = ProcessPoolExecutor(max_workers=N_PARALLEL) # 操控并发量
with Manager() as manager:
queue = manager.Queue(maxsize=50) # 操控缓存量

workers = [executor.submit(worker, i, queue) for i in range(N_PARALLEL)]
for i in range(50):
queue.put(i)

print('all task data submitted')

queue.put(None)
wait(workers)
print('all done')


main()


我常常给新人讲,是否能慎重的对待并行编程,是一个区别初级和资深后端开发的分水岭。业界有一句老话:“没有正确的并行程序,只要不行量的并行度”,由此可见并行开发的杂乱程度。

我个人以为考虑并行时首要是在考虑两个问题:同步操控和资源用量。


关于同步操控,你在 thread, multiprocessing, asyncio 几个包里都会发现一系列的东西:

  • Lock 互斥锁
  • RLock 可重入锁
  • Queue 行列
  • Condition 条件锁
  • Event 作业锁
  • Semaphore 信号量

这个就不打开细谈了,归于另一个言语无关的大范畴。(从前写过一个很简略的简介:并行编程中的各种锁(https://blog.laisky.com/p/concurrency-lock/))


关于资源操控,一般来说首要便是两个当地:

  • 缓存区有多大(Queue 长度)
  • 并发量有多大(workers 数量)

一般来说,前者直接确认了你内存的耗费量,最好挑选一个刚好或略高于消费量的数。后者一般直接决议了你的 CPU 运用率,过高的并发量会添加切换开支,因小失大。


已然说到了 workers,略微简略打开一下“池”这个概念。咱们常常说到线程池、进程池、衔接池。说白了便是关于一些可重用的资源,不用每次都创立新的,而是运用结束后收回留下下一个数据持续运用。比方你能够挑选不断地开子线程,也能够挑选预先开好一批线程,然后经过 queue 来不断的获取和处理数据。


所以说运用“池”的首要意图便是削减资源的耗费。另一个长处是,运用池能够十分便利的操控并发度(许多新人以为 Queue 是用来操控并发度的,这是过错的,Queue 操控的是缓存量)。

关于衔接池,还有另一层优点,那便是端口资源是有限的,并且收回端口的速度很慢,你不断的创立衔接会导致端口敏捷耗尽。


这儿做一个用语的修订。Queue 操控的应该是缓冲量(buffer),而不是缓存量(cache)。一般来说,咱们习气大将写入行列称为缓冲,将读取行列称为缓存(有源)。


对前面介绍的 python 中进程/线程做一个小结,线程池能够用来处理 I/O 的堵塞,而进程能够用来处理 GIL 对 CPU 的约束(由于每一个进程内都有一个 GIL)。所以你能够开 N 个(小于等于核数)进程池,然后在每一个进程中发动一个线程池,全部的线程池都能够订阅同一个 Queue,来完结真实的多核并行。


十分简略的描绘一下进程/线程,关于操作体系而言,能够以为进程是资源的最小单位(在 PCB 内保存如图 1 的数据)。而线程是调度的最小单位。同一个进程内的线程同享除栈和寄存器外的全部数据。

所以在开发时分,要当心进程内多线程数据的抵触,也要留意多进程数据间的阻隔(需求特别运用进程间通讯)

  • 操作体系笔记:进程(https://blog.laisky.com/p/os-process/)
  • 操作体系笔记:调度(https://blog.laisky.com/p/os-scheduler/)

再简略的弥补一下,进程间通讯的手法有:管道、信号、音讯行列、信号量、同享内存和套接字。不过在 Py 里,单机上最常用的进程间通讯便是 multiprocessing 里的 Queue 和 sharedctypes。

顺带一提,由于 CPython 的 refcnt 机制,所以 COW(copy on write)并不牢靠。


人们在见到他人的“过错写法”时,倾向于无视或吐槽挖苦。可是这个行为除了让自己爽一下外没有任何含义,不明白的仍是不明白,终究真实发挥影响的仍是那些能够描绘一整条学习途径的办法。

我一向希望能看到一个“朴素诚实”的切合工程实践的教程,而不是网上撒播的入门大全和网课兜销骗钱的结构调参速成。


关于进程间的内存阻隔,弥补一个简略直观的比如。能够看到一般变量 normal_v 在两个子进程内变成了两个独立的变量(都输出 1),而同享内存的 shared_v 仍然是同一个变量,别离输出了 1 和 2。

 
from time import sleep
from concurrent.futures import ProcessPoolExecutor, wait
from multiprocessing import Manager, Queue
from ctypes import c_int64


def worker(i, normal_v, shared_v):
normal_v += 1 # 由于进程间内存阻隔,所以每个进程都会得到 1
shared_v.value += 1 # 由于运用了同享内存,所以会别离得到 1 和 2

print(f'worker[{i}] got normal_v {normal_v}, shared_v {shared_v.value}')


def main():
executor = ProcessPoolExecutor(max_workers=2)
with Manager() as manager:
lock = manager.Lock()
shared_v = manager.Value(c_int64, 0, lock=lock)
normal_v = 0

workers = [executor.submit(worker, i, normal_v, shared_v) for i in range(2)]
wait(workers)
print('all done')


main()

从曩昔的作业阅历中,我总结了一个简略粗犷的规则:假如你要运用多进程,那么在程序发动的时分就把进程池发动起来,然后需求任何资源都请在进程内自行创立运用。假如有数据需求同享,必定要显式的选用同享内存或 queue 的办法进行传递。

见过太多在进程间同享不应同享的东西而导致的极为怪异的数据行为。


最早,一台机器自始至终只精干一件作业。

后来,有了分时体系,咱们能够开许多进程,一起干许多事。

可是进程的上下文切换开支太大,所以又有了线程,这样一个核能够一向跑一个进程,而仅需求切换进程内人线程的栈和寄存器。

直到遇到了 C10K 问题,人们发觉切换几万个线程仍是挺重的,是否能更轻?


这儿简略的打开一下,内存在操作体系中会被区分为内核态和用户态两部分,内核态供内核运转,用户态供一般的程序用。


运用程序经过体系 API(俗称 syscall)和内核发作交互。拿常见的 HTTP 恳求来说,其实便是一次同步堵塞的 socket 调用,每次调用都会导致线程堵塞等候内核呼应(内核堕入)。


而被堵塞的线程就会导致切换的发作。所以自然会问,能不能削减这种切换开支?换句话说,能不能在一个当地把作业做完,而不要切来切去的。

这个问题有两个处理思路,一是把全部的作业放进内核去做(略)。

另一个思路便是把尽或许多的作业放到用户态来做。这需求内核接口供给额定的支撑:异步体系调用。


如 socket 这样的调用就支撑非堵塞调用,调用后会拿到一个未安排妥当的 fp,将这个 fp 交给担任办理 I/O 多路复用的 selector,再注册好需求监听的作业和回调函数(或许像 tornado 相同选用守时 poll),就能够在作业安排妥当(如 HTTP 恳求的回来已安排妥当)时履行相关函数。

https://github.com/tornadoweb/tornado/blob/f1824029db933d822f5b0d02583e4e6137f2bfd2/tornado/ioloop.py#L746


这样就能够完结在一个线程内,发动多个从前会导致线程被切换的体系调用,然后在一个线程内监听这些调用的作业,谁先安排妥当就处理谁,将切换的开支降到了最小。

有一个需求特别留意的关键,你会发现主线程其实便是一个死循环,全部的调用都发作在这个循环之内。所以,你写的代码必定要防止任何堵塞。


听上去很夸姣,这是个全能计划吗?

很可惜不是的,最直接的一个问题是,并不是全部的 syscall 都供给了异步办法,关于这种调用,能够用线程池进行封装。关于 CPU 密集型调用,能够用进程池进行封装,asyncio 里供给了 executor 和协程进行联动的办法,这儿供给一个线程池的简略比如,进程池其实同理。

 
from time import sleep
from asyncio import get_event_loop, sleep as asleep, gather, ensure_future
from concurrent.futures import ThreadPoolExecutor, wait, Future
from functools import wraps


executor = ThreadPoolExecutor(max_workers=10)
ioloop = get_event_loop()


def nonblocking(func) -> Future:
@wraps(func)
def wrapper(*args):
return ioloop.run_in_executor(executor, func, *args)
return wrapper


@nonblocking # 用线程池封装无法协程化的一般堵塞程序
def foo(n: int):
"""伪装我是个很耗时的堵塞调用"""
print('start blocking task...')
sleep(n)
print('end blocking task')


async def coroutine_demo(n: int):
"""我便是个一般的协程"""

# 协程内不能呈现任何的堵塞调用,所谓一朝协程,永世协程
# 那我偏要调一个一般的堵塞函数怎么办?
# 最简略的办法,套一个线程池…
await foo(n)


async def coroutine_demo_2():
print('start coroutine task...')
await asleep(1)
print('end coroutine task')


async def coroutine_main():
"""一般咱们会写一个 coroutine 的 main 函数,专门担任办理协程"""
await gather(
coroutine_demo(1),
coroutine_demo_2()
)


def main():
ioloop.run_until_complete(coroutine_main())
print('all done')


main()
  • Python3 asyncio 简介(https://blog.laisky.com/p/asyncio/)

上面的比如全部都根据 3.7,假如你还在运用 Py2,那么你也能够经过 gevent、tornado 用上协程。

我个人倾向于 tornado,由于更为白盒,并且写法和 3 挨近,假如你也附和,那么能够试试我从前给公司写的 kipp 库,根据 tornado 封装了更多的东西。

https://github.com/Laisky/kipp/blob/2bc5bda6e7f593f89be662f46fed350c9daabded/kipp/aio/__init__.py

Gevent Demo:

 
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Gevent Pool & Child Tasks
=========================

You can use gevent.pool.Pool to limit the concurrency of coroutines.

And you can create unlimit subtasks in each coroutine.


Benchmark
=========

cost 2.675039052963257s for url http://httpbin.org/
cost 2.66813588142395s for url http://httpbin.org/ip
cost 2.674264907836914s for url http://httpbin.org/user-agent
cost 2.6776888370513916s for url http://httpbin.org/get
cost 3.97711181640625s for url http://httpbin.org/headers
total cost 3.9886841773986816s
"""
import time

import gevent
from gevent.pool import Pool
import gevent.monkey


pool = Pool(10) # set the concurrency limit
gevent.monkey.patch_socket()

try:
import urllib2
except ImportError:
import urllib.request as urllib2


TARGET_URLS = (
'http://httpbin.org/',
'http://httpbin.org/ip',
'http://httpbin.org/user-agent',
'http://httpbin.org/headers',
'http://httpbin.org/get',
)


def demo_child_task():
"""Sub coroutine task"""
gevent.sleep(2)


def demo_task(url):
"""Main coroutine

You should wrap your each task into one entry coroutine,
then spawn its own sub coroutine tasks.
"""
start_ts = time.time()
r = urllib2.urlopen(url)
demo_child_task()
print('cost {}s for url {}'.format(time.time() - start_ts, url))


def main():
start_ts = time.time()
pool.map(demo_task, TARGET_URLS)
print('total cost {}s'.format(time.time() - start_ts))


if __name__ == '__main__':
main()

tornado demo:

 
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
cost 0.5578329563140869s, get http://httpbin.org/get
cost 0.5621621608734131s, get http://httpbin.org/ip
cost 0.5613000392913818s, get http://httpbin.org/user-agent
cost 0.5709919929504395s, get http://httpbin.org/
cost 0.572376012802124s, get http://httpbin.org/headers
total cost 0.5809519290924072s
"""
import time

import tornado
import tornado.web
import tornado.httpclient


TARGET_URLS = [
'http://httpbin.org/',
'http://httpbin.org/ip',
'http://httpbin.org/user-agent',
'http://httpbin.org/headers',
'http://httpbin.org/get',
]


@tornado.gen.coroutine
def demo_hanlder(ioloop):
for i, url in enumerate(TARGET_URLS):
demo_task(url, ioloop=ioloop)


@tornado.gen.coroutine
def demo_task(url, ioloop=None):
start_ts = time.time()
http_client = tornado.httpclient.AsyncHTTPClient()
r = yield http_client.fetch(url)
# r is the response object
end_ts = time.time()
print('cost {}s, get {}'.format(end_ts - start_ts, url))
TARGET_URLS.remove(url)
if not TARGET_URLS:
ioloop.stop()


def main():
start_ts = time.time()
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.add_future(demo_hanlder(ioloop), lambda f: None)
ioloop.start()

# total cost will equal to the longest task
print('total cost {}s'.format(time.time() - start_ts))


if __name__ == '__main__':
main()

kipp demo:

 
from time import sleep

from kipp.aio import coroutine2, run_until_complete, sleep, return_in_coroutine
from kipp.utils import ThreadPoolExecutor, get_logger


executor = Thr彩票大赢家双色走势图-Python 从业十年是种什么体会?老程序员的一篇万字经历共享eadPoolExecutor(10)
logger = get_logger()


@coroutine2
def coroutine_demo():
logger.info('start coroutine_demo')
yield sleep(1)
logger.info('coroutine_demo done')
yield executor.submit(blocking_func)
return_in_coroutine('yeo')


def blocking_func():
logger.info('start blocking task...')
sleep(1)
logger.info('blocking task return')
return 'hard'


@coroutine2
def coroutine_main():
logger.info('start coroutine_main')
r = yield coroutine_demo()
logger.info('coroutine_demo return: {}'.format(r))

yield sleep(1)
return_in_coroutine('coroutine_main yo')


def main():
f = coroutine_main()
run_until_complete(f)
logger.info('coroutine_main return: {}'.format(f.result()))


if __name__ == '__main__':
main()


运用 tornado 时需求留意,由于它依靠 generator 来模仿协程,所以函数无法回来,只能用 raise gen.Return 来模仿。3.4 里引进了 yield from 到 3.6 的 async/await 才算彻底处理了这个问题。还有便是当心 tornado 里的 Future 不是线程安全的。

至于 gevent,容我吐个槽,求别再提 monkey_patch 了…


https://docs.python.org/3/library/asyncio-task.html 官方文档关于 asyncio 的描绘很明晰易懂,引荐一读。 一个小提示,async 函数被调用后会创立一个 coroutine,这时分该协程并不会运转,需求经过 ensure_future 或 create_task 办法生成 Task 后才会被调度履行。

别的,一个进程内不要创立多个 ioloop。


做一个小结,一个简略的做法是,发动程序后,别离创立一个进程池(进程数小于等于可用核数)、线程池和 ioloop,ioloop 担任调度全部的协程,遇到堵塞的调用时,I/O 型的扔进线程池,CPU 型的扔进进程池,这样代码逻辑简略,还能尽或许的运用机器功用。 一个简略的完好示例:

 
"""
✗ python process_thread_coroutine.py

[2019-08-11 09:09:37,670Z - INFO - kipp] - main running...
[2019-08-11 09:09:37,671Z - INFO - kipp] - coroutine_main running...
[2019-08-11 09:09:37,671Z - INFO - kipp] -彩票大赢家双色走势图-Python 从业十年是种什么体会?老程序员的一篇万字经历共享 io_blocking_task running...
[2019-08-11 09:09:37,690Z - INFO - kipp] - coroutine_task running...
[2019-08-11 09:09:37,691Z - INFO - kipp] - coroutine_error running...
[2019-艺术08-11 09:09:37,691Z - INFO - kipp] - coroutine_error end, cost 0.00s
[2019-08-11 09:09:37,693Z - INFO - kipp] - cpu_blocking_task running...
[2019-08-11 09:09:38,674Z - INFO - kipp] - io_blocking_task end, cost 1.00s
[2019-08-11 09:09:38,695Z - INFO - kipp] - coroutine_task end, cost 1.00s
[2019-08-11 09:09:39,580Z - INFO - kipp] - cpu_blocking_task end, cost 1.89s
[2019-08-11 09:09:39,582Z - INFO - kipp] - coroutine_main got [None, AttributeError('yo'), None, None]
[2019-08-11 09:09:39,582Z - INFO - kipp] - coroutine_main end, cost 1.91s
[2019-08-11 09:09:39,582Z - INFO - kipp] - main end, cost 1.91s
"""


from time import sleep, time
from asyncio import get_event_loop, sleep as asleep, gather, ensure_future, iscoroutine
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, wait
from functools import wraps

from kipp.utils import get_logger


logger = get_logger()


N_FORK = 4
N_THREADS = 10

thread_executor = ThreadPoolExecutor(max_workers=N_THREADS)
process_executor = ProcessPoolExecutor(max_workers=N_FORK)
ioloop = get_event_loop()


def timer(func):
@wraps(func)
def wrapper(*args, **kw):
logger.info(f"{func.__name__} running...")
start_at = time()
try:
r = func(*args, **kw)
finally:
logger.info(f"{func.__name__} end, cost {time() - start_at:.2f}s")

return wrapper


def async_timer(func):
@wraps(func)
async def wrapper(*args, **kw):
logger.info(f"{func.__name__} running...")
start_at = time()
try:
return await func(*args, **kw)
finally:
logger.info(f"{func.__name__} end, cost {time() - start_at:.2f}s")

return wrapper


@timer
def io_blocking_task():
"""I/O 型堵塞调用"""
sleep(1)


@timer
def cpu_blocking_task():
"""CPU 型堵塞调用"""
for _ in range(1 << 26):
pass


@async_timer
async def coroutine_task():
"""异步协程调用"""
await asleep(1)


@async_timer
async def coroutine_error():
"""会抛出反常的协程调用"""
raise AttributeError("yo")


@async_timer
async def coroutine_main():
ioloop = get_event_loop()
r = await gather(
coroutine_task(),
coroutine_error(),
ioloop.run_in_executor(thread_executor, io_blocking_task),
ioloop.run_in_executor(process_executor, cpu_blocking_task),
return_exceptions=True,
)
logger.info(f"coroutine_main got {r}")


@timer
def main():
get_event_loop().run_until_complete(coroutine_main())


if __name__ == "__main__":
main()


学到这一步,你现已能够娴熟的运用协程、线程、进程处理不同类型的使命。接着拿上面说到的废物 4 核虚机举例,你现在应该能够比较轻松的完结到达 1k QPS 的服务,在白日十小时里能够处理超越一亿恳求,费用仍然仅 20元/天。你还有什么托言说是由于 Python 慢呢?


人们在聊到言语/结构/东西功用时,考虑的是“当程序员尽或许的优化后,东西功用会成为终究的瓶颈,所以咱们必定要选一个最快的”。

但事实上是,程序员自身才是功用的最大瓶颈,而东西真实表现出来的价值,是在程序员很烂时,所能供给的兜底功用。

假如你觉得自己并不是那个瓶颈,那也没必要来听我讲了


在功用优化上有两句老话:

  • 必定要针对瓶颈做优化
  • 过早优化是万恶之源

所以我觉得要敞开、冷静地看待东西的功用。在一套完好的事务体系中,结构东西往往是耗时占比最低的那个,在扩容、缓存技能如此兴旺的今日,你现已很难说出东西功用不行这样的话了。

生长的空间很大,多在自己身上找原因。


一个阅历调查,即便在作业中不断的实践操练,关于异步协程这种全新的思想方式,从学会到能在作业中娴熟运用且不犯大错,比较聪明的人也需求一个月。

换成 go 也不会好许多,await 也能完结同步写法,并且你仍然需求面临我前文说到过的同步操控和资源用量两个中心问题。


简略提一下功用剖析,py 能够运用 cProfile、line_profiler、memory_profiler、vprof、objgraph 等东西生成耗时、内存占用、调用联系图、火焰图等。

关于功用剖析范畴的更多办法论和理念,引荐阅览《功用之巅》(曩昔做的关于功用之巅的部分摘录 https://twitter.com/ppcelery/status/1051832271001382912)。

有必要着重:优化有必要要有满足的数据支撑,包含优化前和优化后。


功用优化其实是一个十分杂乱的范畴,尽管上面说到的东西能够生成林林总总的看上去就很厉害的图,可是优化不是简略的你看哪慢就去改哪,而是需求有极端厚实的根底常识和大局思想的。

并且,上述东西得出的目标,在功用没有迫临极限时,或许会有适当大的误导性,运用的时分也要当心。


有一些较为普适的阅历:

  • I/O 越少越好,尽量在内存里完结
  • 内存分配越少越好,尽量复用
  • 变量尽或许少,gc 友爱
  • 尽量进步局部性
  • 尽量用内建函数,不要草率造彩票大赢家双色走势图-Python 从业十年是种什么体会?老程序员的一篇万字经历共享轮子

下列办法如非瓶颈不要轻易用:

  • 循环打开
  • 内存对齐
  • zero copy(mmap、sendfile)

测验是开发人员很简略忽视的一个环节,许多人以为交给 QA 即可,但其实测验也是开发过程中的一个重要组成部分,不光能够进步软件的交给质量,还能够增进你的代码组织能力。

最常见的区分能够称之为黑盒 & 白盒,前者是只针对接口行为的测验,后者是深化了解完结细节,针对完结办法进行的针对性测验。


对 Py 开发者而言,最简略有用的东西便是 unitest.TestCase 和 pytest,在包内任何故 test*.py 命名的文件,内含 TestCase 类的以 test* 命名的办法都会被履行。

测验办法也很简略,你给定入参,然后调用想要测验的函数,然后查看其回来是否符合需求,不符合就抛出反常。

https://docs.彩票大赢家双色走势图-Python 从业十年是种什么体会?老程序员的一篇万字经历共享pytest.org/en/lates彩票大赢家双色走势图-Python 从业十年是种什么体会?老程序员的一篇万字经历共享t/

 
"""
test_demo.py
"""

from unittest import TestCase
from typing import List

def demo(l: List[int]) -> int:
return l[0]

class DemoTestCase(TestCase):

def setUp(self):
print("first run")

def tearDown(self):
print("last run")

def test_demo(self):
data = []
self.assertRaises(IndexError, demo, data)


开端写测验后,你才会认识到你的许多函数十分难以测验。由于它们或许有嵌套调用,或许有内含状况,或许有外部依靠等等。

可是需求着重的是,这不光不是不写测验的理由,这其实正是写测验的意图!

经过努力地写测验,会逼迫你开端编写精简、功用单一、无状况、依靠注入、防止链式调用的函数。


一个简略直观的“好坏比照”,链式调用的函数很难测验,它内含了太多其他函数的调用,一旦测验就变成了一个“集成测验”。而将其依照过程逐个拆分后,就能够对其进行精细化的“单元测验”,这能够符合你开发的脚步,稳扎稳打稳步推动。

 
"""
这是很糟糕的链式调用
"""

def main():
func1()


def func1():
return func2()

def func2():
return func3()

def func3():
return "shit"



"""
这样写会好许多
"""
def step1():
return "yoo"


def step2(v):
return f"hello, {v}"


def step3(v):
return f"you know nothing, {v}"


def main():
r1 = step1()
r2 = step2(r1)
step3(r2)


顺带一提,关于一些无法绕开的外部调用,如网络恳求、数据库恳求。单元测验的原则之一便是“扫除全部外部要素”,你不应该建议任何真实的外部调用的,由于这会引进不可控的数据。 正确做法是经过依靠注入 Mock 目标,或许经过 patch 去改写调用的接口目标。

从前写过一篇简介:https://blog.laisky.com/p/unittest-mock/


单元测验应该统筹黑盒、白盒。你既应该编写面临接口的事例,也应该尽或许的打听内部的完结途径(添加覆盖率)。

你还能够逐渐地把线上遇到的各种 bug 都编写为事例,这些事例会成为项目名贵的财富,为回归测验供给强有力的支撑。并且有这么多测验事例供给维护,coding 的时分也会安心许多。


在单元测验的根底上,人们开展出了 TDD,可是在实践的过程中,发现有些“奸刁的”开发会针对事例的特例进行编程。 为此,人们决议应该扔掉方式,回归根源,从办法论的高度来探寻测验的路途。其间光亮一方,便是 PBT,企图经过描绘问题的本质,来主动生成测验事例。

一篇简介:https://blog.laisky.com/p/pbt-hypothesis/


另一个漆黑的方向便是 Fuzzing,它爽性彻底疏忽函数的完结,遵循黑盒究竟,经过遗传算法,随机的生成入参,以测验到世界止境的决计,对函数进行死缠烂打,发掘出正常人底子想不到猜不着的犄角角落里的 bug。



上一条      下一条
返回顶部