来尝个鲜,Python3.8几大新功能体验,冲鸭!

来尝个鲜,Python3.8几大新功能体验,冲鸭!

 

 

 

 

转自微信“菜鸟学Python”

继3.7版本之后Python再次发布了新版本,虽然新版本带来了不少调整,但是其中很大一部分都是对代码底层设计的修改,又或是typing、pickle等不常用的功能,对多数用户而言影响不大,今天我想重点聊一聊那些将对我们的代码编写产生较大影响的新功能。

在体验开始前先说下准备工作,由于Python3.8还没有正式发布,因此通过Anaconda的多版本管理搭建Python3.8新环境的方法是行不通的,我的做法是到官网下载对应的最新版本后单独安装。

来尝个鲜,Python3.8几大新功能体验,冲鸭!

 

为了避免与现有环境冲突,将其更名为Python38(下图),下文中的Python如无特殊说明均为Python3.6, Python38为Python3.8接下来就正式开始新特性体验。

来尝个鲜,Python3.8几大新功能体验,冲鸭!

 

1.字典逆序

 

我们都知道Python中的字典是无序的,Python3.6对这一问题进行了修订,默认情况下会按照键的创建顺序进行排序,但也仅限于此,你无法像列表那样对字典直接进行排序操作。

这一情况在Python3.8中进一步得到改善,Python3.8中reversed()方法增加了对字典对象的支持,可以对字典进行逆序操作

在下面这段代码中,对字典进行简单的迭代,将会按照顺序输出字典的键。

来尝个鲜,Python3.8几大新功能体验,冲鸭!

现在改变一下代码,加入reversed()方法:

来尝个鲜,Python3.8几大新功能体验,冲鸭!

先来看使用Python3.6的运行结果(下图),可以看到在Python3.6中,字典是不支持recersed()方法的。

 

来尝个鲜,Python3.8几大新功能体验,冲鸭!

然后用Python3.8运行结果如下可以看到,字典按照键创建顺序的逆序进行了输出。虽然只是非常小的一点功能提升,但是在某些场景下对于字典对象的应用可能会起到非常关键的作用。

来尝个鲜,Python3.8几大新功能体验,冲鸭!

 

 

2.位置参数

 

在Python3.8中的参数传递方面引入了一个新的特性:PEP 570 Positional-Only Argument——限定位置参数,下面就详细聊聊这究竟是怎么回事。

一般来说,Python中的参数传递有三种形式:位置参数、关键字参数和可变参数,为了避免不必要的麻烦,规定在可变参数之后只允许使用关键字参数。可是即便如此还是给程序员们留下了很大的自由空间,比如在可变参数之前,位置参数和关键字参数的使用几乎不受限制。这样就出现了一个问题,假如一个团队中很多人进行合作开发,函数的定义形式和调用模式是很难规范和统一的

因此Python3.8就引入了一个“Positional-Only Argument”的概念和分隔符“/”,在分隔符“/”左侧的参数,只允许使用位置参数的形式进行传递。

举个例子来进行说明,首先建立下面这样一个函数,由于函数中使用了分隔符“/”,因此只能使用Python3.8运行。

def add_num(x, y, z=100, /, a=100):
    print(x + y + z + a)

 

尝试以下面这种方式调用函数:

add_num(1, 2, z=4, a=5)

结果在运行的时候发生了报错:

来尝个鲜,Python3.8几大新功能体验,冲鸭!

接着尝试全部以位置参数的形式调用函数(如下),结果顺利执行。可见“Positional-Only Argument”对分隔符“/”右侧的参数形式并没有限制

# 输入
add_num(1, 2, 4, 5)
# 输出
12

那么如果只给定前两个参数,后面两个参数使用默认值又如何呢?通过下面的调用可以发现,也是可以正常运行的。

# 输入
add_num(1, 2)
# 输出
203

通过上面这个例子我们发现Python3.8对于参数传递的限制仅仅作用于分隔符“/”的左侧,而且只是在函数调用时发生作用。

 

3.赋值表达式

 

Python3.8中新增了赋值表达式“:=”操作符,简单来说就是把运算操作和赋值操作放在了一起,有点类似于“a+=b”这种表达方式,我想赋值表达式的出现应该是python追求简洁的传统理念所致。

来看下面这段代码,在func函数的if语句中,运算、赋值、判断操作在同一条语句中完成,即使变量a原本不存在也没关系。

# 输入
def func(x, y, z):
    if (a := x + y) != z:
        print(a)
    else:
        print(z)
func(1, 2, 5)
# 输出
3

当然,就上面这段代码本身来看,将 x+y 的结果进行赋值似乎意义不大,但是如果运算表达式的计算量非常大或者要进行大规模独写等操作的话,重复执行对代码的效率将造成大的影响;而如果事先对运算表达式赋值则需要多写一行代码。

 

目前来看,赋值表达式最重要的作用就是使代码变得更加简洁,至于运行效率的差异,目前还没有验证。

 

4.快速调试

 

在之前的Python版本中,“f表达式”——f'{expr}’的作用与eval()函数基本相同,例如:

  • f'{[1, 2, 3, 4, 5, 6]}’的结果是列表[1, 2, 3, 4, 5, 6];

  • f'{3 + 2}’的结果是运算后的值5。

Python3.8中对该功能进行了优化,f'{expr}’语句中增加了对等号“=”的支持,在保留原来功能的基础上,还能够同时输出运算表达式本身。

例如执行先面这段代码,除了计算并输出运算结果外,还会将“=”和其左侧的算式一并输出:

x = 3
print(f'{x * 2 = }')

执行结果:

来尝个鲜,Python3.8几大新功能体验,冲鸭!

f'{expr}’不仅适用于基本的算术运算,还能够进行其他对象的操作,以列表为例,令lst=[1, 2, 3, 4, 5, 6],现在对其进行扩展操作:

lst = eval('[1, 2, 3, 4, 5, 6]')
print(f'{lst = }')
print(f'{lst + [7] = }')

运行结果如下:

来尝个鲜,Python3.8几大新功能体验,冲鸭!

 

函数运算同样适用,例如对两个列表求交集,执行下面这段代码:

lst1 = [1, 2, 3, 4, 5]
lst2 = [3, 5, 7]
print(f'{list(set(lst1).intersection(set(lst2))) = }')

运行结果:

来尝个鲜,Python3.8几大新功能体验,冲鸭!

 

相比仅输出结果,连带运算表达式一起输出有助于定位检查,在调试代码的时候使用真的是快捷又方便

 

5.共享内存

 

进程是系统进行资源分配的独立单位,在以前的python版本中,进程间的数据交互只能通过Queue、Pipes等方式来实现,数据无法直接共享。

在Python 3.8中,multiprocessing模块提供了SharedMemory类,可以在不同的Python进程之间创建共享的内存block。目前支持int、float、str、bytes、bool、None、numpy.ndarray等一部分Python对象。

还是举个例子来进行说明,在下面这段代码中建立了2个进程,在进程1中对列表中的每个元素执行+10操作,进程1结束后执行进程2,输出列表内容。

来尝个鲜,Python3.8几大新功能体验,冲鸭!

由于进程之间数据无法共享,因此进程2中输出的列表是没有进行过+10操作的内容:

来尝个鲜,Python3.8几大新功能体验,冲鸭!

 

现在我们对代码进行一下小小的修改,nums不是作为一个普通的list,而是作为一个共享内存对象来创建,代码如下:

来尝个鲜,Python3.8几大新功能体验,冲鸭!

由于shared_memory是Python3.8中的新增内容,因此在Python3.6下运行会出错,我们还是用Python3.8来运行这段代码(结果如下)可以看到,进程2中输出的结果与进程1中是一样的,两个进程之间通过shared_memory实现了数据共享。

来尝个鲜,Python3.8几大新功能体验,冲鸭!

当然,shared_memory在实际应用中肯定不会如此简单,

  • 比如SharedMemory.ShareableList和SharedMemory.SharedMemory的使用本身有很多规则和限制、

  • 比如需要考虑数据锁的问题等等,

  • 但是共享内存确实为进程间通讯提供了一个新的解决方案,而且据说其通讯效率也是非常之高的。

Python3.8发布的新特性和新功能还有很多,对一些内置模块的改进和优化则更多,想尝鲜的同学可以点击阅读原文,了解Python3.8详情!你对Python3.8新特性怎么看,欢迎吱一声留言!

往期热门:

值得收藏|菜鸟学Python【入门文章大全】

 

学习群:

小密圈人气很高的两个实战项目

小密圈的趣味实战-微信主题

3个月还没入门Python,看这100名小密圈的同学3周学Python的杰作

Python爬虫框架

