《Selenium2自動化測試實戰基於Python語言》讀書筆記--第3章

二的平方發表於2018-05-02

第3章 Python基礎

由於作者寫的這本書完全是以Python語言為基礎的,所以需要讀者具備一定的Python程式設計能力。如果說最好的Python基礎教程,那應該說是《笨方法學Python》了。

3.1 Python哲學

Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:42:59) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
>>> 

翻譯為中文就是:

  • 優美勝於醜陋
  • 明瞭勝於晦澀
  • 簡單勝過複雜
  • 扁平勝於巢狀
  • 間隔勝於緊湊
  • 可讀性很重要
  • 即使假錯特例的實用性之名,也不違背這些規則
  • 雖然實用性次於純度
  • 錯誤不應該被無聲的忽略
  • 除非明確的沉默
  • 當存在多種可能時,不要嘗試去猜測
  • 應該有一個,最好只有一個,很明顯可以做到這一點
  • 雖然這種方式可能不容易,除非你是Python之父
  • 現在做總比不做好
  • 雖然過期從未比現在好
  • 如果這個實現不容易解釋,那麼它肯定是壞主意
  • 如果這個實現容易解釋,那麼它很可能是個好主意
  • 名稱空間是一種絕妙的理念,應當多加利用

3.2 輸出與輸入

3.2.1 print列印

Python提供print()方法來列印資訊:

>>>print ("hello python")
hello python

列印變數:

>>> name = "zhangsan"
>>> print ("hello %s , Nice to meet you!" % name)
hello zhangsan , Nice to meet you!
>>> name = "Lisi"
>>> print ("hello %s , Nice to meet you!" % name)
hello Lisi , Nice to meet you!

%s 只能列印字串,如果想要列印陣列,需要使用%d:

>>> age = 27
>>> print ("You are %d !" % age)
You are 27 !

如果不知道自己列印的型別,可以使用%r:

>>> age = 27
>>> print ("You are %d !" % age)
You are 27 !
>>> 
>>> n =100
>>> print ("You print is %r ." % n)
You print is 100 .
>>> n = "abc"
>>> print ("You print is %r ." % n)
You print is 'abc' .
>>> name = "zhangsan"
>>> age = 22
>>> print ("student info: %s %d ." % (name, age))
student info: zhangsan 22 .

3.2.2 input輸入

如果希望列印的是程式執行過程中由使用者來決定的內容,可以使用input()方法:

n = input("Enter any content: ")
print "Your input is %r" % n

執行上述指令碼時,使用者輸入的資訊就會被列印出來。

3.2.3 引號與註釋

Python不區分單引號、雙引號,都可以用來表示一個字串:

>>> print ("hello")
hello
>>> print ('hello')
hello

單引號和雙引號可以相互巢狀使用,但不能交叉使用:

