Python3 面向对象
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。
如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
接下来我们先来简单的了解下面向对象的一些基本特征。
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法:类中定义的函数。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 实例变量:定义在方法中的变量,只作用于当前实例的类。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
创建类
class语句创建一个新的类定义。 该类的名称紧跟在关键字类后跟一个冒号,如下所示 -
class ClassName: 'Optional class documentation string' class_suite
- 该类有一个文档字符串,可以通过ClassName .__ doc__进行访问。
- class_suite由定义类成员,数据属性和函数的所有组件语句组成。
以下是一个简单的Python类的例子 -
class Employee:
'Common base class for all employees'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print ("Total Employee %d" % Employee.empCount)
def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
- 变量empCount是一个类变量,其值在该类的所有实例中共享。 这可以从类内部或类以外的Employee.empCount访问。
- 第一种方法__init __()是一种特殊的方法,称为类构造函数或初始化方法,当您创建此类的新实例时,Python会调用它。
- 除了每个方法的第一个参数是self之外,您声明其他类方法(如普通函数)。 Python将自我参数添加到列表中; 您在调用方法时不需要包含它。
创建实例对象
要创建类的实例,可以使用类名称调用该类,并传入__init__方法接受的任何参数。
This would create first object of Employee class
emp1 = Employee("Zara", 2000)
This would create second object of Employee class
emp2 = Employee("Manni", 5000)
访问属性
您可以使用带运算符的点运算符来访问对象的属性。 类变量可以使用类名访问,如下所示 -
emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)
现在,把所有的概念放在一起 -
#!/usr/bin/python3
class Employee:
'Common base class for all employees'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print ("Total Employee %d" % Employee.empCount)
def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
#This would create first object of Employee class"
emp1 = Employee("Zara", 2000)
#This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)
当上面的代码被执行时,它会产生以下结果 -
Name : Zara ,Salary: 2000 Name : Manni ,Salary: 5000 Total Employee 2
您可以随时添加,删除或修改类和对象的属性 -
emp1.age = 27 # Add an 'age' attribute. emp1.name = 'xyz' # Modify 'name' attribute. del emp1.salary # Delete 'salary' attribute.
您可以使用以下功能(而不是使用常规语句访问属性)
- getattr(obj,name [,default]) - 访问对象的属性。
- hasattr(obj,name) - 检查属性是否存在。
- setattr(obj,name,value) - 设置属性。 如果属性不存在,那么它将被创建。
- delattr(obj,name) - 删除一个属性。
内置的类属性
每个Python类都保持以下内置属性,并且可以像使用其他任何属性一样使用点运算符来访问它们 -
- __dict__ - 包含类名称空间的字典。
- __doc__ - 类文档字符串或无,如果未定义。
- __name__ - 类名。
- __module__ - 定义类的模块名称。 交互模式下该属性为“__main__”。
- __bases__ - 包含基类的可能为空的元组,它们按照它们在基类列表中出现的顺序排列。
对于上面的类,让我们尝试访问所有这些属性 -
#!/usr/bin/python3
class Employee:
'Common base class for all employees'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print ("Total Employee %d" % Employee.empCount)
def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )
当上面的代码被执行时,它会产生以下结果 -
Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'="">,)
Employee.__dict__: {
'displayCount': ,
'__module__': '__main__', '__doc__': 'Common base class for all employees',
'empCount': 2, '__init__':
, 'displayEmployee':
,
'__weakref__':
<attribute '__weakref__'="" of="" 'employee'="" objects="">, '__dict__':
<attribute '__dict__'="" of="" 'employee'="" objects="">
}
销毁对象(垃圾收集)
Python会自动删除不需要的对象(内置类型或类实例)以释放内存空间。 Python定期回收不再使用的内存块的过程称为垃圾收集。
Python的垃圾收集器在程序执行期间运行,并在对象的引用计数达到零时触发。 对象的引用计数随着指向它的别名数量的变化而变化。
当一个对象的引用计数被分配一个新的名字或放入一个容器(列表,元组或字典)时,引用计数会增加。 当使用del删除对象的引用计数时,它的引用计数会减少,引用被重新分配或引用超出作用域。 当一个对象的引用计数达到零时,Python会自动收集它。
a = 40 # Create object <40> b = a # Increase ref. count of <40> c = [b] # Increase ref. count of <40> del a # Decrease ref. count of <40> b = 100 # Decrease ref. count of <40> c[0] = -1 # Decrease ref. count of <40>
当垃圾收集器销毁孤立实例并回收其空间时,您通常不会注意到这一点。 然而,一个类可以实现被称为析构函数的特殊方法__del __(),该函数在实例将要被销毁时被调用。 此方法可用于清除实例使用的任何非内存资源。
这个__del __()析构函数输出即将销毁的实例的类名 -
#!/usr/bin/python3 class Point: def __init__( self, x=0, y=0): self.x = x self.y = y def __del__(self): class_name = self.__class__.__name__ print (class_name, "destroyed") pt1 = Point() pt2 = pt1 pt3 = pt1 print (id(pt1), id(pt2), id(pt3)); # prints the ids of the obejcts del pt1 del pt2 del pt3
当上面的代码被执行时,它会产生以下结果 -
3083401324 3083401324 3083401324 Point destroyed
注 - 理想情况下,您应该在一个单独的文件中定义您的类,然后您应该使用import语句将它们导入到主程序文件中。
在上面的例子中,假设Point类的定义包含在point.py中,并且其中没有其他可执行代码。
#!/usr/bin/python3 import point p1 = point.Point()
类继承
除了从头开始,您可以通过从预先存在的类派生类,并通过在新类名后面的括号中列出父类来创建类。
子类继承其父类的属性,并且可以像在子类中定义那样使用这些属性。 子类也可以覆盖父数据成员和方法。
派生类的声明与其父类非常相似; 然而,在类名后面给出了继承基类的列表 -
class SubClassName (ParentClass1[, ParentClass2, ...]): 'Optional class documentation string' class_suite
#!/usr/bin/python3
class Parent: # define parent class
parentAttr = 100
def __init__(self):
print ("Calling parent constructor")
def parentMethod(self):
print ('Calling parent method')
def setAttr(self, attr):
Parent.parentAttr = attr
def getAttr(self):
print ("Parent attribute :", Parent.parentAttr)
class Child(Parent): # define child class
def __init__(self):
print ("Calling child constructor")
def childMethod(self):
print ('Calling child method')
c = Child() # instance of child
c.childMethod() # child calls its method
c.parentMethod() # calls parent's method
c.setAttr(200) # again call parent's method
c.getAttr() # again call parent's method
当上面的代码被执行时,它会产生以下结果 -
Calling child constructor Calling child method Calling parent method Parent attribute : 200
以类似的方式,您可以按如下方式从多个父类驱动一个类 -
class A: # define your class A ..... class B: # define your calss B ..... class C(A, B): # subclass of A and B .....
您可以使用issubclass()或isinstance()函数来检查两个类和实例的关系。
- 如果给定的子类sub确实是超类sup的子类,则issubclass(sub,sup)布尔函数返回True。
- isinstance(obj,Class)布尔函数返回True,如果obj是类Class的实例或者是Class的子类的实例
方法重写
您始终可以覆盖您的父类方法。 覆盖父方法的一个原因是您可能需要在您的子类中使用特殊或不同的功能。
#!/usr/bin/python3
class Parent: # define parent class
def myMethod(self):
print ('Calling parent method')
class Child(Parent): # define child class
def myMethod(self):
print ('Calling child method')
c = Child() # instance of child
c.myMethod() # child calls overridden method
当上面的代码被执行时,它会产生以下结果 -
Calling child method
基类重载方法
下表列出了您可以在自己的类中重写的一些通用功能 -
| 序号 | 方法, 描述 & 例子 |
|---|---|
| 1 | __init__ ( self [,args...] ) 构造函数 (多个可选参数) 例子: obj = className(args) |
| 2 | __del__( self ) 析构函数, 删除一个对象 例子: del obj |
| 3 | __repr__( self ) 可评估的字符串表示 例子 : repr(obj) |
| 4 | __str__( self ) 可打印的字符串表示 例子: str(obj) |
| 5 | __cmp__ ( self, x ) 对象比较 例子: cmp(obj, x) |
重载操作符
假设您创建了一个Vector类来表示二维向量。 当你使用加号运算符来添加它们时会发生什么? 很可能Python会吼你。
但是,您可以在您的类中定义__add__方法来执行向量添加,然后加号运算符将按照预期行事 -
#!/usr/bin/python3 class Vector: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector (%d, %d)' % (self.a, self.b) def __add__(self,other): return Vector(self.a + other.a, self.b + other.b) v1 = Vector(2,10) v2 = Vector(5,-2) print (v1 + v2)
当上面的代码被执行时,它会产生以下结果 -
Vector(7,8)
数据隐藏
对象的属性可能在类定义之外也可能不可见。 您需要使用双下划线前缀命名属性,然后这些属性将不会被外部人员直接看到。
#!/usr/bin/python3 class JustCounter: __secretCount = 0 def count(self): self.__secretCount += 1 print (self.__secretCount) counter = JustCounter() counter.count() counter.count() print (counter.__secretCount)
当上面的代码被执行时,它会产生以下结果 -
1 2 Traceback (most recent call last): File "test.py", line 12, in <module> print counter.__secretCount AttributeError: JustCounter instance has no attribute '__secretCount'
Python通过内部更改名称来包含类名来保护这些成员。 您可以访问诸如object._className__attrName之类的属性。 如果你想将最后一行替换为以下内容,那么它适用于你 -
......................... print (counter._JustCounter__secretCount)
当上面的代码被执行时,它会产生以下结果 -
1 2 2
Python3 正则表达式
正则表达式是一个特殊的字符序列,可以使用模式中保存的专用语法来帮助您匹配或查找其他字符串或字符串集。 正则表达式在UNIX世界中被广泛使用。
该模块提供对Python中类Perl的正则表达式的全面支持。 如果在编译或使用正则表达式时发生错误,re模块会引发异常re.error。
我们将介绍两个重要的函数,它们将用于处理正则表达式。 然而,首先是一个小问题:有各种各样的字符,当它们用于正则表达式时会有特殊的含义。 为了避免在处理正则表达式时出现混淆,我们将使用Raw Strings作为r'表达式'。
匹配单个字符的基本模式
| 序号 | 表达式 & 匹配 |
|---|---|
| 1 | a, X, 9, < 原始字符匹配自己。 |
| 2 | . (a period) 匹配任何一个单个字符(不包括换行符'\n') |
| 3 | \w 匹配“单词”字符:字母或数字或下划线[a-zA-Z0-9_]。 |
| 4 | \W 匹配任何非单词字符。 |
| 5 | \b 单词与非单词之间的界限 |
| 6 | \s 匹配单个空白字符 - 空格,换行符,返回,制表符 |
| 7 | \S 匹配任何非空白字符。 |
| 8 | \t, \n, \r tab, 换行,return |
| 9 | \d 十进制数字[0-9] |
| 10 | ^ 匹配字符串的开始 |
| 11 | $ 匹配字符串的结尾 |
| 12 | \ 转义字符,跟在其后的字符将失去作为特殊元字符的含义。 |
编译标志
使用编译标志可以修改正则表达式工作方式的某些方面。 标志在re模块中有两个名字,一个长名字,例如IGNORECASE和一个简短的单字母形式,例如I.
| 序号 | 标志 & 含义 |
|---|---|
| 1 | ASCII, A 使\ w,\ b,\ s和\ d等多个转义符仅与具有相应属性的ASCII字符匹配。 |
| 2 | DOTALL, S 使 . 匹配包括换行在内的所有字符 |
| 3 | IGNORECASE, I 做不区分大小写的匹配 |
| 4 | LOCALE, L 做本地化识别(locale-aware)匹配 |
| 5 | MULTILINE, M 多行匹配,影响^和$; |
| 6 | VERBOSE, X (for ‘extended’) 为了增加可读性,忽略空格和' # '后面的注释 |
march() 函数
该函数尝试将RE模式与可选标志进行匹配。
这里是这个函数的语法 -
re.match(pattern, string, flags = 0)
这里是参数的描述 -
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
匹配成功re.match方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
| 匹配对象方法 | 描述 |
|---|---|
| group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
| groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
#!/usr/bin/python3
import re
line = "Cats are smarter than dogs"
matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I)
if matchObj:
print ("matchObj.group() : ", matchObj.group())
print ("matchObj.group(1) : ", matchObj.group(1))
print ("matchObj.group(2) : ", matchObj.group(2))
else:
print ("No match!!")
当上面的代码被执行时,它会产生以下结果 -
matchObj.group() : Cats are smarter than dogs matchObj.group(1) : Cats matchObj.group(2) : smarter
re.search方法
re.search 扫描整个字符串并返回第一个成功的匹配。
函数语法
re.search(pattern, string, flags=0)
函数参数说明
| 参数 | 描述 |
|---|---|
| pattern | 匹配的正则表达式 |
| string | 要匹配的字符串。 |
| flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志 |
匹配成功re.search方法返回一个匹配的对象,否则返回None。
我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
| 匹配对象方法 | 描述 |
|---|---|
| group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 |
| groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 |
#!/usr/bin/python3
import re
line = "Cats are smarter than dogs";
searchObj = re.search( r'(.*) are (.*?) .*', line, re.M|re.I)
if searchObj:
print ("searchObj.group() : ", searchObj.group())
print ("searchObj.group(1) : ", searchObj.group(1))
print ("searchObj.group(2) : ", searchObj.group(2))
else:
print ("Nothing found!!")
当上面的代码被执行时,它会产生以下结果 -
searchObj.group() : Cats are smarter than dogs searchObj.group(1) : Cats searchObj.group(2) : smarter
re.match与re.search的区别
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。
#!/usr/bin/python3
import re
line = "Cats are smarter than dogs";
matchObj = re.match( r'dogs', line, re.M|re.I)
if matchObj:
print ("match --> matchObj.group() : ", matchObj.group())
else:
print ("No match!!")
searchObj = re.search( r'dogs', line, re.M|re.I)
if searchObj:
print ("search --> searchObj.group() : ", searchObj.group())
else:
print ("Nothing found!!")
当上面的代码被执行时,它会产生以下结果 -
No match!! search --> matchObj.group() : dogs
Search和Replace
Python 的re模块提供了re.sub用于替换字符串中的匹配项。
re.sub(pattern, repl, string, max=0)
该方法用repl替换字符串中出现的所有RE模式,除非提供了max,否则将替换所有出现的字符串。 此方法返回修改的字符串。
#!/usr/bin/python3
import re
phone = "2004-959-559 # This is Phone Number"
# Delete Python-style comments
num = re.sub(r'#.*$', "", phone)
print ("Phone Num : ", num)
# Remove anything other than digits
num = re.sub(r'\D', "", phone)
print ("Phone Num : ", num)
当上面的代码被执行时,它会产生以下结果 -
Phone Num : 2004-959-559 Phone Num : 2004959559
正则表达式修饰符:选项标志
正则表达式文字可能包含一个可选的修饰符来控制匹配的各个方面。 修饰符被指定为可选标志。 您可以使用异或(OR)提供多个修饰符,如前所示,可以用其中之一表示 -
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
正则表达式模式
模式字符串使用特殊的语法来表示一个正则表达式
字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。
多数字母和数字前加一个反斜杠时会拥有不同的含义。
标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。
反斜杠本身需要使用反斜杠转义。
由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r'\t',等价于 \\t )匹配相应的特殊字符。
下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。
| 模式 | 描述 |
|---|---|
| ^ | 匹配字符串的开头 |
| $ | 匹配字符串的末尾。 |
| . | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 |
| [...] | 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k' |
| [^...] | 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。 |
| re* | 匹配0个或多个的表达式。 |
| re+ | 匹配1个或多个的表达式。 |
| re? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 |
| re{ n} | 匹配n个前面表达式。例如,"o{2}"不能匹配"Bob"中的"o",但是能匹配"food"中的两个o。 |
| re{ n,} | 精确匹配n个前面表达式。例如,"o{2,}"不能匹配"Bob"中的"o",但能匹配"foooood"中的所有o。"o{1,}"等价于"o+"。"o{0,}"则等价于"o*"。 |
| re{ n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 |
| a| b | 匹配a或b |
| (re) | G匹配括号内的表达式,也表示一个组 |
| (?imx) | 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。 |
| (?-imx) | 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。 |
| (?: re) | 类似 (...), 但是不表示一个组 |
| (?imx: re) | 在括号中使用i, m, 或 x 可选标志 |
| (?-imx: re) | 在括号中不使用i, m, 或 x 可选标志 |
| (?#...) | 注释. |
| (?= re) | 前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。 |
| (?! re) | 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功。 |
| (?> re) | 匹配的独立模式,省去回溯。 |
| \w | 匹配数字字母下划线 |
| \W | 匹配非数字字母下划线 |
| \s | 匹配任意空白字符,等价于 [\t\n\r\f]。 |
| \S | 匹配任意非空字符 |
| \d | 匹配任意数字,等价于 [0-9]。 |
| \D | 匹配任意非数字 |
| \A | 匹配字符串开始 |
| \Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。 |
| \z | 匹配字符串结束 |
| \G | 匹配最后匹配完成的位置。 |
| \b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 |
| \B | 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 |
| \n, \t, 等。 | 匹配一个换行符。匹配一个制表符, 等 |
| \1...\9 | 匹配第n个分组的内容。 |
| \10 | 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。 |
正则表达式实例
字符匹配
| 实例 | 描述 |
|---|---|
| python | 匹配 "python". |
字符类
| 实例 | 描述 |
|---|---|
| [Pp]ython | 匹配 "Python" 或 "python" |
| rub[ye] | 匹配 "ruby" 或 "rube" |
| [aeiou] | 匹配中括号内的任意一个字母 |
| [0-9] | 匹配任何数字。类似于 [0123456789] |
| [a-z] | 匹配任何小写字母 |
| [A-Z] | 匹配任何大写字母 |
| [a-zA-Z0-9] | 匹配任何字母及数字 |
| [^aeiou] | 除了aeiou字母以外的所有字符 |
| [^0-9] | 匹配除了数字外的字符 |
特殊字符类
| 实例 | 描述 |
|---|---|
| . | 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。 |
| \d | 匹配一个数字字符。等价于 [0-9]。 |
| \D | 匹配一个非数字字符。等价于 [^0-9]。 |
| \s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
| \S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
| \w | 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。 |
| \W | 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。 |
Python3 CGI编程
通用网关接口(Common Gateway Interface,CGI)是一组定义如何在Web服务器和自定义脚本之间交换信息的标准。 CGI规范目前由NCSA维护。
什么是CGI?
- 通用网关接口(即CGI)是外部网关程序与HTTP服务器等信息服务器连接的标准。
- 目前的版本是CGI / 1.1,CGI / 1.2正在进行中。
Web浏览
为了理解CGI的概念,让我们看看当我们点击一个超级链接浏览特定的网页或URL时会发生什么。
- 使用你的浏览器访问URL并连接到HTTP web 服务器。
- Web服务器接收到请求信息后会解析URL,并查找访问的文件在服务器上是否存在,如果存在返回文件的内容,否则返回错误信息。
- 浏览器从服务器上接收信息,并显示接收的文件或者错误信息。
但是,可以设置HTTP服务器,以便每当请求某个目录中的文件时,该文件不被发回; 而是作为一个程序来执行,并且无论该程序输出什么,都会被发送回来供浏览器显示。 这个函数被称为通用网关接口或CGI,这些程序被称为CGI脚本。 这些CGI程序可以是Python脚本,PERL脚本,Shell脚本,C或C ++程序等。
Web服务器支持及配置
在继续进行CGI编程之前,请确保您的Web服务器支持CGI并将其配置为处理CGI程序。 所有由HTTP服务器执行的CGI程序都保存在预先配置的目录中。 这个目录被称为CGI目录,按照惯例它被命名为/var/www/cgi-bin。 按照惯例,CGI文件的扩展名为。 cgi,但是你可以用python扩展名.py保存你的文件。
默认情况下,Linux服务器配置为只运行/var/www中cgi-bin目录下的脚本。 如果要指定任何其他目录来运行CGI脚本,请在httpd.conf文件中注释以下行 -
<Directory "/var/www/cgi-bin"> AllowOverride None Options ExecCGI Order allow,deny Allow from all </Directory> <Directory "/var/www/cgi-bin"> Options All </Directory>
在这里,我们假设您已经成功运行了Web Server,并且您可以运行任何其他CGI程序,如Perl或Shell等。
第一个CGI程序
这是一个简单的链接,它链接到名为hello.py的CGI脚本。 该文件保存在/ var / www / cgi-bin目录中,它具有以下内容。 在运行CGI程序之前,请确保使用chmod 755 hello.py UNIX命令使文件更改为可执行文件。
#!/usr/bin/python print "Content-type:text/html\r\n\r\n" print '<html>' print '<head>' print '<title>Hello Word - First CGI Program</title>' print '</head>' print '<body>' print '<h2>Hello Word! This is my first CGI program</h2>' print '</body>' print '</html>'
注 - 脚本中的第一行必须是Python可执行文件的路径。 在Linux中它应该是#!/usr/bin/python3
在浏览器中输入以下URL
http://localhost:8080/cgi-bin/hello.py
Hello Word! This is my first CGI program
这个hello.py脚本是一个简单的Python脚本,它将输出写到STDOUT文件即屏幕上。 有一个重要和额外的功能是要打印的第一行Content-type:text/html\r\n\r\n。 该行被发送回浏览器,并指定要在浏览器屏幕上显示的内容类型。
到目前为止,您必须了解CGI的基本概念,并且您可以使用Python编写许多复杂的CGI程序。 该脚本可以与任何其他外部系统交互以交换信息,如RDBMS。
HTTP头部
Content-type:text/html\r\n\r\n是发送给浏览器以理解内容的HTTP标头的一部分。 所有的HTTP头将以下面的形式 -
HTTP Field Name: Field Content For Example Content-type: text/html\r\n\r\n
有几个其他重要的HTTP标题,你会经常在你的CGI编程中使用。
| 头 | 描述 |
|---|---|
| Content-type: | 请求的与实体对应的MIME信息。例如: Content-type:text/html |
| Expires: Date | 响应过期的日期和时间 |
| Location: URL | 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 |
| Last-modified: Date | 请求资源的最后修改时间 |
| Content-length: N | 请求的内容长度 |
| Set-Cookie: String | 设置Http Cookie |
CGI环境变量
所有的CGI程序都接收以下的环境变量,这些变量在CGI程序中发挥了重要的作用
| 变量名 | 描述 |
|---|---|
| CONTENT_TYPE | 这个环境变量的值指示所传递来的信息的MIME类型。目前,环境变量CONTENT_TYPE一般都是:application/x-www-form-urlencoded,他表示数据来自于HTML表单。 |
| CONTENT_LENGTH | 如果服务器与CGI程序信息的传递方式是POST,这个环境变量即使从标准输入STDIN中可以读到的有效数据的字节数。这个环境变量在读取所输入的数据时必须使用。 |
| HTTP_COOKIE | 客户机内的 COOKIE 内容。 |
| HTTP_USER_AGENT | 提供包含了版本数或其他专有数据的客户浏览器信息。 |
| PATH_INFO | 这个环境变量的值表示紧接在CGI程序名之后的其他路径信息。它常常作为CGI程序的参数出现。 |
| QUERY_STRING | 如果服务器与CGI程序信息的传递方式是GET,这个环境变量的值即使所传递的信息。这个信息经跟在CGI程序名的后面,两者中间用一个问号'?'分隔。 |
| REMOTE_ADDR | 这个环境变量的值是发送请求的客户机的IP地址,例如上面的192.168.1.67。这个值总是存在的。而且它是Web客户机需要提供给Web服务器的唯一标识,可以在CGI程序中用它来区分不同的Web客户机。 |
| REMOTE_HOST | 这个环境变量的值包含发送CGI请求的客户机的主机名。如果不支持你想查询,则无需定义此环境变量。 |
| REQUEST_METHOD | 提供脚本被调用的方法。对于使用 HTTP/1.0 协议的脚本,仅 GET 和 POST 有意义。 |
| SCRIPT_FILENAME | CGI脚本的完整路径 |
| SCRIPT_NAME | CGI脚本的的名称 |
| SERVER_NAME | 这是你的 WEB 服务器的主机名、别名或IP地址。 |
| SERVER_SOFTWARE | 这个环境变量的值包含了调用CGI程序的HTTP服务器的名称和版本号。例如,上面的值为Apache/2.2.14(Unix) |
以下是一个简单的CGI脚本输出CGI的环境变量
#!/usr/bin/python import os print "Content-type: text/html\r\n\r\n"; print "<font size=+1>Environment</font><\br>"; for param in os.environ.keys(): print "<b>%20s</b>: %s<\br>" % (param, os.environ[param])
GET和POST方法
当您需要将一些信息从浏览器传递到Web服务器并最终传递到CGI程序时,您一定遇到过很多情况。 最常见的情况是,浏览器使用两种方法将这些信息传递给Web服务器。 这些方法是GET方法和POST方法。
使用GET方法传递信息
GET方法发送附加到页面请求的编码后的用户信息。 页面和编码信息由? 字符如下 -
http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2
GET方法是将信息从浏览器传递到Web服务器的默认方法,它会生成一个显示在浏览器的“位置”框中的长字符串。 如果您有密码或其他敏感信息传递给服务器,切勿使用GET方法。 GET方法具有大小限制:请求字符串中只能发送1024个字符。 GET方法使用QUERY_STRING标头发送信息,并可通过CGI程序通过QUERY_STRING环境变量访问。
您可以通过简单地连接键和值对以及任何URL来传递信息,也可以使用HTML
Python3 数据库访问
数据库接口的Python标准是Python DB-API。 大多数Python数据库接口都遵守这个标准。
您可以为您的应用程序选择正确的数据库。 Python数据库API支持广泛的数据库服务器,例如 -
- MySQL
- PostgreSQL
- Microsoft SQL Server 2000
- Informix
- Interbase
- Oracle
- Sybase
以下是可用的Python数据库接口列表:Python数据库接口和API。 您必须为每个需要访问的数据库下载一个单独的DB API模块。 例如,如果您需要访问Oracle数据库以及MySQL数据库,则必须同时下载Oracle和MySQL数据库模块。
尽可能使用Python结构和语法来处理数据库的DB API提供了最低标准。 这个API包括以下内容 -
- 导入API模块。
- 获取与数据库的连接。
- 发出SQL语句和存储过程。
- 关闭连接
MySQLdb
MySQLdb是一个用于从Python连接到MySQL数据库服务器的接口。 它实现了Python数据库API v2.0,并建立在MySQL C API之上。
在继续之前,你要确保你的机器上安装了MySQLdb。 只需在你的Python脚本中输入以下内容并执行它 -
#!/usr/bin/python import MySQLdb
如果它产生以下结果,则表示MySQLdb模块未安装 -
Traceback (most recent call last): File "test.py", line 3, in <module> import MySQLdb ImportError: No module named MySQLdb
要安装MySQLdb模块,请使用以下命令 −
For Ubuntu, use the following command - $ sudo apt-get install python-pip python-dev libmysqlclient-dev For Fedora, use the following command - $ sudo dnf install python python-devel mysql-devel redhat-rpm-config gcc For Python command prompt, use the following command - pip install MySQL-python
注 - 确保您具有root权限以安装上述模块。
数据库连接
在连接到MySQL数据库之前,请确保以下内容 -
- 你已经创建了一个数据库TESTDB。
- 你已经在TESTDB中创建了一个表EMPLOYEE。
- 此表的字段为FIRST_NAME,LAST_NAME,AGE,SEX和INCOME。
- 用户ID“testuser”和密码“test123”被设置为访问TESTDB。
- Python模块MySQLdb在您的机器上正确安装。
- 你已经通过MySQL教程了解MySQL基础知识。
以下是连接MySQL数据库“TESTDB”的例子
#!/usr/bin/python
import MySQLdb
# Open database connection
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )
# prepare a cursor object using cursor() method
cursor = db.cursor()
# execute SQL query using execute() method.
cursor.execute("SELECT VERSION()")
# Fetch a single row using fetchone() method.
data = cursor.fetchone()
print "Database version : %s " % data
# disconnect from server
db.close()
运行这个脚本时,它在我的Linux机器上产生下面的结果。
Database version : 5.0.45
如果与数据源建立连接,则返回连接对象并将其保存到数据库中以备后用,否则将数据库设置为无。 接下来,db对象用于创建一个游标对象,该对象又用于执行SQL查询。 最后,在出来之前,它确保关闭数据库连接并释放资源。
创建数据库表
一旦建立了数据库连接,我们就可以使用创建的游标的execute方法在数据库表中创建表或记录。
让我们创建数据库表EMPLOYEE -
#!/usr/bin/python
import MySQLdb
# Open database connection
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )
# prepare a cursor object using cursor() method
cursor = db.cursor()
# Drop table if it already exist using execute() method.
cursor.execute("DROP TABLE IF EXISTS EMPLOYEE")
# Create table as per requirement
sql = """CREATE TABLE EMPLOYEE (
FIRST_NAME CHAR(20) NOT NULL,
LAST_NAME CHAR(20),
AGE INT,
SEX CHAR(1),
INCOME FLOAT )"""
cursor.execute(sql)
# disconnect from server
db.close()
INSERT操作
当您想要将记录创建到数据库表中时,它是必需的。
以下示例执行SQL INSERT语句以创建一条记录到EMPLOYEE表中 -
#!/usr/bin/python
import MySQLdb
# Open database connection
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )
# prepare a cursor object using cursor() method
cursor = db.cursor()
# Prepare SQL query to INSERT a record into the database.
sql = """INSERT INTO EMPLOYEE(FIRST_NAME,
LAST_NAME, AGE, SEX, INCOME)
VALUES ('Mac', 'Mohan', 20, 'M', 2000)"""
try:
# Execute the SQL command
cursor.execute(sql)
# Commit your changes in the database
db.commit()
except:
# Rollback in case there is any error
db.rollback()
# disconnect from server
db.close()
上面的例子可以写成如下动态创建SQL查询 -
#!/usr/bin/python
import MySQLdb
# Open database connection
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )
# prepare a cursor object using cursor() method
cursor = db.cursor()
# Prepare SQL query to INSERT a record into the database.
sql = "INSERT INTO EMPLOYEE(FIRST_NAME, \
LAST_NAME, AGE, SEX, INCOME) \
VALUES ('%s', '%s', '%d', '%c', '%d' )" % \
('Mac', 'Mohan', 20, 'M', 2000)
try:
# Execute the SQL command
cursor.execute(sql)
# Commit your changes in the database
db.commit()
except:
# Rollback in case there is any error
db.rollback()
# disconnect from server
db.close()
以下代码段是可以直接传递参数的另一种执行形式 -
..................................
user_id = "test123"
password = "password"
con.execute('insert into Login values("%s", "%s")' % \
(user_id, password))
..................................
READ操作
对任何数据库的READ操作意味着从数据库中获取一些有用的信息。
一旦我们建立了数据库连接,您就可以对这个数据库进行查询了。 您可以使用fetchone()方法来获取单个记录或fetchall()方法,以便从数据库表中获取多个值。
- fetchone() - 它获取查询结果集的下一行。 结果集是使用游标对象查询表时返回的对象。
- fetchall() - 它获取结果集中的所有行。 如果某些行已经从结果集中提取,那么它将从结果集中检索剩余的行。
- rowcount - 这是一个只读属性,并返回受execute()方法影响的行数。
以下过程查询工资超过1000的EMPLOYEE表中的所有记录 -
#!/usr/bin/python
import MySQLdb
# Open database connection
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )
# prepare a cursor object using cursor() method
cursor = db.cursor()
sql = "SELECT * FROM EMPLOYEE \
WHERE INCOME > '%d'" % (1000)
try:
# Execute the SQL command
cursor.execute(sql)
# Fetch all the rows in a list of lists.
results = cursor.fetchall()
for row in results:
fname = row[0]
lname = row[1]
age = row[2]
sex = row[3]
income = row[4]
# Now print fetched result
print "fname=%s,lname=%s,age=%d,sex=%s,income=%d" % \
(fname, lname, age, sex, income )
except:
print "Error: unable to fecth data"
# disconnect from server
db.close()
这将产生以下结果 -
fname=Mac, lname=Mohan, age=20, sex=M, income=2000
UPDATE操作
更新对任何数据库的操作意味着更新数据库中已有的一个或多个记录。
以下过程更新SEX为“M”的所有记录。 在这里,我们增加了所有男性的年龄一年。
#!/usr/bin/python
import MySQLdb
# Open database connection
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )
# prepare a cursor object using cursor() method
cursor = db.cursor()
# Prepare SQL query to UPDATE required records
sql = "UPDATE EMPLOYEE SET AGE = AGE + 1
WHERE SEX = '%c'" % ('M')
try:
# Execute the SQL command
cursor.execute(sql)
# Commit your changes in the database
db.commit()
except:
# Rollback in case there is any error
db.rollback()
# disconnect from server
db.close()
DELETE操作
当您想从数据库中删除一些记录时,需要执行DELETE操作。 以下是在AGE超过20的情况下从EMPLOYEE删除所有记录的过程 -
#!/usr/bin/python
import MySQLdb
# Open database connection
db = MySQLdb.connect("localhost","testuser","test123","TESTDB" )
# prepare a cursor object using cursor() method
cursor = db.cursor()
# Prepare SQL query to DELETE required records
sql = "DELETE FROM EMPLOYEE WHERE AGE > '%d'" % (20)
try:
# Execute the SQL command
cursor.execute(sql)
# Commit your changes in the database
db.commit()
except:
# Rollback in case there is any error
db.rollback()
# disconnect from server
db.close()
事务处理
事务是一种确保数据一致性的机制。 交易具有以下四个属性 -
- 原子性 - 事务完成或者根本没有任何事情发生。
- 一致性 - 事务必须以一致的状态开始并使系统保持一致状态。
- 隔离 - 交易的中间结果在当前交易之外不可见。
- 耐久性 - 一旦交易完成,即使系统发生故障,效果也是持久的。
Python DB API 2.0提供了两种提交或回滚事务的方法。
你已经知道如何实现交易。 这是一个类似的例子 -
# Prepare SQL query to DELETE required records sql = "DELETE FROM EMPLOYEE WHERE AGE > '%d'" % (20) try: # Execute the SQL command cursor.execute(sql) # Commit your changes in the database db.commit() except: # Rollback in case there is any error db.rollback()
COMMIT操作
Commit是一个操作,它向数据库发出一个绿色信号以完成更改,在此操作之后,不会恢复任何更改。
这是一个简单的例子来调用commit方法。
db.commit()
ROLLBACK操作
如果您对一个或多个更改不满意并想要完全还原这些更改,请使用rollback()方法。
这里是一个简单的例子来调用rollback()方法。
db.rollback()
断开数据库
要断开数据库连接,请使用close()方法。
db.close()
如果用户使用close()方法关闭与数据库的连接,则任何未完成的事务都由数据库回滚。 但是,不要依赖任何数据库较低级别的实现细节,您的应用程序最好是明确调用commit或rollback。
错误处理
DB API中定义了一些数据库操作的错误及异常,下表列出了这些错误和异常:
| 异常 | 描述 |
|---|---|
| Warning | 当有严重警告时触发,例如插入数据是被截断等等。必须是 StandardError 的子类。 |
| Error | 警告以外所有其他错误类。必须是 StandardError 的子类。 |
| InterfaceError | 当有数据库接口模块本身的错误(而不是数据库的错误)发生时触发。 必须是Error的子类。 |
| DatabaseError | 和数据库有关的错误发生时触发。 必须是Error的子类。 |
| DataError | 当有数据处理时的错误发生时触发,例如:除零错误,数据超范围等等。 必须是DatabaseError的子类。 |
| OperationalError | 指非用户控制的,而是操作数据库时发生的错误。例如:连接意外断开、 数据库名未找到、事务处理失败、内存分配错误等等操作数据库是发生的错误。 必须是DatabaseError的子类。 |
| IntegrityError | 完整性相关的错误,例如外键检查失败等。必须是DatabaseError子类。 |
| InternalError | 数据库的内部错误,例如游标(cursor)失效了、事务同步失败等等。 必须是DatabaseError子类。 |
| ProgrammingError | 程序错误,例如数据表(table)没找到或已存在、SQL语句语法错误、 参数数量错误等等。必须是DatabaseError的子类。 |
| NotSupportedError | 不支持错误,指使用了数据库不支持的函数或API等。例如在连接对象上 使用.rollback()函数,然而数据库并不支持事务或者事务已关闭。 必须是DatabaseError的子类。 |
Python3 网络编程
Python 提供了两个级别访问的网络服务。:
- 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
- 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。
什么是 Socket?
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
socket()函数
Python 中,我们用 socket()函数来创建套接字,语法格式如下
socket.socket([family[, type[, proto]]])
参数
- family: 套接字家族可以使AF_UNIX或者AF_INET
- type: 套接字类型可以根据是面向连接的还是非连接分为
SOCK_STREAM或SOCK_DGRAM - protocol: 一般不填默认为0.
Socket 对象(内建)方法
服务器端套接字
| 函数 | 描述 |
|---|---|
| s.bind() | 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 |
| s.listen() | 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。 |
| s.accept() | 被动接受TCP客户端连接,(阻塞式)等待连接的到来 |
客户端套接字
| 函数 | 描述 |
|---|---|
| s.connect() | 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 |
| s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数
| 函数 | 描述 |
|---|---|
| s.recv() | 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。 |
| s.send() | 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 |
| s.sendall() | 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 |
| s.recvfrom() | 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 |
| s.sendto() | 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 |
| s.close() | 关闭套接字 |
| s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 |
| s.getsockname() | 返回套接字自己的地址。通常是一个元组(ipaddr,port) |
| s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 |
| s.getsockopt(level,optname[.buflen]) | 返回套接字选项的值。 |
| s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) |
| s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
| s.fileno() | 返回套接字的文件描述符。 |
| s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 |
| s.makefile() | 创建一个与该套接字相关连的文件 |
简单的服务器
要编写Internet服务器,我们使用套接字模块中提供的套接字函数来创建套接字对象。 然后使用套接字对象调用其他函数来设置套接字服务器。
现在调用bind(hostname,port)函数为给定主机上的服务指定一个端口。
接下来,调用返回对象的accept方法。 此方法一直等待,直到客户端连接到您指定的端口,然后返回一个表示与该客户端的连接的连接对象。
#!/usr/bin/python3 # This is server.py file
import socket
# create a socket object
serversocket = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
# get local machine name
host = socket.gethostname()
port = 9999
# bind to the port
serversocket.bind((host, port))
# queue up to 5 requests
serversocket.listen(5)
while True:
# establish a connection
clientsocket,addr = serversocket.accept()
print("Got a connection from %s" % str(addr))
msg = 'Thank you for connecting'+ "\r\n"
clientsocket.send(msg.encode('ascii'))
clientsocket.close()
简单的客户端
让我们编写一个非常简单的客户端程序,它打开与给定端口12345和给定主机的连接。 使用Python的套接字模块函数创建套接字客户端非常简单。
socket.connect(hosname,port)在端口上打开到主机名的TCP连接。 打开套接字后,可以像读取任何IO对象一样读取它。 完成后,记得关闭它,就像关闭文件一样。
以下代码是一个非常简单的客户端,它连接到给定的主机和端口,从套接字读取任何可用数据,然后退出 -
#!/usr/bin/python3 # This is client.py file
import socket
# create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# get local machine name
host = socket.gethostname()
port = 9999
# connection to hostname on the port.
s.connect((host, port))
# Receive no more than 1024 bytes
msg = s.recv(1024)
s.close()
print (msg.decode('ascii'))
现在在后台运行这个server.py,然后运行上面的client.py来查看结果。
# Following would start a server in background. $ python server.py & # Once server is started run client as follows: $ python client.py
这会产生以下结果 -
on server terminal
Got a connection from ('192.168.1.10', 3747)
On client terminal
Thank you for connecting
Python互联网模块
下面给出了Python网络/ Internet编程中一些重要模块的列表 -
| 协议 | 功能用处 | 端口号 | Python 模块 |
|---|---|---|---|
| HTTP | 网页访问 | 80 | httplib, urllib, xmlrpclib |
| NNTP | 阅读和张贴新闻文章,俗称为"帖子" | 119 | nntplib |
| FTP | 文件传输 | 20 | ftplib, urllib |
| SMTP | 发送邮件 | 25 | smtplib |
| POP3 | 接收邮件 | 110 | poplib |
| IMAP4 | 获取邮件 | 143 | imaplib |
| Telnet | 命令行 | 23 | telnetlib |
| Gopher | 信息查找 | 70 | gopherlib, urllib |
Python3 发送邮件
简单邮件传输协议(SMTP)是一种协议,用于在邮件服务器之间发送电子邮件和路由电子邮件。
Python提供了smtplib模块,该模块定义了一个SMTP客户端会话对象,该对象可用于通过SMTP或ESMTP侦听器守护程序将邮件发送到任何Internet计算机。
这里有一个简单的语法来创建一个SMTP对象,稍后可以用它来发送电子邮件 -
import smtplib smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )
这里是参数的细节 -
- host - 这是运行SMTP服务器的主机。 您可以指定主机的IP地址或像tutorialspoint.com这样的域名。 这是一个可选参数。
- port - 如果您提供主机参数,则需要指定SMTP服务器正在侦听的端口。 通常这个端口是25。
- local_hostname - 如果您的SMTP服务器正在本地计算机上运行,那么您可以指定localhost该选项。
一个SMTP对象有一个名为sendmail的实例方法,它通常用于完成邮件发送的工作。 它需要三个参数 -
- 发件人 - 带有发件人地址的字符串。
- 接收者 - 字符串列表,每个接收者一个。
- 消息 - 按照各种RFC中指定的格式设置字符串的消息。
以下是使用Python脚本发送一封电子邮件的简单方法。 尝试一次 -
#!/usr/bin/python3
import smtplib
sender = 'from@fromdomain.com'
receivers = ['to@todomain.com']
message = """From: From Person <from@fromdomain.com>
To: To Person <to@todomain.com>
Subject: SMTP e-mail test
This is a test e-mail message.
"""
try:
smtpObj = smtplib.SMTP('localhost')
smtpObj.sendmail(sender, receivers, message)
print "Successfully sent email"
except SMTPException:
print "Error: unable to send email"
在这里,你已经在消息中放入了一个基本的电子邮件,注意正确地格式化标题。 一封电子邮件需要一个From,To和一个Subject标题,与电子邮件正文分开,并以空行显示。
要发送邮件,请使用smtpObj连接到本地计算机上的SMTP服务器。 然后使用sendmail方法以及邮件,发件人地址和目标地址作为参数(即使发件人和发件人地址位于电子邮件本身内,这些邮件并不总是用于路由邮件)。
如果您未在本地计算机上运行SMTP服务器,则可以使用smtplib客户端与远程SMTP服务器进行通信。 除非您使用的是webmail服务(例如gmail或Yahoo! Mail),否则您的电子邮件提供商必须向您提供您可以提供的发送邮件服务器详细信息,如下所示 -
mail = smtplib.SMTP('smtp.gmail.com', 587)
使用Python发送HTML电子邮件
当您使用Python发送文本消息时,所有内容都被视为简单文本。 即使您在文本消息中包含HTML标记,它也会显示为简单的文本,并且HTML标记不会根据HTML语法进行格式化。 但是,Python提供了将HTML消息作为实际HTML消息发送的选项。
发送电子邮件时,您可以指定Mime版本,内容类型和字符集以发送HTML电子邮件。
以下是将HTML内容作为电子邮件发送的示例。 尝试一次 -
#!/usr/bin/python3
import smtplib
message = """From: From Person <from@fromdomain.com>
To: To Person <to@todomain.com>
MIME-Version: 1.0
Content-type: text/html
Subject: SMTP HTML e-mail test
This is an e-mail message to be sent in HTML format
<b>This is HTML message.</b>
<h1>This is headline.</h1>
"""
try:
smtpObj = smtplib.SMTP('localhost')
smtpObj.sendmail(sender, receivers, message)
print "Successfully sent email"
except SMTPException:
print "Error: unable to send email"
将附件作为电子邮件发送
要发送具有混合内容的电子邮件,需要将Content-type标头设置为多部分/混合。 然后,可以在边界内指定文本和附件部分。
边界以两个连字符开头,后面跟着一个唯一的数字,该数字不能出现在电子邮件的消息部分。 表示电子邮件最后一部分的最后边界也必须以两个连字符结尾。
附加文件应使用pack(“m”)函数进行编码,以便在传输之前进行base 64编码。
以下是一个示例,它以附件形式发送文件/tmp/test.txt。 尝试一次 -
#!/usr/bin/python3
import smtplib
import base64
filename = "/tmp/test.txt"
# Read a file and encode it into base64 format
fo = open(filename, "rb")
filecontent = fo.read()
encodedcontent = base64.b64encode(filecontent) # base64
sender = 'webmaster@tutorialpoint.com'
reciever = 'amrood.admin@gmail.com'
marker = "AUNIQUEMARKER"
body ="""
This is a test email to send an attachement.
"""
# Define the main headers.
part1 = """From: From Person <me@fromdomain.net>
To: To Person <amrood.admin@gmail.com>
Subject: Sending Attachement
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=%s
--%s
""" % (marker, marker)
# Define the message action
part2 = """Content-Type: text/plain
Content-Transfer-Encoding:8bit
%s
--%s
""" % (body,marker)
# Define the attachment section
part3 = """Content-Type: multipart/mixed; name=\"%s\"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename=%s
%s
--%s--
""" %(filename, filename, encodedcontent, marker)
message = part1 + part2 + part3
try:
smtpObj = smtplib.SMTP('localhost')
smtpObj.sendmail(sender, reciever, message)
print "Successfully sent email"
except Exception:
print ("Error: unable to send email")
Python3 多线程编程
运行多个线程类似于同时运行多个不同的程序,但具有以下优点 -
- 进程中的多个线程与主线程共享相同的数据空间,因此可以更容易地共享信息或彼此通信,而不像它们是单独的进程。
- 线程有时称为轻量级进程,并且不需要太多的内存开销; 它们比流程便宜。
一个线程有一个开始,一个执行序列和一个结论。 它有一个指令指针,用于跟踪当前正在运行的上下文中的什么位置。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
有两种不同类型的线程 -
- 内核线程
- 用户线程
Python3 线程中常用的两个模块为
- _thread
- threading(推荐使用)
thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。
开始一个新线程
为了产生另一个线程,你需要在线程模块中调用以下方法 -
_thread.start_new_thread ( function, args[, kwargs] )
此方法调用可以快速有效地在Linux和Windows中创建新线程。
该方法调用立即返回并且子线程开始并使用传递的参数列表调用函数。 当函数返回时,线程终止。
在这里,参数是一个参数的元组; 使用空元组来调用函数而不传递任何参数。 kwargs是关键字参数的可选字典。
#!/usr/bin/python3
import _thread
import time
# Define a function for the thread
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print ("%s: %s" % ( threadName, time.ctime(time.time()) ))
# Create two threads as follows
try:
_thread.start_new_thread( print_time, ("Thread-1", 2, ) )
_thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print ("Error: unable to start thread")
while 1:
pass
当上面的代码被执行时,它会产生以下结果 -
Thread-1: Fri Feb 19 09:41:39 2016 Thread-2: Fri Feb 19 09:41:41 2016 Thread-1: Fri Feb 19 09:41:41 2016 Thread-1: Fri Feb 19 09:41:43 2016 Thread-2: Fri Feb 19 09:41:45 2016 Thread-1: Fri Feb 19 09:41:45 2016 Thread-1: Fri Feb 19 09:41:47 2016 Thread-2: Fri Feb 19 09:41:49 2016 Thread-2: Fri Feb 19 09:41:53 2016
程序进入无限循环。 你将不得不按ctrl-c停止
虽然它对低级线程非常有效,但相比较新的线程模块,线程模块非常有限。
线程模块
Python3 通过两个标准库 _thread 和 threading 提供对线程的支持。
_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
- isAlive(): 返回线程是否活动的。
- getName(): 返回线程名。
- setName(): 设置线程名。
使用线程模块创建线程
要使用线程模块实现新线程,您必须执行以下操作 -
- 定义Thread类的新子类。
- 重写__init __(self [,args])方法来添加其他参数。
- 然后,重写run(self [,args])方法来实现线程在启动时应该执行的操作。
一旦创建了新的Thread子类,就可以创建它的一个实例,然后通过调用start()来启动一个新的线程,start()又调用run()方法。
#!/usr/bin/python3
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("Starting " + self.name)
print_time(self.name, self.counter, 5)
print ("Exiting " + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")
当我们运行上面的程序时,它会产生以下结果 -
Starting Thread-1 Starting Thread-2 Thread-1: Fri Feb 19 10:00:21 2016 Thread-2: Fri Feb 19 10:00:22 2016 Thread-1: Fri Feb 19 10:00:22 2016 Thread-1: Fri Feb 19 10:00:23 2016 Thread-2: Fri Feb 19 10:00:24 2016 Thread-1: Fri Feb 19 10:00:24 2016 Thread-1: Fri Feb 19 10:00:25 2016 Exiting Thread-1 Thread-2: Fri Feb 19 10:00:26 2016 Thread-2: Fri Feb 19 10:00:28 2016 Thread-2: Fri Feb 19 10:00:30 2016 Exiting Thread-2 Exiting Main Thread
同步线程
Python提供的线程模块包含一个简单实现的锁定机制,允许您同步线程。 通过调用Lock()方法创建一个新的锁,该方法返回新的锁。
新锁对象的acquire(阻塞)方法用于强制线程同步运行。 可选的阻塞参数使您可以控制线程是否等待获取锁。
如果阻塞设置为0,则线程立即返回0值(如果无法获取锁定),如果锁定获取则返回1。 如果阻塞设置为1,则线程将阻塞并等待锁释放。
不再需要新锁对象的release()方法来释放锁。
#!/usr/bin/python3
import threading
import time
class myThread (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print ("Starting " + self.name)
# Get lock to synchronize threads
threadLock.acquire()
print_time(self.name, self.counter, 3)
# Free lock to release next thread
threadLock.release()
def print_time(threadName, delay, counter):
while counter:
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
threadLock = threading.Lock()
threads = []
# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
# Add threads to thread list
threads.append(thread1)
threads.append(thread2)
# Wait for all threads to complete
for t in threads:
t.join()
print ("Exiting Main Thread")
当上面的代码被执行时,它会产生以下结果 -
Starting Thread-1 Starting Thread-2 Thread-1: Fri Feb 19 10:04:14 2016 Thread-1: Fri Feb 19 10:04:15 2016 Thread-1: Fri Feb 19 10:04:16 2016 Thread-2: Fri Feb 19 10:04:18 2016 Thread-2: Fri Feb 19 10:04:20 2016 Thread-2: Fri Feb 19 10:04:22 2016 Exiting Main Thread
多线程优先队列
队列模块允许您创建一个新的队列对象,该对象可以容纳特定数量的项目。 有以下方法来控制队列 -
- get() - get()从队列中移除并返回一个项目。
- put() - put将项添加到队列中。
- qsize() - qsize()返回当前队列中的项目数。
- empty() - 如果队列为空,则empty()返回True; 否则,False。
- full() - 如果队列已满,full()返回True; 否则,False。
#!/usr/bin/python3
import queue
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, q):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.q = q
def run(self):
print ("Starting " + self.name)
process_data(self.name, self.q)
print ("Exiting " + self.name)
def process_data(threadName, q):
while not exitFlag:
queueLock.acquire()
if not workQueue.empty():
data = q.get()
queueLock.release()
print ("%s processing %s" % (threadName, data))
else:
queueLock.release()
time.sleep(1)
threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1
# Create new threads
for tName in threadList:
thread = myThread(threadID, tName, workQueue)
thread.start()
threads.append(thread)
threadID += 1
# Fill the queue
queueLock.acquire()
for word in nameList:
workQueue.put(word)
queueLock.release()
# Wait for queue to empty
while not workQueue.empty():
pass
# Notify threads it's time to exit
exitFlag = 1
# Wait for all threads to complete
for t in threads:
t.join()
print ("Exiting Main Thread")
当上面的代码被执行时,它会产生以下结果 -
Starting Thread-1 Starting Thread-2 Starting Thread-3 Thread-1 processing One Thread-2 processing Two Thread-3 processing Three Thread-1 processing Four Thread-2 processing Five Exiting Thread-3 Exiting Thread-1 Exiting Thread-2 Exiting Main Thread
Python3 XML处理
XML是一种可移植的开源语言,允许程序员开发可由其他应用程序读取的应用程序,而不管操作系统和/或开发语言如何。
什么是XML?
可扩展标记语言(XML)是一种非常类似于HTML或SGML的标记语言。 这由万维网联盟推荐并作为开放标准提供。
XML对于跟踪中小量数据而无需基于SQL的数据非常有用。
XML解析器体系结构和API
Python标准库提供了一组最少但有用的接口来处理XML。
XML数据的两个最基本和广泛使用的API是SAX和DOM接口。
- 简单的XML(SAX)API - 在这里,您为感兴趣的事件注册回调,然后让解析器继续处理文档。 当文档很大或者存在内存限制时,它会很有用,它会在文件从磁盘读取文件时解析文件,并且整个文件永远不会存储在内存中。
- 文档对象模型(DOM)API - 这是一个万维网联盟的建议,其中整个文件被读入内存并以分层(基于树)的形式存储以表示XML文档的所有特征。
当处理大文件时,SAX显然不能像DOM一样快速处理信息。 另一方面,专门使用DOM可以真正杀死你的资源,特别是在许多小文件上使用时。
SAX是只读的,而DOM允许更改XML文件。 由于这两种不同的API在字面上相互补充,所以没有理由不能将它们用于大型项目。
对于我们所有的XML代码示例,让我们使用一个简单的XML文件movies.xml作为输入 -
<collection shelf = "New Arrivals"> <movie title = "Enemy Behind"> <type>War, Thriller</type> <format>DVD</format> <year>2003</year> <rating>PG</rating> <stars>10</stars> <description>Talk about a US-Japan war</description> </movie> <movie title = "Transformers"> <type>Anime, Science Fiction</type> <format>DVD</format> <year>1989</year> <rating>R</rating> <stars>8</stars> <description>A schientific fiction</description> </movie> <movie title = "Trigun"> <type>Anime, Action</type> <format>DVD</format> <episodes>4</episodes> <rating>PG</rating> <stars>10</stars> <description>Vash the Stampede!</description> </movie> <movie title = "Ishtar"> <type>Comedy</type> <format>VHS</format> <rating>PG</rating> <stars>2</stars> <description>Viewable boredom</description> </movie> </collection>
使用SAX API解析XML
SAX是用于事件驱动的XML解析的标准接口。用SAX解析XML通常需要通过继承xml.sax.ContentHandler来创建自己的ContentHandler。
你的ContentHandler处理你的XML特征的特定标签和属性。 ContentHandler对象提供了处理各种解析事件的方法。它拥有的解析器在分析XML文件时调用ContentHandler方法。
startDocument和endDocument方法在XML文件的开始和结束处被调用。方法字符(文本)通过参数文本传递XML文件的字符数据。
ContentHandler在每个元素的开始和结束处被调用。如果解析器不处于命名空间模式,则会调用startElement(tag,attributes)和endElement(tag)方法;否则,调用相应的startElementNS和endElementNS方法。这里,tag是元素标签,属性是Attributes对象。
在继续之前,以下是其他重要的方法 -
make_parser方法
以下方法创建一个新的解析器对象并将其返回。 所创建的解析器对象将是系统找到的第一个解析器类型。
xml.sax.make_parser( [parser_list] )
这里是参数的细节 -
parser_list - 可选参数,包含要使用的解析器列表,必须全部实现make_parser方法。
parse方法
以下方法创建一个SAX解析器并使用它来解析文档。
xml.sax.parse( xmlfile, contenthandler[, errorhandler])
这里是参数的细节 -
- xmlfile - 这是要读取的XML文件的名称。
- contenthandler - 这必须是一个ContentHandler对象。
- errorhandler - 如果指定,errorhandler必须是SAX ErrorHandler对象。
parseString方法
还有一种方法可以创建SAX解析器并解析指定的XML字符串。
xml.sax.parseString(xmlstring, contenthandler[, errorhandler])
这里是参数的细节 -
- xmlstring - 这是要读取的XML字符串的名称。
- contenthandler - 这必须是一个ContentHandler对象。
- errorhandler - 如果指定,errorhandler必须是SAX ErrorHandler对象。
#!/usr/bin/python3
import xml.sax
class MovieHandler( xml.sax.ContentHandler ):
def __init__(self):
self.CurrentData = ""
self.type = ""
self.format = ""
self.year = ""
self.rating = ""
self.stars = ""
self.description = ""
# Call when an element starts
def startElement(self, tag, attributes):
self.CurrentData = tag
if tag == "movie":
print ("*****Movie*****")
title = attributes["title"]
print ("Title:", title)
# Call when an elements ends
def endElement(self, tag):
if self.CurrentData == "type":
print ("Type:", self.type)
elif self.CurrentData == "format":
print ("Format:", self.format)
elif self.CurrentData == "year":
print ("Year:", self.year)
elif self.CurrentData == "rating":
print ("Rating:", self.rating)
elif self.CurrentData == "stars":
print ("Stars:", self.stars)
elif self.CurrentData == "description":
print ("Description:", self.description)
self.CurrentData = ""
# Call when a character is read
def characters(self, content):
if self.CurrentData == "type":
self.type = content
elif self.CurrentData == "format":
self.format = content
elif self.CurrentData == "year":
self.year = content
elif self.CurrentData == "rating":
self.rating = content
elif self.CurrentData == "stars":
self.stars = content
elif self.CurrentData == "description":
self.description = content
if ( __name__ == "__main__"):
# create an XMLReader
parser = xml.sax.make_parser()
# turn off namepsaces
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
# override the default ContextHandler
Handler = MovieHandler()
parser.setContentHandler( Handler )
parser.parse("movies.xml")
这会产生以下结果 -
*****Movie***** Title: Enemy Behind Type: War, Thriller Format: DVD Year: 2003 Rating: PG Stars: 10 Description: Talk about a US-Japan war *****Movie***** Title: Transformers Type: Anime, Science Fiction Format: DVD Year: 1989 Rating: R Stars: 8 Description: A schientific fiction *****Movie***** Title: Trigun Type: Anime, Action Format: DVD Rating: PG Stars: 10 Description: Vash the Stampede! *****Movie***** Title: Ishtar Type: Comedy Format: VHS Rating: PG Stars: 2 Description: Viewable boredom
有关SAX API文档的完整详细信息,请参阅标准Python SAX API
用DOM API解析XML
文档对象模型(“DOM”)是万维网联盟(W3C)的跨语言API,用于访问和修改XML文档。
DOM对随机访问应用程序非常有用。 SAX只允许您一次查看文档的一个位。 如果您正在查看一个SAX元素,则无法访问其他元素。
以下是快速加载XML文档并使用xml.dom模块创建minidom对象的最简单方法。 minidom对象提供了一种简单的解析器方法,可以从XML文件快速创建DOM树。
该示例短语调用minidom对象的parse(file [,parser])函数以将由文件指定的XML文件解析为DOM树对象。
#!/usr/bin/python3
from xml.dom.minidom import parse
import xml.dom.minidom
# Open XML document using minidom parser
DOMTree = xml.dom.minidom.parse("movies.xml")
collection = DOMTree.documentElement
if collection.hasAttribute("shelf"):
print ("Root element : %s" % collection.getAttribute("shelf"))
# Get all the movies in the collection
movies = collection.getElementsByTagName("movie")
# Print detail of each movie.
for movie in movies:
print ("*****Movie*****")
if movie.hasAttribute("title"):
print ("Title: %s" % movie.getAttribute("title"))
type = movie.getElementsByTagName('type')[0]
print ("Type: %s" % type.childNodes[0].data)
format = movie.getElementsByTagName('format')[0]
print ("Format: %s" % format.childNodes[0].data)
rating = movie.getElementsByTagName('rating')[0]
print ("Rating: %s" % rating.childNodes[0].data)
description = movie.getElementsByTagName('description')[0]
print ("Description: %s" % description.childNodes[0].data)
这会产生以下结果 -
Root element : New Arrivals *****Movie***** Title: Enemy Behind Type: War, Thriller Format: DVD Rating: PG Description: Talk about a US-Japan war *****Movie***** Title: Transformers Type: Anime, Science Fiction Format: DVD Rating: R Description: A schientific fiction *****Movie***** Title: Trigun Type: Anime, Action Format: DVD Rating: PG Description: Vash the Stampede! *****Movie***** Title: Ishtar Type: Comedy Format: VHS Rating: PG Description: Viewable boredom
有关DOM API文档的完整详细信息,请参阅标准的Python DOM API
Python3 GUI编程(Tkinter)
Python为开发图形用户界面(GUI)提供了各种选项。 下面列出了最重要的功能。
- Tkinter - Tkinter是Python附带的Tk GUI工具包的Python界面。 我们将在本章中看看这个选项。
- wxPython - 这是一个wxWidgets GUI工具包的开源Python接口。 你可以在这里找到关于WxPython的完整教程。
- PyQt - 这也是一个流行的跨平台Qt GUI库的Python界面。
- JPython - JPython是Java的Python端口,它使Python脚本可以无缝地访问本地机器上的Java类库http://www.jython.org。
还有许多其他可用的接口,您可以在网上找到它们。
Tkinter编程
Tkinter是Python的标准GUI库。 与Tkinter结合使用时,Python提供了一种创建GUI应用程序的快捷方式。 Tkinter为Tk GUI工具包提供了强大的面向对象的接口。
使用Tkinter创建GUI应用程序是一件容易的事。 您所需要做的就是执行以下步骤 -
- 导入Tkinter模块。
- 创建GUI应用程序主窗口。
- 将一个或多个上述小部件添加到GUI应用程序中。
- 进入主事件循环以对由用户触发的每个事件采取行动。
#!/usr/bin/python3 import tkinter # note that module name has changed from Tkinter in Python 2 to tkinter in Python 3 top = tkinter.Tk() # Code to add widgets will go here... top.mainloop()
这将创建一个以下窗口 -
Tkinter Widgets
Tkinter提供各种控件,例如GUI应用程序中使用的按钮,标签和文本框。 这些控件通常称为小部件。
目前Tkinter中有15种小部件。
标准属性
让我们看看它们的一些常用属性如尺寸,颜色和字体是如何指定的。(Dimensions,Colors,Fonts,Anchors,Relief styles,Bitmaps,Cursors)
几何管理
所有Tkinter小部件都可以访问特定的几何管理方法,这些方法的目的是在整个父窗口小部件区域中组织窗口小部件。 Tkinter公开了以下几何管理器类:包,网格和地点。
- pack()方法 - 此几何管理器在将它们放入父窗口小部件之前,先将它们组织在块中。
- grid()方法 - 此几何管理器在父窗口小部件中以类似于表格的结构组织窗口小部件。
- place()方法 - 此几何管理器通过将小部件放置在父小部件中的特定位置来组织小部件。
Python3 使用C语言进行扩展编程
任何使用C,C ++或Java等编译语言编写的代码都可以集成或导入到另一个Python脚本中。 此代码被视为“扩展”。
Python扩展模块只不过是一个普通的C库。 在Unix机器上,这些库通常以.so结尾(用于共享对象)。 在Windows机器上,您通常会看到.dll(用于动态链接的库)。
编写扩展的先决条件
要开始编写你的扩展,你需要Python头文件。
- 在Unix机器上,这通常需要安装特定于开发人员的软件包,例如python2.5-dev。
- Windows用户在使用二进制Python安装程序时将这些头文件作为程序包的一部分。
此外,假定您有C或C++的丰富知识,可以使用C编程编写任何Python扩展。
Python扩展
首先看一下Python扩展模块,你需要将你的代码分为四部分 -
- 头文件Python.h。
- 您想要公开的C函数作为模块的接口。
- 将Python函数的名称映射为Python开发人员在扩展模块中将其视为C函数的表。
- 一个初始化函数。
头文件Python.h
您需要在您的C源文件中包含Python.h头文件,该文件允许您访问用于将模块挂接到解释器的内部Python API。
确保在可能需要的任何其他头文件之前包含Python.h。 你需要跟随包含你想从Python调用的函数。
C函数
您的函数的C实现的签名总是采用以下三种形式之一 -
static PyObject *MyFunction( PyObject *self, PyObject *args ); static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw); static PyObject *MyFunctionWithNoArgs( PyObject *self );
前面的每个声明都返回一个Python对象。 在C中没有像Python中的void函数那样的东西。如果你不想让你的函数返回一个值,返回Python的None值的C等价物。 Python头文件定义了一个宏,Py_RETURN_NONE,它为我们做了这个。
您的C函数的名称可以是任何您喜欢的名称,因为它们在扩展模块之外从未见过。 它们被定义为静态函数。
您的C函数通常通过将Python模块和函数名称组合在一起来命名,如下所示 -
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
这是一个在模块模块中称为func的Python函数。 您将把C函数的指针放入源代码中通常出现的模块的方法表中。
方法映射表
此方法表是PyMethodDef结构的简单数组。 那个结构看起来像这样 -
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
这里是这个结构的成员的描述 -
- ml_name - 这是Python解释器在Python程序中使用时显示的函数名称。
- ml_meth - 这是具有上一节中描述的任何签名的函数的地址。
- ml_flags - 这告诉解释器ml_meth使用三个签名中的哪一个。
- 该标志通常具有METH_VARARGS的值。
- 如果你想允许关键字参数进入你的函数,这个标志可以与METH_KEYWORDS进行按位或运算。
- 这也可以具有METH_NOARGS的值,表示您不想接受任何参数。
- ml_doc - 这是该函数的文档字符串,如果您不想编写它,则可能为NULL。
该表需要由适当成员的由NULL和0值组成的标记结束。
对于上面定义的函数,我们有以下方法映射表 -
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
初始化函数
扩展模块的最后一部分是初始化函数。 这个函数在模块加载时由Python解释器调用。 要求函数名为initModule,其中Module是模块的名称。
初始化函数需要从您将要构建的库中导出。 Python头文件定义了PyMODINIT_FUNC以包含适合我们正在编译的特定环境发生的咒语。 你所要做的就是在定义函数时使用它。
您的C初始化函数通常具有以下总体结构 -
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
这里是Py_InitModule3函数的描述 -
- func - 这是要导出的功能。
- module_methods - 这是上面定义的映射表名称。
- docstring - 这是您要在扩展程序中提供的评论。
把所有这些放在一起,看起来像下面这样 -
#include
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC initModule() {
Py_InitModule3(func, module_methods, "docstring...");
}
一个简单的例子,利用所有上述的概念 -
#include
static PyObject* helloworld(PyObject* self)
{
return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] =
"helloworld( ): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
{"helloworld", (PyCFunction)helloworld,
METH_NOARGS, helloworld_docs},
{NULL}
};
void inithelloworld(void)
{
Py_InitModule3("helloworld", helloworld_funcs,
"Extension module example!");
}
这里使用Py_BuildValue函数来构建一个Python值。 将上面的代码保存在hello.c文件中。 我们将看到如何编译和安装这个模块,以便从Python脚本中调用。
构建和安装扩展
distutils软件包使得以标准方式分发Python模块(包括纯Python和扩展模块)变得非常简单。 模块分布在源代码表单中,通过通常称为setup.py的安装脚本构建和安装。
对于上述模块,您需要准备以下setup.py脚本 -
from distutils.core import setup, Extension
setup(name = 'helloworld', version = '1.0', \
ext_modules = [Extension('helloworld', ['hello.c'])])
现在,使用以下命令,该命令将执行所有需要的编译和链接步骤,使用正确的编译器和链接器命令和标记,并将生成的动态库复制到适当的目录中 -
$ python setup.py install
在基于Unix的系统上,您很可能需要以root用户身份运行此命令才能有权写入站点包目录。 这通常不是Windows上的问题。
导入扩展
一旦你安装了你的扩展,你将能够在你的Python脚本中导入和调用该扩展,如下所示 -
#!/usr/bin/python3 import helloworld print helloworld.helloworld()
这会产生以下结果 -
Hello, Python extensions!!
传递函数参数
由于您很可能想要定义接受参数的函数,因此可以使用其他特征中的一个用于C函数。 例如,接受一些参数的以下函数可以像这样定义 -
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Parse args and do something interesting here. */
Py_RETURN_NONE;
}
包含新函数的条目的方法表看起来像这样 -
static PyMethodDef module_methods[] = {
{ "func", (PyCFunction)module_func, METH_NOARGS, NULL },
{ "func", module_func, METH_VARARGS, NULL },
{ NULL, NULL, 0, NULL }
};
您可以使用API PyArg_ParseTuple函数从传入您的C函数的一个PyObject指针中提取参数。
PyArg_ParseTuple的第一个参数是args参数。 这是您将要解析的对象。 第二个参数是一个描述参数的格式字符串,如您所期望的那样。 每个参数由格式字符串中的一个或多个字符表示,如下所示。
static PyObject *module_func(PyObject *self, PyObject *args) {
int i;
double d;
char *s;
if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
return NULL;
}
/* Do something interesting here. */
Py_RETURN_NONE;
}
编译新版本的模块并导入它,可以使用任意类型的任意数量的参数调用新函数 -
module.func(1, s = "three", d = 2.0) module.func(i = 1, d = 2.0, s = "three") module.func(s = "three", d = 2.0, i = 1)
PyArg_ParseTuple函数
这是PyArg_ParseTuple函数的标准签名 -
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
此函数返回0表示错误,成功时返回值不等于0。 元组是PyObject *,它是C函数的第二个参数。 这里的格式是一个C字符串,描述了强制参数和可选参数。
返回值
Py_BuildValue的格式字符串很像PyArg_ParseTuple。 您不必传入您正在构建的值的地址,而是传递实际值。 这里有一个例子展示了如何实现一个add函数 -
static PyObject *foo_add(PyObject *self, PyObject *args) {
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("i", a + b);
}
这是如果在Python中实现的话 -
def add(a, b): return (a + b)
你可以从你的函数中返回两个值,如下所示。 这将使用Python中的列表捕获。
static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
int a;
int b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("ii", a + b, a - b);
}
这是如果在Python中实现的话 -
def add_subtract(a, b): return (a + b, a - b)
Py_BuildValue 函数
这是Py_BuildValue函数的标准签名 -
PyObject* Py_BuildValue(char* format,...)
这里的格式是一个C字符串,用于描述要构建的Python对象。 Py_BuildValue的参数是构建结果的C值。 PyObject *结果是一个新的参考。