如何用六个步骤封装Python代码包

如何用六个步骤封装Python代码包

假设你很喜欢用同一段Python代码,里面有几个相关的小型函数,或者是含有几百行代码的中型模块。程序员可能会把它复制到不同的项目或存储库中,或者从特别设置的实用工具代码文件夹中导入这段代码。

这很正常。程序员在编写代码的过程中都会不断积累这些个性化的小工具。相比其他编程语言来说,Python更容易积累这些语句——这些代码非常实用。

如果无需复制,就可以轻松导入自己开发的小工具,并进行更新和维护,岂不是更好吗?如果不依赖于特定的文件或路径,让这些代码在不同的环境、机器和语境中都适用?如果可以将这些个性化工具版本化,并使相关代码清楚地反映出其依赖性呢?如果这个工具能为大众所用呢?

没错,它都可以做到。

当然,这个概念不是*次提了。这就是通常在编程语言中使用模块、包和库的原因,特别是在Python的开发环境中。它的实现可使Python功能更加强大;只需简单的pip install 和 import就能获得BeautifulSoup的html解析功能或pandas的数据帧处理功能。

另外,人人都可以将自己的代码在PyPI上编写和发布(PyPI是Python包的官方索引:http://pypi.python.org/pypi),使它们与sklearn、requests或delorean(都是非常实用、流行的Python包)一样简单易得。以下是它的几点优势:

· 即使只有很少人使用,共享自己的代码仍是一件很有趣的事;程序员可以在工作、社群活动或求职面试中分享并展示自己的劳动成果。

· 通过强制性地整理和记录代码,公开给同行进行评价,从而改进代码。

· 它还能弥补社群的不足。你会惊讶地发现,很多人会注意到你的序列化十分高效,比如将HTTP报头序列化到JSON。或者发现自己创建的用来验证输入MongoDB查询文档修饰符的工具有多么实用。

心动了吗?忘记那个旧的Python模块,开始制作小型Python包吧。

图1: Python dust

步骤一:命名

首先是命名。好的名字通常比较简短,便于在pip install 或 import 完成之后输入(尽管现在已经出现了“自动输入”);还要包含足够的信息便于理解,或者在安装完成后之后提示其中的内容。

 

 

requests负责处理HTTP请求、delorean负责日期和时间,sklearn负责提供机器学习框架,这些都是很好的例子。在为pandas管道包(由于pandas 通常以pd这样较短别名导入,故使用pdpipe:https://github.com/shaypal5/pdpipe)和缓存包(cachier:https://github.com/shaypal5/cachier)命名时,笔者也尝试过这些例子。

不过老实说,这些并不是固守的规则。流行的Python包都有pandas、 keras、 django、 boto、 jinja、 flask 和 pytorch等名称,大家能记住这些名字,所以读者也可以使用任何简短且可读的名称(例如,由于可读性问题,笔者将“Scikit-Learn Wrappers for FastText”缩写成了skift)。本文以chocobo为例。

步骤二:确定代码包的基本结构

接下来,通过几个简短的步骤,制作一种通用的结构:

1.用代码包的准确名称创建一个Github存储库,不要使用驼峰式或过多的个人发挥。然后在本地进行复制。

2.在该存储库中新建一个文件夹,用代码包的准确名称命名;这就是保存代码包的文件夹。这是一种规范,只需记住外部的chocobo 文件夹(在本例中)就是存储库的文件夹,而内部的chocobo 文件夹是包的文件夹。

3.将自己的模块和涉及到的任何其他模块放在内部的chocobo文件夹中。如果存在缺失的部分,请添加__init__.py 文件。

4.将用户直接调用的重要对象(通常是函数)从各自的模块中导入至__init__.py文件。有了代码包的命名空间,就可以使用这些函数、分类和变量了。如果愿意,也可以使用代码包的API。

5.虽然不是强制规定,笔者强烈建议在代码包或在存储库的根目录中都应包含一个 .gitignore 文件。

示例:https://github.com/github/gitignore/blob/master/Python.gitignore

现在有了一个结构,可以添加不同类型的文件组成代码包;内部文件夹保存的是包的代码,外部文件夹保存的是辅助包文件和其他与存储库相关的文件。

因此,初始模块chocobo.py如下所示:

“””My chocobo cooking script.”””

import os

def chocobo_roast(num_guests, hotness_level):

# amazing python code here

新建存储库文件夹如下所示:

chocobo/

chocobo/

__init__.py

chocobo.py

.gitignore

__init__.py 文件应如下所示:

“””chocobo is a python package for delicious Chocobo recipes.”””

from .chocobo import (

chocobo_roast,

)

那么在完成封装之后,chocobo包可以有这样的使用方法:

“””I’m a script or a different package using chocobo.”””

import chococbo

def my_feast(num_guests):

snacks = bobbish()

main_course = chocobo.chocobo_roast(num_guests, 0)

dressing = szechuan_chicken_mcnugget_sauce()

以上就是一些要点。

步骤三:许可问题

使用共享许可发布代码是较为可取的;如果要将自己的代码公开分享,程序员会想要在保留版权的前提下得到重用代码的许可,或者让那些扩展自己代码的人保证衍生代码可以自由使用。获得许可能轻松解决这些问题。

对于无足轻重的小项目,可以考虑MIT许可(https://choosealicense.com/licenses/mit/)。choosealicense.com(https://choosealicense.com/)提供了很多GitHub和开源社区中的实用建议。

无论选择哪种许可,都比根本不用要好。很多时候,在没有许可的情况下公开代码还不如不公开;如果程序员不明确自己对代码的所有权,大多数公司会因为可能造成的法律纠纷而放弃,从而失去许多潜在用户。

选择许可后,在存储库中创建LICENSE许可文件(不需要文件扩展名),并导入所选许可的确切文本。

步骤四:安装文件

现在创建Python封装工具所需的基本文件(以setuptools为例);setup.py.setup.py 包含了构建和发行时使用的实际指令。

下面是一个初始模板(别担心,稍后会进行详细检查)

“””Setup for the chocobo package.”””

import setuptools

with open(‘README.md’) as f:

README = f.read()

setuptools.setup(

author=”Shay Palachy”,

author_email=”shay.palachy@gmail.com”,

name=’chocobo’,

license=”MIT”,

description=’chocobo is a python package for delicious chocobo recipes.’,

version=’v0.0.3′,

long_description=README,

url=’https://github.com/shaypal5/chocobo’,

packages=setuptools.find_packages(),

python_requires=”>=3.5″,

install_requires=[‘requests’],

classifiers=[

# Trove classifiers

# (https://pypi.python.org/pypi?%3Aaction=list_classifiers)

‘Development Status :: 4 – Beta’,

‘License :: OSI Approved :: MIT License’,

‘Programming Language :: Python’,

‘Programming Language :: Python :: 3.5’,

‘Programming Language :: Python :: 3.6’,

‘Topic :: Software Development :: Libraries’,

‘Topic :: Software Development :: Libraries :: Python Modules’,

‘Intended Audience :: Developers’,

],

)

首先,导入setuptools。这是一个非常有用代码包,可轻松对Python包进行发行,即使它不包括在标准库中(类似的distutils是比不了的),它仍然是当今Python包发行的标准,应该牢记于心。本文只使用了setuptools包中的两个函数:setup和find_packagges。

在导入setuptools之后,调用setup()函数之前,只需将README.md 文件的内容读入到全局变量 README中即可。

然后只需通过以下变量调用setuptools.setup() 函数即可:

· author:输入姓名。

· author_email:输入邮箱。

· name:代码包的名称,在本例中为“chocobo”。

· license:在本例中为字符串“MIT”,或选择其他许可证。

· description:代码包的简短介绍,控制在一行以内。例如:“chocobo代码包是制作美味chocobo的食谱”

· version:表示封装的当前版本的字符串。笔者在之后的文章中会介绍更简洁的处理方法,但是目前,只需要在想要发布新版本时手动增加一个数字就可以了。通常的做法是将版本号前加入字母V,因此v1是*个版本的版本字符串,但笔者建议将v0.0.1 视为等效版本字符串并使用此格式。后文将详细介绍这种做法的意义。

· long_description:表示README的内容。该部分是代码包的详细描述。也就是该页面PyPI的内容(示例:https://pypi.org/project/pdpipe/)。

· url:可链接到代码包的主页。如果读者没有专用的站点,那么存储库的URL是一个不错的选择。

· packages: 又一次提到了setuptools!根据命令,这个参数获取要生成和发行/安装的所有代码包的名称数组。从技术上讲,可以直接使用[“chocobo”]这个名字,但是*好是将其通用化,并使用setuptools 函数,它能处理更复杂的包和存储库结构。有两个可选参数可以作为输入数据,where 和exclude,但在这里忽略不计。作为结果,where可链接至安装文件所在的目录,包括所有子目录,一般来说这样已经足够了。

· python_requires: 如果你的电脑支持Python的所有版本,就不必顾及此参数。如果不能,应该选择一个适当的值。从技术上讲,笔者不赞成使用未经测试的版本,但保险期间目前我们可以进行适当的假设:

(1) 如果读者正在使用Python2,特别是Python2.7版本,可以得出以下两点结论:(a)你独树一帜,十分优秀(b)你的电脑配置只需支持Python2.7即可,所以可以使用“>=2.7”这个字符来编辑这个参数。另外,时代在进步,试试Python3吧。

(2) 如果读者使用的是Python3,那么任何Python版本都大于或等于用来开发代码包的版本。以此类推,如果使用的是Python3.5,那么应该设置成“>=3.5”。

· install_requires: 此处列出的是所有非标准库代码包的使用前提。例如,如果chocobo 需要requests和 pytz 才能运行的话,那么该参数应设置为:[“ requests”,“pytz”]。

· classifiers: 连同其他成千上万个代码包一起,你的代码包也会很快PyPI上线。为了进行区分,作者可以向PyPI提供一个列表,列出trove分类器来对每个版本进行分类,描述其用途、支持的系统和开发进度。然后,社区成员可以使用这些标准化的分类器,根据自己的需求来查找项目(尽管不确定谁会进行这项操作)。

这里有所有可能用到的分类器:https://pypi.python.org/pypi?%3Aaction=list_classifiers

建议从以下几个开始:

– “Development Status :: 3 — Alpha”

– “License :: OSI Approved :: MIT License”

– “ Programming Language :: Python”

– “ Programming Language :: Python :: 3.5”

– “ Programming Language :: Python :: 3.6”

– “ Programming Language :: Python :: 3.7”

– “Topic :: Software Development :: Libraries”

– “Topic :: Software Development :: Libraries :: Python Modules”

– “Intended Audience :: Developers”

以上就是这一环节的全部内容。

 

图2:选择trove分类器后的艾斯·文图拉

步骤五:建立发行文件

Python包位于发行文件中,这些文件会统一被上传到一个服务器中(通常是PyPI全局服务器),供公众下载。

本文不会详细介绍发行格式。笔者将使用标准方法(https://packaging.python.org/tutorials/packaging-projects/)构建两个文件:源发行文件(基本上包含了代码包)和wheel发行文件(wheel build distribution file)。

首先,确保安装了*新版本的setuptools 以及 wheel:

python3 -m pip install –user –upgrade setuptools wheel

要构建发行文件,只需在setup.py所在的存储库的根目录中运行以下命令:

python setup.py sdist bdist_wheel

在这一步,需要Python运行setup.py这个脚本,并向它发送两个参数,生成源文件(参数sdist),以及wheel工具来构建发行文件(参数bdist_wheel)。

运行此命令时,将在调用目录中创建三个文件夹: build, dist 和 chocobo.egg-info。对于.gitignore 文件来说,这三个可以忽略不计。如果这些目录已经存在(例如,该命令之前已经运行过了,*好用rm -rf build dist将这些目录删除掉,因为dist 下的任何有效代码包文件都将被上传。

要上传的两个文件位于dist 文件夹中:分别为chocobo-0.0.3-py-none.any.whl (构建发行;是一个wheel 文件)和chocobo-0.0.3.tar.gz (源发行;是一个压缩的tar 文件)。创建成功后,我们继续上传的步骤!

步骤六:上传

剩下的步骤就是将代码包上传到PyPI全局服务器!然而,用户必须先在PyPI网站上注册。按照注册的步骤填写用户名和密码。

如果想在上传到PyPI全局服务器之前测试包,程序员也可以在测试PyPI网站上注册一个用户。

现在,用于上传的Python包将在.pypirc文本文件中查找PyPI用户名和密码(通过PyPI服务器进行验证),该文件通常位于主文件夹中。创建后按如下所示进行填写(testpypi 部分视具体情况而定):

[distutils]

index-servers =

pypi

testpypi

[pypi]

username: teapot48

password: myPYPIpassword

[testpypi]

repository: https://test.pypi.org/legacy/

username: teapot48

password: MYtestPYPIpassword

本文依照*新的方法将文件上传到PyPI服务器中,并使用twine(上传Python包的实用工具),而不是使用过时的python setup.py upload 。只需运行:

twine upload dist/*

如果想在PyPI服务器上进行测试,只需运行 twine upload — repository testpypi dist/*

不论如何,上传.whl 文件时都应该能看到一个进度条,上传.tar.gz 文档时应该也能看到一个进度条,然后上传就完成了。

现在可以在PyPI官方网站上看到自己的Python包页面了,大家也都能看到!

示例:https://pypi.org/project/birch/

图3:PyPI网站上包页面的示例

 

如何避开Python下载安装的坑?

如何避开Python下载安装的坑?

现在的职场竞争越来越激烈,不学上一两门新技能,保持自己知识更新,很容易被年轻后辈超越。有些人选择学一门外语,有些人选择学习职场上为人处事的能力。
有人选择Python。
因为,就业市场需要Python。

Python在各大城市的招聘需求量

我们将用于制作或者记述计算机所使用的程序的语言称之为计算机语言,Python便是其中之一。Python由荷兰人Guido van Rossum所开发,于1991年发布了其*个公开源代码0.90版本的源代码。
Python被以一种易读易写的方式设计,十分简洁, 可以用少量的代码高效率地编写程序。同时,它具备能在Windows、 Mac和Linux/Unix上运行的十分出色的兼容性。因此,Python在近期的网页程序、 数据解析、 客户端程序、 嵌入式开发、 游戏, 以及深度学习等方面被广泛利用,成为了一大主流计算机语言。
Python的热度一直居高不下,除了技术人员使用外,比如运营维护、自动化测试、后端开发、机器学习等,Python的用武之地真是太多了。非技术开发人员,学会Python也能使日常工作效率产生质的提升。很多高薪行业都纷纷在招聘JD中,给出了“熟悉Python软件的优先”这样的招聘条件,因为Python在人工智能、数据分析等方面的功能,就能使你的职场工作开挂一样。
安装软件相信对大家来说都非常的简单,next,再next,然后再next………,好了安装完成,就开始使用了。也许大多数软件需要这种傻瓜式的安装,但是也有许多软件挖了坑,等着你往里跳。python软件安装过程中就有这样的大坑,如果不注意的话,就等着叫苦吧。
有许多人在安装python软件后,操作出错,抱怨连天。殊不知你已经入坑,而且还没爬出来。其实安装软件的时候可以很轻易的避免入坑的,就看你注意到了没有。
今天职场君就给大家介绍一下,如何规范有效地安装Python软件。
Python的安装
1.下载安装工具
首先,从Python的官方网站下载安装工具。
https://www.python.org/
浏览上述网页,将打开如下界面。

下载界面

本次希望使用64bit版的Python,将鼠标指针移动到上端菜单中的[Download]上,在打开的二级菜单中选择并单击[Windows]。
点击右边栏的[Download for Windows]中的[Python 3.6.3], 将下载32bit版的完整程序安装包。

单击[Windows]

在[Python Releases for windows]页面的一览表中,选择[Python 3.6.3-2017-10-03]下的[Download Windows x86-64 web-based installer]并单击,将安装工具下载到任意指定位置。

下载

2.安装
下载结束后开始安装。
勾选[Add Python 3.6 to PATH],单击[install now]。勾选[Add Python 3.6 to PATH]后,将自动配置环境变量Path,省去事后手动添加Path的不便。

勾选配置

开始安装。

准备安装

显示如下界面时, Python的安装则已结束。单击[close], 结束安装过程。

结束安装

3.安装的确认
*后,确认一下是否已正确安装。
启动PowerShell,输入“Python-V”。如果正确显示如下版本信息,则说明Python的安装已经成功。

确认是否安装

扩展包的安装
使用pip进行软件包的安装方法。
1.PyPI和pip
Python中,爱好者制作了大量的软件包, 这些安装包通常在一个名为PyPI(Python Package Index)的网站上上传/开放。 我们可以通过以下URL来浏览PyPI网站。
https://pypi.python.org/pypi

PyPI网页

pip(Pip Installs Packages)是一种可以用来管理软件包的工具。
仅仅使用pip命令,就可以轻松进行软件包的安装、卸载、升级,以及管理软件包之间的相互关系。 pip原本被提供时仅作为一种外部工具,Python2.7.9之后和Python3.4之后均已作为默认安装中的一部分。
2.运行pip命令
举例来说,如果要安装p.192使用的”requests”,则进行以下命令。没有指定版本时, 则默认安装*新版。
PS > pip install requests
要安装特定版本时,在软件包名称后添加“==”来指定版本。
PS > pip install requests==2.18.4
卸载时,使用如下命令。
PS > pip uninstall requests
升级软件包时,使用“install -U”来指定。
PS > pip install -U requests
也可以确认已安装的版本。
PS > pip list
pip还配有其他许多种命令,通过进一步添加设置,可以更详细地确定对软件包的操作。详细内容请参考PyPI中的pip页面。
想要学习更多有关Python的精彩内容,看这里——

《图解Python–轻松快速掌握实力派脚本语言精华》

本书是编程入门书籍,通过大量图解向新手读者介绍Python编程入门知识,内容生动有趣、简单易懂。本书编写形式以一到两页为一个知识点,同时避免使用大段理论文字,而是通过易懂的图解来解释技术问题,让读者可以轻松学习Python。本书主要讲解了编程基础,计算中的运算符,列表,for、while等流程控制语句,函数,字符串,文件和例外处理,类和对象等重点知识。对于没有任何编程基础的读者来说,本书是一本不可多得的Python入门参考用书。

12个Python开源框架

目前*受欢迎的12个Python开源框架,你用过几个?

今天技术学派给大家带来了12个在GitHub等开源网站中*受欢迎的Python开源框架。如果你正在学习python,那么这12个开源框架,千万别错过,这些框架包括事件I/O,OLAP,Web开发,高性能网络通信,测试,爬虫等。虽说不上是全都有,但也足够满足你了。

1.Django

Django是一款用Python语言写的免费开源的 Python Web应用开发框架,它遵循模型 -视图-控制器(MVC)的架构模式。它是由非营利Django维护软件基金会(DSF)和3条款BSD许可下可用,鼓励快速发展和实用的设计。Django同时是一款在数据库功能、后台功能、末班系统、网址匹配、缓存系统等方面有“先天”优势的开源框架。它可以通过几行简单的代码就让你的网站拥有一个强大的后台,轻松管理你的内容;强大,易扩展的模板系统,设计简易,代码,样式分开设计,更容易管理;如果与memcached或其它的缓存系统联用,更出色的表现,更快的加载速度。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:https://github.com/haiiiiiyun/awesome-django-cn

2.Tornado

Tornado源意为龙卷风,这里是一款可扩展的,非阻塞的Web服务器,应用开发框架,以及异步联网库。它*初是在FriendFeed开发(在2009年被Facebook收购)。Tornado是高度灵活,专门设计为开发人员和第三方工具厂商提供了一个开放环境。已有部分应用程序接口可以利用并附带参考书目,内容从开发环境接口到连接实现。另外,它还配备了一个WSGI服务器,其他WSGI Python应用程序(和框架)也可以使用。在开源和Apache 2.0许可下可用。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:http://www.tornadoweb.org/en/stable/

3.Twisted

Twisted是一款事件驱动的网络编程框架,支持许多常见的传输及应用层协议,如TCP、UDP、SSL/TLS、HTTP、IMAP、SSH、IRC以及FTP。还支持Unix domain sockets,在MIT许可下应用。

Twisted附带了一个可以web服务器上运行的WSGI,它能够为其他Python web应用程序允许开发人员使用的服务器定制HTTP服务。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:https://twistedmatrix.com/trac/

4.Pulsar

Pulsar是一个来自eBay的高扩展性、高可用性、基于事件驱动的开源实时分析平台和流处理框架,它能够实时收集和处理用户行为和业务事件。有了pulsar,你可以写出在不同进程或线程中运行一个或多个活动的异步服务器。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:https://pypi.org/project/pulsar/

5.Bottle

Bottle是一个简单高效的遵循WSGI的微型python Web框架。说微型,是因为它只有一个文件,除Python标准库外,它不依赖于任何第三方模块。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:http://www.bottlepy.org/docs/dev/

6.Diesel

Diesel是基于Greenlet的事件I/O框架,它提供一个整洁的API来编写网络客户端和服务器。支持TCP和UDP。非阻塞I/O使得diesel非常快速并且容易扩展。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:https://pypi.org/project/diesel/

7.Numpy

Numpy(Numerical Python)提供了python对多维数组对象的支持:ndarray,具有矢量运算能力,快速、节省空间。numpy支持高级大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:http://www.numpy.org/

8.Scrapy

Scrapy是Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。是一个使用Python编写的,轻量级的,简单轻巧,并且使用起来非常的方便。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:https://scrapy.org/

9.Cubes

Cubes是一个轻量级Python框架,包含OLAP、多维数据分析和浏览聚合数据(aggregated data)等工具。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:http://cubes.databrewery.org/explore.html

10.Falcon

Falcon是一个构建云API的高性能Python框架,是一个面向Hadoop的数据集和处理过程的管理平台。它鼓励使用REST架构风格,尽可能以*少的力气做*多的事情。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:http://falconframework.org/

11.Web2py

Web2py是 Google 在 web.py 基础上二次开发而来的,兼容 Google App Engine 。是一个为Python语言提供的全功能Web应用框架,旨在敏捷快速的开发Web应用,具有快速、安全以及可移植的数据库驱动的应用。

目前*受欢迎的12个Python开源框架,你用过几个?

项目地址:http://www.web2py.com/

12.Zerorpc

Zerorpc是一个基于基于ZeroMQ的高性能分布式RPC框架。ZeroMQ和MessagePack开发的远程过程调用协议(RPC)实现。和 Zerorpc 一起使用的 Service API 被称为 zeroservice。Zerorpc 可以通过编程或命令行方式调用。

目前*受欢迎的12个Python开源框架,你用过几个?

云开发一旦成为常态

云开发一旦成为常态,程序员将何去何从?

有接触小程序开发的同学应该知道,云开发成为了小程序开发的标配。有了云开发,无需搭建服务器,即可使用云端能力。

现今开发互联网产品,很少不用App(或小程序)的了,而App数据是从后台数据服务接口获取的,而提供数据服务接口的,是用Springboot、nodejs等提供技术实现的。

提供一个数据接口容易,实现一个功能也容易,难的是解决数据的并发性,负载均衡,数据库吞吐量等难题,而这些恰恰是影响数据响应速度的关键点。

不是说解决不了,而是时间问题。按我当技术总监多年的经验所知,后端数据服务功能,要达到稳定、满意状态,大多是以年为单位的,而时间越久,程序员就越不稳定,企业负责人就越没有耐心,因为大家都不可避免的要面对成本问题。

如果现在有第3方(云服务)企业把这些接口都开发好了,按需提供,而且他们投入专业的团队做这一块,你说企业老板有怎么选择–如果是我,我也选择成熟,稳定、快速可支撑上线的产品。

实现后端数据服务的功能系统,在业界被称为云开发。

“云开发为移动开发者提供的一站式后端云服务,它帮助开发者统一构建和管理资源,免去了移动应用开发过程中繁琐的服务器搭建及运维、域名注册及备案、数据接口实现等繁琐流程,让开发者可以专注于业务逻辑的实现,而无需理解后端逻辑及服务器运维知识,开发门槛更低,效率更高。”

从上面所描述可知,云开发为开发者提供完整的原生云端支持,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代。

其实,这个技术在国外早流行并被使用多年,国内随着小程序的兴起,逐步推进当中。身为程序员的我们,除了关注云开发技术之外,是否还有更重要的问题需要考虑么?

不知以后是否还有机会,用半年或一年的时间搭建后端数据服务?不知现任老板是否会”变脸”,要求我们改用云开发

就算老板不要求,在咱们做新产品时,是否会考虑就用云开发技术?

一旦后端不用架构了,程序员是不是就少了些工作机会了,云开发一旦成为常态,程序员将何去何从

程序员学习Python只需这6本书,从入门到进阶!

程序员学习Python只需这6本书,从入门到进阶!

编程语言Python语法简单,代码可读性高,不仅适合初学者学习,而且岗位需求大,薪资一路也是水涨船高,即使是刚毕业的应届毕业生,薪资也在12500元每月。

因此,很多程序员很乐意去研究这门编程语言,那么有哪些值得收藏的Python书单呢?

Python入门

0、《“笨办法”学Python(第3版)》

%title插图%num

 

这本书结构简单,非常适合初学编程,对编程感兴趣的读者使用,通过*基础的编程技术,让你体验开发过程。

亲测有效!

1、《Python编程快速上手——让繁琐工作自动化》

程序员学习Python只需这6本书,从入门到进阶!

 

本书介绍了Python语言的基础知识,通过项目实践教会读者如何应用这些知识和技能。

这本书就好在学习完每个章节后会有相应的习题和实践项目,并且附有习题答案,帮助理解,而不是出个题目就跑。

2、《Python编程初学者指南》

程序员学习Python只需这6本书,从入门到进阶!

 

这本书让人惊喜的地方是学习起来不会过于枯燥,每章节的学习都会通过游戏来给你启发,寓教于乐,降低了学习的难度。

Python进阶

0、《Python机器学习实践指南》

程序员学习Python只需这6本书,从入门到进阶!

 

机器学习是近年来逐渐热门的领域,这本书将机器学习和编程语言Python两大热门相结合,将Python 语言在数据分析方面的优势发挥到*致。

1、《用Python写网络爬虫》

程序员学习Python只需这6本书,从入门到进阶!

 

本书讲解了如何使用Python来编写网络爬虫程序,并在*后使用本书介绍的数据抓取技术对几个真实的网站进行了抓取,旨在帮助读者活学活用书中介绍的技术。

本书适合有一定Python编程经验,而且对爬虫技术感兴趣的读者阅读。

2、《Python核心编程(第3版)》

程序员学习Python只需这6本书,从入门到进阶!

 

经典畅销图书,分为三个部分,*部分是Python的通用应用,第二部分主要是与web相关,第三部分是一些补充内容,包括文本处理。

如果你有一定的Python开发经验,它会非常适合你。

看书不可少,但是更好的学习方法是结合w3cschool上的教程进行实践。

*火的五大 python 开源项目

*火的五大 python 开源项目

python 语言易学,且开发高效,用 python 语言开发的开源项目活跃度很高,下面介绍目前*活跃的五大 python 开源项目:

 

1.) 排名*位的当然是有关机器学习的Google开源项目 TensorFlow,TensorFlow 是一个采用数据流图,用于数值统计计算的开源项目,此项目一经公布就有过万的 star 和 fork,被广泛的用于机器学习和深度神经网络方面的研究。

 

2.) 排名第二位的是python web 方向的新贵 Flask,Flask 是一个微型的 Python Web 开发框架,它基于WSGI工具箱和 jinja2模板引擎,可以*灵活*快速的配置开发属于自己的 Python Web站点。

 

3.) 排名第三位的是python web 方向的经典 Django,Django 是一个采用 M-V-T 设计模式的 Web 框架。在Django中,由于控制器接受用户输入的部分由框架自行处理,所以 Django 编程更关注的是模型(Model)、模板(Template)和视图(Views)。使用 Django,我们可以迅速创建高品质、易维护、和数据库驱动的Web服务程序,尤其对不甚熟悉数据库操作的建站同学来说,如获至宝。

 

4.)排名第四位的是功能测试框架 pytest,pytest 是一个成熟的全功能的Python测试框架,可以帮助你更好的写出自动化测试程序,它能适应从简单的单元测试到复杂的集成测试在内的全功能测试,并且支持众多的插件开发。

 

5.)排名第五位的是数据采集工具 scrapy,scrapy 是一个优秀的网络数据爬虫框架,用户只需定制开发几个模块就可以轻松的实现一个完善的网络爬虫,用其来抓取网页内容数据以及各种图片文件等非常方便;Scrapy 还提供了多种类型的爬虫基类,如 BaseSpider 和sitemap等;Scrapy被广泛的运用于数据挖掘、网络监测和自动化测试等领域。

 

需要说明的是以上的排名是根据代码submit 和 fork 的活跃度的排名,仅仅反映的是开源代码的受欢迎程度,并非就是企业市场应用的火爆度,注意区别,你要根据自己的需求选择合适你项目的python 开源框架和工具。

python命令是如何操作文件file的?

python命令是如何操作文件file的?

 

无论哪一个操作系统,亦或是哪一门语言,当使用到一些高级层次的时候,必然会和文件打交道,因为所有的内容不可能都在内存中存储和传输。用到文件,就必然会有创建文件、打开文件、写文件、复制文件、删除文件这些操作,下面我们来看看在python中是如何利用命令来操作file文件的。

 

python操作file的命令及使用

1.打开和关闭文件

file object= open(file_name [, access_mode][, buffering]),通过open命令来打开一个文件。file_name是指文件的路径,access_mode是打开的方式,包括只读r、写入w和追加a。r取readonly的首字母,w取write的首字母,a取append的首字母。buffering代表缓冲区的大小。如果取负值,寄存区的缓冲大小则为系统默认。

File 对象的 close()方法刷新缓冲区里任何还没写入的信息,并关闭该文件,这之后便不能再进行写入。在java中也是利用close来关闭文件,一般都会配合flush一起使用。

2.写文件

write()方法可将任何字符串写入一个打开的文件。需要重点注意的是,Python字符串可以是二进制数据,而不是仅仅是文字。write()方法不会在字符串的结尾添加换行符(‘\n’)。

例如:#!/usr/bin/python

# -*- coding: UTF-8 -*-

# 打开一个文件

fo = open(“welcome.txt”,”w”)

fo.write(“Welcome to Qingdao!\nHave a good rest!\n”)

# 关闭打开的文件

fo.close()

这段代码是将两行字符串写入welcome.txt文本中,写完后关闭文件。

3.读文件

读文件使用read命令,read后面跟字符的数量,如下面示例,是指从welcome.txt中读取10个字符,也就是”Welcome to”。

fo = open(“welcome.txt”,”r+”)

str = fo.read(10)

结语

本节先介绍文件的打开关闭和读写操作,下一节我们接着学习文件的查找、重命名、删除等命令的使用方法。

 

Python爬虫学前普及

爬虫篇 | Python爬虫学前普及

【摘要】*近整理一个爬虫系列方面的文章,不管大家的基础如何,我从头开始整一个爬虫系列方面的文章,让大家循序渐进的学习爬虫,小白也没有学习障碍,那么今天,环球网校的小编就来针对Python讲讲爬虫学前普及,希望今天的文章对您可以有所帮助。

恩,准备进入正题了!*近一段时间没有怎么更新公众号,主要就是在做爬虫教程的一些准备工作,看看爬虫需要用到那些技术,然后做个计划出来,确定一下学习课程中缝,这不今天就先列出一些玩爬虫需要的准备工作!

Python爬虫这门技术你可以做得很简单,你也可以玩得很深入.打比方用简单的爬虫方式爬取1000万条数据可能需要一周时间,但如果你的爬虫玩得比较厉害,你可以采用分布式爬虫技术1天就能完成了1000万条数据。虽然都是爬虫,但这就是菜鸟与大牛的区别!这就和太*拳似的,易学难精!

这里面的技术点挺多的!现在来简单聊聊爬虫需要涉及的知识点。

网页知识

html,js,css,xpath这些知识,虽然简单,但一定需要了解。你得知道这些网页是如何构成的,然后才能去分解他们.

HTTP知识

一般爬虫你需要模拟浏览器的操作,才能去获取网页的信息

如果有些网站需要登录,才能获取更多的资料,你得去登录,你得把登录的账号密码进行提交

有些网站登录后需要保存cookie信息才能继续获取更多资料

正则表达式

有了正则表达式才能更好的分割网页信息,获取我们想要的数据,所以正则表达式也是需要了解的.

一些重要的爬虫库

url,url2,requests

beautiulSoup4,re,lxml

数据库

爬取到的数据我们得有个地方来保存,可以使用文件,也可以使用数据库,这里我会使用mysql,还有更适合爬虫的MongoDB数据库,以及分布式要用到的redis 数据库

爬虫框架

PySpider和Scrapy 这两个爬虫框架是非常NB的,简单的爬虫可以使用urllib与urllib2以及正则表达式就能完成,但高级的爬虫还得用这两个框架。这两个框架需要另行安装。后面一起学习.

反爬虫

有时候你的网站数据想禁止别人爬取,可以做一些反爬虫处理操作。打比方百度上就无法去查找淘宝上的数据,这样就避开了搜索引擎的竞争,淘宝就可以搞自己的一套竞价排名

分布式爬虫

使用多个redis实例来缓存各台主机上爬取的数据。

爬虫要学的东西还是挺多的,想把爬虫玩得666,基本就是这些知识点吧!好了,上面的东西我也只是粗略整理,笔误在所难免,后面我们会一起来学习爬虫知识吧!可以为您带来帮助。以上的内容都可以为您的python学习之路带来便利,小编祝您学习之路顺利。

Python 优化提速的 8 个小技巧

Python 优化提速的 8 个小技巧

%title插图%num

Python 是一种脚本语言,相比 C/C++ 这样的编译语言,在效率和性能方面存在一些不足。但是,有很多时候,Python 的效率并没有想象中的那么夸张。本文对一些 Python 代码加速运行的技巧进行整理。

0. 代码优化原则

本文会介绍不少的 Python 代码加速运行的技巧。在深入代码优化细节之前,需要了解一些代码优化基本原则。

*个基本原则是不要过早优化。很多人一开始写代码就奔着性能优化的目标,“让正确的程序更快要比让快速的程序正确容易得多”。因此,优化的前提是代码能正常工作。过早地进行优化可能会忽视对总体性能指标的把握,在得到全局结果前不要主次颠倒。

第二个基本原则是权衡优化的代价。优化是有代价的,想解决所有性能的问题是几乎不可能的。通常面临的选择是时间换空间或空间换时间。另外,开发代价也需要考虑。

第三个原则是不要优化那些无关紧要的部分。如果对代码的每一部分都去优化,这些修改会使代码难以阅读和理解。如果你的代码运行速度很慢,首先要找到代码运行慢的位置,通常是内部循环,专注于运行慢的地方进行优化。在其他地方,一点时间上的损失没有什么影响。

1. 避免全局变量

  1. # 不推荐写法。代码耗时:26.8
  2. import math
  3. size = 10000
  4. for x in range(size):
  5.     for y in range(size):
  6.         z = math.sqrt(x) + math.sqrt(y)

许多程序员刚开始会用 Python 语言写一些简单的脚本,当编写脚本时,通常习惯了直接将其写为全局变量,例如上面的代码。但是,由于全局变量和局部变量实现方式不同,定义在全局范围内的代码运行速度会比定义在函数中的慢不少。通过将脚本语句放入到函数中,通常可带来 15% – 30% 的速度提升。

  1. # 推荐写法。代码耗时:20.6
  2. import math
  3. def main():  # 定义到函数中,以减少全部变量使用
  4.     size = 10000
  5.     for x in range(size):
  6.         for y in range(size):
  7.             z = math.sqrt(x) + math.sqrt(y)
  8. main()

2. 避免.

2.1 避免模块和函数属性访问

  1. # 不推荐写法。代码耗时:14.5
  2. import math
  3. def computeSqrt(size: int):
  4.     result = []
  5.     for i in range(size):
  6.         result.append(math.sqrt(i))
  7.     return result
  8. def main():
  9.     size = 10000
  10.     for _ in range(size):
  11.         result = computeSqrt(size)
  12. main()

每次使用.(属性访问操作符时)会触发特定的方法,如__getattribute__()__getattr__(),这些方法会进行字典操作,因此会带来额外的时间开销。通过from import语句,可以消除属性访问。

  1. # *次优化写法。代码耗时:10.9
  2. from math import sqrt
  3. def computeSqrt(size: int):
  4.     result = []
  5.     for i in range(size):
  6.         result.append(sqrt(i))  # 避免math.sqrt的使用
  7.     return result
  8. def main():
  9.     size = 10000
  10.     for _ in range(size):
  11.         result = computeSqrt(size)
  12. main()

在第 1 节中我们讲到,局部变量的查找会比全局变量更快,因此对于频繁访问的变量sqrt,通过将其改为局部变量可以加速运行。

  1. # 第二次优化写法。代码耗时:9.9
  2. import math
  3. def computeSqrt(size: int):
  4.     result = []
  5.     sqrt = math.sqrt  # 赋值给局部变量
  6.     for i in range(size):
  7.         result.append(sqrt(i))  # 避免math.sqrt的使用
  8.     return result
  9. def main():
  10.     size = 10000
  11.     for _ in range(size):
  12.         result = computeSqrt(size)
  13. main()

除了math.sqrt外,computeSqrt函数中还有.的存在,那就是调用listappend方法。通过将该方法赋值给一个局部变量,可以彻底消除computeSqrt函数中for循环内部的.使用。

  1. # 推荐写法。代码耗时:7.9
  2. import math
  3. def computeSqrt(size: int):
  4.     result = []
  5.     append = result.append
  6.     sqrt = math.sqrt    # 赋值给局部变量
  7.     for i in range(size):
  8.         append(sqrt(i))  # 避免 result.append 和 math.sqrt 的使用
  9.     return result
  10. def main():
  11.     size = 10000
  12.     for _ in range(size):
  13.         result = computeSqrt(size)
  14. main()

2.2 避免类内属性访问

  1. # 不推荐写法。代码耗时:10.4
  2. import math
  3. from typing import List
  4. class DemoClass:
  5.     def __init__(self, value: int):
  6.         self._value = value
  7.     def computeSqrt(self, size: int) -> List[float]:
  8.         result = []
  9.         append = result.append
  10.         sqrt = math.sqrt
  11.         for _ in range(size):
  12.             append(sqrt(self._value))
  13.         return result
  14. def main():
  15.     size = 10000
  16.     for _ in range(size):
  17.         demo_instance = DemoClass(size)
  18.         result = demo_instance.computeSqrt(size)
  19. main()

避免.的原则也适用于类内属性,访问self._value的速度会比访问一个局部变量更慢一些。通过将需要频繁访问的类内属性赋值给一个局部变量,可以提升代码运行速度。

  1. # 推荐写法。代码耗时:8.0
  2. import math
  3. from typing import List
  4. class DemoClass:
  5.     def __init__(self, value: int):
  6.         self._value = value
  7.     def computeSqrt(self, size: int) -> List[float]:
  8.         result = []
  9.         append = result.append
  10.         sqrt = math.sqrt
  11.         value = self._value
  12.         for _ in range(size):
  13.             append(sqrt(value))  # 避免 self._value 的使用
  14.         return result
  15. def main():
  16.     size = 10000
  17.     for _ in range(size):
  18.         demo_instance = DemoClass(size)
  19.         demo_instance.computeSqrt(size)
  20. main()

3. 避免不必要的抽象

  1. # 不推荐写法,代码耗时:0.55
  2. class DemoClass:
  3.     def __init__(self, value: int):
  4.         self.value = value
  5.     @property
  6.     def value(self) -> int:
  7.         return self._value
  8.     @value.setter
  9.     def value(self, x: int):
  10.         self._value = x
  11. def main():
  12.     size = 1000000
  13.     for i in range(size):
  14.         demo_instance = DemoClass(size)
  15.         value = demo_instance.value
  16.         demo_instance.value = i
  17. main()

任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装代码时,都会让代码变慢。大部分情况下,需要重新进行审视使用属性访问器的定义是否有必要,使用getter/setter函数对属性进行访问通常是 C/C++ 程序员遗留下来的代码风格。如果真的没有必要,就使用简单属性。

  1. # 推荐写法,代码耗时:0.33
  2. class DemoClass:
  3.     def __init__(self, value: int):
  4.         self.value = value  # 避免不必要的属性访问器
  5. def main():
  6.     size = 1000000
  7.     for i in range(size):
  8.         demo_instance = DemoClass(size)
  9.         value = demo_instance.value
  10.         demo_instance.value = i
  11. main()

4. 避免数据复制

4.1 避免无意义的数据复制

  1. # 不推荐写法,代码耗时:6.5
  2. def main():
  3.     size = 10000
  4.     for _ in range(size):
  5.         value = range(size)
  6.         value_list = [x for x in value]
  7.         square_list = [x * x for x in value_list]
  8. main()

上面的代码中value_list完全没有必要,这会创建不必要的数据结构或复制。

  1. # 推荐写法,代码耗时:4.8
  2. def main():
  3.     size = 10000
  4.     for _ in range(size):
  5.         value = range(size)
  6.         square_list = [x * x for x in value]  # 避免无意义的复制
  7. main()

另外一种情况是对 Python 的数据共享机制过于偏执,并没有很好地理解或信任 Python 的内存模型,滥用 copy.deepcopy()之类的函数。通常在这些代码中是可以去掉复制操作的。

4.2 交换值时不使用中间变量

  1. # 不推荐写法,代码耗时:0.07
  2. def main():
  3.     size = 1000000
  4.     for _ in range(size):
  5.         a = 3
  6.         b = 5
  7.         temp = a
  8.         a = b
  9.         b = temp
  10. main()

上面的代码在交换值时创建了一个临时变量temp,如果不借助中间变量,代码更为简洁、且运行速度更快。

  1. # 推荐写法,代码耗时:0.06
  2. def main():
  3.     size = 1000000
  4.     for _ in range(size):
  5.         a = 3
  6.         b = 5
  7.         a, b = b, a  # 不借助中间变量
  8. main()

4.3 字符串拼接用join而不是+

  1. # 不推荐写法,代码耗时:2.6
  2. import string
  3. from typing import List
  4. def concatString(string_list: List[str]) -> str:
  5.     result = 
  6.     for str_i in string_list:
  7.         result += str_i
  8.     return result
  9. def main():
  10.     string_list = list(string.ascii_letters * 100)
  11.     for _ in range(10000):
  12.         result = concatString(string_list)
  13. main()

当使用a + b拼接字符串时,由于 Python 中字符串是不可变对象,其会申请一块内存空间,将ab分别复制到该新申请的内存空间中。因此,如果要拼接 n 个字符串,会产生 n-1 个中间结果,每产生一个中间结果都需要申请和复制一次内存,严重影响运行效率。而使用join()拼接字符串时,会首先计算出需要申请的总的内存空间,然后一次性地申请所需内存,并将每个字符串元素复制到该内存中去。

  1. # 推荐写法,代码耗时:0.3
  2. import string
  3. from typing import List
  4. def concatString(string_list: List[str]) -> str:
  5.     return .join(string_list)  # 使用 join 而不是 +
  6. def main():
  7.     string_list = list(string.ascii_letters * 100)
  8.     for _ in range(10000):
  9.         result = concatString(string_list)
  10. main()

5. 利用if条件的短路特性

  1. # 不推荐写法,代码耗时:0.05
  2. from typing import List
  3. def concatString(string_list: List[str]) -> str:
  4.     abbreviations = {‘cf.’‘e.g.’‘ex.’‘etc.’‘flg.’‘i.e.’‘Mr.’‘vs.’}
  5.     abbr_count = 0
  6.     result = 
  7.     for str_i in string_list:
  8.         if str_i in abbreviations:
  9.             result += str_i
  10.     return result
  11. def main():
  12.     for _ in range(10000):
  13.         string_list = [‘Mr.’‘Hat’‘is’‘Chasing’‘the’‘black’‘cat’‘.’]
  14.         result = concatString(string_list)
  15. main()

if 条件的短路特性是指对if a and b这样的语句, 当aFalse时将直接返回,不再计算b;对于if a or b这样的语句,当aTrue时将直接返回,不再计算b。因此, 为了节约运行时间,对于or语句,应该将值为True可能性比较高的变量写在or前,而and应该推后。

  1. # 推荐写法,代码耗时:0.03
  2. from typing import List
  3. def concatString(string_list: List[str]) -> str:
  4.     abbreviations = {‘cf.’‘e.g.’‘ex.’‘etc.’‘flg.’‘i.e.’‘Mr.’‘vs.’}
  5.     abbr_count = 0
  6.     result = 
  7.     for str_i in string_list:
  8.         if str_i[-1] == ‘.’ and str_i in abbreviations:  # 利用 if 条件的短路特性
  9.             result += str_i
  10.     return result
  11. def main():
  12.     for _ in range(10000):
  13.         string_list = [‘Mr.’‘Hat’‘is’‘Chasing’‘the’‘black’‘cat’‘.’]
  14.         result = concatString(string_list)
  15. main()

6. 循环优化

6.1 用for循环代替while循环

  1. # 不推荐写法。代码耗时:6.7
  2. def computeSum(size: int) -> int:
  3.     sum_ = 0
  4.     i = 0
  5.     while i < size:
  6.         sum_ += i
  7.         i += 1
  8.     return sum_
  9. def main():
  10.     size = 10000
  11.     for _ in range(size):
  12.         sum_ = computeSum(size)
  13. main()

Python 的for循环比while循环快不少。

  1. # 推荐写法。代码耗时:4.3
  2. def computeSum(size: int) -> int:
  3.     sum_ = 0
  4.     for i in range(size):  # for 循环代替 while 循环
  5.         sum_ += i
  6.     return sum_
  7. def main():
  8.     size = 10000
  9.     for _ in range(size):
  10.         sum_ = computeSum(size)
  11. main()

6.2 使用隐式for循环代替显式for循环

针对上面的例子,更进一步可以用隐式for循环来替代显式for循环

  1. # 推荐写法。代码耗时:1.7
  2. def computeSum(size: int) -> int:
  3.     return sum(range(size))  # 隐式 for 循环代替显式 for 循环
  4. def main():
  5.     size = 10000
  6.     for _ in range(size):
  7.         sum = computeSum(size)
  8. main()

6.3 减少内层for循环的计算

  1. # 不推荐写法。代码耗时:12.8
  2. import math
  3. def main():
  4.     size = 10000
  5.     sqrt = math.sqrt
  6.     for x in range(size):
  7.         for y in range(size):
  8.             z = sqrt(x) + sqrt(y)
  9. main()

上面的代码中sqrt(x)位于内侧for循环, 每次训练过程中都会重新计算一次,增加了时间开销。

  1. # 推荐写法。代码耗时:7.0
  2. import math
  3. def main():
  4.     size = 10000
  5.     sqrt = math.sqrt
  6.     for x in range(size):
  7.         sqrt_x = sqrt(x)  # 减少内层 for 循环的计算
  8.         for y in range(size):
  9.             z = sqrt_x + sqrt(y)
  10. main()

7. 使用numba.jit

我们沿用上面介绍过的例子,在此基础上使用numba.jitnumba可以将 Python 函数 JIT 编译为机器码执行,大大提高代码运行速度。关于numba的更多信息见下面的主页:http://numba.pydata.org/numba.pydata.org

  1. # 推荐写法。代码耗时:0.62
  2. import numba
  3. @numba.jit
  4. def computeSum(size: float) -> int:
  5.     sum = 0
  6.     for i in range(size):
  7.         sum += i
  8.     return sum
  9. def main():
  10.     size = 10000
  11.     for _ in range(size):
  12.         sum = computeSum(size)
  13. main()

8. 选择合适的数据结构

Python 内置的数据结构如strtuplelistsetdict底层都是 C 实现的,速度非常快,自己实现新的数据结构想在性能上达到内置的速度几乎是不可能的。

list类似于 C++ 中的std::vector,是一种动态数组。其会预分配一定内存空间,当预分配的内存空间用完,又继续向其中添加元素时,会申请一块更大的内存空间,然后将原有的所有元素都复制过去,之后销毁之前的内存空间,再插入新元素。

删除元素时操作类似,当已使用内存空间比预分配内存空间的一半还少时,会另外申请一块小内存,做一次元素复制,之后销毁原有大内存空间。

因此,如果有频繁的新增、删除操作,新增、删除的元素数量又很多时,list的效率不高。此时,应该考虑使用collections.dequecollections.deque是双端队列,同时具备栈和队列的特性,能够在两端进行 O(1) 复杂度的插入和删除操作。

list的查找操作也非常耗时。当需要在list频繁查找某些元素,或频繁有序访问这些元素时,可以使用bisect维护list对象有序并在其中进行二分查找,提升查找的效率。

另外一个常见需求是查找*小值或*大值,此时可以使用heapq模块将list转化为一个堆,使得获取*小值的时间复杂度是 O(1)。

下面的网页给出了常用的 Python 数据结构的各项操作的时间复杂度:https://wiki.python.org/moin/TimeComplexity

参考资料

  • David Beazley & Brian K. Jones. Python Cookbook, Third edition. O’Reilly Media, ISBN: 9781449340377, 2013.
  • 张颖 & 赖勇浩. 编写高质量代码:改善Python程序的91个建议. 机械工业出版社, ISBN: 9787111467045, 2014.

MySQL原理解析:MySQL的索引结构为什么使用B+树?

前言

在MySQL中,无论是Innodb还是MyIsam,都使用了B+树作索引结构(这里不考虑hash等其他索引)。本文将从*普通的二叉查找树开始,逐步说明各种树解决的问题以及面临的新问题,从而说明MySQL为什么选择B+树作为索引结构。

一、二叉查找树(BST):不平衡

二叉查找树(BST,Binary Search Tree),也叫二叉排序树,在二叉树的基础上需要满足:任意节点的左子树上所有节点值不大于根节点的值,任意节点的右子树上所有节点值不小于根节点的值。如下是一颗BST

 

MySQL原理解析:MySQL的索引结构为什么使用B+树?

 

 

当需要快速查找时,将数据存储在BST是一种常见的选择,因为此时查询时间取决于树高,平均时间复杂度是O(lgn)。然而,BST可能长歪而变得不平衡,如下图所示,此时BST退化为链表,时间复杂度退化为O(n)。

为了解决这个问题,引入了平衡二叉树。

 

MySQL原理解析:MySQL的索引结构为什么使用B+树?

 

 

二、平衡二叉树(AVL):旋转耗时

AVL树是严格的平衡二叉树,所有节点的左右子树高度差不能超过1;AVL树查找、插入和删除在平均和*坏情况下都是O(lgn)。

AVL实现平衡的关键在于旋转操作:插入和删除可能破坏二叉树的平衡,此时需要通过一次或多次树旋转来重新平衡这个树。当插入数据时,*多只需要1次旋转(单旋转或双旋转);但是当删除数据时,会导致树失衡,AVL需要维护从被删除节点到根节点这条路径上所有节点的平衡,旋转的量级为O(lgn)。

由于旋转的耗时,AVL树在删除数据时效率很低;在删除操作较多时,维护平衡所需的代价可能高于其带来的好处,因此AVL实际使用并不广泛。

三、红黑树:树太高

与AVL树相比,红黑树并不追求严格的平衡,而是大致的平衡:只是确保从根到叶子的*长的可能路径不多于*短的可能路径的两倍长。从实现来看,红黑树*大的特点是每个节点都属于两种颜色(红色或黑色)之一,且节点颜色的划分需要满足特定的规则(具体规则略)。红黑树示例如下:

 

MySQL原理解析:MySQL的索引结构为什么使用B+树?

 

 

与AVL树相比,红黑树的查询效率会有所下降,这是因为树的平衡性变差,高度更高。但红黑树的删除效率大大提高了,因为红黑树同时引入了颜色,当插入或删除数据时,只需要进行O(1)次数的旋转以及变色就能保证基本的平衡,不需要像AVL树进行O(lgn)次数的旋转。总的来说,红黑树的统计性能高于AVL。

因此,在实际应用中,AVL树的使用相对较少,而红黑树的使用非常广泛。例如,Java中的TreeMap使用红黑树存储排序键值对;Java8中的HashMap使用链表+红黑树解决哈希冲突问题(当冲突节点较少时,使用链表,当冲突节点较多时,使用红黑树)。

对于数据在内存中的情况(如上述的TreeMap和HashMap),红黑树的表现是非常优异的。但是对于数据在磁盘等辅助存储设备中的情况(如MySQL等数据库),红黑树并不擅长,因为红黑树长得还是太高了。当数据在磁盘中时,磁盘IO会成为*大的性能瓶颈,设计的目标应该是尽量减少IO次数;而树的高度越高,增删改查所需要的IO次数也越多,会严重影响性能。

四、B树:为磁盘而生

B树也称B-树(其中-不是减号),是为磁盘等辅存设备设计的多路平衡查找树,与二叉树相比,B树的每个非叶节点可以有多个子树。因此,当总节点数量相同时,B树的高度远远小于AVL树和红黑树(B树是一颗“矮胖子”),磁盘IO次数大大减少。

定义B树*重要的概念是阶数(Order),对于一颗m阶B树,需要满足以下条件:

  • 每个节点*多包含 m 个子节点。
  • 如果根节点包含子节点,则至少包含 2 个子节点;除根节点外,每个非叶节点至少包含 m/2 个子节点。
  • 拥有 k 个子节点的非叶节点将包含 k – 1 条记录。
  • 所有叶节点都在同一层中。

可以看出,B树的定义,主要是对非叶结点的子节点数量和记录数量的限制。

下图是一个3阶B树的例子:

 

MySQL原理解析:MySQL的索引结构为什么使用B+树?

 

 

B树的优势除了树高小,还有对访问局部性原理的利用。所谓局部性原理,是指当一个数据被使用时,其附近的数据有较大概率在短时间内被使用。B树将键相近的数据存储在同一个节点,当访问其中某个数据时,数据库会将该整个节点读到缓存中;当它临近的数据紧接着被访问时,可以直接在缓存中读取,无需进行磁盘IO;换句话说,B树的缓存命中率更高。

B树在数据库中有一些应用,如mongodb的索引使用了B树结构。但是在很多数据库应用中,使用了是B树的变种B+树。

五、B+树

B+树也是多路平衡查找树,其与B树的区别主要在于:

  • B树中每个节点(包括叶节点和非叶节点)都存储真实的数据,B+树中只有叶子节点存储真实的数据,非叶节点只存储键。在MySQL中,这里所说的真实数据,可能是行的全部数据(如Innodb的聚簇索引),也可能只是行的主键(如Innodb的辅助索引),或者是行所在的地址(如MyIsam的非聚簇索引)。
  • B树中一条记录只会出现一次,不会重复出现,而B+树的键则可能重复重现——一定会在叶节点出现,也可能在非叶节点重复出现。
  • B+树的叶节点之间通过双向链表链接。
  • B树中的非叶节点,记录数比子节点个数少1;而B+树中记录数与子节点个数相同。

由此,B+树与B树相比,有以下优势:

  • 更少的IO次数:B+树的非叶节点只包含键,而不包含真实数据,因此每个节点存储的记录个数比B数多很多(即阶m更大),因此B+树的高度更低,访问时所需要的IO次数更少。此外,由于每个节点存储的记录数更多,所以对访问局部性原理的利用更好,缓存命中率更高。
  • 更适于范围查询:在B树中进行范围查询时,首先找到要查找的下限,然后对B树进行中序遍历,直到找到查找的上限;而B+树的范围查询,只需要对链表进行遍历即可。
  • 更稳定的查询效率:B树的查询时间复杂度在1到树高之间(分别对应记录在根节点和叶节点),而B+树的查询复杂度则稳定为树高,因为所有数据都在叶节点。

B+树也存在劣势:由于键会重复出现,因此会占用更多的空间。但是与带来的性能优势相比,空间劣势往往可以接受,因此B+树的在数据库中的使用比B树更加广泛。

六、感受B+树的威力

前面说到,B树/B+树与红黑树等二叉树相比,*大的优势在于树高更小。实际上,对于Innodb的B+索引来说,树的高度一般在2-4层。下面来进行一些具体的估算。

树的高度是由阶数决定的,阶数越大树越矮;而阶数的大小又取决于每个节点可以存储多少条记录。Innodb中每个节点使用一个页(page),页的大小为16KB,其中元数据只占大约128字节左右(包括文件管理头信息、页面头信息等等),大多数空间都用来存储数据。

  • 对于非叶节点,记录只包含索引的键和指向下一层节点的指针。假设每个非叶节点页面存储1000条记录,则每条记录大约占用16字节;当索引是整型或较短的字符串时,这个假设是合理的。延伸一下,我们经常听到建议说索引列长度不应过大,原因就在这里:索引列太长,每个节点包含的记录数太少,会导致树太高,索引的效果会大打折扣,而且索引还会浪费更多的空间。
  • 对于叶节点,记录包含了索引的键和值(值可能是行的主键、一行完整数据等,具体见前文),数据量更大。这里假设每个叶节点页面存储100条记录(实际上,当索引为聚簇索引时,这个数字可能不足100;当索引为辅助索引时,这个数字可能远大于100;可以根据实际情况进行估算)。

对于一颗3层B+树,*层(根节点)有1个页面,可以存储1000条记录;第二层有1000个页面,可以存储10001000条记录;第三层(叶节点)有10001000个页面,每个页面可以存储100条记录,因此可以存储10001000100条记录,即1亿条。而对于二叉树,存储1亿条记录则需要26层左右。

七、总结

*后,总结一下各种树解决的问题以及面临的新问题:

  1. 二叉查找树(BST):解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表;
  2. 平衡二叉树(AVL):通过旋转解决了平衡的问题,但是旋转操作效率太低;
  3. 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多;
  4. B树:通过将二叉树改为多路平衡查找树,解决了树过高的问题;
  5. B+树:在B树的基础上,将非叶节点改造为不存储数据的纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。