>>> print ("你說:'早上你好'")
你說:'早上你好'
>>> print ('我說:"今天天氣不錯"')
我說:"今天天氣不錯"
>>> print ("你微笑著'向我道別"')

SyntaxError: invalid syntax

Python的單行註釋使用“#”表示:

>>>#單行註釋
>>>print ("hello world") #列印hello world
hello world

多行註釋使用三對引號表示,同樣不區分單、雙引號:

"""
我們實現一個偉大的程式
那麼是
print 一行資料 ^_^
"""
'''
This is a
Multi line comment
'''
print ("Hello World")

3.3 分支與迴圈

結構化程式實質上是由有限個順序、分支和迴圈三種基本結構排列、巢狀而成。

3.3.1 if語句

if實現分支判斷,一般語法為:if…else…

>>>a = 2
>>>b = 3
>>>if a > b:
    print ("a max!")
else:
    print ("b max!")

b max!

注意: Python沒有像其他語言一樣使用“{}”表示語句體,而是通過語句的縮排來判斷語句體,縮排預設為4個空格。

if語句通過“==”運算子判斷相等:

>>>student = "xiaoming"
>>>if student == "xiaoming":
    print ("xiaoming, You are on duty today.")
else:
    print ("Please call xiaoming to duty")

xiaoming, You are on duty today.

if語句通過“!=”運算子判斷不相等;

if語句還可以使用“in”、“not in”表示包含關係:

>>>hi = "hello world"
>>>if "hello" in hi:
    print ("Contain")
else:
    print ("Not Contain")

Contain

if語句還可以進行布林型別的判斷:

>>>a = True
>>>if a:
    print ("a is True")
else:
    print ("a is not True")

a is True

練習:

results = 72

if results >= 90:
    print ("優秀")
elif results >= 70:
    print ("良好")
elif results >= 60:
    print ("及格")
else:
    print ("不及格")

答案:良好。

3.3.2 for語句

Python提供了兩種迴圈:while迴圈、for迴圈。

for迴圈的使用更加靈活、簡單,可以直接對一個字串進行遍歷:

>>> for i in "hello world":
    print (i)


h
e
l
l
o

w
o
r
l
d

對一個字典進行遍歷:

>>> fruits = ['banana', 'apple', 'mango']
>>> for fruit in fruits:
    print fruit


banana
apple
mango

使用range()函式來進行指定次數的迴圈:

>>> for i in range(5):
    print (i)


0
1
2
3
4

注意: range()函式預設從0開始迴圈。

指定起始位置和步長的迴圈:

>>> for i in range(1, 10, 2):
    print (i)


1
3
5
7
9

range(start, end[, step])
start表示開始位置、end表示結束位置、step表示每一次迴圈的步長。

python2:range() 是一個生成器,xrange()是一個陣列,功能完全一樣,效能後者優於前者;
python3:range()和xrange()相同,都是一個陣列。

3.4 陣列與字典

3.4.1 陣列

陣列:用方括號[]表示,裡面的每一項用逗號“,”分隔。

>>> lists = [1, 2, 3, 'a', 5]
>>> lists
[1, 2, 3, 'a', 5]
>>> lists[0]
1
>>> lists[4]
5
>>> lists[4] = 'b'
>>> lists[4]
'b'
>>> lists.append('c')
>>> lists
[1, 2, 3, 'a', 'b', 'c']

注意:

  • Python允許在陣列裡任意地防止數字或字串
  • 陣列下標是從0開始
  • append()函式可以向陣列末尾追加新項

3.4.2 字典

字典:用花括號{}表示,裡面的項是成對出現,一個key對應一個value,key與value之間用冒號分隔,不同的項之間用逗號“,”分隔。

>>> dicts = {"username":"zhangsan",'password':123456}
>>> dicts.keys()
['username', 'password']
>>> dicts.values()
['zhangsan', 123456]
>>> dicts.items()
[('username', 'zhangsan'), ('password', 123456)]
>>> for k,v in dicts.items():
    print ("dicts keys is %r" % k)
    print ("dicts values is %r" % v)


dicts keys is 'username'
dicts values is 'zhangsan'
dicts keys is 'password'
dicts values is 123456

注意: Python規定一個字典中的key必須獨一無二,valuekey相同。

  • keys()函式:返回字典key的列表
  • values()函式:返回字典value的列表
  • items()函式:將所有的字典項以列表方式返回

按存放的順序輸出字典:

# 通過zip方法合併兩個List為Dictionary
# 遍歷會按原先的順序
keys = ["b", "a", "c", "e", "d"]
values = ["2", "1", "3", "5", "4"]

for key,value in zip(keys, values):
    print (key, value)

輸出結果:
b 2
a 1
c 3
e 5
d 4

3.5 函式、類和方法

3.5.1 函式

Python通過def關鍵字來定義函式:

>>> def add(a, b):
    print (a + b)


>>> add(3, 5)
8

通常函式不會直接列印結果,而是將結果通過return關鍵字返回:

>>>def add(a, b):
    return a + b
>>>add(3, 5)
8

如果在呼叫函式時不想傳參,可以為函式設定預設引數:

>>> def add(a=1, b=2):
    return a + b

>>> add()
3
>>> add(3, 5)
8

3.5.2 類和方法

在物件導向程式設計的世界裡,一切皆為物件,抽象的一組物件就是類。

eg:汽車是一個類,而張三家的奇瑞汽車就是一個具體的物件。

Python使用class關鍵字來建立類。

class A(object):

    def add(self, a, b):
        return a + b

count = A()
print (count.add(3, 5))

輸出結果為:8

對上述程式碼分析:

  • 建立一個類A(在Python 3中object為所有類的基類,所有類在建立時預設繼承object,所以不宣告繼承object也可以)
  • 在類下面建立一個add()方法,注意,方法的第一個引數必須是存在的,一般習慣命名為self,但在呼叫這個方法時不需要為這個引數傳值

一般在建立類時會首先宣告初始化方法__init__(),注意,init的兩側是雙下劃線,當我們在呼叫該類時,可以用來進行一下初始化工作:

class A(object):

    def __init__(self, a, b):
        self.a = int(a)
        self.b = int(b)

    def add(self):
        return self.a + self.b

count = A('4', 5)
print (count.add())

輸出結果為:9

對上述程式碼分析:

  • 當呼叫A類時首先會執行它的__init__()方法,所以需要對其進行傳參
  • 初始化的工作就是將輸入的引數型別轉化為int型別,保證程式容錯性
  • add()方法可以直接拿初始化方法的self.a和self.b兩個數進行計算
  • 所以在呼叫A類下面的add()方法時,不需要再傳參

Python中類的繼承:

class A():

    def add(self, a, b):
        return a + b

class B(A):

    def sub(self, a, b):
        return a - b

print (B().add(4, 5))

輸出結果為:9

對上述程式碼分析:

  • 建立一個A類,在下面建立add()方法用於計算兩個引數相加
  • 建立一個B類,繼承A類,並且又繼續建立sub()方法用於計算兩個引數相減
  • 因為B類繼承了A類,所以B類自然也擁有了add()方法,從而可以直接通過B類呼叫add()方法

3.6 模組

模組=類庫、模組。

3.6.1 引用模組

通過:import…或者from…import…的方式引用模組,eg:

>>> import time
>>> print time.ctime()
Mon Mar 19 16:20:26 2018

如果確定只需要使用time下面的ctime()方法,還可以這樣引用,eg:

>>> from time import ctime
>>> print ctime()
Mon Mar 19 16:22:00 2018

現在使用時就不必告訴Python,ctime()方法是time模組所提供的了。
但是實際情況,可能是我們還需要使用time模組下的sleep()方法,我們也可以這樣引入進來,但是還有其他方法呢,所以我們可以一次性把time模組下的所有方法都引入進來,eg:

from time import *

print ctime()
print "休眠兩秒"
sleep(2)
print ctime()

最終輸出結果是:

Mon Mar 19 16:46:22 2018
休眠兩秒
Mon Mar 19 16:46:24 2018

其中,星號(*),代表了模組下的所有方法。通過匯入模組後,可以使用help檢視模組的doc,eg:

import time
help(time)

pip所安裝的Python第三方庫或框架,都可以使用這種方式來檢視。

3.6.2 模組呼叫

一個軟體專案不可能把所有程式碼都放在一個檔案中實現,一般會按照一定規則在不同的目錄和檔案中實現。

建立一個目錄project,並在目錄下建立兩個檔案,結構如下:

project/
    |--pub.py
    |--count.py

在pub.py檔案中建立add函式:

def add(a, b):
    return a + b

在count.py檔案中呼叫pub.py檔案中的add()函式:

from pub import add

print add(4, 5)

輸出結果為:9

這樣就實現了跨檔案的函式呼叫。

注意: 以下情況會在你使用Python3.5版本中出現,在project目錄下,多了一個__pycache__/pub.cpython-35.pyc檔案,它的作用是為了提高模組載入的速度,每個模組都會在__pycache__資料夾中放置該模組的預編譯模組,命名為module.version.pyc,version是模組的預編譯版本編碼,通常會包含Python的版本號,例如在CPython發行版3.5中,pub.py檔案的預編譯檔案就是:__pycache__/pub.cpython-35.pyc。

3.6.3 跨目錄模組呼叫

如果呼叫檔案與被呼叫檔案在一個目錄下面,那麼可以使用上面的方法,但是如果不在同一個目錄下,假設檔案目錄結構如下:

project/
    |--model/
        |--pub.py
    |--count.py

如果還還想在count.py中呼叫add方法,則需要將目錄的完整路徑補全,eg:

from model.pub import add

print add(4, 5)

執行結果,依然是:9

3.6.4 進一步討論跨目錄模組呼叫

目錄結構更加複雜的情況:

project/
    |--model/
        |--count.py
        |--new_count.py
    |--test.py

count.py程式碼如下:

class A():

    def add(self, a, b):
        return a + b

new_count.py程式碼如下:

from count import A

class B(A):

    def sub(self, a, b):
        return a - b
resule = B().add(2, 5)
print resule

輸出結果:7

上述結果沒問題,接下來與model目錄平級建立test.py檔案:

from model import new_count

test = new_count()
test.add(2, 5)

輸出結果,會出現:ImportError: No module named 'count'

通過提示資訊,在new_count.py檔案中,找不到“count”模組,可剛才在執行new_count.py時是可以正常執行的,那麼,要想弄清楚錯誤的原因,首先要知道當Python執行import時到底做了哪些操作?

重點:

當Python在執行import語句時,執行了如下操作:

  • 第1步:建立一個新的module物件(它可能包含多個module);

  • 第2步:把這個module物件插到sys.module中;

  • 第3步:裝載module的程式碼(如果需要,則必須先編譯);

  • 第4步:執行新的module中對應的程式碼;

在執行第3步的時候,首先需要找到module程式所在的位置,搜尋的順序是:

當前路徑(以及從當前目錄指定的sys.path),PythonPATH,再後是Python安裝時設定的相關的預設路徑。正因為存在這樣的順序,所以如果當前路徑或PythonPATH中存在於標準module同樣的module,則會覆蓋標準module。也就是說,如果當前目錄下存在xml.py,那麼在執行import xml時匯入的就是當前目錄下的module,而不是系統標準的xml。

瞭解了這些後,我們就可以先構建一個package,以普通module的方式匯入,這樣即可直接訪問此package中各個module,注意,Python2中的package必須包含一個__init__.py的檔案。

所以上面問題的錯誤原因就是:

站在new_count.py的位置,執行from count import A,可檢視當前目錄下是否存在“count”名字的檔案或目錄,是可以找到的,但是站在test.py的位置,執行from count import A時,在當前目錄下是找不到“count”名字的檔案或目錄的,所以丟擲異常。

解決辦法:

最簡單的辦法就是將匯入方法修改為from .count import A,在“count”前面加個點,用來告訴程式count是相對於new_content.py的一個匯入。

上面解決辦法引入的新的問題:

修改new_count.py程式碼如下:

from .count import A

class B(A):

    def sub(self, a, b):
        return a - b

result = B().add(2, 5)
print result

當再次執行test.py程式碼時,出現了新的錯誤:

Traceback (most recent call last):
  File "/tmp/project/model/new_count.py", line 1, in <module>
    from .count import A
[Finished in 0.1s with exit code 1]SystemError: Parent module '' not loaded, cannot perform relative import

這句話的意思是:“未載入父模組,不能執行相對匯入”。

解決辦法:

需要將匯入模組所在目錄新增到系統環境變數PATH目錄下才可以。

修改test.py的程式碼:

import sys
sys.path.append("./model")  #將model目錄新增到系統環境變數path下
from model import new_count

test = new_count()
test.add(2, 5)

輸出結果:7,注意如果是python2版本需要在model目錄下建立init檔案才行。

3.7 異常

Python用異常物件(exception object)來表示異常情況。遇到錯誤後,會引發異常,如果異常物件並未被處理或者捕捉,則程式就會用所謂的回溯(Traceback,一種錯誤資訊)來終止執行。

3.7.1 認識異常

看下面的程式碼:

>>> open("abc.txt", 'r')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'abc.txt'

錯誤很明顯,我們根本沒建立這個檔案,自然也無法open操作。我們可以視線捕捉這個異常:Python通過try…except…語句來接收並處理這個異常。

>>> try:
...     open("abc.txt", "r")
... except IOError:
...     print "異常了"
... 
異常了

注意: 我們這裡使用的仍然是Python2版本,所以和書中的Python3版本略有不同,以下都是按Python2版本進行演示的。

再來看下面的例子:

>>> try:
...     print aa
... except IOError:
...     print "異常了"
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
NameError: name 'aa' is not defined

上面的例子,雖然捕捉異常,但是由於捕捉的異常和實際異常的錯誤型別並不相符,所以導致失效。

只需要更換一個正確的接收異常的型別就可以了:

>>> try:
...     print aa
... except NameError:
...     print "異常了"
... 
異常了

那麼,程式是怎麼丟擲不同型別的錯誤呢?

異常的丟擲機制:

  1. 如果在執行時發生異常,則直譯器會查詢相應的處理語句(稱為handler);
  2. 如果在當前函式裡沒有找到的話,則它會將異常傳遞給上層的呼叫函式,看看那裡能不能處理;
  3. 如果在最外層(全域性main)還是沒有找到的話,那麼直譯器就會退出,同時列印出Traceback,以便讓使用者找到錯誤產生的原因。

注意: 異常不一定等於錯誤,有時候它只是一個警告,有時候它只是一個終止訊號,例如退出迴圈等。

Python中所有異常都整合Exception,所以可以使用它來接收所有型別的異常:

try:
    open("abc.txt", "r")
except Exception:
    print "異常了"

異常了

從Python 2.5之後,所有的異常類都有了新的基類BaseException,Exception同樣也繼承自BaseException,所以我們可以使用BaseException來接收所有型別的異常:

>>> try:
...     open("abc.txt", "r")
...     print aa
... except BaseException:
...     print "異常了"
... 
異常了

對於這個例子來說,只要其中一行出現了異常就會列印出異常資訊,但是如何才能準確知道是哪一行程式碼引起的異常呢,又如何讓Python直接告訴我們異常的原因呢?

>>> try:
...     open("abc.txt", "r")
...     print aa
... except BaseException, e:
...     print e
... 
[Errno 2] No such file or directory: 'abc.txt'

這樣就可以列印出異常的詳細資訊了。

注意: Python 2中使用“, e”這種方式來捕捉異常詳細資訊,而Python 3中使用“as msg”的方式,這是兩者的區別。

Python常見的異常

異常 描述
BaseException 新的所有異常類的基類
Exception 所有異常類的基類,但繼承BaseException類
AssertionError assert語句失敗
FileNotFoundError 試圖開啟一個不存在的檔案或目錄
AttributeError 試圖訪問的物件沒有屬性
OSError 當系統函式返回一個系統相關的錯誤,包括I/O故障,如“找不到檔案”或“磁碟已滿”時,引發此異常
NameError 使用一個還未賦值物件的變數
IndexError 當一個序列超出了範圍
SyntaxError 當解析器遇到一個語法錯誤時引發
KeyboardInterrupt Ctrl+C被按下,程式被強行終止
TypeError 傳入的物件型別與要求不符

3.7.2 更多異常用法

異常的更多用法:try...except...else

try:
    aa = "異常測試:"
    print aa
except Exception as msg:
    print msg
else:
    print "沒有異常!"

輸出結果:

異常測試:
沒有異常!

上面程式碼是因為沒有異常所以執行了else,但是例如檔案的關閉、鎖的釋放、把資料庫連線返還給連線池等操作,這些操作無論是否異常都希望可以被執行,就需要用到:try...except...finally...

try:
    print aa
except Exception as e:
    print e
finally:
    print "不管是否異常,我都會被執行。"

輸出結果:

name 'aa' is not defined
不管是否異常,我都會被執行。

修改上述程式碼,定義aa變數:

try:
    aa = "異常測試:"
    print aa
except Exception as e:
    print e
finally:
    print "不管是否異常,我都會被執行。"

輸出結果:

異常測試:
不管是否異常,我都會被執行。

3.7.3 丟擲異常

print方法只是列印異常,而raise方法可以丟擲一個異常資訊。

from random import randint

# 生成一個1到9之間的隨機整數
number = randint(1, 9)

if number % 2 == 0:
    raise NameError("%d is even" % number)
else:
    raise NameError("%d is odd" % number)

輸出結果:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
NameError: 3 is odd

判斷奇偶數與NameError之間沒有任何關係,這裡只是為了演示如何通過raise方法丟擲各種型別的異常。

注意: raise只能使用Python中所提供的異常類,如果自定義一個abcError,會提示錯誤abcError未定義。

總結

本章內容只是Python語法的入門的入門,如果想要開始後面的教程,還需要有Python基礎,而最佳快速入門的就是《笨方法學Python》了,有興趣的同學可以搜尋並學習。

相關文章