python中的第三方提供的可用编程模块_python笔记-第三方模块和项目.txt

常用第三方模块

除了内建的模块外,Python还有大量的第三方模块。

基本上,所有的第三方模块都会在PyPI - the Python Package Index上注册,

只要找到对应的模块名字,即可用pip安装。

此外,在安装第三方模块一节中,我们强烈推荐安装Anaconda,

安装后,数十个常用的第三方模块就已经就绪,不用pip手动安装。

Pillow

PIL:Python Imaging Library,已经是Python平台事实上的图像处理标准库了。

PIL功能非常强大,但API却非常简单易用。

由于PIL仅支持到Python 2.7,加上年久失修,于是一群志愿者在PIL的基础上创建了兼容的版本,名字叫Pillow,

支持最新Python 3.x,又加入了许多新特性,因此,我们可以直接安装使用Pillow。

安装Pillow

如果安装了Anaconda,Pillow就已经可用了。否则,需要在命令行下通过pip安装:

$ pip install pillow

如果遇到Permission denied安装失败,请加上sudo重试。

操作图像

来看看最常见的图像缩放操作,只需三四行代码:

from PIL import Image

# 打开一个jpg图像文件,注意是当前路径:

im = Image.open('test.jpg')

# 获得图像尺寸:

w, h = im.size

print('Original image size: %sx%s' % (w, h))

# 缩放到50%:

im.thumbnail((w//2, h//2))

print('Resize image to: %sx%s' % (w//2, h//2))

# 把缩放后的图像用jpeg格式保存:

im.save('thumbnail.jpg', 'jpeg')

其他功能如切片、旋转、滤镜、输出文字、调色板等一应俱全。

比如,模糊效果也只需几行代码:

from PIL import Image, ImageFilter

# 打开一个jpg图像文件,注意是当前路径:

im = Image.open('test.jpg')

# 应用模糊滤镜:

im2 = im.filter(ImageFilter.BLUR)

im2.save('blur.jpg', 'jpeg')

PIL的ImageDraw提供了一系列绘图方法,让我们可以直接绘图。比如要生成字母验证码图片:

from PIL import Image, ImageDraw, ImageFont, ImageFilter

import random

# 随机字母:

def rndChar():

return chr(random.randint(65, 90))

# 随机颜色1:

def rndColor():

return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))

# 随机颜色2:

def rndColor2():

return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))

# 240 x 60:

width = 60 * 4

height = 60

image = Image.new('RGB', (width, height), (255, 255, 255))

# 创建Font对象:

font = ImageFont.truetype('Arial.ttf', 36)

# 创建Draw对象:

draw = ImageDraw.Draw(image)

# 填充每个像素:

for x in range(width):

for y in range(height):

draw.point((x, y), fill=rndColor())

# 输出文字:

for t in range(4):

draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2())

# 模糊:

image = image.filter(ImageFilter.BLUR)

image.save('code.jpg', 'jpeg')

我们用随机颜色填充背景,再画上文字,最后对图像进行模糊

如果运行的时候报错:

IOError: cannot open resource

这是因为PIL无法定位到字体文件的位置,可以根据操作系统提供绝对路径,比如:

'/Library/Fonts/Arial.ttf'

要详细了解PIL的强大功能,请请参考Pillow官方文档:

https://pillow.readthedocs.org/

requests

我们已经讲解了Python内置的urllib模块,用于访问网络资源。

但是,它用起来比较麻烦,而且,缺少很多实用的高级功能。

更好的方案是使用requests。它是一个Python第三方库,处理URL资源特别方便。

安装requests

如果安装了Anaconda,requests就已经可用了。否则,需要在命令行下通过pip安装:

$ pip install requests

如果遇到Permission denied安装失败,请加上sudo重试。

使用requests

要通过GET访问一个页面,只需要几行代码:

>>> import requests

>>> r = requests.get('https://www.douban.com/') # 豆瓣首页

>>> r.status_code

200

>>> r.text

r.text

对于带参数的URL,传入一个dict作为params参数:

>>> r = requests.get('https://www.douban.com/search', params={'q': 'python', 'cat': '1001'})

>>> r.url # 实际请求的URL

'https://www.douban.com/search?q=python&cat=1001'

requests自动检测编码,可以使用encoding属性查看:

>>> r.encoding

'utf-8'

无论响应是文本还是二进制内容,我们都可以用content属性获得bytes对象:

>>> r.content

b'\n\n

\n\n...'

requests的方便之处还在于,对于特定类型的响应,例如JSON,可以直接获取:

>>> r = requests.get('https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20%3D%202151330&format=json')

>>> r.json()

