[《Python2.1宝典》笔记] 15、19、25章

时间:2021-10-25 08:07:57

第十五章 联网

15.1联网背景

套接字是一个网络连接的端点。域名是为了方便记忆。

略,还有很多很基本的概念。


15.2处理地址和主机名

socket模块提供了几个函数处理主机名和地址。socket模块包装了C套接字库,并且与C版本一样,也支持所有种类的选项。模块内定义了直接映射到C等价的变量。

gethostname()将返回当前计算机的主机名。gethostbyname(name)将试图把指定主机名分解成IP地址。失败将产生异常。我测试的机器没有连接,抛出错误为:

socket.gaierror: (11001, 'getaddrinfo failed')

扩展格式gethostbyname_ex(name)将返回一个三元组,包含特定地址的主要主机名,相同IP地址的可选择机器名列表,以及一个主机界面上的其他IP地址列表:

>>> socket.gethostbyname('www.yahoo.com')

'64.58.76.178'

>>> socket.gethostbyname_ex('www.yahoo.com')

('www.yahoo.akadns.net',['www.yahoo.com'],

['64.58.76.178','64.58.76.176','216.32.74.52',

'216.32.74.50','64.58.76.179','216.32.74.53',

'64.58.76.177','216.32.74.51','216.32.74.55'])

gethostbyaddr(address)完成同样的任务,只不过参数是IP

getservbyname(service,protocol)获取一个服务名,如telnetftp,和一个协议,如tcpudp。并返回服务端口号。

>>> socket.getservbyname('http','tcp')

80

通常,非Python程序以32位的打包格式存储和使用IP地址:

inet_aton(ip_addr)

inet_ntoa(packed)

这两个函数在32位打包格式和IP地址字符串之间进行转换。如:

>>> socket.inet_aton('177.20.1.201')

'/261/024/001/311' # 一个4字节的字符串

>>> socket.inet_ntoa('/x7F/x00/x00/x01')

'127.0.0.1'

socket定义了几个保留IP地址:

INADDR_ANYINADDR_BROADCAST是保留的IP地址,用于任意IP和广播。INADDR_LOOPBACK引用127.0.0.1这个地址的环回设备。几个保留IP地址都是保留的32位格式。

getfqdn([name])函数返回主机名的合格域名,省略name则返回本机的。是Python2.0中的新特性:

>>> socket.getfqdn('')

'dialup84.lasal.net'


15.3与低层套接字通信

15.3.1创建和撤消套接字

socket模块的socket(family,type[,protocol])创建一个新的套接字对象。family常取AF_INET,偶尔也用其他值如AF_IPX,取决于平台。type最常用的是SOCK_STREAM(就是TCP)SOCK_DGRAM(就是UDP)

familytype的组合就可以指定一个协议了,但是仍然可以指定protocol参数,如IPPROTO_TCPIPPROTO_RAW。可用函数getprotobyname(proto)获取协议的代码:

>>> getprotobyname('tcp')

6

>>> IPPROTO_TCP

6

fromfd(fd,family,type[,proto])用于从打开的文件描述符(文件的fileno()方法获得)创建套接字对象。套接字对象的方法fileno()将返回这个套接字的文件描述符(一个整数)

用完套接字之后应该用close()方法关闭。显式的关闭较好,尽管可以自动关闭,使用弱引用?。也可用shutdown(how)方法关闭一端或两端,参数为0时停止接受,1停止发送,2双方停止。

15.3.2连接套接字

一个TCP连接,监听端先要bind(address)一个地址和端口的元组,之后调用listen([backlog])侦听传入连接,之后用accept()接受新的传入连接。

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.bind(('127.0.0.1',4444))

>>> s.listen(1)

>>> q,v=s.accept() #返回套接字q和地址v

上面的代码在未接到连接时会挂起。只要另外启动一个解释器连接即可,连接函数connect(address)

发送数据q.send('hello'),返回已发送的字节数

接受数据s.recv(1024),最多一次接受1024字节的数据,返回数据

"地址"是由IP地址和端口构成的二元组。

扩展函数connect_ex(address)如果遇到调用的错误则返回错误代码,成功返回0,而不会产生异常,更接近于C socket

调用listen时需要的参数是暂时无法响应时保存连接的队列长度,一般设为5socket.SOMAXCONN变量指明了最大允许的数量。

accept()方法返回一个地址,包含IP和端口。

UDP套接字不是面向连接的,但仍可用connect()

15.3.3发送和接收数据

send(string[,flags])发送指定数据到远程套接字。sendto(string [,flags],address)把指定字符串发送到特殊地址。通常send用于面向连接的,sendto用于无连接的。但是如果UDP套接字调用了connect(),就可以用send而不必是sendto了。

都返回实际发送的字节数。如下函数用于确保发送整个消息:

def safeSend(sock,msg):

sent=0

while msg:

i=sock.send(msg)

if i==-1: # Error

return -1

sent+=i

msg=msg[i:]

time.sleep(25) #等待队列为空

return sent

这样当一次发送不完时也可以持续的发送数据,并最终返回发送成功的字节数。

recv(bufsize[,flags])方法接收数据,如果有很多数据则只返回前bufsize字节的数据。recvfrom(bufsize[,flags])完成同样的任务,只是带有返回信息(data,(ipAddress,port))AF_INET套接字,可以看到信息的发源地。

sendsendtorecvrecvfrom都接受一个flags参数,默认值为0。可以把一些socket.MSG_*变量按位OR操作之后创建flags值。可用值的平台相关的。

MSG_OOB处理超区段数据

MSG_DONTROUTE不使用路由表,直接发送到界面

MSG_PEEK在不从队列删除的情况下,返回等待数据

例如使用MSG_PEEK就可以偷偷的查看消息而不必从数据队列中删除。

makefile([mode[,bufsize]])方法返回一个类似文件的对象,该对象包装了这个套接字。这样可以使用文件的操作方法操作套接字。可选的modebufsize参数获取的值与内置的open函数相同。

经测试在Windows2000SP4+Python2.4.2下的UDP编程中,最大允许发送的UDP数据报为65527字节,而这个数据报是收不到的。最大能够接收的数据报大小为65507字节。在6550765527字节之间的数据报发送会丢失,发送端不会出错,接收端却不会收到。对于超过65527字节的数据报会发生socket.error错误,winsock错误号为10040,信息"Message too long" 。这个问题同样存在于Linux中。经测试linux-2.6.9内核中这个数据长度区间是6250165507字节。

Windows2000SP4+Python2.4.2下尝试在recv中使用MSG_OOB标志,但是返回10045winsock错误,提示"Operation not supported"。另外,如果还没有bind()就使用recv()接收数据会发生10022winsock错误,提示"Invalid argument"

15.3.4使用套接字选项

套接字getpeername()getsockname()方法将返回一个2元组,包含IP地址和端口。getpeername()返回远程套接字信息,getsockname()返回本地套接字信息。

默认情况下的套接字是阻塞的,例如发送缓冲区已满的情况下,send函数也会阻塞,直到可以把更多的数据写入到缓冲区。通过采用0值调用setblocking(flag)方法,就可以改变这种行为。