1.ScrapyScrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。。用这个框架可以轻松爬下来如亚马逊商品信息之类的数据。项目地址:https://scrapy.org/

2.PySpiderpyspider 是一个用python实现的功能强大的网络爬虫系统,能在浏览器界面上进行脚本的编写,功能的调度和爬取结果的实时查看,后端使用常用的数据库进行爬取结果的存储,还能定时设置任务与任务优先级等。项目地址:https://github.com/binux/pyspider

3.CrawleyCrawley可以高速爬取对应网站的内容,支持关系和非关系数据库,数据可以导出为JSON、XML等。项目地址:http://project.crawley-cloud.com/

4.PortiaPortia是一个开源可视化爬虫工具,可让您在不需要任何编程知识的情况下爬取网站!简单地注释您感兴趣的页面,Portia将创建一个蜘蛛来从类似的页面提取数据。项目地址:https://github.com/scrapinghub/portia

5.NewspaperNewspaper可以用来提取新闻、文章和内容分析。使用多线程,支持10多种语言等。项目地址:https://github.com/codelucas/newspaper

6.Beautiful SoupBeautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间。项目地址:https://www.crummy.com/software/BeautifulSoup/bs4/doc/

7.GrabGrab是一个用于构建Web刮板的Python框架。借助Grab,您可以构建各种复杂的网页抓取工具,从简单的5行脚本到处理数百万个网页的复杂异步网站抓取工具。Grab提供一个API用于执行网络请求和处理接收到的内容,例如与HTML文档的DOM树进行交互。项目地址:http://docs.grablib.org/er-user-manual8.ColaCola是一个分布式的爬虫框架,对于用户来说,只需编写几个特定的函数,而无需关注分布式运行的细节。任务会自动分配到多台机器上,整个过程对用户是透明的。项目地址:https://github.com/chineking/cola

转载源:https:/www.toutiao.com/i6560240315519730190/

Python 3D绘图

转自tedu.cn

在一些大型的科幻片中,常常能看到这样的场景,需要地图的时候,往往不是拿出一张纸,而是出现非常炫酷的3d投影地图,一目了然。在如今,3d技术早已经被人们所熟知,并且熟练地应用。网购鞋子的时候,出现的3d鞋子图片,可以全方位的了解鞋子的样式,还有3D网络广告,3D电影等等。

我们今天就一起来学习用python建一个3D的模型图。老规矩,先导入功能库:

import numpy as np

import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d.axes3d import Axes3D

#用matplotlib绘制的图显示在页面里而不是弹出一个窗口

%matplotlib inline

先生成3D坐标轴来看看效果吧

fig1=plt.figure()#创建一个绘图对象  

ax=Axes3D(fig1)#用这个绘图对象创建一个Axes对象(有3D坐标)  

plt.show()#显示模块中的所有绘图对象  

然后设置算法,不同的算法会生成不同的3D模型图。

#系数,有X,Y生成Z

a = 0.7

#将圆周率赋值给b

b = np.pi          

#计算Z轴的值

def mk_Z(X,Y):

    return 2+a-2*np.cos(X)*np.cos(Y)-a*np.cos(b-2*X)

生成X,Y,Z的数据

 #生成X,Y,Z

#numpy.linspace(start, stop, num=xxx, endpoint=True, retstep=False, dtype=None)

#在指定的间隔内返回均匀间隔的100个数字

x = np.linspace(0,2*np.pi,100)

y = np.linspace(0,2*np.pi,100)

##用这两个对象中的可能取值一一映射去扩充为所有可能的取样点

X,Y = np.meshgrid(x,y)

Z = mk_Z(X,Y)

查看数据类型

生成3D图形

 #创建绘图对象,设置对象大小

fig = plt.figure(figsize=(14,6))

#创建3d的视图,使用属性projection

#add_subplot在一块画布上确定图形分布,1行,2列,占据第一列

ax = fig.add_subplot(1, 2, 1, projection=’3d’)

#rstride和cstride表示行列隔多少个取样点建一个小面

ax.plot_surface(X,Y,Z,rstride = 5,cstride = 5)

生成带有颜色的3D图像

 #创建3d视图,使用colorbar,添加颜色柱

#add_subplot在一块画布上确定图形分布,1行,2列,占据第二列

ax = fig.add_subplot(1, 2, 2, projection=’3d’)

#rstride和cstride表示行列隔多少个取样点建一个小面,cmap表示绘制曲面的颜色,rainbow代表彩虹色

p = ax.plot_surface(X, Y, Z, rstride=5, cstride=5, cmap=’rainbow’, antialiased=True)

cb = fig.colorbar(p, shrink=0.5)

图像显示

如果不喜欢这种图片呢,我们还可以换一种算法来创建不同的3D图形,

 #网兜图形

def fun(x,y):  

    return np.power(x,2)+np.power(y,2)

怎么样,小伙伴们快来试一试吧!

matplotlib用法

1、matplotlib-绘制精美的图表

matplotlib 是python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图。而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中。

它的文档相当完备,并且 Gallery页面 中有上百幅缩略图,打开之后都有源程序。因此如果你需要绘制某种类型的图,只需要在这个页面中浏览/复制/粘贴一下,基本上都能搞定。

本章节作为matplotlib的入门介绍,将较为深入地挖掘几个例子,从中理解和学习matplotlib绘图的一些基本概念。

1.1 快速绘图

matplotlib的pyplot子库提供了和matlab类似的绘图API,方便用户快速绘制2D图表。让我们先来看一个简单的例子:

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10, 1000)
y = np.sin(x)
z = np.cos(x**2)

plt.figure(figsize=(8,4))
plt.plot(x,y,label="$sin(x)$",color="red",linewidth=2)
plt.plot(x,z,"b--",label="$cos(x^2)$")
plt.xlabel("Time(s)")
plt.ylabel("Volt")
plt.title("PyPlot First Example")
plt.ylim(-1.2,1.2)
plt.legend()
plt.show()
_images/pyplot_simple_plot.png

图1.1 调用pyplot库快速将数据绘制成曲线图

matplotlib中的快速绘图的函数库可以通过如下语句载入:

import matplotlib.pyplot as plt

pylab模块

matplotlib还提供了名为pylab的模块,其中包括了许多numpy和pyplot中常用的函数,方便用户快速进行计算和绘图,可以用于IPython中的快速交互式使用。

接下来调用figure创建一个绘图对象,并且使它成为当前的绘图对象。

plt.figure(figsize=(8,4))

也可以不创建绘图对象直接调用接下来的plot函数直接绘图,matplotlib会为我们自动创建一个绘图对象。如果需要同时绘制多幅图表的话,可以是给figure传递一个整数参数指定图标的序号,如果所指定序号的绘图对象已经存在的话,将不创建新的对象,而只是让它成为当前绘图对象。

通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;dpi参数指定绘图对象的分辨率,即每英寸多少个像素,缺省值为80。因此本例中所创建的图表窗口的宽度为8*80 = 640像素。

但是用工具栏中的保存按钮保存下来的png图像的大小是800*400像素。这是因为保存图表用的函数savefig使用不同的DPI配置,savefig函数也有一个dpi参数,如果不设置的话,将使用matplotlib配置文件中的配置,此配置可以通过如下语句进行查看,关于配置文件将在后面的章节进行介绍:

>>> import matplotlib
>>> matplotlib.rcParams["savefig.dpi"]
100

下面的两行程序通过调用plot函数在当前的绘图对象中进行绘图:

plt.plot(x,y,label="$sin(x)$",color="red",linewidth=2)
plt.plot(x,z,"b--",label="$cos(x^2)$")

plot函数的调用方式很灵活,第一句将x,y数组传递给plot之后,用关键字参数指定各种属性:

  • label : 给所绘制的曲线一个名字,此名字在图示(legend)中显示。只要在字符串前后添加”$”符号,matplotlib就会使用其内嵌的latex引擎绘制的数学公式。
  • color : 指定曲线的颜色
  • linewidth : 指定曲线的宽度

第二句直接通过第三个参数”b–“指定曲线的颜色和线型,这个参数称为格式化参数,它能够通过一些易记的符号快速指定曲线的样式。其中b表示蓝色,”–“表示线型为虚线。在IPython中输入 “plt.plot?” 可以查看格式化字符串的详细配置。

接下来通过一系列函数设置绘图对象的各个属性:

plt.xlabel("Time(s)")
plt.ylabel("Volt")
plt.title("PyPlot First Example")
plt.ylim(-1.2,1.2)
plt.legend()
  • xlabel : 设置X轴的文字
  • ylabel : 设置Y轴的文字
  • title : 设置图表的标题
  • ylim : 设置Y轴的范围
  • legend : 显示图示