{'query': {'count': 1, 'created': '2017-11-17T07:14:12Z', ...

需要传入HTTP Header时,我们传入一个dict作为headers参数:

headers={'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'}

r = requests.get('https://www.douban.com/', headers=headers)

要发送POST请求,只需要把get()方法变成post(),然后传入data参数作为POST请求的数据:

>>> r = requests.post('https://accounts.douban.com/login', data={'form_email': 'abc@example.com', 'form_password': '123456'})

requests默认使用application/x-www-form-urlencoded对POST数据编码。如果要传递JSON数据,可以直接传入json参数:

params = {'key': 'value'}

r = requests.post(url, json=params) # 内部自动序列化为JSON

类似的,上传文件需要更复杂的编码格式,但是requests把它简化成files参数:

>>> upload_files = {'file': open('report.xls', 'rb')}

>>> r = requests.post(url, files=upload_files)

在读取文件时,注意务必使用'rb'即二进制模式读取,这样获取的bytes长度才是文件的长度。

把post()方法替换为put(),delete()等,就可以以PUT或DELETE方式请求资源。

除了能轻松获取响应内容外,requests对获取HTTP响应的其他信息也非常简单。例如,获取响应头:

>>> r.headers

{Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Content-Encoding': 'gzip', ...}

>>> r.headers['Content-Type']

'text/html; charset=utf-8'

requests对Cookie做了特殊处理,使得我们不必解析Cookie就可以轻松获取指定的Cookie:

>>> r.cookies['ts']

'example_cookie_12345'

要在请求中传入Cookie,只需准备一个dict传入cookies参数:

>>> cs = {'token': '12345', 'status': 'working')

>>> r = requests.get(url, cookies=cs)

最后,要指定超时,传入以秒为单位的timeout参数:

>>> r = requests.get(url, timeout=2.5) # 2.5秒后超时

chardet

字符串编码一直是令人非常头疼的问题,尤其是我们在处理一些不规范的第三方网页的时候。

虽然Python提供了Unicode表示的str和bytes两种数据类型,并且可以通过encode()和decode()方法转换,

但是,在不知道编码的情况下,对bytes做decode()不好做。

对于未知编码的bytes,要把它转换成str,需要先“猜测”编码。

猜测的方式是先收集各种编码的特征字符,根据特征字符判断,就能有很大概率“猜对”。

当然,我们肯定不能从头自己写这个检测编码的功能,这样做费时费力。

chardet这个第三方库正好就派上了用场。用它来检测编码,简单易用。

使用chardet

当我们拿到一个bytes时,就可以对其检测编码。用chardet检测编码,只需要一行代码:

>>> chardet.detect(b'Hello, world!')

{'encoding': 'ascii', 'confidence': 1.0, 'language': ''}

检测出的编码是ascii,注意到还有个confidence字段,表示检测的概率是1.0(即100%)。

我们来试试检测GBK编码的中文:

>>> data = '离离原上草,一岁一枯荣'.encode('gbk')

>>> chardet.detect(data)

{'encoding': 'GB2312', 'confidence': 0.7407407407407407, 'language': 'Chinese'}

检测的编码是GB2312,注意到GBK是GB2312的超集,两者是同一种编码,检测正确的概率是74%,language字段指出的语言是'Chinese'。

对UTF-8编码进行检测:

>>> data = '离离原上草,一岁一枯荣'.encode('utf-8')

>>> chardet.detect(data)

{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}

我们再试试对日文进行检测:

>>> data = '最新の主要ニュース'.encode('euc-jp')

>>> chardet.detect(data)

{'encoding': 'EUC-JP', 'confidence': 0.99, 'language': 'Japanese'}

可见,用chardet检测编码,使用简单。获取到编码后,再转换为str,就可以方便后续处理。

chardet支持检测的编码列表请参考官方文档Supported encodings。

psutil

用Python来编写脚本简化日常的运维工作是Python的一个重要用途。

在Linux下,有许多系统命令可以让我们时刻监控系统运行的状态,如ps,top,free等等。

要获取这些系统信息,Python可以通过subprocess模块调用并获取结果。但这样做显得很麻烦,尤其是要写很多解析代码。

在Python中获取系统信息的另一个好办法是使用psutil这个第三方模块。

顾名思义,psutil = process and system utilities,

它不仅可以通过一两行代码实现系统监控,还可以跨平台使用,支持Linux/UNIX/OSX/Windows等,

是系统管理员和运维小伙伴不可或缺的必备模块。

获取CPU信息

我们先来获取CPU的信息:

>>> import psutil

>>> psutil.cpu_count() # CPU逻辑数量

4

>>> psutil.cpu_count(logical=False) # CPU物理核心

2

# 2说明是双核超线程, 4则是4核非超线程

统计CPU的用户/系统/空闲时间:

>>> psutil.cpu_times()

scputimes(user=10963.31, nice=0.0, system=5138.67, idle=356102.45)

再实现类似top命令的CPU使用率,每秒刷新一次,累计10次:

>>> for x in range(10):

... psutil.cpu_percent(interval=1, percpu=True)

...

[14.0, 4.0, 4.0, 4.0]

[12.0, 3.0, 4.0, 3.0]

[8.0, 4.0, 3.0, 4.0]

[12.0, 3.0, 3.0, 3.0]

[18.8, 5.1, 5.9, 5.0]

[10.9, 5.0, 4.0, 3.0]

[12.0, 5.0, 4.0, 5.0]

[15.0, 5.0, 4.0, 4.0]

[19.0, 5.0, 5.0, 4.0]

[9.0, 3.0, 2.0, 3.0]

获取内存信息

使用psutil获取物理内存和交换内存信息,分别使用:

>>> psutil.virtual_memory()

svmem(total=8589934592, available=2866520064, percent=66.6, used=7201386496, free=216178688, active=3342192640, inactive=2650341376, wired=1208852480)

>>> psutil.swap_memory()

sswap(total=1073741824, used=150732800, free=923009024, percent=14.0, sin=10705981440, sout=40353792)

返回的是字节为单位的整数,可以看到,总内存大小是8589934592 = 8 GB,已用7201386496 = 6.7 GB,使用了66.6%。

而交换区大小是1073741824 = 1 GB。

获取磁盘信息

可以通过psutil获取磁盘分区、磁盘使用率和磁盘IO信息:

>>> psutil.disk_partitions() # 磁盘分区信息

[sdiskpart(device='/dev/disk1', mountpoint='/', fstype='hfs', opts='rw,local,rootfs,dovolfs,journaled,multilabel')]

>>> psutil.disk_usage('/') # 磁盘使用情况

sdiskusage(total=998982549504, used=390880133120, free=607840272384, percent=39.1)

>>> psutil.disk_io_counters() # 磁盘IO

sdiskio(read_count=988513, write_count=274457, read_bytes=14856830464, write_bytes=17509420032, read_time=2228966, write_time=1618405)

可以看到,磁盘'/'的总容量是998982549504 = 930 GB,使用了39.1%。文件格式是HFS,opts中包含rw表示可读写,journaled表示支持日志。

获取网络信息

psutil可以获取网络接口和网络连接信息:

>>> psutil.net_io_counters() # 获取网络读写字节/包的个数

snetio(bytes_sent=3885744870, bytes_recv=10357676702, packets_sent=10613069, packets_recv=10423357, errin=0, errout=0, dropin=0, dropout=0)

>>> psutil.net_if_addrs() # 获取网络接口信息

{

'lo0': [snic(family=, address='127.0.0.1', netmask='255.0.0.0'), ...],

'en1': [snic(family=, address='10.0.1.80', netmask='255.255.255.0'), ...],

'en0': [...],

'en2': [...],

'bridge0': [...]

}

>>> psutil.net_if_stats() # 获取网络接口状态

{

'lo0': snicstats(isup=True, duplex=, speed=0, mtu=16384),

'en0': snicstats(isup=True, duplex=, speed=0, mtu=1500),

'en1': snicstats(...),

'en2': snicstats(...),

'bridge0': snicstats(...)

}

要获取当前网络连接信息,使用net_connections():

>>> psutil.net_connections()

Traceback (most recent call last):

...

PermissionError: [Errno 1] Operation not permitted

During handling of the above exception, another exception occurred:

Traceback (most recent call last):

...

psutil.AccessDenied: psutil.AccessDenied (pid=3847)

你可能会得到一个AccessDenied错误,原因是psutil获取信息也是要走系统接口,而获取网络连接信息需要root权限,这种情况下,可以退出Python交互环境,用sudo重新启动:

$ sudo python3

Password: ******

Python 3.6.3 ... on darwin

Type "help", ... for more information.

>>> import psutil

>>> psutil.net_connections()

[

sconn(fd=83, family=, type=1, laddr=addr(ip='::127.0.0.1', port=62911), raddr=addr(ip='::127.0.0.1', port=3306), status='ESTABLISHED', pid=3725),

sconn(fd=84, family=, type=1, laddr=addr(ip='::127.0.0.1', port=62905), raddr=addr(ip='::127.0.0.1', port=3306), status='ESTABLISHED', pid=3725),

sconn(fd=93, family=, type=1, laddr=addr(ip='::', port=8080), raddr=(), status='LISTEN', pid=3725),

sconn(fd=103, family=, type=1, laddr=addr(ip='::127.0.0.1', port=62918), raddr=addr(ip='::127.0.0.1', port=3306), status='ESTABLISHED', pid=3725),

sconn(fd=105, family=, type=1, ..., pid=3725),

sconn(fd=106, family=, type=1, ..., pid=3725),

sconn(fd=107, family=, type=1, ..., pid=3725),

...

sconn(fd=27, family=, type=2, ..., pid=1)

]

获取进程信息

获取进程信息

通过psutil可以获取到所有进程的详细信息:

>>> psutil.pids() # 所有进程ID

[3865, 3864, 3863, 3856, 3855, 3853, 3776, ..., 45, 44, 1, 0]

>>> p = psutil.Process(3776) # 获取指定进程ID=3776,其实就是当前Python交互环境

>>> p.name() # 进程名称

'python3.6'

>>> p.exe() # 进程exe路径

'/Users/michael/anaconda3/bin/python3.6'

>>> p.cwd() # 进程工作目录

'/Users/michael'

>>> p.cmdline() # 进程启动的命令行

['python3']

>>> p.ppid() # 父进程ID

3765

>>> p.parent() # 父进程

>>> p.children() # 子进程列表

[]

>>> p.status() # 进程状态

'running'

>>> p.username() # 进程用户名

'michael'

>>> p.create_time() # 进程创建时间

1511052731.120333

>>> p.terminal() # 进程终端

'/dev/ttys002'

>>> p.cpu_times() # 进程使用的CPU时间

pcputimes(user=0.081150144, system=0.053269812, children_user=0.0, children_system=0.0)

>>> p.memory_info() # 进程使用的内存

pmem(rss=8310784, vms=2481725440, pfaults=3207, pageins=18)

>>> p.open_files() # 进程打开的文件

[]

>>> p.connections() # 进程相关网络连接

[]

>>> p.num_threads() # 进程的线程数量

1

>>> p.threads() # 所有线程信息

[pthread(id=1, user_time=0.090318, system_time=0.062736)]

>>> p.environ() # 进程环境变量

{'SHELL': '/bin/bash', 'PATH': '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:...', 'PWD': '/Users/michael', 'LANG': 'zh_CN.UTF-8', ...}

>>> p.terminate() # 结束进程

Terminated: 15

和获取网络连接类似,获取一个root用户的进程需要root权限,启动Python交互环境或者.py文件时,需要sudo权限。

psutil还提供了一个test()函数,可以模拟出ps命令的效果:

$ sudo python3

Password: ******

Python 3.6.3 ... on darwin

Type "help", ... for more information.

>>> import psutil

>>> psutil.test()

USER PID %MEM VSZ RSS TTY START TIME COMMAND

root 0 24.0 74270628 2016380 ? Nov18 40:51 kernel_task

root 1 0.1 2494140 9484 ? Nov18 01:39 launchd

root 44 0.4 2519872 36404 ? Nov18 02:02 UserEventAgent

root 45 ? 2474032 1516 ? Nov18 00:14 syslogd

root 47 0.1 2504768 8912 ? Nov18 00:03 kextd

root 48 0.1 2505544 4720 ? Nov18 00:19 fseventsd

_appleeven 52 0.1 2499748 5024 ? Nov18 00:00 appleeventsd

root 53 0.1 2500592 6132 ? Nov18 00:02 configd

...

小结

psutil使得Python程序获取系统信息变得易如反掌。

psutil还可以获取用户信息、Windows服务等很多有用的系统信息,

具体请参考psutil的官网:https://github.com/giampaolo/psutil

virtualenv 虚拟环境

图形界面

Python支持多种图形界面的第三方库,包括:

Tk

wxWidgets

Qt

GTK

等等。

但是Python自带的库是支持Tk的Tkinter,使用Tkinter,无需安装任何包,就可以直接使用。

本章简单介绍如何使用Tkinter进行GUI编程。

Tkinter

我们来梳理一下概念:

我们编写的Python代码会调用内置的Tkinter,Tkinter封装了访问Tk的接口;

Tk是一个图形库,支持多个操作系统,使用Tcl语言开发;

Tk会调用操作系统提供的本地GUI接口,完成最终的GUI。

所以,我们的代码只需要调用Tkinter提供的接口就可以了。

第一个GUI程序

使用Tkinter十分简单,我们来编写一个GUI版本的“Hello, world!”。

第一步是导入Tkinter包的所有内容:

from tkinter import *

第二步是从Frame派生一个Application类,这是所有Widget的父容器:

class Application(Frame):

def __init__(self, master=None):

Frame.__init__(self, master)

self.pack()

self.createWidgets()

def createWidgets(self):

self.helloLabel = Label(self, text='Hello, world!')

self.helloLabel.pack()

self.quitButton = Button(self, text='Quit', command=self.quit)

self.quitButton.pack()

在GUI中,每个Button、Label、输入框等,都是一个Widget。

Frame则是可以容纳其他Widget的Widget,所有的Widget组合起来就是一棵树。

pack()方法把Widget加入到父容器中,并实现布局。pack()是最简单的布局,grid()可以实现更复杂的布局。

在createWidgets()方法中,我们创建一个Label和一个Button,当Button被点击时,触发self.quit()使程序退出。

第三步,实例化Application,并启动消息循环:

app = Application()

# 设置窗口标题:

app.master.title('Hello World')

# 主消息循环:

app.mainloop()

GUI程序的主线程负责监听来自操作系统的消息,并依次处理每一条消息。

因此,如果消息处理非常耗时,就需要在新线程中处理。

运行这个GUI程序,可以看到下面的窗口:

tk-hello-world

点击“Quit”按钮或者窗口的“x”结束程序。

输入文本

我们再对这个GUI程序改进一下,加入一个文本框,让用户可以输入文本,然后点按钮后,弹出消息对话框。

from tkinter import *

import tkinter.messagebox as messagebox

class Application(Frame):

def __init__(self, master=None):

Frame.__init__(self, master)

self.pack()

self.createWidgets()

def createWidgets(self):

self.nameInput = Entry(self)

self.nameInput.pack()

self.alertButton = Button(self, text='Hello', command=self.hello)

self.alertButton.pack()

def hello(self):

name = self.nameInput.get() or 'world'

messagebox.showinfo('Message', 'Hello, %s' % name)

app = Application()

# 设置窗口标题:

app.master.title('Hello World')

# 主消息循环:

app.mainloop()

当用户点击按钮时,触发hello(),通过self.nameInput.get()获得用户输入的文本后,使用tkMessageBox.showinfo()可以弹出消息对话框。

程序运行结果如下:

tk-say-hello

小结

Python内置的Tkinter可以满足基本的GUI程序的要求,如果是非常复杂的GUI程序,建议用操作系统原生支持的语言和库来编写。

TCP编程

Socket是网络编程的一个抽象概念。

通常我们用一个Socket表示“打开了一个网络链接”,

而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。

客户端

所以,我们要创建一个基于TCP连接的Socket,可以这样做:

# 导入socket库:

import socket

# 创建一个socket:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 建立连接:

s.connect(('www.sina.com.cn', 80))

创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。

SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。

我们连接新浪服务器的代码如下:

s.connect(('www.sina.com.cn', 80))

注意参数是一个tuple,包含地址和端口号。

建立TCP连接后,我们就可以向新浪服务器发送请求,要求返回首页的内容:

# 发送数据:

s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')

TCP连接创建的是双向通道,双方都可以同时给对方发数据。

但是谁先发谁后发,怎么协调,要根据具体的协议来决定。

例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。

发送的文本格式必须符合HTTP标准,如果格式没问题,接下来就可以接收新浪服务器返回的数据了:

# 接收数据:

buffer = []

while True:

# 每次最多接收1k字节:

d = s.recv(1024)

if d:

buffer.append(d)

else:

break

data = b''.join(buffer)

接收数据时,调用recv(max)方法,一次最多接收指定的字节数,

因此,在一个while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。

当我们接收完数据后,调用close()方法关闭Socket,这样,一次完整的网络通信就结束了:

# 关闭连接:

s.close()

接收到的数据包括HTTP头和网页本身,我们只需要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件:

header, html = data.split(b'\r\n\r\n', 1)

print(header.decode('utf-8'))

# 把接收的数据写入文件:

with open('sina.html', 'wb') as f:

f.write(html)

现在,只需要在浏览器中打开这个sina.html文件,就可以看到新浪的首页了。

服务器

服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。

由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。

一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

但是服务器还需要同时响应多个客户端的请求,

所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。

我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello再发回去。

首先,创建一个基于IPv4和TCP协议的Socket:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

然后,我们要绑定监听的地址和端口。

服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,

也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。

127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,

也就是说,外部的计算机无法连接进来。

端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用9999这个端口号。

请注意,小于1024的端口号必须要有管理员权限才能绑定:

# 监听端口:

s.bind(('127.0.0.1', 9999))

紧接着,调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量:

s.listen(5)

print('Waiting for connection...')

接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接:

while True:

# 接受一个新连接:

sock, addr = s.accept()

# 创建新线程来处理TCP连接:

t = threading.Thread(target=tcplink, args=(sock, addr))

t.start()

每个连接都必须创建新线程(或进程)来处理,否则,单线程在处理连接的过程中,无法接受其他客户端的连接:

def tcplink(sock, addr):

print('Accept new connection from %s:%s...' % addr)

sock.send(b'Welcome!')

while True:

data = sock.recv(1024)

time.sleep(1)

if not data or data.decode('utf-8') == 'exit':

break

sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))

sock.close()

print('Connection from %s:%s closed.' % addr)

连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。

如果客户端发送了exit字符串,就直接关闭连接。

要测试这个服务器程序,我们还需要编写一个客户端程序:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 建立连接:

s.connect(('127.0.0.1', 9999))

# 接收欢迎消息:

print(s.recv(1024).decode('utf-8'))

for data in [b'Michael', b'Tracy', b'Sarah']:

# 发送数据:

s.send(data)

print(s.recv(1024).decode('utf-8'))

s.send(b'exit')

s.close()

我们需要打开两个命令行窗口,一个运行服务器程序,另一个运行客户端程序,就可以看到效果了:

需要注意的是,客户端程序运行完毕就退出了,而服务器程序会永远运行下去,必须按Ctrl+C退出程序。

小结

用TCP协议进行Socket编程在Python中十分简单,对于客户端,要主动连接服务器的IP和指定端口,

对于服务器,要首先监听指定端口,然后,对每一个新的连接,创建一个线程或进程来处理。

通常,服务器程序会无限运行下去。

同一个端口,被一个Socket绑定了以后,就不能被别的Socket绑定了。

UDP编程

TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。

但是,能不能到达就不知道了。

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

我们来看看如何通过UDP协议传输数据。

和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 绑定端口:

s.bind(('127.0.0.1', 9999))

创建Socket时,SOCK_DGRAM指定了这个Socket的类型是UDP。

绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据:

print('Bind UDP on 9999...')

while True:

# 接收数据:

data, addr = s.recvfrom(1024)

print('Received from %s:%s.' % addr)

s.sendto(b'Hello, %s!' % data, addr)

recvfrom()方法返回数据和客户端的地址与端口,

这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。

注意这里省掉了多线程,因为这个例子很简单。

客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

for data in [b'Michael', b'Tracy', b'Sarah']:

# 发送数据:

s.sendto(data, ('127.0.0.1', 9999))

# 接收数据:

print(s.recv(1024).decode('utf-8'))

s.close()

从服务器接收数据仍然调用recv()方法。

小结

UDP的使用与TCP类似,但是不需要建立连接。

此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口可以各自绑定。

电子邮件

SMTP发送邮件

SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。

Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件

首先,我们来构造一个最简单的纯文本邮件:

from email.mime.text import MIMEText

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

注意到构造MIMEText对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入'plain'表示纯文本,

最终的MIME就是'text/plain',最后一定要用utf-8编码保证多语言兼容性。

用set_debuglevel(1)就可以打印出和SMTP服务器交互的所有信息。

SMTP协议就是简单的文本命令和响应。

login()方法用来登录SMTP服务器,sendmail()方法就是发邮件,由于可以一次发给多个人,所以传入一个list,

邮件正文是一个str,as_string()把MIMEText对象变成str。

邮件主题、如何显示发件人、收件人等信息并不是通过SMTP协议发给MTA,而是包含在发给MTA的文本中的,

所以,我们必须把From、To和Subject添加到MIMEText中,才是一封完整的邮件:

from email import encoders

from email.header import Header

from email.mime.text import MIMEText

from email.utils import parseaddr, formataddr

import smtplib

def _format_addr(s):

name, addr = parseaddr(s)

return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = input('From: ')

password = input('Password: ')

to_addr = input('To: ')

smtp_server = input('SMTP server: ')

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

msg['From'] = _format_addr('Python爱好者 ' % from_addr)

msg['To'] = _format_addr('管理员 ' % to_addr)

msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

server = smtplib.SMTP(smtp_server, 25)

server.set_debuglevel(1)

server.login(from_addr, password)

server.sendmail(from_addr, [to_addr], msg.as_string())

server.quit()

我们编写了一个函数_format_addr()来格式化一个邮件地址。

注意不能简单地传入name ,因为如果包含中文,需要通过Header对象进行编码。

msg['To']接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可。

发送HTML邮件

如果我们要发送HTML邮件,而不是普通的纯文本文件怎么办?

方法很简单,在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html就可以了:

msg = MIMEText('

Hello

' +

'

send by Python...

' +

'', 'html', 'utf-8')

发送附件

如果Email中要加上附件怎么办?带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,

所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,

再继续往里面加上表示附件的MIMEBase对象即可:

# 邮件对象:

msg = MIMEMultipart()

# 邮件正文是MIMEText:

msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:

with open('/Users/michael/Downloads/test.png', 'rb') as f:

# 设置附件的MIME和文件名,这里是png类型:

mime = MIMEBase('image', 'png', filename='test.png')

# 加上必要的头信息:

mime.add_header('Content-Disposition', 'attachment', filename='test.png')

mime.add_header('Content-ID', '<0>')

mime.add_header('X-Attachment-Id', '0')

# 把附件的内容读进来:

mime.set_payload(f.read())

# 用Base64编码:

encoders.encode_base64(mime)

# 添加到MIMEMultipart:

msg.attach(mime)

然后,按正常发送流程把msg(注意类型已变为MIMEMultipart)发送出去,就可以收到如下带附件的邮件:

发送图片

要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,

然后,在HTML中通过引用src="cid:0"就可以把附件作为图片嵌入了。

如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。

把上面代码加入MIMEMultipart的MIMEText从plain改为html,然后在适当的位置引用图片:

msg.attach(MIMEText('

Hello

' +

'

' +

'', 'html', 'utf-8'))

同时支持HTML和Plain格式

如果我们发送HTML邮件,收件人通过浏览器或者Outlook之类的软件是可以正常浏览邮件内容的,但是,如果收件人使用的设备太古老,查看不了HTML邮件怎么办?

办法是在发送HTML的同时再附加一个纯文本,如果收件人无法查看HTML格式的邮件,就可以自动降级查看纯文本邮件。

利用MIMEMultipart就可以组合一个HTML和Plain,要注意指定subtype是alternative:

msg = MIMEMultipart('alternative')

msg['From'] = ...

msg['To'] = ...

msg['Subject'] = ...

msg.attach(MIMEText('hello', 'plain', 'utf-8'))

msg.attach(MIMEText('

Hello

', 'html', 'utf-8'))

# 正常发送msg对象...

加密SMTP

我们来看看如何通过Gmail提供的安全SMTP发送邮件。

必须知道,Gmail的SMTP端口是587,因此,修改代码如下:

smtp_server = 'smtp.gmail.com'

smtp_port = 587

server = smtplib.SMTP(smtp_server, smtp_port)

server.starttls()

# 剩下的代码和前面的一模一样:

server.set_debuglevel(1)

...

只需要在创建SMTP对象后,立刻调用starttls()方法,就创建了安全连接。

后面的代码和前面的发送邮件代码完全一样。

小结

使用Python的smtplib发送邮件十分简单,只要掌握了各种邮件类型的构造方法,正确设置好邮件头,就可以顺利发出。

构造一个邮件对象就是一个Messag对象,如果构造一个MIMEText对象,就表示一个文本邮件对象,

如果构造一个MIMEImage对象,就表示一个作为附件的图片,要把多个对象组合起来,就用MIMEMultipart对象,

而MIMEBase可以表示任何对象。它们的继承关系如下:

Message

+- MIMEBase

+- MIMEMultipart

+- MIMENonMultipart

+- MIMEMessage

+- MIMEText

+- MIMEImage

这种嵌套关系就可以构造出任意复杂的邮件。你可以通过email.mime文档查看它们所在的包以及详细的用法。

POP3收取邮件

Python内置一个poplib模块,实现了POP3协议,可以直接用来收邮件。

注意到POP3协议收取的不是一个已经可以阅读的邮件本身,而是邮件的原始文本,

这和SMTP协议很像,SMTP发送的也是经过编码后的一大段文本。

要把POP3收取的文本变成可以阅读的邮件,还需要用email模块提供的各种类来解析原始文本,变成可阅读的邮件对象。

所以,收取邮件分两步:

第一步:用poplib把邮件的原始文本下载到本地;

第二部:用email解析原始文本,还原为邮件对象。

通过POP3下载邮件

POP3协议本身很简单,以下面的代码为例,我们来获取最新的一封邮件内容:

from email.parser import Parser

from email.header import decode_header

from email.utils import parseaddr

import poplib

# 输入邮件地址, 口令和POP3服务器地址:

email = input('Email: ')

password = input('Password: ')

pop3_server = input('POP3 server: ')

# 连接到POP3服务器:

server = poplib.POP3(pop3_server)

#server = poplib.POP3_SSL(pop3_server) 加密

# 可以打开或关闭调试信息:

server.set_debuglevel(1)

# 可选:打印POP3服务器的欢迎文字:

print(server.getwelcome().decode('utf-8'))

# 身份认证:

server.user(email)

server.pass_(password)

# stat()返回邮件数量和占用空间:

print('Messages: %s. Size: %s' % server.stat())

# list()返回所有邮件的编号:

resp, mails, octets = server.list()

# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]

print(mails)

# 获取最新一封邮件, 注意索引号从1开始:

index = len(mails)

resp, lines, octets = server.retr(index)

# lines存储了邮件的原始文本的每一行,

# 可以获得整个邮件的原始文本:

msg_content = b'\r\n'.join(lines).decode('utf-8')

# 稍后解析出邮件:

msg = Parser().parsestr(msg_content)

# 可以根据邮件索引号直接从服务器删除邮件:

# server.dele(index)

# 关闭连接:

server.quit()

用POP3获取邮件其实很简单,要获取所有邮件,只需要循环使用retr()把每一封邮件内容拿到即可。

真正麻烦的是把邮件的原始内容解析为可以阅读的邮件对象。

但是这个Message对象本身可能是一个MIMEMultipart对象,即包含嵌套的其他MIMEBase对象,嵌套可能还不止一层。

所以我们要递归地打印出Message对象的层次结构:

# indent用于缩进显示:

def print_info(msg, indent=0):

if indent == 0:

for header in ['From', 'To', 'Subject']:

value = msg.get(header, '')

if value:

if header=='Subject':

value = decode_str(value)

else:

hdr, addr = parseaddr(value)

name = decode_str(hdr)

value = u'%s ' % (name, addr)

print('%s%s: %s' % (' ' * indent, header, value))

if (msg.is_multipart()):

parts = msg.get_payload()

for n, part in enumerate(parts):

print('%spart %s' % (' ' * indent, n))

print('%s--------------------' % (' ' * indent))

print_info(part, indent + 1)

else:

content_type = msg.get_content_type()

if content_type=='text/plain' or content_type=='text/html':

content = msg.get_payload(decode=True)

charset = guess_charset(msg)

if charset:

content = content.decode(charset)

print('%sText: %s' % (' ' * indent, content + '...'))

else:

print('%sAttachment: %s' % (' ' * indent, content_type))

邮件的Subject或者Email中包含的名字都是经过编码后的str,要正常显示,就必须decode:

def decode_str(s):

value, charset = decode_header(s)[0]

if charset:

value = value.decode(charset)

return value

decode_header()返回一个list,因为像Cc、Bcc这样的字段可能包含多个邮件地址,

所以解析出来的会有多个元素。上面的代码我们偷了个懒,只取了第一个元素。

文本邮件的内容也是str,还需要检测编码,否则,非UTF-8编码的邮件都无法正常显示:

def guess_charset(msg):

charset = msg.get_charset()

if charset is None:

content_type = msg.get('Content-Type', '').lower()

pos = content_type.find('charset=')

if pos >= 0:

charset = content_type[pos + 8:].strip()

return charset

把上面的代码整理好,我们就可以来试试收取一封邮件

我们从打印的结构可以看出,这封邮件是一个MIMEMultipart,它包含两部分:

第一部分又是一个MIMEMultipart,第二部分是一个附件。

而内嵌的MIMEMultipart是一个alternative类型,它包含一个纯文本格式的MIMEText和一个HTML格式的MIMEText。

小结

用Python的poplib模块收取邮件分两步:第一步是用POP3协议把邮件获取到本地,第二步是用email模块把原始邮件解析为Message对象,

然后,用适当的形式把邮件内容展示给用户即可。

访问数据库

NoSQL

你也许还听说过NoSQL数据库,很多NoSQL宣传其速度和规模远远超过关系数据库,

所以很多同学觉得有了NoSQL是否就不需要SQL了呢?

千万不要被他们忽悠了,连SQL都不明白怎么可能搞明白NoSQL呢?

数据库类别

既然我们要使用关系数据库,就必须选择一个关系数据库。目前广泛使用的关系数据库也就这么几种:

付费的商用数据库:

Oracle,典型的高富帅;

SQL Server,微软自家产品,Windows定制专款;

DB2,IBM的产品,听起来挺高端;

Sybase,曾经跟微软是好基友,后来关系破裂,现在家境惨淡。

无论是Google、Facebook,还是国内的BAT,无一例外都选择了免费的开源数据库:

MySQL,大家都在用,一般错不了;

PostgreSQL,学术气息有点重,其实挺不错,但知名度没有MySQL高;

sqlite,嵌入式数据库,适合桌面和移动应用。

使用SQLite

要操作关系数据库,首先需要连接到数据库,一个数据库连接称为Connection;

连接到数据库后,需要打开游标,称之为Cursor,通过Cursor执行SQL语句,然后,获得执行结果。

Python定义了一套操作数据库的API接口,任何数据库要连接到Python,

只需要提供符合Python标准的数据库驱动即可。

由于SQLite的驱动内置在Python标准库中,所以我们可以直接来操作SQLite数据库。

我们在Python交互式命令行实践一下:

# 导入SQLite驱动:

>>> import sqlite3

# 连接到SQLite数据库

# 数据库文件是test.db

# 如果文件不存在,会自动在当前目录创建:

>>> conn = sqlite3.connect('test.db')

# 创建一个Cursor:

>>> cursor = conn.cursor()

# 执行一条SQL语句,创建user表:

>>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')

# 继续执行一条SQL语句,插入一条记录:

>>> cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')

# 通过rowcount获得插入的行数:

>>> cursor.rowcount

1

# 关闭Cursor:

>>> cursor.close()

# 提交事务:

>>> conn.commit()

# 关闭Connection:

>>> conn.close()

我们再试试查询记录:

>>> conn = sqlite3.connect('test.db')

>>> cursor = conn.cursor()

# 执行查询语句:

>>> cursor.execute('select * from user where id=?', ('1',))

# 获得查询结果集:

>>> values = cursor.fetchall()

>>> values

[('1', 'Michael')]

>>> cursor.close()

>>> conn.close()

使用Python的DB-API时,只要搞清楚Connection和Cursor对象,打开后一定记得关闭,就可以放心地使用。

使用Cursor对象执行insert,update,delete语句时,执行结果由rowcount返回影响的行数,

就可以拿到执行结果。

使用Cursor对象执行select语句时,通过featchall()可以拿到结果集。

结果集是一个list,每个元素都是一个tuple,对应一行记录。

如果SQL语句带有参数,那么需要把参数按照位置传递给execute()方法,

有几个?占位符就必须对应几个参数,例如:

cursor.execute('select * from user where name=? and pwd=?', ('abc', 'password'))

SQLite支持常见的标准SQL语句以及几种常见的数据类型。具体文档请参阅SQLite官方网站。

小结

在Python中操作数据库时,要先导入数据库对应的驱动,

然后,通过Connection对象和Cursor对象操作数据。

要确保打开的Connection对象和Cursor对象都正确地被关闭,否则,资源就会泄露。

如何才能确保出错的情况下也关闭掉Connection对象和Cursor对象呢?

请回忆try:...except:...finally:...的用法。

使用MySQL

MySQL是Web世界中使用最广泛的数据库服务器。

SQLite的特点是轻量级、可嵌入,但不能承受高并发访问,适合桌面和移动应用。

而MySQL是为服务器端设计的数据库,能承受高并发访问,同时占用的内存也远远大于SQLite。

此外,MySQL内部有多种数据库引擎,最常用的引擎是支持数据库事务的InnoDB。

在Windows上,安装时请选择UTF-8编码,以便正确地处理中文。

在Mac或Linux上,需要编辑MySQL的配置文件,把数据库默认的编码全部改为UTF-8。

MySQL的配置文件默认存放在/etc/my.cnf或者/etc/mysql/my.cnf:

[client]

default-character-set = utf8

[mysqld]

default-storage-engine = INNODB

character-set-server = utf8

collation-server = utf8_general_ci

重启MySQL后,可以通过MySQL的客户端命令行检查编码:

mysql> show variables like '%char%';

看到utf8字样就表示编码设置正确。

注:如果MySQL的版本≥5.5.3,可以把编码设置为utf8mb4,utf8mb4和utf8完全兼容,

但它支持最新的Unicode标准,可以显示emoji字符。

安装MySQL驱动

由于MySQL服务器以独立的进程运行,并通过网络对外服务,

所以,需要支持Python的MySQL驱动来连接到MySQL服务器。

MySQL官方提供了mysql-connector-python驱动,但是安装的时候需要给pip命令加上参数--allow-external:

$ pip install mysql-connector-python --allow-external mysql-connector-python

如果上面的命令安装失败,可以试试另一个驱动:

$ pip install mysql-connector

我们演示如何连接到MySQL服务器的test数据库:

# 导入MySQL驱动:

>>> import mysql.connector

# 注意把password设为你的root口令:

>>> conn = mysql.connector.connect(user='root', password='password', database='test')

>>> cursor = conn.cursor()

# 创建user表:

>>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')

# 插入一行记录,注意MySQL的占位符是%s:

>>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])

>>> cursor.rowcount

1

# 提交事务:

>>> conn.commit()

>>> cursor.close()

# 运行查询:

>>> cursor = conn.cursor()

>>> cursor.execute('select * from user where id = %s', ('1',))

>>> values = cursor.fetchall()

>>> values

[('1', 'Michael')]

# 关闭Cursor和Connection:

>>> cursor.close()

True

>>> conn.close()

由于Python的DB-API定义都是通用的,所以,操作MySQL的数据库代码和SQLite类似。

小结

执行INSERT等操作后要调用commit()提交事务;

MySQL的SQL占位符是%s。

使用SQLAlchemy

数据库表是一个二维表,包含多行多列。

把一个表的内容用Python的数据结构表示出来的话,可以用一个list表示多行,

list的每一个元素是tuple,表示一行记录,

比如,包含id和name的user表:

ORM技术:Object-Relational Mapping,把关系数据库的表结构映射到对象上

在Python中,最有名的ORM框架是SQLAlchemy。我们来看看SQLAlchemy的用法。

首先通过pip安装SQLAlchemy:

$ pip install sqlalchemy

然后,利用上次我们在MySQL的test数据库中创建的user表,用SQLAlchemy来试试:

第一步,导入SQLAlchemy,并初始化DBSession:

# 导入:

from sqlalchemy import Column, String, create_engine

from sqlalchemy.orm import sessionmaker

from sqlalchemy.ext.declarative import declarative_base

# 创建对象的基类:

Base = declarative_base()

# 定义User对象:

class User(Base):

# 表的名字:

__tablename__ = 'user'

# 表的结构:

id = Column(String(20), primary_key=True)

name = Column(String(20))

# 初始化数据库连接:

engine = create_engine('mysql+mysqlconnector://root:password@localhost:3306/test')

# 创建DBSession类型:

DBSession = sessionmaker(bind=engine)

以上代码完成SQLAlchemy的初始化和具体每个表的class定义。如果有多个表,就继续定义其他class,例如School:

class School(Base):

__tablename__ = 'school'

id = ...

name = ...

create_engine()用来初始化数据库连接。SQLAlchemy用一个字符串表示连接信息:

'数据库类型+数据库驱动名称://用户名:口令@机器地址:端口号/数据库名'

你只需要根据需要替换掉用户名、口令等信息即可。

下面,我们看看如何向数据库表中添加一行记录。

由于有了ORM,我们向数据库表中添加一行记录,可以视为添加一个User对象:

# 创建session对象:

session = DBSession()

# 创建新User对象:

new_user = User(id='5', name='Bob')

# 添加到session:

session.add(new_user)

# 提交即保存到数据库:

session.commit()

# 关闭session:

session.close()

可见,关键是获取session,然后把对象添加到session,最后提交并关闭。

DBSession对象可视为当前数据库连接。

如何从数据库表中查询数据呢?有了ORM,查询出来的可以不再是tuple,而是User对象。SQLAlchemy提供的查询接口如下:

# 创建Session:

session = DBSession()

# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:

user = session.query(User).filter(User.id=='5').one()

# 打印类型和对象的name属性:

print('type:', type(user))

print('name:', user.name)

# 关闭Session:

session.close()

运行结果如下:

type:

name: Bob

可见,ORM就是把数据库表的行与相应的对象建立关联,互相转换。

由于关系数据库的多个表还可以用外键实现一对多、多对多等关联,

相应地,ORM框架也可以提供两个对象之间的一对多、多对多等功能。

例如,如果一个User拥有多个Book,就可以定义一对多关系如下:

class User(Base):

__tablename__ = 'user'

id = Column(String(20), primary_key=True)

name = Column(String(20))

# 一对多:

books = relationship('Book')

class Book(Base):

__tablename__ = 'book'

id = Column(String(20), primary_key=True)

name = Column(String(20))

# “多”的一方的book表是通过外键关联到user表的:

user_id = Column(String(20), ForeignKey('user.id'))

当我们查询一个User对象时,该对象的books属性将返回一个包含若干个Book对象的list。

小结

ORM框架的作用就是把数据库表的一行记录与一个对象互相做自动转换。

正确使用ORM的前提是了解关系数据库的原理。

Web开发

MVC:为了解决直接用脚本语言嵌入HTML导致的可维护性差的问题,

Web应用也引入了Model-View-Controller的模式,来简化Web开发。

ASP发展为ASP.Net,JSP和PHP也有一大堆MVC框架。

目前,Web开发技术仍在快速发展中,异步开发、新的MVVM前端技术层出不穷。

Python的诞生历史比Web还要早,由于Python是一种解释型的脚本语言,开发效率高,所以非常适合用来做Web开发。

一键复制

编辑

Web IDE

原始数据

标准视图

历史

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值