在非阻塞时,如果发现recv()方法没有返回数据则会发生错误socket.errorWinsock错误号为10035。在套接字运行中也可以随时改变阻塞状态。在非阻塞状态中,一般放入一个带有延时的循环中。在延时发生的时候,其他线程可以改变循环的进入条件,控制程序合法退出。

在非阻塞时,也可以在监听套接字上用selectpoll来检测是否有新的连接到达。

setsockopt(level,name,value)getsockopt(level,name[,buflen ])方法用于设置和返回套接字选项。level参数指定引用选项的层,如套接字层、TCP层、IP层。level的值以SOL_开头,如SOL_SOCKETSOL_TCP等。选项的名字指定了正在交换的选项,socket模块定义了读者的平台上可以使用的选项。

C版本的setsockopt要求用缓冲区传递value参数,但在Python中,这个选项输入数字,可直接用数字或用字符串(代表缓冲区)

getsockopt函数不指定buflen时会返回数字值,而提供了buflen时返回一个字符串(代表缓冲区),并且最大长度为buflen个字节。

各个平台的选项不同,各个选项的值类型也不尽相同。如设置发送缓冲区为65KB

>>> s.setsockopt(SOL_SOCKET,SO_SNDBUF,65535)

SOL_SOCKET的选项(选项名、值、说明)

SO_TYPE(只读)套接字类型(SOCK_STREAM)

SO_ERROR(只读)套接字的最后一个错误

SO_LINGERBoolean如果数据存在则拖延close

SO_RCVBUFNumber接收缓冲区大小

SO_SNDBUFNumber发送缓冲区大小

SO_RCVTIMEOTime struct接收超时

SO_SNDTIMEOTime struct发送超时

SO_REUSEADDRBoolean启用本地地址/端口的多个用户

SOL_TCP选项:

TCP_NODELAYBoolean立即发送数据,不等待最小发送量

SOL_IP选项:

IP_TTL0-255包能够传送的最大转发器数

IP_MULTICAST_TTL0-255包能够广播的最大转发器数

IP_MULTICAST_IF inct_aton(ip)选择界面,以便通过它传输

IP_MULTICAST_LOOPBoolean启用发送器,以接收它发送的多

点传送包的副本

IP_ADD_MENBERSHIPip_mreq联接一个多点传送组

IP_DROP_MENBERSHIPip_mreq去掉多点传送组

15.3.5转换数字

平台字节序和网络字节序多半不同,所以通过网络传输之前需要先转换。nthol(x)ntohs(x)函数从网络数字转换到本地字节序。htonl(x)htons(x)函数从本地字节序转换到网络字节序。


15.4示例:多点传送的聊天应用程序

一个聊天程序,允许在共享的白板上绘画。

使用IP多播实现,代码太长见page224-228

在互联网上运行会有问题,因为有些路由器不允许IP多播的转发。而且设置TTL最好足够大。SO_REUSEADDR选项允许一个地址(IP,port)允许被绑定到多个socketIP_ADD_MEMBERSHIP连接一个IP多播组,退出多播组用IP_DROP_MENBERSHIP


15.5使用SocketServers

15.5.1SocketServer

TCPServerUDPServer都是SocketServer的子类。

SocketServer模块同时提供UnixStreamServer(TCPServer的子类)UnixDatagramServer(UDPServer的子类)。他们的父类相同,只是监听套接字是AF_UNIX族,而不是AF_INET

默认的套接字服务器一次处理一个连接,但可以使用ThreadingMixForkingMixIn类创建SocketServer的线程版本或分支版本。还有一些已经实现好的类:ForkingUDPServerForkingTCPServerThreadingUDPServerThreadingTCPServerThreadingUnixStreamServerThreadingUnixDatagramServer。线程版本只能在支持线程的操作系统上运行。分支版本也只可以在支持os.fork的平台上运行。

关于混合类见第7章;分支的解释见11章;线程见26章。

SocketServer以常规方式处理传入的连接,可以自己定义请求处理程序类,只要继承BaseRequestHandler类即可。然后将此类传递给构造函数即可。

import SocketServer

...#创建请求处理程序类

addr=('IP',port)

server=SocketServer.ThreadingTCPServer(addr,请求处理程序类)

server.serve_forever()

这样每次有新的连接时就创建一个"请求处理类"的实例对象,并调用他的handle()方法。代替用server_forever,也可用handle_request() ,可用于等待、接收单个连接。server_forever()仅限在无限循环中调用handle_request()

如果需要创建自己的套接字服务器类,需要实现如下方法:

__init__()函数中调用server_bind()方法,把self.socket绑定到正确的地址self.server_address。然后调用server_activate()激活服务器,默认情况下还会调用套接字的listen()方法。

在调用handle_request()serve_forever()之前,套接字服务器不会进行任何操作。handle_request()调用get_request(),并等待接受一个新的套接字连接,然后调用verify_request(request,client_address) ,以便查看服务器是否应该处理该连接(可以把这种方法用于访问控制-默认情况下,verify_request()总是返回真)。如果处理请求成功,则handle_request()会调用process_request(request,client_address),如果process_request()产生了异常,则调用handle_error(request, client_address)。默认时,process_request()简单的调用finish_request(request,client_address)

分支和线程混合类将会启动一个分支或线程来调用finish_requestfinish_request()是一个新的请求处理程序,接下来会调用他的handle()方法。

SocketServer创建一个新的请求处理程序时,会把处理程序的__init__函数传递给self变量。

SocketServerfileno()方法返回监听套接字的文件描述符。address_family成员变量指定了监听套接字的套接字族,如AF_INETserver_address保留了监听套接字绑定到的地址。socket变量保留了监听套接字本身。

15.5.2请求处理程序

包含setup()handle()finish()方法,而且默认情况下他们不作任何事情,可以通过重载来定制自己的行为。BaseRequestHandler__init__函数调用setup()完成初始化工作,调用handle()处理请求,调用finish()进行清除工作。如果handle()setup产生了异常则不会进入finish

每个请求创建了请求处理程序的一个新实例。

request成员变量包含流(TCP)服务器中新接受的套接字。对数据报(UDP)服务器,它是一个元组,包含传入的消息及监听套接字。client_address保存发送者地址,server具有对SocketServer的引用。

下面是一个Echo协议的例子EchoRequestHandler

import SocketServer

class EchoRequestHandler(/

SocketServer.BaseRequestHandler):

def handle(self):

print 'Got new Connection!'

while True:

msg=self.request.recv(1024)

if not msg:

break

print 'Recived:',msg

self.request.send(msg)

print 'Done with connection'

if __name__=='__main__':

server=SocketServer.ThreadingTCPServer(/

('127.0.0.1',12321),EchoRequestHandler)

server.handle_request() #开始等待一个连接

下面的程序构造一个客户端进行连接:

>>> from socket import *

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.connect(('127.0.0.1',12321))

>>> s.send('Hello!')

6

>>> print s.recv(1024)

Hello!

>>> s.close()

SocketServer模块也定义了BaseRequestHandler的两个子类:StreamRequestHandlerDatagramRequestHandler。他们重载了setupfinish两个方法,并创建了两个文件对象rfilewfile,可以使用他们进行对客户端的读写数据。