最后调用plt.show()显示出我们创建的所有绘图对象。

1.1.1 配置属性

matplotlib所绘制的图的每个组成部分都对应有一个对象,我们可以通过调用这些对象的属性设置方法set_*或者pyplot的属性设置函数setp设置其属性值。例如plot函数返回一个 matplotlib.lines.Line2D 对象的列表,下面的例子显示如何设置Line2D对象的属性:

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> x = np.arange(0, 5, 0.1)
>>> line, = plt.plot(x, x*x) # plot返回一个列表,通过line,获取其第一个元素
>>> # 调用Line2D对象的set_*方法设置属性值
>>> line.set_antialiased(False)
>>> # 同时绘制sin和cos两条曲线,lines是一个有两个Line2D对象的列表
>>> lines = plt.plot(x, np.sin(x), x, np.cos(x)) #
>>> # 调用setp函数同时配置多个Line2D对象的多个属性值
>>> plt.setp(lines, color="r", linewidth=2.0)

这段例子中,通过调用Line2D对象line的set_antialiased方法,关闭对象的反锯齿效果。或者通过调用plt.setp函数配置多个Line2D对象的颜色和线宽属性。

同样我们可以通过调用Line2D对象的get_*方法,或者plt.getp函数获取对象的属性值:

>>> line.get_linewidth()
1.0
>>> plt.getp(lines[0], "color") # 返回color属性
'r'
>>> plt.getp(lines[1]) # 输出全部属性
alpha = 1.0
animated = False
antialiased or aa = True
axes = Axes(0.125,0.1;0.775x0.8)
... ...

注意getp函数只能对一个对象进行操作,它有两种用法:

  • 指定属性名:返回对象的指定属性的值
  • 不指定属性名:打印出对象的所有属性和其值

matplotlib的整个图表为一个Figure对象,此对象在调用plt.figure函数时返回,我们也可以通过plt.gcf函数获取当前的绘图对象:

>>> f = plt.gcf()
>>> plt.getp(f)
alpha = 1.0
animated = False
...

Figure对象有一个axes属性,其值为AxesSubplot对象的列表,每个AxesSubplot对象代表图表中的一个子图,前面所绘制的图表只包含一个子图,当前子图也可以通过plt.gca获得:

>>> plt.getp(f, "axes")
[<matplotlib.axes.AxesSubplot object at 0x05CDD170>]
>>> plt.gca()
<matplotlib.axes.AxesSubplot object at 0x05CDD170>

用plt.getp可以发现AxesSubplot对象有很多属性,例如它的lines属性为此子图所包括的 Line2D 对象列表:

>>> alllines = plt.getp(plt.gca(), "lines")
>>> alllines
<a list of 3 Line2D objects>
>>> alllines[0] == line # 其中的第一条曲线就是最开始绘制的那条曲线
True

通过这种方法我们可以很容易地查看对象的属性和它们之间的包含关系,找到需要配置的属性。

1.2 绘制多轴图

一个绘图对象(figure)可以包含多个轴(axis),在Matplotlib中用轴表示一个绘图区域,可以将其理解为子图。上面的第一个例子中,绘图对象只包括一个轴,因此只显示了一个轴(子图)。我们可以使用subplot函数快速绘制有多个轴的图表。subplot函数的调用形式如下:

subplot(numRows, numCols, plotNum)

subplot将整个绘图区域等分为numRows行 * numCols列个子区域,然后按照从左到右,从上到下的顺序对每个子区域进行编号,左上的子区域的编号为1。如果numRows,numCols和plotNum这三个数都小于10的话,可以把它们缩写为一个整数,例如subplot(323)和subplot(3,2,3)是相同的。subplot在plotNum指定的区域中创建一个轴对象。如果新创建的轴和之前创建的轴重叠的话,之前的轴将被删除。

下面的程序创建3行2列共6个轴,通过axisbg参数给每个轴设置不同的背景颜色。

for idx, color in enumerate("rgbyck"):
    plt.subplot(320+idx+1, axisbg=color)
plt.show()
_images/pyplot_subplot01.png

图1.2 用subplot函数将Figure分为六个子图区域

如果希望某个轴占据整个行或者列的话,可以如下调用subplot:

plt.subplot(221) # 第一行的左图
plt.subplot(222) # 第一行的右图
plt.subplot(212) # 第二整行
plt.show()
_images/pyplot_subplot02.png

图1.3 将Figure分为三个子图区域

当绘图对象中有多个轴的时候,可以通过工具栏中的Configure Subplots按钮,交互式地调节轴之间的间距和轴与边框之间的距离。如果希望在程序中调节的话,可以调用subplots_adjust函数,它有left, right, bottom, top, wspace, hspace等几个关键字参数,这些参数的值都是0到1之间的小数,它们是以绘图区域的宽高为1进行正规化之后的坐标或者长度。

1.3 配置文件

一幅图有许多需要配置的属性,例如颜色、字体、线型等等。我们在绘图时,并没有一一对这些属性进行配置,许多都直接采用了Matplotlib的缺省配置。Matplotlib将缺省配置保存在一个文件中,通过更改这个文件,我们可以修改这些属性的缺省值。

Matplotlib 使用配置文件 matplotlibrc 时的搜索顺序如下:

  • 当前路径 : 程序的当前路径
  • 用户配置路径 : 通常为 HOME/.matplotlib/,可以通过环境变量MATPLOTLIBRC修改
  • 系统配置路径 : 保存在 matplotlib的安装目录下的 mpl-data 下

通过下面的语句可以获取用户配置路径:

>>> import matplotlib
>>> matplotlib.get_configdir()
'C:\\Documents and Settings\\zhang\\.matplotlib'

通过下面的语句可以获得目前使用的配置文件的路径:

>>> import matplotlib
>>> matplotlib.matplotlib_fname()
'C:\\Python26\\lib\\site-packages\\matplotlib\\mpl-data\\matplotlibrc'

由于在当前路径和用户配置路径中都没有找到位置文件,因此最后使用的是系统配置路径下的配置文件。如果你将matplotlibrc复制一份到脚本的当前目录下:

>>> import os
>>> os.getcwd()
'C:\\zhang\\doc'

复制配置文件之后再运行:

>>> matplotlib.matplotlib_fname()
'C:\\zhang\\doc\\matplotlibrc'

如果你用文本编辑器打开此配置文件的话,你会发现它实际上是定义了一个字典。为了对众多的配置进行区分,关键字可以用点分开。

配置文件的读入可以使用 rc_params 函数,它返回一个配置字典:

>>> matplotlib.rc_params()
{'agg.path.chunksize': 0,
 'axes.axisbelow': False,
 'axes.edgecolor': 'k',
 'axes.facecolor': 'w',
 ... ...

在matplotlib模块载入的时候会调用rc_params,并把得到的配置字典保存到rcParams变量中:

>>> matplotlib.rcParams
{'agg.path.chunksize': 0,
'axes.axisbelow': False,
... ...

matplotlib将使用rcParams中的配置进行绘图。用户可以直接修改此字典中的配置,所做的改变会反映到此后所绘制的图中。例如下面的脚本所绘制的线将带有圆形的点标识符:

>>> matplotlib.rcParams["lines.marker"] = "o"
>>> import pylab
>>> pylab.plot([1,2,3])
>>> pylab.show()

为了方便配置,可以使用rc函数,下面的例子同时配置点标识符、线宽和颜色:

>>> matplotlib.rc("lines", marker="x", linewidth=2, color="red")

如果希望恢复到缺省的配置(matplotlib载入时从配置文件读入的配置)的话,可以调用 rcdefaults 函数。

>>> matplotlib.rcdefaults()

如果手工修改了配置文件,希望重新从配置文件载入最新的配置的话,可以调用:

>>> matplotlib.rcParams.update( matplotlib.rc_params() )

1.4 Artist对象

matplotlib API包含有三层:

  • backend_bases.FigureCanvas : 图表的绘制领域
  • backend_bases.Renderer : 知道如何在FigureCanvas上如何绘图
  • artist.Artist : 知道如何使用Renderer在FigureCanvas上绘图

FigureCanvas和Renderer需要处理底层的绘图操作,例如使用wxPython在界面上绘图,或者使用PostScript绘制PDF。Artist则处理所有的高层结构,例如处理图表、文字和曲线等的绘制和布局。通常我们只和Artist打交道,而不需要关心底层的绘制细节。

Artists分为简单类型和容器类型两种。简单类型的Artists为标准的绘图元件,例如Line2D、 Rectangle、 Text、AxesImage 等等。而容器类型则可以包含许多简单类型的Artists,使它们组织成一个整体,例如Axis、 Axes、Figure等。

直接使用Artists创建图表的标准流程如下:

  • 创建Figure对象
  • 用Figure对象创建一个或者多个Axes或者Subplot对象
  • 调用Axies等对象的方法创建各种简单类型的Artists

下面首先调用pyplot.figure辅助函数创建Figure对象,然后调用Figure对象的add_axes方法在其中创建一个Axes对象,add_axes的参数是一个形如[left, bottom, width, height]的列表,这些数值分别指定所创建的Axes对象相对于fig的位置和大小,取值范围都在0到1之间:

>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_axes([0.15, 0.1, 0.7, 0.3])

然后我们调用ax的plot方法绘图,创建一条曲线,并且返回此曲线对象(Line2D)。

>>> line, = ax.plot([1,2,3],[1,2,1])
>>> ax.lines
[<matplotlib.lines.Line2D object at 0x0637A3D0>]
>>> line
<matplotlib.lines.Line2D object at 0x0637A3D0>

ax.lines是一个为包含ax的所有曲线的列表,后续的ax.plot调用会往此列表中添加新的曲线。如果想删除某条曲线的话,直接从此列表中删除即可。

Axes对象还包括许多其它的Artists对象,例如我们可以通过调用set_xlabel设置其X轴上的标题:

>>> ax.set_xlabel("time")

如果我们查看set_xlabel的源代码的话,会发现它是通过调用下面的语句实现的:

self.xaxis.set_label_text(xlabel)

如果我们一直跟踪下去,会发现Axes的xaxis属性是一个XAxis对象:

>>> ax.xaxis
<matplotlib.axis.XAxis object at 0x06343230>

XAxis的label属性是一个Text对象:

>>> ax.xaxis.label
<matplotlib.text.Text object at 0x06343290>

而Text对象的_text属性为我们设置的值:

>>> ax.xaxis.label._text
'time'

这些对象都是Artists,因此也可以调用它们的属性获取函数来获得相应的属性:

>>> ax.xaxis.label.get_text()
'time'

1.4.1 Artist的属性

图表中的每个元素都用一个matplotlib的Artist对象表示,而每个Artist对象都有一大堆属性控制其显示效果。例如Figure对象和Axes对象都有patch属性作为其背景,它的值是一个Rectangle对象。通过设置此它的一些属性可以修改Figrue图表的背景颜色或者透明度等属性,下面的例子将图表的背景颜色设置为绿色:

>>> fig = plt.figure()
>>> fig.show()
>>> fig.patch.set_color("g")
>>> fig.canvas.draw()

patch的color属性通过set_color函数进行设置,属性修改之后并不会立即反映到图表的显示上,还需要调用fig.canvas.draw()函数才能够更新显示。

下面是Artist对象都具有的一些属性:

  • alpha : 透明度,值在0到1之间,0为完全透明,1为完全不透明
  • animated : 布尔值,在绘制动画效果时使用
  • axes : 此Artist对象所在的Axes对象,可能为None
  • clip_box : 对象的裁剪框
  • clip_on : 是否裁剪
  • clip_path : 裁剪的路径
  • contains : 判断指定点是否在对象上的函数
  • figure : 所在的Figure对象,可能为None
  • label : 文本标签
  • picker : 控制Artist对象选取
  • transform : 控制偏移旋转
  • visible : 是否可见
  • zorder : 控制绘图顺序

Artist对象的所有属性都通过相应的 get_* 和 set_* 函数进行读写,例如下面的语句将alpha属性设置为当前值的一半:

>>> fig.set_alpha(0.5*fig.get_alpha())

如果你想用一条语句设置多个属性的话,可以使用set函数:

>>> fig.set(alpha=0.5, zorder=2)

使用前面介绍的 matplotlib.pyplot.getp 函数可以方便地输出Artist对象的所有属性名和值。

>>> plt.getp(fig.patch)
    aa = True
    alpha = 1.0
    animated = False
    antialiased or aa = True
    ... ...

1.4.2 Figure容器

现在我们知道如何观察和修改已知的某个Artist对象的属性,接下来要解决如何找到指定的Artist对象。前面我们介绍过Artist对象有容器类型和简单类型两种,这一节让我们来详细看看容器类型的内容。

最大的Artist容器是matplotlib.figure.Figure,它包括组成图表的所有元素。图表的背景是一个Rectangle对象,用Figure.patch属性表示。当你通过调用add_subplot或者add_axes方法往图表中添加轴(子图时),这些子图都将添加到Figure.axes属性中,同时这两个方法也返回添加进axes属性的对象,注意返回值的类型有所不同,实际上AxesSubplot是Axes的子类。

>>> fig = plt.figure()
>>> ax1 = fig.add_subplot(211)
>>> ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3])
>>> ax1
<matplotlib.axes.AxesSubplot object at 0x056BCA90>
>>> ax2
<matplotlib.axes.Axes object at 0x056BC910>
>>> fig.axes
[<matplotlib.axes.AxesSubplot object at 0x056BCA90>,
<matplotlib.axes.Axes object at 0x056BC910>]

为了支持pylab中的gca()等函数,Figure对象内部保存有当前轴的信息,因此不建议直接对Figure.axes属性进行列表操作,而应该使用add_subplot, add_axes, delaxes等方法进行添加和删除操作。但是使用for循环对axes中的每个元素进行操作是没有问题的,下面的语句打开所有子图的栅格。

>>> for ax in fig.axes: ax.grid(True)

Figure对象可以拥有自己的文字、线条以及图像等简单类型的Artist。缺省的坐标系统为像素点,但是可以通过设置Artist对象的transform属性修改坐标系的转换方式。最常用的Figure对象的坐标系是以左下角为坐标原点(0,0),右上角为坐标(1,1)。下面的程序创建并添加两条直线到fig中:

>>> from matplotlib.lines import Line2D
>>> fig = plt.figure()
>>> line1 = Line2D([0,1],[0,1], transform=fig.transFigure, figure=fig, color="r")
>>> line2 = Line2D([0,1],[1,0], transform=fig.transFigure, figure=fig, color="g")
>>> fig.lines.extend([line1, line2])
>>> fig.show()
_images/pyplot_artist01.png

图1.4 在Figure对象中手工绘制直线

注意为了让所创建的Line2D对象使用fig的坐标,我们将fig.TransFigure赋给Line2D对象的transform属性;为了让Line2D对象知道它是在fig对象中,我们还设置其figure属性为fig;最后还需要将创建的两个Line2D对象添加到fig.lines属性中去。

Figure对象有如下属性包含其它的Artist对象:

  • axes : Axes对象列表
  • patch : 作为背景的Rectangle对象
  • images : FigureImage对象列表,用来显示图片
  • legends : Legend对象列表
  • lines : Line2D对象列表
  • patches : patch对象列表
  • texts : Text对象列表,用来显示文字

1.4.3 Axes容器

Axes容器是整个matplotlib库的核心,它包含了组成图表的众多Artist对象,并且有许多方法函数帮助我们创建、修改这些对象。和Figure一样,它有一个patch属性作为背景,当它是笛卡尔坐标时,patch属性是一个Rectangle对象,而当它是极坐标时,patch属性则是Circle对象。例如下面的语句设置Axes对象的背景颜色为绿色:

>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.patch.set_facecolor("green")

当你调用Axes的绘图方法(例如plot),它将创建一组Line2D对象,并将所有的关键字参数传递给这些Line2D对象,并将它们添加进Axes.lines属性中,最后返回所创建的Line2D对象列表:

>>> x, y = np.random.rand(2, 100)
>>> line, = ax.plot(x, y, "-", color="blue", linewidth=2)
>>> line
<matplotlib.lines.Line2D object at 0x03007030>
>>> ax.lines
[<matplotlib.lines.Line2D object at 0x03007030>]

注意plot返回的是一个Line2D对象的列表,因为我们可以传递多组X,Y轴的数据,一次绘制多条曲线。

与plot方法类似,绘制直方图的方法bar和绘制柱状统计图的方法hist将创建一个Patch对象的列表,每个元素实际上都是Patch的子类Rectangle,并且将所创建的Patch对象都添加进Axes.patches属性中:

>>> ax = fig.add_subplot(111)
>>> n, bins, rects = ax.hist(np.random.randn(1000), 50, facecolor="blue")
>>> rects
<a list of 50 Patch objects>
>>> rects[0]
<matplotlib.patches.Rectangle object at 0x05BC2350>
>>> ax.patches[0]
<matplotlib.patches.Rectangle object at 0x05BC2350>

一般我们不会直接对Axes.lines或者Axes.patches属性进行操作,而是调用add_line或者add_patch等方法,这些方法帮助我们完成许多属性设置工作:

>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> rect = matplotlib.patches.Rectangle((1,1), width=5, height=12)
>>> print rect.get_axes() # rect的axes属性为空
None
>>> rect.get_transform() # rect的transform属性为缺省值
BboxTransformTo(Bbox(array([[  1.,   1.],
       [  6.,  13.]])))
>>> ax.add_patch(rect) # 将rect添加进ax
<matplotlib.patches.Rectangle object at 0x05C34E50>
>>> rect.get_axes() # 于是rect的axes属性就是ax
<matplotlib.axes.AxesSubplot object at 0x05C09CB0>
>>> # rect的transform属性和ax的transData相同
>>> rect.get_transform()
... # 太长,省略
>>> ax.transData
... # 太长,省略
>>> ax.get_xlim() # ax的X轴范围为0到1,无法显示完整的rect
(0.0, 1.0)
>>> ax.dataLim._get_bounds() # 数据的范围和rect的大小一致
(1.0, 1.0, 5.0, 12.0)
>>> ax.autoscale_view() # 自动调整坐标轴范围
>>> ax.get_xlim() # 于是X轴可以完整显示rect
(1.0, 6.0)
>>> plt.show()

通过上面的例子我们可以看出,add_patch方法帮助我们设置了rect的axes和transform属性。

下面详细列出Axes包含各种Artist对象的属性:

  • artists : Artist对象列表
  • patch : 作为Axes背景的Patch对象,可以是Rectangle或者Circle
  • collections : Collection对象列表
  • images : AxesImage对象列表
  • legends : Legend对象列表
  • lines : Line2D对象列表
  • patches : Patch对象列表
  • texts : Text对象列表
  • xaxis : XAxis对象
  • yaxis : YAxis对象

下面列出Axes的创建Artist对象的方法:

Axes的方法所创建的对象添加进的列表
annotateAnnotatetexts
barsRectanglepatches
errorbarLine2D, Rectanglelines,patches
fillPolygonpatches
histRectanglepatches
imshowAxesImageimages
legendLegendlegends
plotLine2Dlines
scatterPolygonCollectionCollections
textTexttexts

下面以绘制散列图(scatter)为例,验证一下:

>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> t = ax.scatter(np.random.rand(20), np.random.rand(20))
>>> t # 返回值为CircleCollection对象
<matplotlib.collections.CircleCollection object at 0x06004230>
>>> ax.collections # 返回的对象已经添加进了collections列表中
[<matplotlib.collections.CircleCollection object at 0x06004230>]
>>> fig.show()
>>> t.get_sizes() # 获得Collection的点数
20
_images/pyplot_artist02.png

图1.5 用scatter函数绘制散列图

1.4.4 Axis容器

Axis容器包括坐标轴上的刻度线、刻度文本、坐标网格以及坐标轴标题等内容。刻度包括主刻度和副刻度,分别通过Axis.get_major_ticks和Axis.get_minor_ticks方法获得。每个刻度线都是一个XTick或者YTick对象,它包括实际的刻度线和刻度文本。为了方便访问刻度线和文本,Axis对象提供了get_ticklabels和get_ticklines方法分别直接获得刻度线和刻度文本:

>>> pl.plot([1,2,3],[4,5,6])
[<matplotlib.lines.Line2D object at 0x0AD3B670>]
>>> pl.show()
>>> axis = pl.gca().xaxis
>>> axis.get_ticklocs() # 获得刻度的位置列表
array([ 1. ,  1.5,  2. ,  2.5,  3. ])
>>> axis.get_ticklabels() # 获得刻度标签列表
<a list of 5 Text major ticklabel objects>
>>> [x.get_text() for x in axis.get_ticklabels()] # 获得刻度的文本字符串
[u'1.0', u'1.5', u'2.0', u'2.5', u'3.0']
>>> axis.get_ticklines() # 获得主刻度线列表,图的上下刻度线共10条
<a list of 10 Line2D ticklines objects>
>>> axis.get_ticklines(minor=True) # 获得副刻度线列表
<a list of 0 Line2D ticklines objects>

获得刻度线或者刻度标签之后,可以设置其各种属性,下面设置刻度线为绿色粗线,文本为红色并且旋转45度:

>>> for label in axis.get_ticklabels():
...     label.set_color("red")
...     label.set_rotation(45)
...     label.set_fontsize(16)
...
>>> for line in axis.get_ticklines():
...     line.set_color("green")
...     line.set_markersize(25)
...     line.set_markeredgewidth(3)

最终的结果图如下:

_images/pyplot_axis01.png

图1.6 手工配置X轴的刻度线和刻度文本的样式

上面的例子中,获得的副刻度线列表为空,这是因为用于计算副刻度的对象缺省为NullLocator,它不产生任何刻度线;而计算主刻度的对象为AutoLocator,它会根据当前的缩放等配置自动计算刻度的位置:

>>> axis.get_minor_locator() # 计算副刻度的对象
<matplotlib.ticker.NullLocator instance at 0x0A014300>
>>> axis.get_major_locator() # 计算主刻度的对象
<matplotlib.ticker.AutoLocator instance at 0x09281B20>

我们可以使用程序为Axis对象设置不同的Locator对象,用来手工设置刻度的位置;设置Formatter对象用来控制刻度文本的显示。下面的程序设置X轴的主刻度为pi/4,副刻度为pi/20,并且主刻度上的文本以pi为单位:

# -*- coding: utf-8 -*-
import matplotlib.pyplot as pl
from matplotlib.ticker import MultipleLocator, FuncFormatter
import numpy as np
x = np.arange(0, 4*np.pi, 0.01)
y = np.sin(x)
pl.figure(figsize=(8,4))
pl.plot(x, y)
ax = pl.gca()

def pi_formatter(x, pos):
    """
    比较罗嗦地将数值转换为以pi/4为单位的刻度文本
    """
    m = np.round(x / (np.pi/4))
    n = 4
    if m%2==0: m, n = m/2, n/2
    if m%2==0: m, n = m/2, n/2
    if m == 0:
        return "0"
    if m == 1 and n == 1:
        return "$\pi$"
    if n == 1:
        return r"$%d \pi$" % m
    if m == 1:
        return r"$\frac{\pi}{%d}$" % n
    return r"$\frac{%d \pi}{%d}$" % (m,n)

# 设置两个坐标轴的范围
pl.ylim(-1.5,1.5)
pl.xlim(0, np.max(x))

# 设置图的底边距
pl.subplots_adjust(bottom = 0.15)

pl.grid() #开启网格

# 主刻度为pi/4
ax.xaxis.set_major_locator( MultipleLocator(np.pi/4) )

# 主刻度文本用pi_formatter函数计算
ax.xaxis.set_major_formatter( FuncFormatter( pi_formatter ) )

# 副刻度为pi/20
ax.xaxis.set_minor_locator( MultipleLocator(np.pi/20) )

# 设置刻度文本的大小
for tick in ax.xaxis.get_major_ticks():
    tick.label1.set_fontsize(16)
pl.show()

关于刻度的定位和文本格式的东西都在matplotlib.ticker中定义,程序中使用到如下两个类:

  • MultipleLocator : 以指定值的整数倍为刻度放置刻度线
  • FuncFormatter : 使用指定的函数计算刻度文本,他会传递给所指定的函数两个参数:刻度值和刻度序号,程序中通过比较笨的办法计算出刻度值所对应的刻度文本

此外还有很多预定义的Locator和Formatter类,详细内容请参考相应的API文档。

_images/pyplot_axis02.png

图1.7 手工配置X轴的刻度线的位置和文本,并开启副刻度

转载:http://old.sebug.net/paper/books/scipydoc/matplotlib_intro.html#figure

Python Bottle路由

原文地址:http://www.linuxyw.com/566.html

静态路由

