8. 模塊
在 Python 中,一個 .py 文件就稱之爲一個模塊(Module)。
模塊是一組Python代碼的集合,可以使用其他模塊,也可以被其他模塊所使用。
模塊命名不要和系統模塊名衝突,在 Python 交互式環境中,import abc
,若成功說明存在此名稱的系統模塊;否則,不存在。
8.1 使用模塊
sys
模塊是 Python 內置的一個模塊。
使用 sys
模塊的第一步,就是導入該模塊:
import sys
導入 sys
模塊後,就有了 sys
變量指向該模塊,這樣通過 sys
變量,就可以訪問 sys
模塊的所有功能。
作用域
正常的函數和變量名是公開的(public),可以被直接引用,比如 abc
, PI
等;
類似__xxx__
這樣的變量是特殊變量,可以被直接引用,但是有特殊的用途,比如 __author__
,__name__
就是特殊變量,自己一般不要這樣命名;
類似_xxx
和 __xxx
這樣的函數或變量就是非公開的(private),不應該被直接引用,如 _abc
, __abc
等。
_xxx
這樣的函數或變量是可以被外界訪問到的。但是,按照約定俗成的規定,這樣的函數或變量表示的含義是“雖然我可以被訪問,但是請把我視爲私有變量,不要隨意訪問”。
8.2 安裝第三方模塊
在 Python 中,安裝第三方模塊,是通過包管理工具 pip 完成的。
第三方庫,必須先知道該庫的名稱,可以在官網或者pypi上搜索。
比如安裝 Pillow,命令是:
pip install Pillow
安裝常用模塊
使用Anaconda,這是一個基於Python的數據處理和科學計算平臺,它已經內置了許多非常有用的第三方庫。Anaconda 自帶 Python。
9. 面向對象編程
在 Python 中,所有數據類型都可以視爲對象,也可以自定義對象。
9.1 類和實例
在 Python 中,通過 class
關鍵字來定義類,如 Student
類:
class Student(object):
pass
class
關鍵字後面跟着的 Student
是類名,類名通常以大寫字母開頭,這點和 Java 是一致的。
緊接着類名的是(object)
,表示該類是從哪個類繼承下來的。通常,如果沒有合適的繼承類,就用 object
類。object
類是所有類最終都會繼承的類。
pass
是空語句,一般用作佔位語句,是爲了保證程序結構的完整性。如果不加的話,會拋出異常:IndentationError: expected an indented block
。之前的筆記裏提到過。這裏算是複習了。
使用 Student
類創建實例:
bart = Student()
print(bart) # <__main__.Student object at 0x7f64b90fc898>
print(Student) # <class '__main__.Student'>
從打印信息可以看出,bart
指向的是一個 Student
的實例,它有內存地址;而Student
本身是一個類。
可以自由地給一個實例綁定屬性
這一點真的很新鮮,它是怎麼做到的?
bart.name = 'Bart Smith'
print(bart.name) # Bart Smith
bart.age = 32
print(bart.age) # 32
bart.salary = 20000.0
print(bart.salary) # 20000.0
把必須綁定的屬性強制寫進去
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
__init__
方法的作用:通過一個特殊的 __init__
方法,在創建實例的時候把 name
,score
等屬性綁定上去。
__init__
是一個特殊的方法,它的第一個參數永遠是 self
,表示創建的實例本身。
在 __init__
方法內部,把各種屬性綁定到 self
。
使用 Student
類創建實例:
# bart = Student() # 報錯:TypeError: __init__() missing 2 required positional arguments: 'name' and 'score'
bart = Student('Bart Smith', 0)
print(bart) # <__main__.Student object at 0x7fad2fbf2b00>
print('name = %s, score = %s' % (bart.name, bart.score)) # name = Bart Smith, score = 0
注意到現在使用 Student()
這種方式直接報錯,這是因爲我們在類中使用了 __init__
方法,就不能再傳入空參數了,必須傳入與 __init__
相匹配的參數,但 self
是個例外,我們不需要傳 self
,Python 解釋器會自己把實例變量 self
傳進去。因此,需要傳入的是 name
和 score
。
普通函數和類中定義的函數有什麼區別?
區別只有一個,就是在類中定義的函數,它的第一個參數永遠是實例變量 self
,而且,在調用時,不用傳遞該參數。
數據封裝
在類中定義方法,通過在實例上調用方法,就可以直接操作對象內部的數據,不需要知道方法內部的實現細節。
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))
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
# 使用 Student 類
bart = Student('Peter Wang', 59)
bart.print_score() # 打印結果:Peter Wang: 59
print(bart.get_grade()) # C
特別注意的一點
和靜態語言不同,Python 允許對實例變量綁定任何數據,也就是說,對於兩個實例變量,雖然它們都是同一個類的不同實例,但擁有的變量名稱可能不同。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
# 對不同的實例變量綁定不同的數據
wang = Student('zhichao', 100)
li = Student('xiaolong', 100)
wang.age = 18
li.height = 176
print(wang.age) # 打印:18
# print(li.age) # 此行報錯
'''
Traceback (most recent call last):
File "student4.py", line 12, in <module>
print(li.age)
AttributeError: 'Student' object has no attribute 'age'
'''
print(li.height) # 打印:176
# print(wang.height) # 此行報錯
'''
Traceback (most recent call last):
File "student4.py", line 20, in <module>
print(wang.height)
AttributeError: 'Student' object has no attribute 'height'
'''
9.2 訪問限制
隱藏變量,對外暴露 getter/setter 方法
# 增加對屬性的訪問限制, 暴露出訪問屬性的方法
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))
# getter/setter 方法
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
def get_score(self):
return self.__score
def set_score(self, score):
if score >= 0 and score <= 100:
self.__score = score
else:
raise ValueError('illegal score')
wang = Student('zhichao', 100)
# print(wang.__name) # 此行報錯
'''
Traceback (most recent call last):
File "student6_access.py", line 12, in <module>
print(wang.__name)
AttributeError: 'Student' object has no attribute '__name'
'''
print('%s: %s' % (wang.get_name(), wang.get_score())) # 打印:zhichao: 100
wang.set_name('zhijie')
# wang.set_score(150) # 不合法的分數,拋異常
wang.set_score(99)
print('%s: %d' % (wang.get_name(), wang.get_score())) # 打印:zhijie: 99
9.3 繼承和多態
繼承
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.run() # 打印:Animal is running...
cat = Cat()
cat.run() # 打印: Animal is running...
上面的代碼裏,Dog
類 繼承了 Animal
類,寫法是把 Animal
寫在 Dog
後面的圓括號裏面。這時,我們說 Dog
類是 Animal
類的子類,Animal
類是 Dog
類的基類、父類或超類。
通過繼承,子類獲取了父類的全部功能。
子類還可以增加一些方法。
多態
# 多態
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
def run_twice(animal):
animal.run()
animal.run()
run_twice(Animal())
run_twice(Dog())
run_twice(Cat())
'''
Animal is running...
Animal is running...
Dog is running...
Dog is running...
Cat is running...
Cat is running...
'''
多態的含義:父類引用指向子類對象。
多態的作用:調用方只管調用,不管細節,當我們新增一種 Animal
的類型時,只要保證 run()
方法編寫正確,不用管原來的代碼是如何調用的。這符合“開閉”原則(Open Closed Principle,OCP):
對擴展開放(open for extension):允許新增 Animal
類的子類;
對修改關閉( closed for modification):新增了 Animal
類的子類,不需要修改依賴 Animal
類的 run_twice()
等函數。
特別注意的一點
對於靜態語言(如 Java)來說,傳入run_twice()
方法中的類型,需要是 Animal
類型,或者是 Animal
類型的子類型,否則,無法調用 run_twice()
方法。
而 Python 是一門動態語言,則不一定需要傳入的是 Animal
類型或者其子類型,只要保證傳入的對象有一個 run()
方法就可以了。
例如,這裏我們定義一個火車Train
類,它有一個 run()
方法,它就可以傳入 run_twice()
方法。
class Train(object):
def run(self):
print('Train is running...')
run_twice(Train())
'''
Train is running...
Train is running...
'''
這就是動態語言的“鴨子類型”,它並不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。
9.4 獲取對象信息
這裏主要學習的是 type()
函數和 isinstance()
函數的使用。它們都是 Python 的內置函數。
type() 函數
type()
函數用來獲取對象的類型,返回的是對應的 Class 類型。
它可以用於基本類型數據:
# 使用 type()來判斷基本類型
print(type(123)) # <class 'int'>
print(type('hello')) # <class 'str'>
print(type(None)) # <class 'NoneType'>
print(type(3.1415926)) # <class 'float'>
print(type(True)) # <class 'bool'>
可以判斷指向函數或類的變量的類型:
print(type(abs)) # <class 'builtin_function_or_method'>
class Animal(object):
pass
a = Animal()
print(type(a)) # <class '__main__.Animal'>
b = Animal
print(type(b)) # <class 'type'>
判斷 type()
函數作用的變量的類型是否相等:
print(type(123) == type(456)) # True
print(type(123) == int) # True
print(type('abc') == type('123')) # True
print(type('abc') == str) # True
print(type(6.18) == type(3.14)) # True
print(type(6.18) == float) # True
print(type(1 > 0) == type(2 > 1)) # True
print(type(1 > 0) == bool) # True
可以看到,可以讓基本數據類型的 int
、float
等參與比較。
判斷 type()
函數作用的對象是否是函數:
import types # 引入 types 模塊,這裏需要它裏面定義的一些常量
def fn():
pass
print(type(fn) == types.FunctionType) # True
print(type(abs) == types.BuiltinMethodType) # True
print(type(lambda x: x + 1) == types.LambdaType) # True
print(type((x for x in range(10))) == types.GeneratorType) # 這種是生成器列型 打印 :True
isinstance() 函數
class Fruit(object):
pass
class Apple(Fruit):
pass
class Hongfushi(Apple):
pass
f = Fruit()
a = Apple()
h = Hongfushi()
print(isinstance(f, Fruit)) # True
print(isinstance(a, Fruit)) # True
print(isinstance(h, Fruit)) # True
print(isinstance(a, Hongfushi)) # False
# isinstance 用於基本數據類型
print(isinstance(123, int)) # True
print(isinstance(True, bool)) # True
print(isinstance(3.14, float)) # True
print(isinstance('hello', str)) # True
# isinstance 判斷一個變量是否某些類型中的一種
print(isinstance([1, 2, 3], (list, tuple))) # True
print('=' * 20, 'I am a divider', '=' * 20)
知識點:type()
函數和 isinstance()
函數的區別是什麼?
type() 不會認爲子類是一種父類類型,不考慮繼承關係。
isinstance() 會認爲子類是一種父類類型,考慮繼承關係。
如果要判斷兩個類型是否相同推薦使用 isinstance()。
dir() 函數
獲取一個對象的所有屬性和方法,使用 dir()
函數,這也是 Python 中的一個內置函數。
print(dir('ABC'))
打印結果:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
可以看到返回的是一個 list,裏面包含了str 對象的所有屬性和方法。
# 獲取一個對象的長度, 使用 len() 函數,內部就是調用對象的 __len__ 函數
print(len('ABC'))
print('ABC'.__len__())
在自己的類中使用 __len__
函數:
# 自己編寫一個類,定義一個 __len__ 方法,也可以使用 len() 函數獲取長度
class MyObject(object):
def __len__(self):
return 100
print(MyObject().__len__()) # 100
print(len(MyObject())) # 100
配合 getattr(), setattr() 和 hasattr(), 直接操作一個對象的狀態
# 配合 getattr(), setattr() 和 hasattr(), 直接操作一個對象的狀態
# 這三個都是內置函數
class MyClass(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyClass()
# 判斷有無屬性 x?
print(hasattr(obj, 'x')) # True
print(obj.x) # 9
# 判斷有無屬性 y?
print(hasattr(obj, 'y')) # False
# 設置一個屬性 y?
setattr(obj, 'y', 19)
# 再判斷有無屬性 y?
print(hasattr(obj, 'y')) # True
# 獲取屬性 y
print(getattr(obj, 'y')) # 19
# 獲取一個不存在的屬性,拋出異常
# getattr(obj, 'z') # 此行拋出異常,如下所示
'''
File "attrs.py", line 36, in <module>
getattr(obj, 'z')
AttributeError: 'MyClass' object has no attribute 'z'
'''
# 獲取屬性,如果不存在,返回默認值
print(getattr(obj, 'z', -1)) # -1
# 獲取對象的方法
print(hasattr(obj, 'power')) # True
print(getattr(obj, 'power')) # <bound method MyClass.power of <__main__.MyClass object at 0x7f5d8dd5cf98>>
fn = getattr(obj, 'power')
print(fn) # <bound method MyClass.power of <__main__.MyClass object at 0x7f5d8dd5cf98>>
print(fn()) # 81
9.5 實例屬性和類屬性
# 實例屬性和類屬性
class Student(object):
# name 是類屬性,歸 Student 類所有
name = 'Student'
wang = Student()
print(wang.name) # Student
li = Student()
print(li.name) # Student
可以看到,對於 name
,我們沒有通過 __init__
函數進行綁定,也沒有通過對象綁定,但是每個實例都已經有了這個屬性。
類屬性作爲計數器的例子:
# 爲了統計學生人數,可以給Student類增加一個類屬性,每創建一個實例,該屬性自動增加:
class Student(object):
count = 0
def __init__(self, name):
self.name = name
Student.count = Student.count + 1
需要注意的是,不要對實例屬性和類屬性使用相同的名字,因爲相同名字的實例屬性會屏蔽掉類屬性。
實例屬性和類屬性的區別:
實例屬性屬於各個實例,互不干擾;
類屬性屬於類所有,所有實例共享一個屬性。