15.6处理Web浏览器请求

有了SocketServer以后,还可以扩展用于各种WEB请求:BaseHTTPServerSimpleHTTPServerCGIHTTPServer

可以把他们作为建立程序的起点。至少应该获得一些内容:

import SocketServer,CGIHTTPServer

SocketServer.ThreadingTCPServer(('127.0.0.1',80),/

CGIHTTPServer.CGIHTTPRequestHandler).serve_forever()

这时通过浏览器浏览http://127.0.0.1/file,这里的file是程序当前目录下的一个文件。

15.6.1BaseHTTPRequestHandler

WEB服务器请求处理程序的公共基类是BaseHTTPServer模块里的BaseHTTPRequestHandler类,是StreamRequestHandler的子类。它接受一个HTTP连接,调用适当的方法处理请求。

BaseHTTPRequestHandler的子类不应该重载__init__方法和handle方法,但对每一个HTTP命令,应该实现一个形如do_<command>的方法,如do_PUT()方法。这样相关请求会自动转到对应方法。

原始请求存放在raw_request实例变量中,并有属性,如command存储HTTP命令(GETPOST)path(例如/index.html)request_version(HTTP/1.0)headersmimetools.Message的一个实例,包含解析版本的请求标题。

mimetools.Message类的信息参阅17章,另外可以更改BaseHTTPRequestHandler.MessageClass类变量的值来指定另外一个类读取和解析标题。

使用rfilewfile可以读写数据。如果请求包含了超过请求标题的其他数据,则rfile会依据处理程序调用适当的do_<command>的时间定位数据的开始位置。

有两个版本信息server_versionprotocol_version,分别是服务器HTTP协议版本和协议版本,默认为HTTP/1.0

do_<command>方法中,发送输出应该用send_response(code [,message])方法,code是一个HTTP代码(200)message是文本消息,而调用此方法时BaseHTTPRequestHandler会添加Date和服务器标题。

如果调用发生错误,可以调用send_error(code[,message])

调用send_response之后,可以在需要时用send_header(key,value) ,以便写出MIME标题,完成处理之后调用end_headers()

def do_GET(self):

self.send_response(200)

self.send_header('Content-type','text/html')

self.send_header('Content-length',len(data))

self.end_headers()

#发送剩余数据

还可以使用日志工具,调用log_request([code[,size]]),可以记录代码和sizelog_message(format,arg0,arg1,...)多用途记录,有格式的输出。

self.log_message('%s :%d','Time taken',425)

这个讲采用NCSA httpd记录格式,每个请求自动记录在stdout中。

15.6.2SimpleHTTPRequestHandler

由于BastHTTPRequestHandler并不处理HTTP命令,而SimpleHTTPServer模块中的SimpleHTTPRequestHandler类可以处理客户机的文件请求(当前目录或子目录中),来对HEADGET命令的支持。如果请求的文件是一个目录则会返回目录列表的WEB页面。

如下例子在8000端口开启WEB服务器,会启动线程:

>>> import Webbrowser,threading,SimpleHTTPServer

>>> def go():

... t=SimpleHTTPServer.test

... threading.Thread(target=t).start()

... Webbrowser.open('http://127.0.0.1:8000')

>>> go()

Serving HTTP on port 8000

endor --[28/Dec/2000 18:00:48] "GET /3dsmax3/ HTTP/1.1" 200

endor --[28/Dec/2000 18:00:50] "GET /3dsmax3/Maxsdk/

HTTP/1.1" 200 -

endor --[28/Dec/2000 18:00:53]"GET /3dsmax3/Maxsdk/Include/

HTTP/1.1" 200 -

SimpleHTTPServer模块中的test()函数在8000端口上启动了一个简单的服务器。

这个类除了从BaseHTTPRequestHandler继承之外还包含extensions_map词典,把文件扩展名映射为MIME数据类型,这样可以使客户的WEB浏览器正确处理接受的文件。可以自己扩展这个列表。

15.6.3CGIHTTPRequestHandler

CGIHTTPRequestHandlerCGIHTTPServer模块中,提供了CGI的支持。

CGI一直有安全性的问题,需要自己学习一下。

对每个到达的GETPOST命令,先检查是否是CGI程序,如果是则以外部程序的方式启动,如果不是则发送文件。只有CGI程序支持POST方法。

CGIHTTPRequestHandler会在cgi_directories成员列表中检查文件是否是CGI程序的路径,一般指/cgi-binhtbin两个目录。如果文件存在于响应目录或子目录中,而且该文件是Python模块或可执行文件则开始执行,并发送输出到客户端。

15.6.4示例:形成处理程序CGI脚本

本节示例CGIHTTPRequestHandler的使用。

如下是HTML表单,保存在一个特定的路径,如c:/temp

<html><body>

<form method=GET

action="http://127.0.0.1:8000/cgi-bin/handleForm.py">

Your Name:<input name="User">

<input type="Submit" value="Go!">

</form>

</body></html>

在如上目录下建立目录cgi-bin,放入CGI脚本handleForm.py

import os

print "Content-type: text/html/r/n<html><body>"

name=os.environ.get('QUERY_STRING','')

print 'Hello, %s!<p>' % name[len('User='):]

print '</body></html>'

在刚才的c:/temp目录下启动解释器:

>>> import CGIHTTPServer

>>> CGIHTTPServer.test()

打开浏览器http://127.0.0.1:8000/form.html,查看CGI功能。

CGIHTTPRequestHandler能把用户信息保存在环境变量中。

写入stdout的信息,采用printsys.stdout.write,会返回到客户端,其信息可以是文本或二进制数据。

CGIHTTPRequestHandler输出一些响应标题,但在需要时可以添加。

在标题的后面,一定要在数据之前加入一个空行

UNIX上,外部程序会与nobody用户ID一起运行。


15.7在不使用线程的情况下处理多个请求

线程之间的切换要耗费大量的时间。可以使用selectasyncore模块一次处理多个请求,避免无意义的线程切换。

select模块中的select(inList,outList,errList[,timeout])函数获取三个列表,这些对象正在等待I/O,或者等待错误。select将返回3个列表,即原始对象的子集,他们包含那些无需阻塞就可以完成I/O的对象。还可以指定timeout超时值,浮点数,等待的秒数。指的是select的超时,即当timeout到达时如果没有有效结果就返回空列表。当timeout0时则进行快速检查而不进行阻塞。

三个列表分别指出了需要等待的结果,都可以为空,对象也可以是整型文件描述符或类似文件的对象(带有fileno()方法)。详细参见第10章。

通过使用select,可以启动几个读写操作来代替阻塞操作。是编写高性能服务器的好方法。

Windows系统上select()只对套接字起作用,UNIX系统上可对任何文件描述符起作用。

UNIX下还可用select.poll()返回一个轮询对象。之后用register(fd[,eventmask])注册特殊的文件描述符(或带有fileno()方法的对象)。可选的eventmask可由如下几个值OR连接:

select.POLLIN用于输入

select.POLLPRI紧急输入