注:以下很多文字说明,我直接是用文档上的bottle开发从hello写起,用vim创建新的py文件:vim main.py#/usr/bin/env python#coding=utf-8from bottle import route, run@route(‘/hello’) #定义路由,即浏览器访问的地址def hello(): #函数名根据功能随意定义吧,只要不使用系统关键字便可,一般推荐按功能命名吧 return “Hellowww.linuxyw.com” #浏览器返回的内容run(host=’0.0.0.0′, port=8080) #开启服务,端口是8080,授受任何IP地址访问 现在这个教程,是直接这台博客服务器下跑的,所以,我只要输入我网站的域名(一般来说,都是在自己电脑上,或虚拟机中跑的程序,那么就应该输入该IP,如:http://192.168.1.22:8080/hello),就可以访问上面的程序在浏览器输入地址:http://linuxyw.com:8080/hellopython bottle框架基础教程:路由(route)hello linuxyw.com 就这么简单!保存为 py 文件并执行:python main.py[root@linuxyw bottle]# python main.py Bottle v0.12.8 server starting up (using WSGIRefServer())…Listening on http://0.0.0.0:8080/Hit Ctrl-C to quit. 用浏览器访问 http://linuxyw.com:8080/hello 就可以看到”Hello www.linuxyw.com”。它的执行流程大致如下:route() 函数将一段代码绑定到一个 URL,在这个例子中, 我们将 hello() 函数绑定给了 /hello 。我们称之为 route (也是该修饰器的函数名) ,这是 Bottle 框架最重要的开发理念。你可以根据需要定义任意多的 route 。在浏览器请求一个 URL 的时候,框架自动调用与之相应的函数,接着将函数的返回值发送给浏览器。就这么简单!最后一行调用的 run() 函数启动了内置的开发服务器。它监听它的 8080 端口并响应请求,Control-c 可将其关闭。到目前为止,这个内置的开发服务器已经足够用于日常的开发测试了。它根本不需要安装,就可以让你的应用跑起来。在教程的后面,你将学会如何让你的应用跑在其他服务器上面 (译者注:内置服务器不能满足生产环境的要求) 调试模式 在早期开发的时候非常有用,但请务必记得,在生产环境中将其关闭。毫无疑问,这是一个十分简单例子,但它展示了用 Bottle 做应用开发的基本理念。接下来你将了解到其他开发方式。

动态路由

动态路由简单的就是说,url地址是可以传递不同的内容到网页内容上去,这样就不需要写很多的静态路由了,用以下的代码示例吧

  1. @route(‘/hello/<name>’)
  2. def helloName(name):
  3. return “hello:%s” % name

在url中,输入不同的值,就会出现不同的内容,

如果我输入http://linuxyw.com:8080/hello/jianjian,那网页就会显示:hello:jianjian

如果我输入http://linuxyw.com:8080/hello/python,那网页就会显示:hello:python

包含通配符的 route,我们称之为动态 route(与之对应的是静态 route),它能匹配多个 URL 地址。一个通配符包含在一对尖括号里面 (像这样 <name> ),通配符之间用”/” 分隔开来。如果我们将 URL 定义为/hello/<name> 这样,那么它就能匹配 /hello/alice 和 /hello/bob 这样的浏览器请求,但不能匹配/hello , /hello/ 和 /hello/mr/smith 。URL 中的通配符都会当作参数传给回调函数,直接在回调函数中使用。这样可以漂亮地实现 RESTful形式的 URL。

用python web框架 bottle 开发网站

python有很多web开发框架,django,bottle,flask,pylons,Tornado,webpy,web2py, Quixote,Pyramid,aiohttp,sani……还有其它各种出名的不出名的框架,但是微框架却不多,抛开语言,最早最有名的sinatra,webpy,直到现在还有不少用户。

python web入门有人推荐学习django或者flask,其实bottle比二者更轻,更小,更容易上手和被初学者接受,bottle不依赖于任何第三方的python模块,单文件,容易部署,性能高,内置web开发基本需要的东西(route,request等等),甚至不亚于Tornado。

用python web框架 bottle 开发网站(一)
性能测试图片

接下来的我们用bottle实现一个简单的无数据库用户登录注册系统,添加螺丝帽验证,即使你没有任何python web基础,跟着流程走一遍,也是能理解用bottle做web开发究竟是怎么回事,其它框架则大同小异。

开发要求:

  • 安装了最新的python(推荐python 版本 3.x)
  • 安装时勾选了添加环境变量
  • 下载bottle.py到本地,或者pip install bottle安装bottle
  • 使用一个简单的编辑器,例如notepad++,visual studio code,不推荐使用记事本
  • 安装cmder(可选)

接下来,我们开始使用bottle做一个简单的了解

from bottle import route,run
@route('/')
def index():
    return 'hello, bottle'
run(host = 'localhost', port = 80)

在你的编辑器中键入以上代码,保存为main.py,然后在命令行中运行python main.py,你会看到一个服务器已经运行了

用python web框架 bottle 开发网站(一)
image.png

打开浏览器,访问localhost或者http://127.0.0.1,显示欢迎页面

用python web框架 bottle 开发网站(一)
image.png

第一节,我们先做一个简单的了解,知道bottle是一个web微框架,然后用短短5行代码,构建并运行一个服务器。

来源:简书

详解python的bottle框架跨域请求报错问题的处理方法(转)

在用python的bottle框架开发时,前端使用ajax跨域访问时,js代码老是进入不了success,而是进入了error,而返回的状态却是200。url直接在浏览器访问也是正常的,浏览器按F12后会发现下面这个错误提示

XMLHttpRequest cannot load http://192.168.0.118:8081/get_mobile_number/?id=1. No ‘Access-Control-Allow-Origin’ header is present on the requestedresource. Origin ‘null‘ is therefore not allowed access.

  通过搜索引擎查询错误,会发现几乎查找出来的答案都说是跨域问题,只需要在主文件的代码中添加下面就可以了,国外的网站好多解决方案都是这样说明

123@hook('after_request')def enable_cors():response.headers['Access-Control-Allow-Origin'] = '*'

  而事实上是按找出来的解决方法添加后还是出现错误,查看浏览器输出的http头并没有看到我们刚刚增加的Access-Control-Allow-Origin

  通过DEBUG,进入bottle的源码中查看

  这个问题我测试过在python2与python3对应的bottle框架中都存在这种问题,我们将它改为:

12345678910111213class HTTPResponse(Response, BottleException):def init(self, body='', status=None, headers=None, **more_headers):super(HTTPResponse, self).init(body, status, headers, **more_headers)def apply(self, response):response._status_code = self._status_coderesponse._status_line = self._status_lineif self._headers:if response._headers:response._headers.update(self._headers)else:response._headers = self._headersresponse._cookies = self._cookiesresponse.body = self.body

   再运行代码就可以看见ajax代码正常了

转自php中文网

Bottle源码解析

1. run的实现

所有的框架请求响应都基于一个原理
http请求 –> wsgi服务器 –> wsgi接口(实际就是框架中自定义实现的函数经过底层封装) –> 响应
可以参考廖雪峰的教程中关于wsgi接口的讲解

下我们先看看bottle是如何实现服务器运行时自动重新加载

def run(app=None,        server='wsgiref',        host='127.0.0.1',        port=8080,        interval=1,        reloader=False,        quiet=False,        plugins=None,        debug=None,        config=None, **kargs):    """ Start a server instance. This method blocks until the server terminates.        :param app: WSGI application or target string supported by               :func:`load_app`. (default: :func:`default_app`)        :param server: Server adapter to use. See :data:`server_names` keys               for valid names or pass a :class:`ServerAdapter` subclass.               (default: `wsgiref`)        :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on               all interfaces including the external one. (default: 127.0.0.1)        :param port: Server port to bind to. Values below 1024 require root               privileges. (default: 8080)        :param reloader: Start auto-reloading server? (default: False)        :param interval: Auto-reloader interval in seconds (default: 1)        :param quiet: Suppress output to stdout and stderr? (default: False)        :param options: Options passed to the server adapter.     """    if NORUN: return    # 自动重载    if reloader and not os.environ.get('BOTTLE_CHILD'):        import subprocess        lockfile = None        try:            # tempfile 临时文件操作模块https://docs.python.org/2/library/tempfile.html            # 第一个相当于执行os.open()函数返回文件handler,第二个表示绝对路径            fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')            os.close(fd)  # We only need this file to exist. We never write to it            # sys.executable 是获取当前python解释器的路径            while os.path.exists(lockfile):                args = [sys.executable] + sys.argv                environ = os.environ.copy()                environ['BOTTLE_CHILD'] = 'true'                environ['BOTTLE_LOCKFILE'] = lockfile                 # 创建一个子进程实例                p = subprocess.Popen(args, env=environ)                # 如果返回None表示子进程未结束                while p.poll() is None:  # Busy wait...                    # 临时文件设置为当前时间                    os.utime(lockfile, None)  # I am alive!                    time.sleep(interval)                # linux 系统的信号机制http://www.cppblog.com/sleepwom/archive/2010/12/27/137564.html                # 3表示按下退出键                # 非正常退出时                if p.poll() != 3:                    # os.unlink 相当于去除remove()                    if os.path.exists(lockfile): os.unlink(lockfile)                    sys.exit(p.poll())        except KeyboardInterrupt:            pass        finally:            if os.path.exists(lockfile):                os.unlink(lockfile)        return

