Python异常处理
在自动化脚本开发程中,往往会出现一些不可预知的错误,这种情况在编程语言中叫做异常。 遇到异常,我们要处理它,不是等到异常发生的时候,让IDE帮你处理,等他给你抛出异常的时候,就晚了。因此需要我们在可能发生异常的地方,主动使用try/except语句。来检测try语句块中的错误,从而让except语句捕获异常信息并处理。异常的处理机制是程序出现错误,它会保证程序的正常退出,它的好处是你不用再绞尽脑汁去考虑各种错误,使编程效率大大提高。异常处理的能力是一门语言好坏的重要特征。python也提供了强大的异常处理功能,在Python当中,若一个程序在运行的时候出错,Python解释器会自动的在出错的地方生成一个异常对象,而后Python解释器会自动的在出错地方的附近寻找有没有对这个异常对象处理的代码,所谓异常处理代码就是try……except语句,如果没有,Python解释器会自动的将这个异常对象抛给其调用函数,就这样层层抛出,如果在main当中也没有对这个异常对象处理的代码,Python解释器(实际上是操作系统)最后会做一个简单粗暴的处理,将整个程序给终止掉,并将错误的信息在显示屏上输出。 Python中的异常处理方法:
try…except
try…except语法声明格式:
try:
<语句> #运行别的代码
except <名字>:
<语句> #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句> #如果引发了'name'异常,获得附加的数据
else:
<语句> #如果没有异常发生
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常);如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息);如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。
try…finally
try…finally语法声明格式:
try:
<语句>
finally:
<语句> #退出try时总会执行
python总会执行finally子句,无论try子句执行时是否发一异常。
如果没有发生异常,python运行try子句,然后是finally子句,然后继续。
如果在try子句发生了异常,python就会回来执行finally子句,然后把异常递交给上层try,控制流不会通过整个try语句。当你想无论是否发生异常都确保执行某些代码时,try/finally是有用的。
实例:
try:
msg =input(">>")
print(int(msg))
except Exception as e:
print("异常的类型是:%s"%type(e))
print("异常的内容是:%s"%e)
else:
print('如果代码块不抛出异常会执行此行代码!')
finally:
print('不管代码块是否抛出异常都会执行此行代码!')
运行结果1:
1
如果代码块不抛出异常会执行此行代码!
不管代码块是否抛出异常都会执行此行代码!
运行结果2:
2
如果代码块不抛出异常会执行此行代码!
不管代码块是否抛出异常都会执行此行代码!
raise
raise语句声明格式:raise Exception(args [, traceback])
python会自动引发异常,也可以通过raise显示地引发异常。一旦执行了raise语句,raise后面的语句将不能执行。
在Python中,要想引发异常,最简单的形式就是输入关键字raise,后跟要引发的异常的名称。异常名称标识出具体的类: Python异常处理是那些类的对象。执行raise语句时,Python会创建指定的异常类的一个对象。raise语句还可指定对异常对象进行初始化的参数。为此,请在异常类的名称后添加一个逗号以及指定的参数(或者由参数构成的一个元组)。
实例:
try:
s = None
if s is None:
print("s 是空对象")
raise NameError #如果引发NameError异常,后面的代码将不能执行
print(len(s)) #这句不会执行,但是后面的except还是会走到
except TypeError:
print("空对象没有长度")
print('程序结束')
运行结果:
s 是空对象
Traceback (most recent call last):
File "C:/SeleniumBook/PyCharmProjects/Study_1/selenium/test2.py",line 5, in
raise NameError #如果引发NameError异常,后面的代码将不能执行
NameError
技术解释:raiseNameError引发错误后,print(len(s)),print('程序结束')和它们之间的代码都没有执行。
Assert
assert断言判断错误, assert是断言的关键字。assert语句根据后面的表达式的真假来控制程序流。若为True,则往下执行。若为False,则中断程序并调用默认的异常处理器,同时输出指定的提示信息。
assert语句声明格式:assert expression,'information'
实例:
mylist = ['aaaaaawerewr']
assert len(mylist) < 1
运行结果:
Traceback (most recent call last):
File "C:/PyCharmProjects/selenium/test2.py", line 2, in
assert len(mylist) <1
AssertionError
技术解释: assert语句用来判断某个条件是真,len(mylist) < 1是确认列表中没有元素,通过asert检验,它在非真的时候会引发AssertionError错误。
函数模块类
函数
接触过C语言那么对函数应该不陌生,函数的目的是为了将一些复杂、繁琐、重复性极高的代码封装成一个方法,它是代码复用的有效手段。函数接受任何数字或者其他类型的输入参数,并且返回数字或者其他类型的结果。和大多数高级语言一样,Python也支持函数,不仅能很灵活的定义函数,而且还有着很多的内置函数,这些函数都非常有用,可以直接调用。
函数的定义
在c语言当中,函数声明和函数定义是区分开的,但是在Python中,函数声明和函数定义是视为一体的。函数定义的基本形式如下:
def function(params):
block
returnexpression/value
在这里说明几点:
(1)在Python中采用def关键字进行函数的定义,不用指定返回值的类型,后接函数标识符名称和圆括号()以及末尾的冒号。
(2)函数参数params可以是零个、一个或者多个,同样的,函数参数也不用指定参数类型,因为在Python中变量都是弱类型的,Python会自动根据值来维护其类型。
(3)return语句是可选的,它可以在函数体内任何地方出现,表示函数调用执行到此结束;如果没有return语句,会自动返回NONE,如果有return语句,但是return后面没有接表达式或者值的话也是返回NONE。
实例:
def appium_start():
config = {
'platformName':'Android', #平台
'platformVersion':os_version, #系统版本
'deviceName':device_id, #测试设备ID
'app':'com.jiuai.apk', #apk路径
'newCommandTimeout':30,
'automationName': 'Appium',
'resetKeyboard':True}
return webdriver.Remote('http://localhost:4723/wd/hub', config)
函数的使用
在定义了函数之后,就可以使用该函数了,Python中要注意一个问题,就是在Python中不允许前向引用,即在函数定义之前,不允许调用该函数。看个实例就明白了:
if name=="main":
print(add(1,2))
def add(a,b):
return a+b
这段程序运行的结果是:
Traceback (most recent call last):
File "C://xxx.py",line 1, in
print(add(1,2))
NameError: name 'add' is not defined
从报的错可知,名字为"add"的函数未进行定义。所以在任何时候调用某个函数,必须确保其定义在调用之前。
函数的形参和实参
形参全称是形式参数,在用def关键字定义函数时函数名后面括号里的变量称作为形式参数。实参全称为实际参数,在调用函数时提供的值或者变量称作为实际参数。
实例:
a和b就是形参
def add(a,b):
return a+b
add(2,3):2和3是实参
x=2
y=3
add(x,y) #这里的x和y是实参
参数的传递和改变,在大多数高级语言当中,对参数的传递方式这个问题的理解一直是个难点和重点,因为它理解起来并不是那么直观明了,但是不理解的话在编写程序的时候又极其容易出错。
参数类型
调用函数时可使用的正式参数类型有以下几种:
A. A. 必备参数
必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。调用printme()函数,你必须传入一个参数,不然会出现语法错误:
实例:
def printmsg(str):
"打印任何传入的字符串"
print(str)
printmsg()# 调用printmsg函数
运行结果:
Traceback (most recent call last):
File "test.py",line 6, in
printmsg()
TypeError: printmsg() missing 1 required positional argument: 'str'
A. B. 关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
实例:在函数 printmsg() 调用时使用参数名
def printmsg(str):
"打印任何传入的字符串"
print(str)
printmsg(str='hello world')# 调用printmsg函数
运行结果:
hello world
实例:变换关键字参数顺序:
def printinfo(name, age):
print('Name:', name)
print('Age:', age)
printinfo(age=50, name="miki") # 调用printinfo函数
运行结果:
Name: miki
Age 50
A. C. 缺省参数
调用函数时,缺省参数的值如果没有传入,则被认为是默认值。
实例:打印默认的age:
def printinfo(name, age=20):
print("Name: ",name)
print("Age ",age)
printinfo(age=65, name="jack")# 调用printinfo函数
printinfo(name="jack")# 调用printinfo函数
运行结果结果:
Name: jack
Age 65
Name: jack
Age 20
A. D. 不定长参数
你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述参数不同,声明时不会命名。基本语法如下:
def functionname([formal_args,] var_args_tuple ):
function_suite
return[expression]
加了星号()的变量名会存放所有未命名的变量参数。选择不多传参数也可。实例:
def printinfo(arg1, *vartuple):
"打印任何传入的参数"
print("输出: ",arg1)
for var in vartuple:
print('输出不定长参数列表:',var)
调用printinfo 函数
printinfo(10)
printinfo(70, 60, 50)
运行结果:
输出: 10
输出: 70
输出不定长参数列表: 60
输出不定长参数列表: 50
变量的作用域
一个程序的所有的变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。 变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称。两种最基本的变量作用域:全局变量,局部变量。在Python中,也存在作用域这个问题。在Python中,会为每个层次生成一个符号表,里层能调用外层中的变量,而外层不能调用里层中的变量,并且当外层和里层有同名变量时,外层变量会被里层变量屏蔽掉。
实例:
def function():
i_tmp=6
count=2
while count>0:
i_tmp=3
print(i_tmp)
count=count-1
在函数function中,while循环外面和while循环里面都有变量i_tmp,此时,while循环外面的变量tmp会被屏蔽掉。注意在函数内部定义的变量作用域都仅限于函数内部,在函数外部是不能够调用的,一般称这种变量为局部变量。
还有一种变量叫做全局变量,它是在函数外部定义的,作用域是整个文件。全局变量可以直接在函数里面应用,但是如果要在函数内部改变全局变量,必须使用global关键字进行声明。
x = 2
def printx():
print(x)
def pringglobalx():
global x
x = 3
print(x)
printx ()
pringglobalx ()
print(x)
运行结果:
2
3
3
模块
一个完整大型的python程序是由模块和包的形式组织起来的,模块是处理某一类问题的函数和类的集合,由代码、函数和类组成。一个文件即是一个模块。每个模块文件是一个独立完备的命名空间,一个模块文件不能看到其他文件定义的变量名,除非它明确地导入了那个文件,模块文件起到了最小化命名冲突的作用。这里还涉及到包的概念,包是由一系列模块组成的集合。当不同作的模块进行按文件夹分类后再组成一个整体的库,可以称为包。为了让Python将目录当做内容包,目录中必须包含init.py文件,用于标识当前文件夹是一个包。最简单的情况下,只需要一个空的init.py文件即可。包就是一个完成特定任务的工具箱,包的作用是实现程序的重用。包导入会让模块扮演的角色更为明显,也使代码更具有可读性。
模块定义
Python 模块是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句,同时模块也是一个命名空间,从而避免了变量名称冲突的问题。模块我们就可以理解为lib库,如果需要使用某个模块中的函数或对象,则要导入这个模块才可以使用,除了系统默认的模块(内置函数)不需要导入外。Python模块需要使用import命令导入以后才能使用。
模块导入
本节主要介绍Python常用的3种导入形式,分别是常规导入,使用from语句导入和相对导入 和绝对导入。
A. A. 常规导入
常规导入应该是最常使用的导入方式,语法如下:
import module1[, module2[,… moduleN]
你只需要使用import一词,然后指定你希望导入的模块或包即可。通过这种方式导入的好处是可以一次性导入多个包或模块:
Import os
Import sys
Import time
或者
import os, sys, time
有时在导入模块时,你想要重命名这个模块。这个功能很容易实现:
import sys as system
print(system.platform)
上面的代码将导入的sys模块重命名为system。我们可以按照和以前一样的方式调用模块的方法,但是用了一个新的模块名。
A. B. 使用from语句导入
Python的from语句让你从模块中导入一个指定的部分到当前命名空间中。语法如下:from modname import name1[, name2[, ... nameN]]
由于这种方式不像常规导入,是直接导入某个函数,所以调用的时候不写模块名,有时会导致混淆,可以在导入函数时进行重命名:
实例:
from math import add
def add(a,b)
return a+b
上面程序中本地自定义一个函数add,和从Math库中导入函数add同名,会造成混淆和程序错误,所以我们对导入函数可以重新命名:
from math import add as new_add
new_add(1,2)
新函数和原函数拥有相同的函数签名和功能。
还有很多时候我们只想要导入一个模块或包中的某个特定功能的模块,这样会节约内存的使用.如我们只想使用Numpy库中的random功能,导入代码如下:
from numpy import random
Numpy是Python的一个科学计算的库,提供了矩阵运算的功能。上面这行代码可以让你直接调用random。如果你按常规方式导入random,那么你就要如下调用random:
Numpy.random.xxxx(*args)
你可以根据你实际的使用场景,选择相应的导入方式。在复杂的代码库中,能够看出某个函数是从哪里导入的这点很有用的。但是有时候为了提高程序效率,使模块化程度高,那么只从某个模块中导入一部分内容也是非常方便和简洁的。
还有一种方式是使用from方法导入模块的全部内容,就像这样:
from module import *
这种做法在少数情况下是挺方便的,但是这样也会打乱你的命名空间。前面有介绍过,你可能定义了一个与导入模块中名称相同的变量或函数,这时如果你试图使用导入模块中的同名变量或函数,实际使用的将是你自己定义的内容。因此,你最后可能会碰到一个相当让人困惑的逻辑错误。
A. C. 相对导入和绝对导入
绝对导入:按照sys.path顺序搜索,先主目录(sys.path中第一项''),然后PYTHONPATH环境变量、标准库路径、pth指定路径等。
相对导入:在模块所在同一个包内搜索,注意该包目录与主目录的区别
下面将具体介绍几种场景:
主程序与模块程序在同一目录下:
如下面程序结构:
-- src
|-- func_mod.py
|-- test.py
若在程序test.py中导入模块func_mod, 则直接使用import func_mod 或from func_mod import *;
主程序所在目录是模块所在目录的父(或祖辈)目录
如下面程序结构:
- src
|-- func_mod.py
|-- lib
| |-- func_second_mod.py
|-- test.py
若在程序test.py中导入模块func_second_mod, 需要在func_second_mod文件夹中建立空文件init.py文件(也可以在该文件中自定义输出模块接口);
变成文件夹结构如下: - src
|-- func_mod.py
|-- lib
| |-- init.py
| |-- func_second_mod.py
|-- test.py
然后使用 from lib.func_second_mod import * 或import lib.func_second_mod.
主程序导入上层目录中模块或其他目录(平级)下的模块
如下面程序结构:
-src
|-- func_mod.py
|-- lib
| |-- func_second_mod.py
|-- sub
| |-- test_second.py
|-- test.py
若在程序test_second.py中导入模块func_mod和func_second_mod。
首先需要在lib下建立init.py文件,src下不必建立该文件。test_second.py 中导入模块func_mod和func_second_mod用sys的调用方式如下:
import sys
sys.path.append("..")
import func_mod
import func_second_mod
sys.path.append导入模块关键是能够根据sys.path环境变量的值,找到具体模块的路径。
类
Python是一个面向对象的解释型语言,所以当然也有类的概念。 在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。面向对象的思想是一样的,但是python做为更高级的语言,在类的定义与使用更加简便。
类的定义
Python使用class关键字定义一个类,类名首字母一定是大写,开发人员定义类,然后用类去创建对象,下面是类的定义方式:
方法一:
class className:
block
方法二:
class className(object): #继承类
block
注意类名后面有冒号,在block块里面就可以定义属性和方法了,第二种方式是继承类,在python3.x中,object已经默认是所有类的基类,所以两种定义方式没有区别了。下面定义一个chinese类:
class Chinese:
country = 'China'
def__init__(self,name,color,age): #init初始化,只干初始化的活
# init方法一定不能有返回值
# if notisinstance(name,str):
# raise TypeError#触发异常
self.name=name
self.color=color
self.age=age
def talk(self):
print('istalking')
def eat(self):
print('is eating')
def sleep(self):
print('issleeping')
对象的创建
当一个类定义完之后,就产生了一个类对象。类对象支持两种操作:引用和实例化。引用操作是通过类对象去调用类中的属性或者方法,而实例化是产生出一个类对象的实例,称作实例对象。对象被创建后,包含3方面特性:对象的句柄,属性和方法。对象的句柄是为了区分不同的对象。
class Chinese:
country = 'China'
def init(self,name,color,age):#init初始化,只干初始化的活
# init方法一定不能有返回值
# if notisinstance(name,str):
# raise TypeError#触发异常
self.name=name
self.color=color
self.age=age
def talk(self):
print('is talking')
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
if name=="main":
jack=Chinese("jack",1111,20) #实例化
类的属性和方法
类的属性是对数据的封装,类的方法则表示对象的具有的行为。类通常由函数和变量组成。上面代码中country是一个属性,talk ( )是一个方法,与某个对象进行绑定的函数称作为方法。一般在类里面定义的函数与类对象或者实例对象绑定了,所以称作为方法;而在类外定义的函数一般没有同对象进行绑定,就称为函数。
• 属性
在类中我们可以定义一些属性,比如:
class Chinese:
country = 'China'
def__init__(self,name,color,age): #init初始化,只干初始化的活
# init方法一定不能有返回值
# if not isinstance(name,str):
# raise TypeError#触发异常
self.name=name
self.color=color
self.age=age
def talk(self):
print('istalking')
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
if name=="main":
jack=Chinese("jack",1111,20)
print(jack.country)
定义了一个Chinese类,里面定义了country属性,默认值为'China'。在定义了类之后,就可以用来产生实例化对象了,jack=Chinese("jack",1111,20,)实例化了一个对象jack,然后就可以通过country来读取属性了,Country属性是公有的,可以直接在类外通过对象名访问,如果想定义成私有的,则需在前面加2个下划线 ' __'。
class Chinese:
country = 'China'
def__init(self,name,color,age): #init初始化,只干初始化的活
# init方法一定不能有返回值
# if notisinstance(name,str):
# raise TypeError#触发异常
self.name=name
self.color=color
self.age=age
def talk(self):
print('istalking')
def eat(self):
print('is eating')
def sleep(self):
print('issleeping')
if name=="main":
jack=Chinese("jack",1111,20)
print(jack.__ country)
这段程序运行会报错:
Traceback (most recent call last):
File " xx.py",line 6, in
print(jack.__ country)
AttributeError: ' Chinese ' object has no attribute '__ country '
提示找不到该属性,因为python不能在类外通过对象直接访问私有属性。Python中没有像C++中public和private这些关键字来区别公有属性和私有属性,它是以属性命名方式来区分,如果在属性名前面加了2个下划线'__',则表明该属性是私有属性,否则为公有属性。
• 方法
可以根据需要在类中定义一些方法,定义方法采用def关键字,在类中定义的方法至少会有一个参数,,一般以名为'self'的变量作为该参数(用其他名称也可以),而且需要是当作第一个参数。下面看个实例:
class Chinese:
country = 'China'
def__init(self,name,color,age): #init初始化,只干初始化的活
# init方法一定不能有返回值
# if notisinstance(name,str):
#raise TypeError#触发异常
self.name=name
self.color=color
self.age=age
def talk(self):
print('istalking')
def eat(self):
print('is eating')
def sleep(self):
print('issleeping')
if name=="main":
jack=Chinese("jack",1111,20)
print(jack.eat())
print(jack.sleep())
运行结果:
is eating
is sleeping
如果对self不好理解的话,可以把它当做C++中类里面的this指针一样理解,就是对象自身的意思,在用某个对象调用该方法时,就将该对象作为第一个参数传递给self。
• 类内置的方法
在Python中有一些内置的方法,这些方法命名都有比较特殊的地方(其方法名以2个下划线开始然后以2个下划线结束)。类中最常用的就是构造方法和析构方法。构造方法init(self,....)在生成对象时调用,可以用来进行一些初始化操作,不需要显示去调用,系统会默认去执行。构造方法支持重载,如果用户自己没有重新定义构造方法,系统就自动执行默认的构造方法。
init():init方法在类的一个对象被建立时,马上运行。这个方法可以用来对你的对象做一些你希望的初始化。注意,这个名称的开始和结尾都是双下划线。
实例:
class Chinese:
country = 'China'
def__init(self,name,color,age): #init初始化,只干初始化的活
# init方法一定不能有返回值
# if notisinstance(name,str):
# raise TypeError#触发异常
self.name=name
self.color=color
self.age=age
def talk(self):
print('istalking')
def eat(self):
print('is eating')
def sleep(self):
print('issleeping')
if name=="main":
jack=Chinese("jack",1111,20,)
print(jack.eat())
print(jack.sleep())
运行结果:
is eating
is sleeping
说明:init方法定义为取三个参数name,color,age(以及普通的参数self)。程序中没有专门调用init方法,只是在创建一个类的新实例的时候,把参数包括在圆括号内跟在类名后面,从而传递给init方法。这是这种方法的重要之处。
new():new()在init()之前被调用,用于生成实例对象.利用这个方法和类属性的特性可以实现设计模式中的单例模式.单例模式设计的类只能实例化一个对象.
class Singleton(object):
__instance = None # 定义实例
def init(self):
pass
def __new__(cls, *args,**kwd): # 在__init__之前调用
ifSingleton.__instance is None: # 生成唯一实例
Singleton.__instance = object.__new__(cls, *args, **kwd)
returnSingleton.__instance
getattr()、setattr()和getattribute():当读取对象的某个属性时,python会自动调用getattr()方法.例如fruit.color将转换为fruit.getattr(color).当使用赋值语句对属性进行设置时,python会自动调用setattr()方法.getattribute()的功能与getattr()类似,用于获取属性的值.但是getattribute()能提供更好的控制,代码更健壮。
实例:
class Fruit(object):
def init(self,color="red", price=0):
self.__color = color
self.__price = price
def __getattribute__(self,name): # 获取属性的方法
returnobject.__getattribute__(self, name)
def __setattr__(self,name, value):
self.__dict__[name] =value
if name == "main":
fruit =Fruit("blue", 10)
print(fruit.dict.get("_Fruit__color")) # 获取color属性
fruit.dict["_Fruit__price"] = 5
print(fruit.dict.get("_Fruit__price")) # 获取price属性
结果:
blue
getitem():如果类把某个属性定义为序列,可以使用getitem()输出序列属性中的某个元素.假设水果店中销售多钟水果,可以通过getitem()方法获取水果店中的没种水果。
class FruitShop:
def getitem(self, i): # 获取水果店的水果
return self.fruits[i]
if name == "main":
shop = FruitShop()
shop.fruits = ["apple","banana"]
print(shop[1])
for item in shop: # 输出水果店的水果
print(item)
结果:
banana
apple banana
str():str()用于表示对象代表的含义,返回一个字符串.实现了str()方法后,可以直接使用print语句输出对象,也可以通过函数str()触发str()的执行.这样就把对象和字符串关联起来,便于某些程序的实现,可以用这个字符串来表示某个类
实例:
class Fruit:
'''Fruit类''' #为Fruit类定义了文档字符串
def str(self): # 定义对象的字符串表示
return self.doc
if name == "main":
fruit = Fruit()
print(str(fruit)) # 调用内置函数str()出发str()方法,输出结果为:Fruit类
print(fruit ) #直接输出对象fruit,返回str()方法的值,输出结果为:Fruit类
运行结果:
Fruit类
Fruit类
call():在类中实现call()方法,可以在对象创建时直接返回call()的内容.使用该方法可以模拟静态方法
实例:
class Fruit:
class Growth: # 内部类
def call(self):
print("grow...")
grow = Growth() # 调用Growth(),此时将类Growth作为函数返回,即为外部类Fruit定义方法grow(),grow()将执行__call__()内的代码
if name == 'main':
fruit = Fruit()
fruit.grow() # 输出结果:grow ...
Fruit.grow() # 输出结果:grow ...
结果:
grow ...
grow ...
析构方法del(self)在释放对象时调用,支持重载,可以在里面进行一些释放资源的操作,不需要显示调用。还有其他的一些内置方法:比如 cmp( ), len( )等。
类属性、实例属性、类方法、实例方法以及静态方法
前面的实例中我们接触到的是类属性,顾名思义,类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问。
class people:
name = 'jack' #公有的类属性
__age = 12 #私有的类属性
p = people()
print(p.name) #正确
print(people.name) #正确
print(p.__age) #错误,不能在类外通过实例对象访问私有的类属性
print(people.__age) #错误,不能在类外通过类对象访问私有的类属性
实例属性是不需要在类中显示定义的,比如:
class people:
name = 'jack'
p = people()
p.age =12
print(p.name) #正确
print(p.age) #正确
print(people.name) #正确
print(people.age) #错误
在类外对类对象people进行实例化之后,产生了一个实例对象p,然后p.age = 12这句给p添加了一个实例属性age,赋值为12。这个实例属性是实例对象p所特有的,注意,类对象people并不拥有它(所以不能通过类对象来访问这个age属性)。当然还可以在实例化对象的时候给age赋值。
class people:
name = 'jack'
#init()是内置的构造方法,在实例化对象时自动调用
def init(self,age):
self.age = age
p = people(12)
print(p.name) #正确
print(p.age) #正确
print(people.name) #正确
print(people.age) #错误
如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
class people:
country = 'china'
print(people.country)
p = people()
print(p.country)
p.country = 'japan'
print(p.country) # 实例属性会屏蔽掉同名的类属性
print(people.country)
del p.country # 删除实例属性
print(p.country)
运行结果:
china
china
japan
china
china
类方法:是类对象所拥有的方法,需要用修饰器"@classmethod"来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以"cls"作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。
class people:
country = 'china'
# 类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
p = people()
print(p.getCountry()) # 可以用过实例对象引用
print(people.getCountry()) # 可以通过类对象引用
运行结果:
china
china
类方法还有一个用途就是可以对类属性进行修改:
class people:
country = 'china'
# 类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
@classmethod
def setCountry(cls,country):
cls.country = country
p = people()
print(p.getCountry()) # 可以用过实例对象引用
print(people.getCountry()) # 可以通过类对象引用
p.setCountry('japan')
print(p.getCountry())
print(people.getCountry())
运行结果:
china
china
japan
japan
结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变。
实例方法:在类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以名为'self'的变量作为第一个参数(当然可以以其他名称的变量作为第一个参数)。在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用。
class people:
country = 'china'
# 实例方法
def getCountry(self):
return self.country
p = people()
print(p.getCountry()) # 正确,可以用过实例对象引用
print(people.getCountry()) # 错误,不能通过类对象引用实例方法
静态方法:需要通过修饰器"@staticmethod"来进行修饰,静态方法不需要多定义参数。
class people:
country = 'china'
@staticmethod
#静态方法
def getCountry():
return people.country
print(people.getCountry())
对于类属性和实例属性,如果在类方法中引用某个属性,该属性必定是类属性,而如果在实例方法中引用某个属性(不作更改),并且存在同名的类属性,此时若实例对象有该名称的实例属性,则实例属性会屏蔽类属性,即引用的是实例属性,若实例对象没有该名称的实例属性,则引用的是类属性;如果在实例方法更改某个属性,并且存在同名的类属性,此时若实例对象有该名称的实例属性,则修改的是实例属性,若实例对象没有该名称的实例属性,则会创建一个同名称的实例属性。想要修改类属性,如果在类外,可以通过类对象修改,如果在类里面,只有在类方法中进行修改。
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。
实例:
本例中用类的方法对Appium的driver进行封装
初始化的driver是Python操作Appium的核心,因此driver在整个代码中重用率是非常高的。新建driver.py文件,专门用来封装driver。代码如下:
from appium import webdriver
class AppiumAndroidTest:
def init(self):
desired_caps ={'platformName': 'Android',
'platformVersion': '5.0.2',
'deviceName': '5136b01e',
'appPackage': 'com.android.calculator2',
'appActivity': ' calculator '}
self.driver =webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
self.driver.implicitly_wait(30)
def get_driver(self):
return self.driver
技术解释:在AppiumAndroidTest类中,初始化函数包含driver的信息,然后在get_driver函数中直接把这个driver返回回去,测试用例中只要在测试类的初始化中调用它,就能获取driver实例。