select.POLLOUT用于输出

select.POLLERR错误

可以注册所需的文件描述符,并且通过轮询对象的unregister(fd)方法从对象中删除。调用轮询对象的poll([timeout])方法,可以查看那些准备完成I/O而无需阻塞的文件描述符。poll会返回(fd,event)格式的元组列表,是状态已经更改的文件描述符。事件可是几个eventmaskOR连接,也可是POLLHUP(挂起)POLLNVAL(无效的文件描述符)

15.7.1asyncore

提供了方便的将selectpoll用于套接字的方法。

asyncore定义了dispatcher类,围绕常规套接字的包装程序。包含connect(addr)send(data)recv(bufsize)listen([backlog])bind(addr)accept()close()方法。

创建套接字create_socket(family,type)

当不同的事件发生时,asyncore会调用程序对象的方法。例如可以在无需阻塞的情况下写入到套接字,调用handle_write()方法。当数据可用于读时,调用handle_read()。连接成功时调用handle_connect()。关闭时调用handle_close()。而且在socket.accept()不会阻塞,将实现handle_accept()

asyncoredispatcher对象的readable()writable()方法查看是否出于可以进行读写的状态,默认总是返回True,可以自己重载,避免没有数据可读时浪费时间检查数据。

将事件放入调度程序对象中,asyncore.poll([timeout])。在UNIX系统上可用asyncore.poll2([timeout])使用轮询,来代替select()asyncore.loop([timeout])。这些函数用select模块检查I/O状态的更改,并生成事件。poll只检查一次(默认超时为0)loop会一直检查,直到没有更多的、为readablewritable返回真值的调度程序对象为止,或者到达了timeout (默认值为30)

下面的示例从WEB站点检索index.html页面,并写入到磁盘。

import asyncore,socket

class AsyncGet(asyncore.dispatcher):

def __init__(self,host):

asyncore.dispatcher.__init__(self)

self.host=host

self.create_socket(socket.AF_INET, /

socket.SOCK_STREAM)

self.connect((host,80))

self.request='GET /index.html HTTP/1.0/r/n/r/n'

self.outf=None

print 'Requesting index.html from',host

def handle_connect(self):

print 'Connect',self.host

def handle_read(self):

if not self.outf:

print 'Creating',self.host

self.outf=open(self.host,'wt')

data=self,recv(8192)

if data:

self.outf.write(data)

def writeable(self):

return len(self.request)>0

def handle_write(self):

#并非所有数据都被发送,所以跟踪检查一下

num_sent=self.send(self.request)

self.request-self.request[num_sent:]#一种return

def handle_close(self):

asyncore.dispatcher.close(self)

print 'Socket closed for',self.host

if self.outf:

self.outf.close()

#获取一些数据

AsyncGet('www.yahoo.com')

AsyncGet('www.cnn.com')

AsyncGet('www.python.org')

asyncore.loop() #等待完成

通过使用这些方法,I/O-bound程序把时间花费在最大性能界线(I/O)上,而不是把时间浪费在无用的线程切换上。


15.8小结

...


完成...





---------------------------------------------------------




第十九章 使用Tkinter

19.1Tkinter简介

Tkinter用于在Python程序中建立GUI,在部分系统如UNIXWindowsMacintosh上是Python的标准库,并且一般是捆绑的。

Tkinter易于使用,建立在高级脚本编程语言Tcl的基础之上。

下例是一个很短的例子,演示了建立GUI

import Tkinter

#创建一个根窗口

root=Tkinter.Tk()

#将一个label放入窗体

LabelText="Ekky-ekky-ekky-ekky-z'Bang,zoom-Boing,/

z'nourrrwringmm"

LabelWidget=Tkinter.Label(root,text=LabelText)

#打包以适应显示

LabelWidget.pack()

#开启主循环,并显示,主循环不会立即返回,窗口关闭时返回

root.mainloop()

Windows系统上使用pythonw.exe而不是python.exe来运行会显得更加专业。脚本的扩展名使用.pyw而不是.py会发送到pythonwpythonw不会创建控制台窗口。使用.py结尾时窗口关闭之前,通过命令行调用的模块不会返回。


19.2创建GUI

19.2.1用部件建立界面

GUI包含不同的部件,对应Java中的组件,Microsoft的控件。大多数部件显示在父部件上,或者所有者上。构造部件的第一个参数的就是他的父部件。

Toplevel部件是特殊的部件,他没有上层。大多数程序需要一个Toplevel,即调用Tkinter.Tk()时创建的根部件。

例如帧是一个部件,存在的目的就是包含其他部件。

root=Tkinter.Tk() #创建一个top-level窗体

frame=Tkinter.Frame(root)

#其他部件安装在Frame之上

label=Tkinter.Label(frame,text="***")

label.pack()

button=Tkinter.Button(frame,text="button")

button.pack()

frame.pack()

frame.mainloop() #root.mainloop()

19.2.2部件选项

部件具有选项(属性)。如大多数部件有background选项,控制背景色。按钮有command选项指定单击按钮需要调用的函数(没有参数)

可用多种不同的方式访问选项:

NewLabel=Tkinter.Label(ParentFrame,background='gray50')

NewLabel['background']='#FFFFFF'

NewLabel.config(background='blue')

CurrentColor=NewLabel['background']

CurrentColor=NewLabel.cget('background')

由于有些选项是Python保留字,使用时要在选项名之后加下划线,例如from

VolumeWidget=Tkinter.Scale(ParentFrame,from_=0,to=200)

VolumeWidget['from']=20 #这里不是用from_


19.3部件布局

geometry manager(布局管理器)负责在平面上安排布局。最简单的布局是packerpacker可以安排在父部件的左、右、顶、底。调用pack方法就是调用packer

grid布局管理器将父部件划分为网格,并将每个子部件安排在一个方格上。(by gashero)通过在部件上的grid(row=x,column=y)激活网格布局管理器。方格编号从0开始。

place准确安排部件位置。

不同的布局管理器很难协调工作。必须在每个部件上调用布局管理器方法,否则不会显示。

19.3.1packer选项

下面就是可以传递给pack方法的选项。默认的包装是在父部件中从上到下的布局(side=TOP),每个部件都居中(anchor=CENTER)。并不会扩展以填充空间(expand=NO),边缘没有额外的填充(padx=pady=0)

side选项指定部件放在父部件的某个指定边,可用LEFTRIGHTTOPBOTTOM。默认是TOP。如果两个部件都布置在父部件的同一个边缘,则第一个安排的部件和边缘最靠近。

在同一个父部件中混合使用LEFT/RIGHTTOP/BOTTOM通常产生很烂的外观。封装很多部件时一般用中间帧或使用网格布局。

expand传递YES使得部件扩展以填充所有的空间。fill传递X,Y,或者BOTH决定扩展哪一个方向。适用于用户控制窗口大小。如下代码将创建画布,并拉伸,状态栏水平拉伸。

DrawingArea=Tkinter.Canvas(root)

DrawingArea.pack(expand=Tkinter.YES,fill=Tkinter.BOTH)

StatusBar=Tkinter.Label(root,text="Ready.")