首先第一次运行时,开启一个新的进程,确保运行server时的进程和python解释器一致
不影响主进程的继续运行

    try:        # 这一部分主要是app的相关设置        if debug is not None: _debug(debug)        app = app or default_app()        if isinstance(app, basestring):            app = load_app(app)        if not callable(app):            raise ValueError("Application is not callable: %r" % app)         for plugin in plugins or []:            if isinstance(plugin, basestring):                plugin = load(plugin)            app.install(plugin)         if config:            app.config.update(config)         if server in server_names:            server = server_names.get(server)        if isinstance(server, basestring):            server = load(server)        if isinstance(server, type):            server = server(host=host, port=port, **kargs)        if not isinstance(server, ServerAdapter):            raise ValueError("Unknown or unsupported server: %r" % server)         server.quiet = server.quiet or quiet        if not server.quiet:            _stderr("Bottle v%s server starting up (using %s)...\n" %                    (__version__, repr(server)))            _stderr("Listening on http://%s:%d/\n" %                    (server.host, server.port))            _stderr("Hit Ctrl-C to quit.\n\n")                # 当选择自动重载时,如果解释器进程已经启动        # 则只需要检测应用相关内容有没有变化,如果有变化终止主线程并重新实现异常捕获        if reloader:            lockfile = os.environ.get('BOTTLE_LOCKFILE')            bgcheck = FileCheckerThread(lockfile, interval)            # 开启新线程检测文件修改,如果修改终止当前主线程,抛出异常            with bgcheck:                # 主线程监听请求                server.run(app)            if bgcheck.status == 'reload':                sys.exit(3)        else:            server.run(app)    except KeyboardInterrupt:        pass     except (SystemExit, MemoryError):        raise    except:        if not reloader: raise        if not getattr(server, 'quiet', quiet):            print_exc()        time.sleep(interval)        sys.exit(3)

FileCheckerThread会对应用相关文件内容变化进行检测
server加载app,由server接收请求并执行相应的应用函数
在此之前,我们先了解FileCheckerThread

2. 应用修改后的自动重载

这是一个上下文管理器,当__enter__时开启一个新的线程,这个线程的任务就是检测应用相关模块文件的变化,决定是否终止主线程,当__exit__时,如果返回True则重现异常,否则正常执行后续代码

class FileCheckerThread(threading.Thread):    """ Interrupt main-thread as soon as a changed module file is detected,        the lockfile gets deleted or gets too old. """     def __init__(self, lockfile, interval):        threading.Thread.__init__(self)        self.daemon = True        self.lockfile, self.interval = lockfile, interval        #: Is one of 'reload', 'error' or 'exit'        self.status = None     def run(self):        exists = os.path.exists        mtime = lambda p: os.stat(p).st_mtime        files = dict()         for module in list(sys.modules.values()):            path = getattr(module, '__file__', '')            if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]            if path and exists(path): files[path] = mtime(path)         while not self.status:            if not exists(self.lockfile)\            or mtime(self.lockfile) < time.time() - self.interval - 5:                self.status = 'error'                thread.interrupt_main()            for path, lmtime in list(files.items()):                if not exists(path) or mtime(path) > lmtime:                    self.status = 'reload'                    thread.interrupt_main()                    break            time.sleep(self.interval)     def __enter__(self):        self.start()        # 这个地方是重新载入更新后模块的关键    # 当检测到文件变化时,终止主线程使监听请求停止,退出上下文管理器时,如果返回True则重现异常捕获    def __exit__(self, exc_type, *_):        if not self.status: self.status = 'exit'  # silent exit        self.join()        return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)

3. server调用应用函数

bottle提供了一个ServerAdapter的适配器类,重写run方法就能使bottle可以使用多种框架提供的server。

class ServerAdapter(object):    quiet = False     def __init__(self, host='127.0.0.1', port=8080, **options):        self.options = options        self.host = host        self.port = int(port)     def run(self, handler):  # pragma: no cover        pass     def __repr__(self):        args = ', '.join(['%s=%s' % (k, repr(v))                          for k, v in self.options.items()])        return "%s(%s)" % (self.__class__.__name__, args)

默认使用了python自带的wsgiref, 从代码中我们可以看到其中主要由三部分组成:接收请求模块,处理请求模块,组装模块

class WSGIRefServer(ServerAdapter):    def run(self, app):  # pragma: no cover        from wsgiref.simple_server import make_server        from wsgiref.simple_server import WSGIRequestHandler, WSGIServer        import socket         class FixedHandler(WSGIRequestHandler):            def address_string(self):  # Prevent reverse DNS lookups please.                return self.client_address[0]             def log_request(*args, **kw):                if not self.quiet:                    return WSGIRequestHandler.log_request(*args, **kw)         handler_cls = self.options.get('handler_class', FixedHandler)        server_cls = self.options.get('server_class', WSGIServer)         if ':' in self.host:  # Fix wsgiref for IPv6 addresses.            if getattr(server_cls, 'address_family') == socket.AF_INET:                 class server_cls(server_cls):                    address_family = socket.AF_INET6         self.srv = make_server(self.host, self.port, app, server_cls,                               handler_cls)        self.port = self.srv.server_port  # update port actual port (0 means random)        try:            self.srv.serve_forever()        except KeyboardInterrupt:            self.srv.server_close()  # Prevent ResourceWarning: unclosed socket            raise

4.WSGIServer

4.1 寻根到底,我们现研究一下WSGIServer 的基类
BaseServer 主要实现线程上的控制,实现一些供上层调用的接口,例如

server_activateserve_forevershutdownhandle_requestverify_requesthandle_error

TCPServer 继承BaseServer, 实现bind,listen,accept, close等函数的封装

    def server_bind(self):        """Called by constructor to bind the socket.        May be overridden.        """        if self.allow_reuse_address:            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)        self.socket.bind(self.server_address)        self.server_address = self.socket.getsockname()     def server_activate(self):        """Called by constructor to activate the server.        May be overridden.        """        self.socket.listen(self.request_queue_size)     def server_close(self):        """Called to clean-up the server.        May be overridden.        """        self.socket.close()

HttpServer 继承TCPServer, 添加了host和port两个属性
WSGIServer 继承HttpServer, 设置了环境变量,提供了获取应用和设置应用的接口

class WSGIServer(HTTPServer):     """BaseHTTPServer that implements the Python WSGI protocol"""     application = None     def server_bind(self):        """Override server_bind to store the server name."""        HTTPServer.server_bind(self)        self.setup_environ()     def setup_environ(self):        # Set up base environment        env = self.base_environ = {}        env['SERVER_NAME'] = self.server_name        env['GATEWAY_INTERFACE'] = 'CGI/1.1'        env['SERVER_PORT'] = str(self.server_port)        env['REMOTE_HOST']=''        env['CONTENT_LENGTH']=''        env['SCRIPT_NAME'] = ''     def get_app(self):        return self.application     def set_app(self,application):        self.application = application

4.2 WSGIRequestHandler的实现
最底层的BaseRequestHandler:处理请求的基类,定义了处理请求的流程
StreamRequestHandler: 继承BaseRequestHandler,提供了处理请求前rfile和wfile属性,使处理请求时能通过类似文件读写获取请求和返回响应

class StreamRequestHandler(BaseRequestHandler):     """Define self.rfile and self.wfile for stream sockets."""     # Default buffer sizes for rfile, wfile.    # We default rfile to buffered because otherwise it could be    # really slow for large data (a getc() call per byte); we make    # wfile unbuffered because (a) often after a write() we want to    # read and we need to flush the line; (b) big writes to unbuffered    # files are typically optimized by stdio even when big reads    # aren't.    rbufsize = -1    wbufsize = 0     # A timeout to apply to the request socket, if not None.    timeout = None     # Disable nagle algorithm for this socket, if True.    # Use only when wbufsize != 0, to avoid small packets.    disable_nagle_algorithm = False     def setup(self):        self.connection = self.request        if self.timeout is not None:            self.connection.settimeout(self.timeout)        if self.disable_nagle_algorithm:            self.connection.setsockopt(socket.IPPROTO_TCP,                                       socket.TCP_NODELAY, True)        self.rfile = self.connection.makefile('rb', self.rbufsize)        self.wfile = self.connection.makefile('wb', self.wbufsize)     def finish(self):        if not self.wfile.closed:            try:                self.wfile.flush()            except socket.error:                # A final socket error may have occurred here, such as                # the local error ECONNABORTED.                pass        self.wfile.close()        self.rfile.close()

BaseHTTPRequestHandler:继承StreamRequestHandler,handle处理一个请求,轮询直到收到一个明确关闭连接;parse_request解析请求requestline,如果一切正常,继续处理请求

