1. 程式人生 > >Python學習筆記- 廖雪峰教程【python 2】//為繼續學習爬蟲準備-02 [待完善]

Python學習筆記- 廖雪峰教程【python 2】//為繼續學習爬蟲準備-02 [待完善]

裝飾器的作用: 1 簡化程式碼,避免每個函式編寫重複性程式碼:列印日誌@log,檢測效能 @performance,資料庫事務 @transaction,URL路由@post('/register')
import time

def performance(f):
    def fn(*args, **kw):
        t1 = time.time()
        r = f(*args, **kw)
        t2 = time.time()
        print 'call %s() in %fs'%(f.__name__,(t2 - t1))
        return r
    return fn

@performance
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))

print factorial(10) 


模組和包的概念 將所有程式碼放入一個py檔案:無法維護 如果將程式碼分拆放入多個py檔案,好處:1. 同一個名字的變數互不影響  模組多了可能重名,不過只要放到不同的包下面就可以解決   在檔案系統中,包就是資料夾,模組就是xxx.py檔案  區分目錄和包檔案:包資料夾下一定有__init__.py 每層都要有,即使是一個空檔案 

如果使用 from...import 匯入 log 函式,勢必引起衝突。這時,可以給函式起個“別名”來避免衝突:

from math import log
from logging import log as logger   # logging的log現在變成了logger

print log(10)   # 呼叫的是math的log
logger(10, 'import from logging')   # 呼叫的是logging的log

動態匯入模組

如果匯入的模組不存在,Python直譯器會報 ImportError 錯誤:

>>> import something
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named something

有的時候,兩個不同的模組提供了相同的功能,比如 StringIO 

和 cStringIO 都提供了StringIO這個功能。

這是因為Python是動態語言,解釋執行,因此Python程式碼執行速度慢。

如果要提高Python程式碼的執行速度,最簡單的方法是把某些關鍵函式用 C 語言重寫,這樣就能大大提高執行速度。

同樣的功能,StringIO 是純Python程式碼編寫的,而 cStringIO 部分函式是 寫的,因此 cStringIO 執行速度更快。

利用ImportError錯誤,我們經常在Python中動態匯入模組:

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

上述程式碼先嚐試從cStringIO匯入,如果失敗了(比如cStringIO沒有被安裝),再嘗試從StringIO匯入。這樣,如果cStringIO模組存在,則我們將獲得更快的執行速度,如果cStringIO不存在,則頂多程式碼執行速度會變慢,但不會影響程式碼的正常執行。

try 的作用是捕獲錯誤,並在捕獲到指定錯誤時執行 except 語句。

任務

利用import ... as ...,還可以動態匯入不同名稱的模組。

Python 2.6/2.7提供了json 模組,但Python 2.5以及更早版本沒有json模組,不過可以安裝一個simplejson模組,這兩個模組提供的函式簽名和功能都一模一樣。

試寫出匯入json 模組的程式碼,能在Python 2.5/2.6/2.7都正常執行。  try:     import json except ImportError:     import simplejson as json print json.dumps({'python':2.7})

使用__future__

Python的新版本會引入新的功能,但是,實際上這些功能在上一個老版本中就已經存在了。要“試用”某一新的特性,就可以通過匯入__future__模組的某些功能來實現。

例如,Python 2.7的整數除法運算結果仍是整數:

>>> 10 / 3
3

但是,Python 3.x已經改進了整數的除法運算,“/”除將得到浮點數,“//”除才仍是整數:

>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3

要在Python 2.7中引入3.x的除法規則,匯入__future__division

>>> from __future__ import division
>>> print 10 / 3
3.3333333333333335
當新版本的一個特性與舊版本不相容時,該特性將會在舊版本中新增到__future__中,以便舊的程式碼能在舊版本中測試新特性。 python提供的模組管理功能 -easy_install, -pip  面向物件程式設計的基本思想:類和例項  面向物件程式設計:資料封裝 

定義類並建立例項

在Python中,類通過 class 關鍵字定義。以 Person 為例,定義一個Person類如下:

class Person(object):
    pass

按照 Python 的程式設計習慣,類名以大寫字母開頭,緊接著是(object),表示該類是從哪個類繼承下來的。類的繼承將在後面的章節講解,現在我們只需要簡單地從object類繼承。

有了Person類的定義,就可以創建出具體的xiaoming、xiaohong等例項。建立例項使用 類名+(),類似函式呼叫的形式建立:

xiaoming = Person()
xiaohong = Person()
class Person(object):
    pass

p1 = Person()
p1.name = 'Bart'

p2 = Person()
p2.name = 'Adam'

p3 = Person()
p3.name = 'Lisa'