StatusBar.pack(side=Tkinter.BOTTOM,expand=/

Tkinter.YES,fill=Tkinter.X)

如果部件具有比他需要的更多的屏幕空间,则锚选项决定了部件位于所分配空间的位置,不影响fill=BOTH的部件。有效值是罗盘方向:NNWWSWSSEENECENTER

padxpady给予部件一些边缘,防止一些误操作,并易于理解。指的是部件距离父部件边缘的象素数。是至少要保留的边缘宽度,而不是指定精确的。

19.3.2网格选项

传递给grid方法的选项。必须为每个部件指定行列值,否则会有不可预料的后果。

row,column指定部件所处方格,0始。总是可以添加新的行列。如下是建立电话号码播盘:

for Digit in range(9):

Tkinter.Button(root,text=Digit+1).grid(row=Digit/3,/

column=Digit%3)

sticky指定部件应该靠近方格的哪一个边,和packeranchor类似。有效值是罗盘方向和CENTER。可以组合值,来在单元格内拉伸部件。如下就是一个在单元格内拉伸:

BigButton=Tkinter.Button(root,text='X')

BigButton.grid(row=0,column=0,sticky=Tkinter.W+Tkinter.E+/

Tkinter.N+Tkinter.S)

columnspan,rowspan用于创建跨越多行或多列的大部件。


19.4示例:Breakfast按钮

通过几个按钮来生成列表:

import Tkinter

#Tkinter中常见的方式是生成Tkinter.Frame的子类,并在子类中

#生成GUI

class FoodWindow(Tkinter.Frame):

def __init__(self):

#调用父类的构造方法

Tkinter.Frame.__init__(self)

self.FoodItems=[]

self.CreateChildWidgets()

def CreateChildWidgets(self):

ButtonFrame=Tkinter.Frame(self)

ButtonFrame.pack(side=Tkinter.TOP,fill=Tkinter.X)

SpamButton=Tkinter.Button(ButtonFrame)

SpamButton['text']='Spam'

SpamButton['command']=self.BuildButtonAction('spam')

SpamButton.pack(side=Tkinter.LEFT)

EggsAction=self.BuildButtonAction('Eggs')

EggsButton=Tkinter.Button(ButtonFrame,test='Eggs',/

command=EggsAction)

EggsButton.pack(side=Tkinter.LEFT)

Tkinter.Button(ButtonFrame,text='Beans',/

command=self.BuildButtonAction('Beans'))./

pack(side=Tkinter.LEFT)

SausageButton=Tkinter.Button(ButtonFrame)

SausageAction=self.BuildButtonAction('Sausage')

SausageButton.config(text='Sausage',command=/

SausageAction)

SausageButton.pack(side=Tkinter.LEFT)

#这里保存一个label的引用,以便以后更改显示文字

self.FoodLabel=Tkinter.Label(self,wraplength=190,/

relief=Tkinter.SUNKEN,borderwidth=2,text='')

self.FoodLabel.pack(side=Tkinter.BOTTOM,pady=0,/

fill=Tkinter.X)

self.pack()

def ChooseFood(self,FoodItem):

#添加一个项到食物列表,并显示

self.FoodItems.append(FoodItem)

LabelText=''

TotalItems=len(self.FoodItems)

for Index in range(TotalItems):

if Index>0:

LabelText+=', '

if TotalItems>1 and Index==TotalItems-1:

LabelText+='and '

LabelText+=self.FoodItems[Index]

self.FoodLabel['text']=LabelText

def BuildButtonAction(self,Label):

#使用lambda可以方便的定义按钮常见行为

Action=lambda Food=self,Text=Label: /

Food.ChooseFood(Text)

return Action

if __name__=='__name__':

MainWindow=FoodWindow()

MainWindow.mainloop()


19.5使用常见选项

19.5.1颜色选项

background,foreground前景色和背景色,同bgfg

activebackground,activeforeground对菜单和按钮的激活状态颜色

disabledforeground禁用按钮或菜单时的前景色

selectforeground,selectforegroundCanvas,Entry,Text或者

Listbox部件的选定元素

highlightcolor,highlightbackground围绕菜单的矩形颜色

19.5.2大小选项

width:部件宽度,以部件字体的平均大小测量,默认的0值代表仅仅容纳当前文本

height:部件高度,以平均字符大小测量

padx,pady:部件外的距离,象素,如部件显示图像则忽略

19.5.3外观选项

text:在部件中显示的文本

image:按钮或选项卡上显示的图像,如有图像则忽略text,之后再传递字符串则删除图像

relief:为部件指定3D边界。有效值为FLATGROOVERAISEDRIDGEDSOLIDSUNKEN

borderwidth:部件的3D部件宽度,象素

font:文本字体

19.5.4行为选项

command:单击部件时调用的函数,没有参数,适于按钮、标尺、滚动条

state:部件状态为NORMALACTIVEDISABLEDDISABLED部件忽略用户输入,并通常以灰色显示。ACTIVE为激活状态

underline:可以使用键盘快捷方式。是部件文本内字母的索引,这个字母成为部件的热键。

takefocus:如为真,则是TAB列表的一部分,可用TAB切换到

19.6搜集用户输入

Entry部件获取输入的一行文本,Checkbox获取布尔值。这些部件将值存放在Tkinter变量中,包括StringVarIntVarDoubleVarBooleanVar。每个Tkinter变量提供了setget方法来存取。

>>> Text=Tkinter.StringVar()

>>> Text.get()

''

>>> Text.set('Howdy!')

>>> Text.get()

'Howdy!'

通过设置某个部件选项,就可以将部件与变量相联系。复选按钮一般选BooleanVar,用variable选项来添加:

SmokingFlag=BooleanVar()

B1=Checkbutton(ParentFrame,text='Smoking',/

variable=SmokingFlag)

SmokingFlag.set(1)

EntryOptionMenu部件一般使用StringVar,用textvariable选项:

PetBunnyName=StringVar()

NameEntry=Entry(ParentFrame,text='Bubbles',/

textvariable=PetBunnyName)

ChocolateName=StringVar()

FoodChoice=OptionMenu(ParentFrame,ChocolateName,/

'Crunchy Frog','Sprint Surprise','Anthrax Ripple')

几个Radiobutton可以共享一个变量,加到variable选项上,value选项存储那个按钮的值。

一些部件如ListboxText使用定制方法(不是Tkinter变量)来访问内容。


19.7示例:打印奇特的文本

下例打印不同颜色和大小的文本。还使用了Tkinter变量来搜集用户输入。

需要引入import tkFont模块来导入字体类。

构造方法初始化各个Tkinter变量,设置初始值。

有一个CreateWidgets()方法用于创建部件并显示。

PaintText()方法更改字体。

def PaintText(self):

if self.TextItem!=None:

self.TextCanvas.delete(self.TextItem)

if self.BoldFlag.get():

FontWeight=tkFont.BOLD

else:

FontWeight=tkFont.NORMAL

#创建并配置字体对象,获取字体列表

TextFont=tkFont.Font(self,'Courier')

TextFont.configure(size=self.FontSize.get(),/

underline=self.UnderlineFlag.get(),/

weight=FontWeight)

