模塊
在Python中,一個.py文件就稱之為一個模塊(Module)。
為了避免模塊名沖突,Python又引入了按目錄來組織模塊的方法,稱為包(Package)。如下mycompany包
mycompany
├─ init.py
├─ abc.py
└─ xyz.py
請注意,每一個包目錄下面都會有一個init.py的文件,這個文件是必須存在的,否則,Python就把這個目錄當(dāng)成普通目錄,而不是一個包。init.py可以是空文件,也可以有Python代碼,因為init.py本身就是一個模塊,而它的模塊名就是mycompany。
模塊是一組Python代碼的集合,可以使用其他模塊,也可以被其他模塊使用。
創(chuàng)建自己的模塊時,要注意:
模塊名要遵循Python變量命名規(guī)范,不要使用中文、特殊字符;
模塊名不要和系統(tǒng)模塊名沖突,最好先查看系統(tǒng)是否已存在該模塊,檢查方法是在Python交互環(huán)境執(zhí)行import abc,若成功則說明系統(tǒng)存在此模塊。
1.使用模塊
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
第1行和第2行是標(biāo)準(zhǔn)注釋,第1行注釋可以讓這個hello.py文件直接在Unix/Linux/Mac上運(yùn)行,第2行注釋表示.py文件本身使用標(biāo)準(zhǔn)UTF-8編碼;
第4行是一個字符串,表示模塊的文檔注釋,任何模塊代碼的第一個字符串都被視為模塊的文檔注釋;
第6行使用author變量把作者寫進(jìn)去,這樣當(dāng)你公開源代碼后別人就可以瞻仰你的大名;
以上就是Python模塊的標(biāo)準(zhǔn)文件模板,當(dāng)然也可以全部刪掉不寫,但是,按標(biāo)準(zhǔn)辦事肯定沒錯。
2.作用域
在一個模塊中(也就是一個.py文件)
正常的函數(shù)和變量名是公開的(public),可以被直接引用,比如:abc,x123,PI等;
類似xxx這樣的變量是特殊變量,可以被直接引用,但是有特殊用途,比如上面的author,name就是特殊變量,hello模塊定義的文檔注釋也可以用特殊變量doc訪問,我們自己的變量一般不要用這種變量名;
類似_xxx和__xxx這樣的函數(shù)或變量就是非公開的(private),不應(yīng)該被直接引用,比如_abc,__abc等;
外部不需要引用的函數(shù)全部定義成private,只有外部需要引用的函數(shù)才定義為public。
3.安裝第三方模塊
在Python中,安裝第三方模塊,是通過包管理工具pip完成的。
一般來說,第三方庫都會在Python官方的pypi.python.org網(wǎng)站注冊,要安裝一個第三方庫,必須先知道該庫的名稱,可以在官網(wǎng)或者pypi上搜索,比如Pillow的名稱叫Pillow,因此,安裝Pillow的命令就是:
pip install Pillow
面向?qū)ο缶幊?/h2>
我們以一個例子來說明面向過程和面向?qū)ο笤诔绦蛄鞒躺系牟煌帯?/p>
假設(shè)我們要處理學(xué)生的成績表,為了表示一個學(xué)生的成績.
#面向過程:
std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
#而處理學(xué)生成績可以通過函數(shù)實現(xiàn),比如打印學(xué)生的成績:
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
#面向?qū)ο?class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
#給對象發(fā)消息實際上就是調(diào)用對象對應(yīng)的關(guān)聯(lián)函數(shù),
#我們稱之為對象的方法(Method)。的程序?qū)懗鰜砭拖襁@樣:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
1.類和實例
#定義類是通過class關(guān)鍵字:
#Student是類名 通常是大寫開頭的單詞
#object 表示該類是從哪個類繼承下來的 如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
class Student(object):
def __init__(self, name, score): #第一個參數(shù)永遠(yuǎn)是self,表示創(chuàng)建的實例本身
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
bart = Student('Bart Simpson', 59) #使用
bart.name = 'Bart Simpson' #可以自由地給一個實例變量綁定屬性
2.訪問限制
在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內(nèi)部可以訪問,外部不能訪問。
需要注意的是,在Python中,變量名類似xxx的,也就是以雙下劃線開頭,并且以雙下劃線結(jié)尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用name、score這樣的變量名。
有些時候,你會看到以一個下劃線開頭的實例變量名,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規(guī)定,當(dāng)你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
3.繼承和多態(tài)
在OOP程序設(shè)計中,當(dāng)我們定義一個class的時候,可以從某個現(xiàn)有的class繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。
繼承有什么好處?最大的好處是子類獲得了父類的全部功能。
當(dāng)子類和父類都存在相同的run()方法時,我們說,子類的run()覆蓋了父類的run(),在代碼運(yùn)行的時候,總是會調(diào)用子類的run()。這樣,我們就獲得了繼承的另一個好處:多態(tài)。
判斷一個變量是否是某個類型可以用isinstance()判斷:
isinstance(a, list) #True
4.靜態(tài)語言 vs 動態(tài)語言
對于靜態(tài)語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調(diào)用run()方法。
對于Python這樣的動態(tài)語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:
class Timer(object):
def run(self):
print('Start...')
獲取對象信息
當(dāng)我們拿到一個對象的引用時,如何知道這個對象是什么類型、有哪些方法呢?
1.使用type()
a=123
print(type(a)) #<class 'int'>
print(type('str')) #<class 'str'>
print(type(None)) #<class 'NoneType'>
print(type(abs)) #<class 'builtin_function_or_method'>
print(type(123)==type(456)) #True
print(type('abc')==str) #True
print(type('abc')==type(123)) #False
import types
def fn():
pass
print(type(fn)==types.FunctionType) #True
print(type(abs)==types.BuiltinFunctionType) #True
print(type(lambda x: x)==types.LambdaType) #True
print(type((x for x in range(10)))==types.GeneratorType) #True
2.使用isinstance()
能用type()判斷的基本類型也可以用isinstance()判斷:
isinstance(d, Dog) and isinstance(d, Animal) #True
并且還可以判斷一個變量是否是某些類型中的一種,比如下面的代碼就可以判斷是否是list或者tuple:
isinstance([1, 2, 3], (list, tuple)) # True
isinstance((1, 2, 3), (list, tuple)) #True
3.使用dir()
如果要獲得一個對象的所有屬性和方法,可以使用dir()函數(shù),它返回一個包含字符串的list,比如,獲得一個str對象的所有屬性和方法:
dir('ABC')
#['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
類似xxx的屬性和方法在Python中都是有特殊用途的,比如len方法返回長度。在Python中,如果你調(diào)用len()函數(shù)試圖獲取一個對象的長度,實際上,在len()函數(shù)內(nèi)部,它自動去調(diào)用該對象的len()方法,所以,下面的代碼是等價的:
len('ABC') == 'ABC'.__len__()
我們自己寫的類,如果也想用len(myObj)的話,就自己寫一個len()方法:
僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態(tài):
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
hasattr(obj, 'x') # 有屬性'x'嗎? True
hasattr(obj, 'y') # 有屬性'y'嗎? False
setattr(obj, 'y', 19) # 設(shè)置一個屬性'y'
hasattr(obj, 'y') # 有屬性'y'嗎? True
getattr(obj, 'y') # 獲取屬性'y'
getattr(obj, 'z') # 獲取屬性'z' 如果試圖獲取不存在的屬性,會拋出AttributeError的錯誤:
getattr(obj, 'z', 404) # 獲取屬性'z',如果不存在,返回默認(rèn)值404
hasattr(obj, 'power') # 有屬性'power'嗎? True
print(getattr(obj, 'power')) # <bound method MyObject.power of <__main__.MyObject object at 0x00000249D7862278>>
實例屬性和類屬性
class Student(object):
count = 0 #類屬性
def __init__(self, name):
Student.count+=1
self.name = name #實例屬性
面向?qū)ο蟾呒壘幊?/h3>
1.使用slots
正常情況下,當(dāng)我們定義了一個class,創(chuàng)建了一個class的實例后,我們可以給該實例綁定任何屬性和方法,這就是動態(tài)語言的靈活性
class Student(object):
pass
s = Student()
s.name = 'Michael' # 動態(tài)給實例綁定一個屬性
def set_age(self, age): # 定義一個函數(shù)作為實例方法
self.age = age
from types import MethodType
s.set_age = MethodType(set_age, s) # 給實例綁定一個方法
s.set_age(25) # 調(diào)用實例方法
#但是,給一個實例綁定的方法,對另一個實例是不起作用的:
#為了給所有實例都綁定方法,可以給class綁定方法:
def set_score(self, score):
self.score = score
Student.set_score = set_score
s.set_score(22)
print(s.score)
但是,如果我們想要限制實例的屬性怎么辦?
為了達(dá)到限制的目的,Python允許在定義class的時候,定義一個特殊的slots變量,來限制該class實例能添加的屬性:
__slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
使用slots要注意,slots定義的屬性僅對當(dāng)前類實例起作用,對繼承的子類是不起作用的:
除非在子類中也定義slots,這樣,子類實例允許定義的屬性就是自身的slots加上父類的slots。
2.使用@property
Python內(nèi)置的@property裝飾器就是負(fù)責(zé)把一個方法變成屬性調(diào)用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter #注意這個
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@property的實現(xiàn)比較復(fù)雜,我們先考察如何使用。把一個getter方法變成屬性,只需要加上@property就可以了,此時,@property本身又創(chuàng)建了另一個裝飾器@score.setter,負(fù)責(zé)把一個setter方法變成屬性賦值,于是,我們就擁有一個可控的屬性操作:
3.多重繼承
class Dog(Mammal, Runnable):
pass
class Bat(Mammal, Flyable):
pass
MixIn
在設(shè)計類的繼承關(guān)系時,通常,主線都是單一繼承下來的,例如,Ostrich繼承自Bird。但是,如果需要“混入”額外的功能,通過多重繼承就可以實現(xiàn),比如,讓Ostrich除了繼承自Bird外,再同時繼承Runnable。這種設(shè)計通常稱之為MixIn。
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是給一個類增加多個功能,這樣,在設(shè)計類的時候,我們優(yōu)先考慮通過多重繼承來組合多個MixIn的功能,而不是設(shè)計多層次的復(fù)雜的繼承關(guān)系
4.定制類
__author__ = 'huangxianchang'
class Student(object):
age=12 #類屬性
def __init__(self):
self.name = "Student——init"
# 實例()時調(diào)用
def __call__(self, *args, **kwargs):
print("call")
return "__call__"
# print(實例)時調(diào)用 __repr__()是為調(diào)試服務(wù)的
def __repr__(self):
return "__repr__"
# print(實例)時調(diào)用 __str__()是為用戶服務(wù)的
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__
#被用于for ... in循環(huán),類似list或tuple那樣
def __iter__(self):
return self # 實例本身就是迭代對象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 計算下一個值
if self.a > 100000: # 退出循環(huán)的條件
raise StopIteration()
return self.a # 返回下一個值
#要表現(xiàn)得像list那樣按照下標(biāo)取出元素,需要實現(xiàn)__getitem__()方法:
# 但是list有個神奇的切片方法: list(range(100))[5:10]
#原因是__getitem__()傳入的參數(shù)可能是一個int,也可能是一個切片對象slice,所以要做判斷:
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
#與之對應(yīng)的是__setitem__()方法,把對象視作list或dict來對集合賦值。
# 最后,還有一個__delitem__()方法,用于刪除某個元素。
#當(dāng)調(diào)用不存在的屬性時,Python解釋器會試圖調(diào)用__getattr__(self, 'score')來嘗試獲得屬性,這樣,我們就有機(jī)會返回score的值:
def __getattr__(self, attr):
if attr=='score':
return 99
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
#默認(rèn)返回就是None
#注意,只有在沒有找到屬性的情況下,才調(diào)用__getattr__,已有的屬性,比如name,不會在__getattr__中查找。
# 類方法,只能訪問類變量,不能訪問實例變量
@classmethod
def talk(cls):
print(cls) # 不能訪問self.name
# 靜態(tài)方法不訪問實例變量和類變量,實例.靜態(tài)方法()時,不會自動傳入的id。一個方法不要訪問了類和實例變量,但類
# 又要用這個方法時可以定義為靜態(tài)方法。
@staticmethod
def walk(cmd):
print("the cmd is :%s"% cmd) # 不訪問self.name和類.age,
# 將方法轉(zhuǎn)為屬性,方法他時,不帶括號。實例.方法。只有輸出,但不接收輸入時可以使用。
@property
def shout(self):
print("shout:%s"%self.name)
return 18
# 這個property.setter裝飾的方法必須是被property裝飾過的方法。
# 否則報錯:TypeError: descriptor 'setter' requires a 'property' object but received a 'function'
@shout.setter
def shout(self,arg):
print("shout:%s, %s"%(self.name,arg))
return 18
通過callable()函數(shù),我們就可以判斷一個對象是否是“可調(diào)用”對象。
callable(Student())
4.使用枚舉類
#枚舉
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
#這樣我們就獲得了Month類型的枚舉類,可以直接使用Month.Jan來引用一個常量,或者枚舉它的所有成員:
#默認(rèn)從1開始計數(shù)
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被設(shè)定為0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
#@unique裝飾器可以幫助我們檢查保證沒有重復(fù)值。
print(Weekday(1)) #Weekday.Mon
#既可以用成員名稱引用枚舉常量,又可以直接根據(jù)value的值獲得枚舉常量
5.使用元類
type()
動態(tài)語言和靜態(tài)語言最大的不同,就是函數(shù)和類的定義,不是編譯時定義的,而是運(yùn)行時動態(tài)創(chuàng)建的。
print(type(Hello))
type()函數(shù)既可以返回一個對象的類型,又可以創(chuàng)建出新的類型,比如,我們可以通過type()函數(shù)創(chuàng)建出Hello類,而無需通過class Hello(object)...的定義:
def fn(self, name='world'): # 先定義函數(shù)
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=fn)) # 創(chuàng)建Hello class
h = Hello()
h.hello()
print(type(Hello)) #<class 'type'>
print(type(h)) #<class '__main__.Hello'>
要創(chuàng)建一個class對象,type()函數(shù)依次傳入3個參數(shù):
class的名稱;
繼承的父類集合,注意Python支持多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
class的方法名稱與函數(shù)綁定,這里我們把函數(shù)fn綁定到方法名hello上。
通過type()函數(shù)創(chuàng)建的類和直接寫class是完全一樣的,因為Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,然后調(diào)用type()函數(shù)創(chuàng)建出class。
正常情況下,我們都用class Xxx...來定義類,但是,type()函數(shù)也允許我們動態(tài)創(chuàng)建出類來,也就是說,動態(tài)語言本身支持運(yùn)行期動態(tài)創(chuàng)建類,這和靜態(tài)語言有非常大的不同,要在靜態(tài)語言運(yùn)行期創(chuàng)建類,必須構(gòu)造源代碼字符串再調(diào)用編譯器,或者借助一些工具生成字節(jié)碼實現(xiàn),本質(zhì)上都是動態(tài)編譯,會非常復(fù)雜。
6.metaclass
metaclass,直譯為元類,簡單的解釋就是:可以把類看成是metaclass創(chuàng)建出來的“實例”。
我們先看一個簡單的例子,這個metaclass可以給我們自定義的MyList增加一個add方法:
定義ListMetaclass,按照默認(rèn)習(xí)慣,metaclass的類名總是以Metaclass結(jié)尾,以便清楚地表示這是一個metaclass:
# metaclass是類的模板,所以必須從`type`類型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
有了ListMetaclass,我們在定義類的時候還要指示使用ListMetaclass來定制類,傳入關(guān)鍵字參數(shù)metaclass:
class MyList(list, metaclass=ListMetaclass):
pass
當(dāng)我們傳入關(guān)鍵字參數(shù)metaclass時,魔術(shù)就生效了,它指示Python解釋器在創(chuàng)建MyList時,要通過ListMetaclass.new()來創(chuàng)建,在此,我們可以修改類的定義,比如,加上新的方法,然后,返回修改后的定義。