L1 = [p1, p2, p3]
L2 = sorted(L1,key=lambda x:x.name)

print L2[0].name
print L2[1].name
print L2[2].name 


初始化例項屬性

雖然我們可以自由地給一個例項繫結各種屬性,但是,現實世界中,一種型別的例項應該擁有相同名字的屬性。例如,Person類應該在建立的時候就擁有 name、gender 和 birth 屬性,怎麼辦?

在定義 Person 類時,可以為Person類新增一個特殊的__init__()方法,當建立例項時,__init__()方法被自動呼叫,我們就能在此為每個例項都統一加上以下屬性:

class Person(object):
    def __init__(self, name, gender, birth):
        self.name = name
        self.gender = gender
        self.birth = birth

__init__() 方法的第一個引數必須是 self(也可以用別的名字,但建議使用習慣用法),後續引數則可以自由指定,和定義函式沒有任何區別。

相應地,建立例項時,就必須要提供除 self 以外的引數:

xiaoming = Person('Xiao Ming', 'Male', '1991-1-1')
xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')

有了__init__()方法,每個Person例項在建立時,都會有 name、gender 和 birth 這3個屬性,並且,被賦予不同的屬性值,訪問屬性使用.操作符:

print xiaoming.name
# 輸出 'Xiao Ming'
print xiaohong.birth
# 輸出 '1992-2-2'

要特別注意的是,初學者定義__init__()方法常常忘記了 self 引數:

>>> class Person(object):
...     def __init__(name, gender, birth):
...         pass
... 
>>> xiaoming = Person('Xiao Ming', 'Male', '1990-1-1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 3 arguments (4 given)
這會導致建立失敗或執行不正常,因為第一個引數name被Python直譯器傳入了例項的引用,從而導致整個方法的呼叫引數位置全部沒有對上。

要定義關鍵字引數,使用 **kw

除了可以直接使用self.name = 'xxx'設定一個屬性外,還可以通過 setattr(self, 'name', 'xxx') 設定屬性。 class Person(object):     def __init__(self, name, gender, birth, **kw):         self.name = name         self.gender = gender         self.birth = birth         for k,v in kw.iteritems():             setattr(self,k,v) xiaoming = Person('Xiao Ming', 'Male', '1990-1-1', job='Student') print xiaoming.name

訪問限制

我們可以給一個例項繫結很多屬性,如果有些屬性不希望被外部訪問到怎麼辦?

Python對屬性許可權的控制是通過屬性名來實現的,如果一個屬性由雙下劃線開頭(__),該屬性就無法被外部訪問。看例子:

class Person(object):
    def __init__(self, name):
        self.name = name
        self._title = 'Mr'
        self.__job = 'Student'
p = Person('Bob')
print p.name
# => Bob
print p._title
# => Mr
print p.__job
# => Error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__job'
可見,只有以雙下劃線開頭的"__job"不能直接被外部訪問。

但是,如果一個屬性以"__xxx__"的形式定義,那它又可以被外部訪問了,以"__xxx__"定義的屬性在Python的類中被稱為特殊屬性,有很多預定義的特殊屬性可以使用,通常我們不要把普通屬性用"__xxx__"定義。

單下劃線開頭的屬性"_xxx"雖然也可以被外部訪問,但是,按照習慣,他們不應該被外部訪問。 class Person(object):     count = 0     def __init__(self,name):         self.name = name         Person.count = Person.count + 1 p1 = Person('Bob') print Person.count p2 = Person('Alice') print Person.count p3 = Person('Tim') print Person.count

方法也是屬性

我們在 class 中定義的例項方法其實也是屬性,它實際上是一個函式物件:

class Person(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def get_grade(self):
        return 'A'

p1 = Person('Bob', 90)
print p1.get_grade
# => <bound method Person.get_grade of <__main__.Person object at 0x109e58510>>
print p1.get_grade()
# => A
也就是說,p1.get_grade 返回的是一個函式物件,但這個函式是一個繫結到例項的函式,p1.get_grade() 才是方法呼叫。

定義類方法

和屬性類似,方法也分例項方法類方法

class中定義的全部是例項方法,例項方法第一個引數 self 是例項本身。

要在class中定義類方法,需要這麼寫:

class Person(object):
    count = 0
    @classmethod
    def how_many(cls):
        return cls.count
    def __init__(self, name):
        self.name = name
        Person.count = Person.count + 1

print Person.how_many()
p1 = Person('Bob')
print Person.how_many()

通過標記一個 @classmethod,該方法將繫結到 Person 類上,而非類的例項。類方法的第一個引數將傳入類本身,通常將引數名命名為 cls,上面的 cls.count 實際上相當於 Person.count

因為是在類上呼叫,而非例項上呼叫,因此類方法無法獲得任何例項變數,只能獲得類的引用。 class Person(object):     __count = 0     def __init__(self, name):         self.name = name         Person.__count = Person.__count + 1     @classmethod     def how_many(cls):         return cls.__count print Person.how_many() p1 = Person('Bob') print Person.how_many() python的繼承:總是從某個類繼承,不要忘記呼叫super().__init__ def __init__(self, args):      super(SubClass, self).__init__(args)      pass

繼承一個類

如果已經定義了Person類,需要定義新的StudentTeacher類時,可以直接從Person類繼承:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

定義Student類時,只需要把額外的屬性加上,例如score:

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

一定要用 super(Student, self).__init__(name, gender) 去初始化父類,否則,繼承自 Person 的 Student 將沒有 name 和 gender

函式super(Student, self)將返回當前類繼承的父類,即 Person ,然後呼叫__init__()方法,注意self引數已在super()中傳入,在__init__()中將隱式傳遞,不需要寫出(也不能寫)

判斷型別

函式isinstance()可以判斷一個變數的型別,既可以用在Python內建的資料型別如str、list、dict,也可以用在我們自定義的類,它們本質上都是資料型別。

假設有如下的 Person、Student 和 Teacher 的定義及繼承關係如下:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

class Teacher(Person):
    def __init__(self, name, gender, course):
        super(Teacher, self).__init__(name, gender)
        self.course = course

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')

當我們拿到變數 p、s、t 時,可以使用 isinstance 判斷型別:

>>> isinstance(p, Person)
True    # p是Person型別
>>> isinstance(p, Student)
False   # p不是Student型別
>>> isinstance(p, Teacher)
False   # p不是Teacher型別

這說明在繼承鏈上,一個父類的例項不能是子類型別,因為子類比父類多了一些屬性和方法。

我們再考察 s 

>>> isinstance(s, Person)
True    # s是Person型別
>>> isinstance(s, Student)
True    # s是Student型別
>>> isinstance(s, Teacher)
False   # s不是Teacher型別

s 是Student型別,不是Teacher型別,這很容易理解。但是,s 也是Person型別,因為Student繼承自Person,雖然它比Person多了一些屬性和方法,但是,把 s 看成Person的例項也是可以的。

這說明在一條繼承鏈上,一個例項可以看成它本身的型別,也可以看成它父類的型別。

多型

類具有繼承關係,並且子類型別可以向上轉型看做父類型別,如果我們從 Person 派生出 StudentTeacher ,並都寫了一個 whoAmI() 方法:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def whoAmI(self):
        return 'I am a Person, my name is %s' % self.name

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score
    def whoAmI(self):
        return 'I am a Student, my name is %s' % self.name

class Teacher(Person):
    def __init__(self, name, gender, course):
        super(Teacher, self).__init__(name, gender)
        self.course = course
    def whoAmI(self):
        return 'I am a Teacher, my name is %s' % self.name

在一個函式中,如果我們接收一個變數 x,則無論該  Person、Student還是 Teacher,都可以正確打印出結果:

def who_am_i(x):
    print x.whoAmI()

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')

who_am_i(p)
who_am_i(s)
who_am_i(t)

執行結果:

I am a Person, my name is Tim
I am a Student, my name is Bob
I am a Teacher, my name is Alice

這種行為稱為多型。也就是說,方法呼叫將作用在 x 的實際型別上。s 是Student型別,它實際上擁有自己的 whoAmI()方法以及從 Person繼承的 whoAmI方法,但呼叫 s.whoAmI()總是先查詢它自身的定義,如果沒有定義,則順著繼承鏈向上查詢,直到在某個父類中找到為止。

由於Python是動態語言,所以,傳遞給函式 who_am_i(x)的引數 x不一定是 Person 或 Person 的子型別。任何資料型別的例項都可以,只要它有一個whoAmI()的方法即可:

class Book(object):
    def whoAmI(self):
        return 'I am a book'
這是動態語言和靜態語言(例如Java)最大的差別之一。動態語言呼叫例項方法,不檢查型別,只要方法存在,引數正確,就可以呼叫。

ython提供了open()函式來開啟一個磁碟檔案,並返回 File 物件。File物件有一個read()方法可以讀取檔案內容:

例如,從檔案讀取內容並解析為JSON結果:

import json
f = open('/path/to/file.json', 'r')
print json.load(f)
由於Python的動態特性,json.load()並不一定要從一個File物件讀取內容。任何物件,只要有read()方法,就稱為File-like Object,都可以傳給json.load()

多重繼承

除了從一個父類繼承外,Python允許從多個父類繼承,稱為多重繼承。

多重繼承的繼承鏈就不是一棵樹了,它像這樣:

class A(object):
    def __init__(self, a):
        print 'init A...'
        self.a = a

class B(A):
    def __init__(self, a):
        super(B, self).__init__(a)
        print 'init B...'

class C(A):
    def __init__(self, a):
        super(C, self).__init__(a)
        print 'init C...'

class D(B, C):
    def __init__(self, a):
        super(D, self).__init__(a)
        print 'init D...'

像這樣,同時繼承自 B 和 C,也就是 D 擁有了 A、B、C的全部功能。多重繼承通過 super()呼叫__init__()方法時,A 雖然被繼承了兩次,但__init__()只調用一次:

>>> d = D('d')
init A...
init C...
init B...
init D...

多重繼承的目的是從兩種繼承樹中分別選擇並繼承出子類,以便組合功能使用。

舉個例子,Python的網路伺服器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而伺服器執行模式有 多程序ForkingMixin 和 多執行緒ThreadingMixin兩種。

要建立多程序模式的 TCPServer

class MyTCPServer(TCPServer, ForkingMixin)
    pass

要建立多執行緒模式的 UDPServer

class MyUDPServer(UDPServer, ThreadingMixin):
    pass
如果沒有多重繼承,要實現上述所有可能的組合需要 4x2=8 個子類。 正確的寫法: class C(A, B) def __init__(self, a, b): A.__init__(self, a) B.__init__(self, b) class Person(object):     def __init__(self, name, gender, **kw):         self.name = name         self.gender = gender         for k,w in kw.iteritems():             setattr(self,k,w) p = Person('Bob', 'Male', age=18, course='Python') print p.age print p.course python特殊方法 任何資料型別的例項都有__str__()這樣一個方法 

__str__和__repr__

如果要把一個類的例項變成 str,就需要實現特殊方法__str__()

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __str__(self):
        return '(Person: %s, %s)' % (self.name, self.gender)

現在,在互動式命令列下用 print 試試:

>>> p = Person('Bob', 'male')
>>> print p
(Person: Bob, male)

但是,如果直接敲變數 p

>>> p
<main.Person object at 0x10c941890>

似乎__str__() 不會被呼叫。

因為 Python 定義了__str__()__repr__()兩種方法,__str__()用於顯示給使用者,而__repr__()用於顯示給開發人員。

有一個偷懶的定義__repr__的方法:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __str__(self):
        return '(Person: %s, %s)' % (self.name, self.gender)
    __repr__ = __str__

__cmp__

對 intstr 等內建資料型別排序時,Python的 sorted() 按照預設的比較函式 cmp 排序,但是,如果對一組 Student 類的例項排序時,就必須提供我們自己的特殊方法 __cmp__()

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def __str__(self):
        return '(%s: %s)' % (self.name, self.score)
    __repr__ = __str__

    def __cmp__(self, s):
        if self.name < s.name:
            return -1
        elif self.name > s.name:
            return 1
        else:
            return 0

上述 Student 類實現了__cmp__()方法,__cmp__用例項自身self和傳入的例項 進行比較,如果 self 應該排在前面,就返回 -1,如果 s 應該排在前面,就返回1,如果兩者相當,返回 0。

Student類實現了按name進行排序:

>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]

注意: 如果list不僅僅包含 Student 類,則 __cmp__ 可能會報錯:

L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)
class Student(object):     def __init__(self, name, score):         self.name = name         self.score = score     def __str__(self):         return '(%s: %s)' % (self.name, self.score)     __repr__ = __str__     def __cmp__(self, s):         if self.score > s.score:             return -1         elif self.score < s.score:             return 1         elif self.name > s.name:             return 1         elif self.name < s.name:             return -1         else:             return 0 L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)] print sorted(L) class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __str__(self):
        return '(%s: %s)' % (self.name, self.score)

    __repr__ = __str__

    def __cmp__(self, s):
        if self.score == s.score:
            return cmp(self.name, s.name)
        return -cmp(self.score, s.score)

L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)
class Fib(object):     def __init__(self, num):         a, b, L = 0, 1, []         for n in range(num):             L.append(a)             a, b = b, a + b         self.numbers = L     def __str__(self):         return str(self.numbers)     __repr__ = __str__     def __len__(self):         return len(self.numbers) f = Fib(10) print f print len(f) gcd() #TBD class Fib(object):     def __call__(self, num):         a, b, L = 0, 1, []         for n in range(num):             L.append(a)             a, b = b, a + b         return L f = Fib() print f(10) Next step: IO: 檔案和Socket; 多工: 程序和執行緒; 資料庫 Web開發 Ref: imooc 廖雪峰python進階篇教程