self.TextItem=self.TextCanvas.create_text(5,5,/

anchor=Tkinter.NW,text=self.Text.get(),/

fill=self.ColorName.get(),font=TextFont)


19.8使用文本部件

文本部件(Tkinter.Text)是多行文本编辑部件,甚至可以嵌入窗口和图形。其内容可以按照行列来索引,行是1始,列是0始。所以文本部件的开头索引是1.0。也可以用特殊索引ENDINSERT(插入光标位置)CURRENT(鼠标位置)

可在文本部件内用get(start[,end])检索文本,将从start返回文本,直到但是不包括end。如果忽略end则返回start处的一个字符。

TextWidget.get('1.0',Tkinter.END)

TextWidget.get('3.0','4.0')#返回第3

TextWidget.get('1.5')

delete(start[,end])按照与get相同的范围删除文本。insert(pos, str)在索引pos之前插入字符串str

TextWidget.insert('1.0','bob')

TextWidget.insert(Tkinter.END,'bob')

TextWidget.insert(Tkinter.CURRENT,'bob')

TextWidget.delete('1.0',Tkinter.END)#删除所有文本


19.9建立菜单

使用菜单部件Tkinter.Menu。之后添加入口来刷新菜单。添加指定的标签用add_command(label=?,command=?)add_separator()添加菜单分组行。

add_cascade(label=?,menu=?)添加复选按钮到菜单上,可以为新的Checkbutton部件传递其他的选项(Variable)add_checkbutton

创建Menu的一个实例表示菜单栏本身,然后为每个真正的菜单创建一个Menu实例。菜单不可以被封装。使用Toplevelmenu选项将菜单添加到窗口。

root=Tkinter.Tk()

MenuBar=Tkinter.Menu(root)

root['menu']=MenuBar

FileMenu=Tkinter.Menu(MenuBar) #创建子菜单

FileMenu.add_command(label="Load",command=LoadFile)

FileMenu.add_command(label="Save",command=SaveFile)

MenuBar.add_cascade(label="File",menu=FileMenu)

创建弹出式菜单用tk_popup(x,y[,default])作为弹出菜单,并指定了位置,并可以指定默认值。

import Tkinter

def MenuCommand():

print 'Howdy!'

def ShowMenu():

PopupMenu.tk_popup(*root.winfo_pointerxy())

root=Tkinter.Tk()

PopupMenu=Tkinter.Menu(root)

PopupMenu.add_command(label="X",command=MenuCommand)

PopupMenu.add_command(label="Y",command=MenuCommand)

Tkinter.Button(root,text='Popup',command=ShowMenu).pack()

root.mainloop()


19.10使用Tkinter对话框

模块tkMessageBox提供了几个函数用于显示对话框。每个都有titlemessage参数来控制窗口标题和显示信息。

showinfo显示通知性信息

showwarning显示警告

showerror显示错误信息

askyesno显示YESNO按钮,如果选择Yes则返回真

askokcancel显示OKCancel按钮,选择OK返回真

askretrycancel显示RetryCancel按钮,选择Retry返回真

askquestionaskyesno一样,只是用字符串返回YESNO

一个在退出之前确认的对话框:

def Quit(self):

if self.FileModified:

if not tkMessageBox.askyesno("Confirm",/

'Really quit?'):

return #不退出

sys.exit()

19.10.1文件对话框

模块tkFileDialog提供了一些函数来打开文件选择对话框。函数askopenfilename让用户选择一个现存文件。asksaveasfilename让用户选择现存文件或新的文件名。两个函数都返回文件的完整路径,用户取消则返回空字符串。

可选的filetypes参数限制文件类型,filetypes是一个元组的列表格式如(描述,扩展名)

MusicFileName=tkFileDialog.askopenfilename(

filetypes=[('music files','mp3')])


19.11实例:文本编辑器

一个简单的文本编辑器,包含文件的打开、保存和编辑。说明了文本部件、菜单和一些标准对话框的使用:

import Tkinter

import tkFileDialog

import tkMessageBox

import os

import sys

#文件类型选择列表

TEXT_FILE_TYPES=[("Text files","txt"),("All files","*")]

class TextEditor:

def __init__(self):

self.FileName=None

self.CreateWidgets()

def CreateWidgets(self):

self.root=Tkinter.Tk()

self.root.title("New file")

MainFrame=Tkinter.Frame(self.root)

#创建文件菜单

MenuFrame=Tkinter.Frame(self.root)

MenuFrame.pack(side=Tkinter.TOP,fill=Tkinter.X)

FileMenuButton=Tkinter.Menubutton(MenuFrame,/

text='File',underline=0)

FileMenuButton.pack(side=Tkinter.LEFT,

anchor=Tkinter.W)

FileMenu=Tkinter.Menu(FileMenuButton,tearoff=0)

FileMenu.add_command(label="New",underline=0,/

command=self.ClearText)

FileMenu.add_command(label="Open",underline=0,/

command=self.Open)

FileMenu.add_command(label="Save",underline=0,/

command=self.Save)

FileMenu.add_command(label="Save as...",/

underline=0,command=self.SaveAs)

FileMenu.add_separator()

self.FixedWidthFlag=Tkinter.BooleanVar()