WSGIRequestHandler:继承了BaseHTTPRequestHandler, 添加get_environ获取环境变量, 重写了handle方法。当requestline >65536时返回414, 实例化一个ServerHandler实例

    def handle(self):        """Handle a single HTTP request"""         self.raw_requestline = self.rfile.readline(65537)        if len(self.raw_requestline) > 65536:            self.requestline = ''            self.request_version = ''            self.command = ''            self.send_error(414)            return         if not self.parse_request(): # An error code has been sent, just exit            return         handler = ServerHandler(            self.rfile, self.wfile, self.get_stderr(), self.get_environ()        )        handler.request_handler = self      # backpointer for logging        handler.run(self.server.get_app())

handler.run(self.server.get_app())实现了从请求到应用函数执行,并把执行后的结果写入wfile返回
我们再看wsgiref.handlers中BaseHandler中,是如何实现的。

    def run(self, application):        """Invoke the application"""        # Note to self: don't move the close()!  Asynchronous servers shouldn't        # call close() from finish_response(), so if you close() anywhere but        # the double-error branch here, you'll break asynchronous servers by        # prematurely closing.  Async servers must return from 'run()' without        # closing if there might still be output to iterate over.        try:            self.setup_environ()            self.result = application(self.environ, self.start_response)            self.finish_response()        except:            try:                self.handle_error()            except:                # If we get an error handling an error, just give up already!                self.close()                raise   # ...and let the actual server figure it out.                    def start_response(self, status, headers,exc_info=None):        """'start_response()' callable as specified by PEP 333"""         if exc_info:            try:                if self.headers_sent:                    # Re-raise original exception if headers sent                    raise exc_info[0], exc_info[1], exc_info[2]            finally:                exc_info = None        # avoid dangling circular ref        elif self.headers is not None:            raise AssertionError("Headers already set!")         assert type(status) is StringType,"Status must be a string"        assert len(status)>=4,"Status must be at least 4 characters"        assert int(status[:3]),"Status message must begin w/3-digit code"        assert status[3]==" ", "Status message must have a space after code"        if __debug__:            for name,val in headers:                assert type(name) is StringType,"Header names must be strings"                assert type(val) is StringType,"Header values must be strings"                assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"        self.status = status        self.headers = self.headers_class(headers)        return self.write

application接受了两个参数,一个envrion, 和一个start_response的方法。因此下一步就是研究我们写的应用函数是如何被封装成适配的application

出处:https://segmentfault.com/a/1190000008400059

Bottle教程

  bottle 是一个轻量级的python web框架, 可以适配各种web服务器,包括python自带的wsgiref(默认),gevent, cherrypy,gunicorn等等。bottle是单文件形式发布,源码在这里可以下载,代码量不多,可以用来学习web框架。这里也有官方文档的中文翻译。   首先我们来运行一下bottle的hello world复制代码from bottle import run if __name__ == ‘__main__’: def application(environ, start_response): start_response(‘200 OK’, [(‘Content-Type’, ‘text/html’)]) return [‘<h1>Hello world!</h1>’] run(host=’localhost’, port=8080, app=application)复制代码  上面的代码看起来也非常符合wsgi的接口规范。启动改代码,可以看到输出 Bottle v0.13-dev server starting up (using WSGIRefServer())… Listening on http://localhost:8080/ Hit Ctrl-C to quit.   输出中加粗部分表明使用的web服务器是python自带的wsgiref。也可以使用其他web server,比如gevent,前提是需要安装gevent,修改后的代码如下:复制代码from bottle import runimport gevent.monkeygevent.monkey.patch_all() if __name__ == ‘__main__’: def application(environ, start_response): start_response(‘200 OK’, [(‘Content-Type’, ‘text/html’)]) return [‘<h1>Hello world!</h1>’] run(host=’localhost’, port=8080, app=application, server = ‘gevent’)复制代码通过server关键字指定web服务器为‘gevent’,输出的第一行变成了: Bottle v0.13-dev server starting up (using GeventServer())… 不管bottle用什么web服务器启动,在浏览器输入127.0.0.1:8080,都可以看到 下面介绍bottle中部分类和接口bottle.Bottle 代表一个独立的wsgi应用,由一下部分组成:routes, callbacks, plugins, resources and configuration。 __call__: Bottle定义了__call__函数, 使得Bottle的实例能成为一个callable。在前文提到,web框架(或Application)需要提供一个callbale对象给web服务器,bottle提供的就是Bottle实例 def __call__(self, environ, start_response):   ””” Each instance of :class:’Bottle’ is a WSGI application. “”” return self.wsgi(environ, start_response) 下面是Bottle.wsgi函数的核心代码,主要调用两个比较重要的函数:_handle, _cast复制代码 def wsgi(self, environ, start_response): “”” The bottle WSGI-interface. “”” try: out = self._cast(self._handle(environ)) # rfc2616 section 4.3 if response._status_code in (100, 101, 204, 304)\ or environ[‘REQUEST_METHOD’] == ‘HEAD’: if hasattr(out, ‘close’): out.close() out = [] start_response(response._status_line, response.headerlist) return out

复制代码

  _handle:处理请求,最终调用到application ,简化后的代码如下:

复制代码
1   def _handle(self, environ):
2         self.trigger_hook('before_request')
3         route, args = self.router.match(environ)
4         out = route.call(**args)
5         self.trigger_hook('after_request')
6         return out
复制代码

    _cast:        标准的wsgi接口对Application的返回值要求严格,必须迭代返回字符串。bottle做了一些扩展,可以允许App返回更加丰富的类型,比如dict,File等。 _cast函数对_handle函数返回值进行处理,使之符合wsgi规范 bottle.Route    封装了路由规则与对应的回调 bottle.Router    A Router is an ordered collection of route->target pairs. It is used to  efficiently match WSGI requests against a number of routes and return the first target that satisfies the request. ServerAdapter    所有bottle适配的web服务器的基类,子类只要实现run方法就可以了,bottle里面有大量的Web服务器的适配。下表来自官网,介绍了bottle支持的各种web服务器,以及各自的特性。    

NameHomepageDescription
cgi Run as CGI script
flupflupRun as FastCGI process
gaegaeHelper for Google App Engine deployments
wsgirefwsgirefSingle-threaded default server
cherrypycherrypyMulti-threaded and very stable
pastepasteMulti-threaded, stable, tried and tested
rocketrocketMulti-threaded
waitresswaitressMulti-threaded, poweres Pyramid
gunicorngunicornPre-forked, partly written in C
eventleteventletAsynchronous framework with WSGI support.
geventgeventAsynchronous (greenlets)
dieseldieselAsynchronous (greenlets)
fapws3fapws3Asynchronous (network side only), written in C
tornadotornadoAsynchronous, powers some parts of Facebook
twistedtwistedAsynchronous, well tested but… twisted
meinheldmeinheldAsynchronous, partly written in C
bjoernbjoernAsynchronous, very fast and written in C
auto Automatically selects an available server adapter

    可以看到,bottle适配的web服务器很丰富。工作模式也很全面,有多线程的(如paste)、有多进程模式的(如gunicorn)、也有基于协程的(如gevent)。具体选择哪种web服务器取决于应用的特性,比如是CPU bound还是IO bound

bottle.run 启动wsgi服务器。几个比较重要的参数 app: wsgi application,即可以是bottle.Bottle 也开始是任何满足wsgi 接口的函数 server: wsgi http server,字符串 host:port: 监听端口 核心逻辑: ServerAdapter.run(app)。 最后,bottle源码中有一些使用descriptor的例子,实现很巧妙,值得一读,前文也有介绍。 references;http://www.bottlepy.org/docs/dev/https://raw.githubusercontent.com/bottlepy/bottle/master/bottle.pyhttp://blog.csdn.net/huithe/article/details/8087645http://simple-is-better.com/news/59http://www.bottlepy.org/docs/dev/deployment.html#server-optionshttp://blog.rutwick.com/use-bottle-python-framework-with-google-app-engine本文版权归作者xybaby(博文地址:http://www.cnblogs.com/xybaby/)所有,欢迎转载和商用,请在文章页面明显位置给出原文链接并保留此段声明,否则保留追究法律责任的权利,其他事项,可留言咨询。

Bottle入门

原文链接:https://www.cnblogs.com/dadong616/p/6839671.html

from collector import *
from scripts.agent import *
import util 
 # bottle中添加路由的两种方法# 第一种,使用route装饰器,需要指定
method(get, put, delete,delete),
# 如果不指定默认为get 方法@route('/', method='GET')def hello():    return "hello, word!"   if __name__ == '__main__':     
# run方法中的reloader是一个
很实用的功能,调试的时候修改完代码    
# 服务会自动重启,方便开发。    run(host='0.0.0.0', port=8080, debug=True, reloader=True)

参考链接:http://www.cnblogs.com/dadong616/tag/bottle/