FileMenu.add_checkbutton(label='Fixed-width',

variable=self.FixedWidthFlag,command=self.SetFont

FileMenu.add_separator()

FileMenu.add_command(label='Exit',underline=1,/

command=sys.exit)

FileMenu.Button['menu']=FileMenu

#创建帮助菜单

HelpMenuButton=Tkinter.MenuButton(MenuFrame,/

text='help',underline=0)

HelpMenu=Tkinter.Menu(HelpMenuButton,tearoff=0)

HelpMenu.add_command(label='About',underline=0,/

command=self.About)

HelpMenuButton['menu']=HelpMenu

HelpMenuButton.pack(side=Tkinter.LEFT,

anchor=Tkinter.W)

#创建主菜单条

self.TextBox=Tkinter.Text(MainFrame)

self.TextBox.pack(fill=Tkinter.BOTH,/

expand=Tkinter.YES)

#pack the top-level widget:

MainFrame.pack(fill=Tkinter.BOTH,expand=Tkinter.YES)

def SetFont(self):

if self.FixedWidthFlag.get():

self.TextBox['font']='Courier'

else:

self.TextBox['font']='Helvetica'

def About(self):

tkMessageBox.showinfo('About textpad ...','hi')

def ClearText(self):

self.TextBox.delete('1.0',Tkinter.END)

def Open(self):

FileName=tkFileDialog.askopenfilename(

filetypes=TEXT_FILE_TYPES)

if FileName==None or FileName=='':

return

try:

File=open(FileName,'r')

NewText=File.read()

File.close()

self.FileName=FileName

self.root.title(FileName)

except IOError:

tkMessageBox.showerror("Read error...",

"Count not read '%s'"%FileName)

return

self.ClearText()

self.TextBox.insert(Tkinter.END,NewText)

def Save(self):

if self.FileName==None or self.FileName=='':

self.SaveAs()

else:

self.SaveToFile(self.FileName)

def SaveAs(self):

FileName=tkFileDialog.asksaveasfilename(

filetypes=TEXT_FILE_TYPES)

if FileName==None or FileName=='':

return

self.SaveToFile(FileName)

def SaveToFile(self,FileName):

try:

File=open(FileName,'w')

NewText=self.TextBox.get('1.0',Tkinter.END)

File.write(NewText)

File.close()

self.FileName=FileName

self.root.title(FileName)

except IOError:

tkMessageBox.showerror('Save error...',

"Could not save to '%s'"%FileName)

return

def Run(self):

self.root.mainloop()

if __name__=='__main__':

TextEditor().Run()


19.12处理颜色和字体

19.12.1颜色

由三个数字来定义,格式如#RGB#RRGGBB#RRRGGGBBB。一些著名的颜色:#FFFFFF是白色、#000000是黑色、#FF00FF是紫色。字符串越长,指定的颜色就越精确。

Tkinter也提供一些预定义颜色。如红绿等,也包含一些特别的颜色。

19.12.2字体

字体描述符是(family,size[,styles])的元组。如下例的一个字体:

root=Tkinter.Tk()

Tkinter.Button(root,text="Fancy",

font=('Helvetica',24,'italic')).pack()

如果字体名没有包含空格,则直接用'family size styles'形式的字符串也可以描述字体。


19.13绘制图形

PhotoImage类可以自己添加图像,支持GIFPPMPGM格式。构造函数可以命名图像,也可以从指定文件中读取图像

MisterT=PhotoImage('Mr. T',file='mrt.gif')

#另一种获取图像的方法:

ImageFile=open('mrt.gif')

ImageData=ImageFile.read()

ImageFile.close()

MisterT=PhotoImage(data=ImageData)

一旦有了PhotoImage对象,就可以使用image选项将其添加到其他部件上。

MisterT=Tkinter.PhotoImage(file='mrt.gif')

Tkinter.Button(root,image=MisterT).pack()

可用widthheight方法查询图像的大小。只能在实例化toplevel实例之后(by gashero)才可以构造PhotoImage对象。

19.13.1画布部件

画布部件是一个窗口,可以编程绘制多种图形。以下是各个函数:

create_line(x1,y1,x2,y2,...,xn,yn)

按照顺序绘制连接点,如果设置了smooth选项则可以绘制平滑曲线。

create_polygon(x1,y"1",x2,y2,...,xn,yn)

和如上类似,使用由fill提供的颜色,默认透明。填充线条包围区域。outline选项提供线条颜色,smooth选项平滑曲线。

create_image(x,y,image=?[,anchor=?])(x,y)

在画布上绘制指定图像,image选项可以是PhotoImage实例或以前创建的PhotoImage名称。anchor的默认值为CENTER,指定图像的哪一个部分在(x,y)

create_oval(x1,y1,x2,y2)

在点(x1,y1)(x2,y2)所定义的矩形内绘制椭圆。在outline选项中传递轮廓颜色。在fill内指定内部填充颜色。width控制轮廓宽度/象素。

create_rectangee(x1,y"1",x2,y2)

绘制矩形。filloutlinewidth同上。

create_text(x,y,text=?[,font=?])

绘制文本,可指定字体。

19.13.2处理画布项

在画布上绘制的各项内容都拥有自己的部件,可以移动,可以绑定事件。用create_开头的方法返回画布项的ID。通过画布方法,可以指定哪个ID来处理画布项。例如delete(ID)可以删除指定的项。move(ID, DeltaX,DeltaY)可以将画布项移动。


19.14使用计时器

Tkinter内部提供计时器。Toplevel部件上调用after(wait, function)可以在wait毫秒之后执行指定的函数。为使得定时的操作重复,可在function的末尾再次调用after

下例就是每10秒调用一次的函数:

import Tkinter

def MinuteElapsed():

print 'Ding!'

root.after(1000*60,MinuteElapsed)

root=Tkinter.Tk()

root.after(10000,MinuteElapsed)

root.mainloop()


19.15实例:反弹图片

一个移动的图片,在各个边上反弹。

import Tkinter

class Bouncer:

def __init__(self,Master):

self.Master=Master

self.X=0

self.Y=0

self.DeltaX=5

self.DeltaY=5

self.Figure=Tkinter.Canvas(self,Master)

self.GrailWidth=GrailPicture.width()

self.GrailHeight=GrailPicture.height()

self.GrailID=self.Figure.create_image(/

0,0,anchor=Tkinter.NW,image=GrailPicture)

self.Figure.pack(fill=Tkinter.BOTH,/

expand=Tkinter.YES)

#0.1秒移动一次

root.after(100,self.MoveImage)

def MoveImage(self):

self.X+=self.DeltaX

self.Y+=self.DeltaY

self.Figure.coords(self.GrailID,self.X,self.Y)

#在边框反弹

if self.X<0:

self.DeltaX=abs(self.DeltaX)

if self.Y<0:

self.DeltaY=abs(self.DeltaY)

if self.X+self.GrailWidth>self.Figure.winfo_width():

self.DeltaX=-abs(self.DeltaX)

if self.Y+self.GrailHeight>/

self.Figure.winfo_height():

self.DeltaY=-abs(self.DeltaY)

#0.1秒后重复

if __name__=='__main__':

root=Tkinter.Tk()

GrailPicture=Tkinter.PhotoImage(file='HolyGrail.gif')

Bouncer(root)

root.mainloop()


19.16小结

...


完成...






---------------------------------------------------------------




第二十五章 图像处理

25.1图像基础

图像由一组像素构成,一般通过图像的宽和高限定尺寸。保存图像的格式很多。GIF具有256色,并可以使用16777217种颜色。还有些格式定义调色板。

大多需要压缩,分为无损和有损的。

有些格式支持透明效果。基于索引的格式把某种颜色定义为透明,其他格式还包括表明各个像素透明程度的alpha通道。


25.2识别图像文件类型

imghdr模块合理的推测文件中的图像格式:

>>> import imghdr

>>> imghdr.what('c://temp//sleep.jpg')

'jpeg'

此模块只是检查文件头的几个字节,不保证文件的完整性。只要传递文件头的几个字节就可以识别。

>>> hdr=open('snake.bmp','rb').read(50)

>>> imghdr.what(' '.h=hdr)

'bmp'

下标是可识别的返回值对应文件类型:

返回值

图像类型

gif

CompuServe可交换图形

jpeg

符合JPEGJFIF

bmp

WindowsOS/2位图

png

可移植网络图形

rgh

SGI图像库(RGB)

tiff

标记图像文件格式

pbm

可移植位图

ppm

可移植像素图

pgm

可移植灰度图

rasl

Sun Raster图像

xbm

X11位图

通过添加imghdrtests函数表,可以让他检查更多的文件类型。此模块仅用于识别,并不更改文件。下例识别Python字节码。

>>> def test_pyc(h,f):

... import imp

... if h.startswith(imp.get_magic()):

... return 'pyc'

>>> imghdr.tests.append(test_pyc)

>>> imghdr.what('leptolex.pyc')

'pyc'

两个参数,前一个带有文件头的几个字节。如果用户调用带有文件名的what,则f是文件打开对象。


25.3在颜色系统间转换

25.3.1颜色系统

colorsys模块支持最流行的4中颜色系统之间的转换。各个系统中都用0.0-1.0之间的三元数代表各种颜色。

RGB:最基本的一种,就是光学三元色的红绿蓝。人眼就是识别这三种颜色的。

YIQ:美国国家电视系统协会(NTSC)所用的颜色系统。Y表示颜色的亮度,占据了绝大多数带宽,I包含了橙青颜色,还支持现实色调颜色,Q带有红绿颜色信息并占有最小的带宽。

HLS:色度、亮度、饱和度。先选定一种纯色(如纯绿色),之后添加黑白来产生色调,色彩,阴影。L表示亮度0.0黑色,1.0白色。S表示色度饱和及,1.0表示饱和(纯色)0.0表示完全不饱和,可以获得灰色阴影。

HSV:除了纯色度的V组件(HLS中的L对应)值为0.5之外,HSV颜色系统非常接近HLS系统。HSV为色度、饱和度、纯度。

25.3.2从一种系统转换到另一种系统

colorsys模块中包含着可将RGB转换与其他系统的转换函数:

>>> import colorsys

>>> colorsys.hls_to_rgb(0.167,0.5,1.0) #黄色

(0.998,1.0,0.0)

其他的颜色系统之间的转换需要RGB作为中介。人眼最多只能识别出83000种(by gashero)颜色。

下例为转换HLSRGB的程序choosecolor.py

from Tkinter import *

import colorsys

def update(*args):

'改变调色板的颜色'

r,g,b=colorsys.hls_to_rgb(h.get()/255.0,

l.get()/255.0,s.get()/255.0)

r,g,b=r*255,g*255,b*255

rgb.configure(text='RGB:(%d,%d,%d)'%(r,g,b))

c.configure(bg='#%02X%02X%02X'%(r,g,b))

rook=Tk()

hue=Label(root,text='Eue')

hue.grid(row=0,column=0)

light=Label(root,text='Lightness')

light.grid(row=0,column=1)

sat=Label(root,text='Saturation')

sat.grid(row=0,column=2)

rgb=Label(root,text='RGB:(0,0,0)')

rgb.grid(row=0,column=3)

h=Scale(root,from_=255,to=0,command=update)

h.grid(row=1,column=0)

l=Scale(root,from_=255,to=0,command=update)

l.grid(row=1,column=1)

s=Scale(root,from_=255,to=0,command=update)

s.grid(row=1,column=2)

c=Canvas(root,width=100,height=100,bg='Black')

c.grid(row=1,column=3)

root.mainloop()


25.4处理原始图像数据

imageop模块用于处理以字节串传递的原始图像数据。数据可能是8位一个字符,代表象素,或者是32位代表一个象素。如果是SGI计算机还可以使用imgfile模块。如果有一个SGI RGB文件,可以使用rgbimg模块载入此文件。

imageop可以裁剪和缩放图像,但还是有大量函数用于转换不同灰度的图像。


25.5使用Python图像库

可在www.pythonware.com中查阅Python图像库(PIL)。免费库。

如果需要了解更多图像处理工具,访问Vautls of Parnassus

http://www.vex.net/parnassus

25.5.1检索图像信息

PIL中的主要模块Image,通过此模块可以完成多种操作。

>>> import Image

>>> i=Image.open('shadow.bmp')

>>> i.mode

'RGB'

>>> i.size

(320,240)

>>> i.format

'BMP'

>>> i.getbands()

('R','G','B')

>>> i.show() #显示图像

图像模式制定了颜色深度和存储位置,如下:

模式

说明

1

1位像素,黑白

L

8位像素,黑白

P

8位像素,256色调色板

RGB

每像素3字节,真彩色

RGBA

每像素4字节,具有alpha带的真彩色

l

32位整型像素

F

32位浮点型像素

图像具有几个波段的数据,如RGB图像具有红绿蓝三个波段。

show()方法是一个调试函数,将用默认浏览器打开图像,打开的图像是将当前图像保存到临时文件夹之后的,不一定是原图像。

有些IDE中使用show()会有问题。

PIL的优秀功能在于:真正需要读入和解码文件数据时才发挥作用。适于处理巨大的图像。

25.5.2复制与转换图像

copy()方法将返回与原图像相同的新对象,可用于修改备份。

convert(mode)返回指定模式下的新图像。

save(filename)将当前图像保存到文件中。PIL会自动按照后缀名保存为对应格式:

>>> Image.open('test.jpg').save('test.gif')#文件格式转换

PIL是在需要进行各种操作的时候才去读取图像文件。可用draft(mode,(w,h))方法在图像装入图像之前进行一些转换。例如一个5000×5000的图像,但是仅需要在较小的256色草稿上处理:

>>> img=Image.open('huge.jpg').draft('p',(250,250))

25.5.3使用带有TkinterPIL

ImageTk 模块支持两个类BitmapImagePhotoImage。可创建Tkinter兼容的位图和图像。还可以用他们来装入较多的文件格式。

Tkinter的内容参见1920章。

PIL还具有如下两种函数:一种函数用于创建与Windows兼容的位图并置于Windows设备相关上下文中,另一种函数可将图像写入PostScript文件或打印机中。

25.5.4裁剪和调整图像尺寸

crop((left,top,right,bottom))返回矩形部分图像

resize((w,h)[,filter])返回调整尺寸之后的图像。filter参数用于控制相对于原始图像应采用的采样类型,可为BILINEARBICUBICNEAREST(默认)中的任一。

另一有用办法是thumbnail((w,h)),这种方法可用于适当的调整对象,同时保持原始图像的高宽比例。

25.5.5修改像素数据

可以通过图像像素的坐标(x,y)来读取并改变像素值,坐标系为左上角(0,0)的。

getpixel((x,y))putpixel((x,y),value)可用于获取和设置单一像素。其中value为合适的图像模式。例如如下给图像加上黑条:

>>> i=Image.open('xxx.bmp')

>>> i.getpixel((10,25))

(156,111,56)

>>> for y in xrange(50,60):

... for x in xrange(i.size[0]):

... i.putpixel((x,y),(0,0,0))

>>> i.show()

getdata()将返回当前图像各个像素的一列字节组,而putdata(data [,scale][,offset])将一系列字节数组存入图像,offset默认为图像的开始部位,scale默认为1.0

PILImageDraw模块支持Draw对象,可用于在图像上添加图形和文字。可用于生成随时要改变的图像。

25.5.6其他PIL功能

新版本的PIL包含更多的功能,如:

  • ImageEnhance:包含可用于调整图像颜色、亮度、对比度和清晰度的类。

  • ImageChops:支持算术图像操作,加亮、变暗、转换图。

  • 提供对动画(多帧)GIFFLI/FLC图像的支持。

  • 转换,包括以任意角度旋转图像和在每一个像素上应用Python函数。

  • 用于模糊图像和发现边界的图像过滤程序。

  • 具有添加面向新文件格式的解码起的能力。


25.6小结

...


完成...