python3 基礎 廖雪峰教程筆記-4


https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318447437605e90206e261744c08630a836851f5183000
1.模塊
為了編寫可維護的代碼,我們把很多函數分組,分別放到不同的文件里。
在Python中一個.py文件就稱為一個模塊
模塊的好處:
1、大大提高代碼的可維護性
2、一個模塊編寫完畢,可以被其他模塊引用
3、使用模塊還可以避免函數名和變量名沖突,相同的函數名以及變量名可以放在不同的模塊當中。

為了避免模塊名沖突,Python又引入了按目錄來組織模塊的方法,稱為包(Package)

2.使用模塊
Python本身就內置了很多非常有用的模塊,只要安裝完畢,這些模塊就可以立刻使用。
#!/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行是標准注釋,第1行注釋可以讓這個hello.py文件直接在Unix/Linux/Mac上運行,第2行注釋表示.py文件本身使用標准UTF-8編碼;
第4行是一個字符串,表示模塊的文檔注釋,任何模塊代碼的第一個字符串都被視為模塊的文檔注釋;
第6行使用__author__變量把作者寫進去,這樣當你公開源代碼后別人就可以瞻仰你的大名;
以上就是Python模塊的標准文件模板,當然也可以全部刪掉不寫,但是,按標准辦事肯定沒錯。

后面開始就是真正的代碼部分。
你可能注意到了,使用sys模塊的第一步,就是導入該模塊:
import sys
導入sys模塊后,我們就有了變量sys指向該模塊,利用sys這個變量,就可以訪問sys模塊的所有功能。
sys模塊有一個argv變量,用list存儲了命令行的所有參數。argv至少有一個元素,因為第一個參數永遠是該.py文件的名稱,例如:

運行python3 hello.py獲得的sys.argv就是['hello.py'];

運行python3 hello.py Michael獲得的sys.argv就是['hello.py', 'Michael]。

最后,注意到這兩行代碼:

if __name__=='__main__':
test()
當我們在命令行運行hello模塊文件時,Python解釋器把一個特殊變量__name__置為__main__,而如果在其他地方導入該hello模塊時,if判斷將失敗,因此,這種if測試可以讓一個模塊通過命令行運行時執行一些額外的代碼,最常見的就是運行測試。

我們可以用命令行運行hello.py看看效果:

$ python3 hello.py
Hello, world!
$ python hello.py Michael
Hello, Michael!
如果啟動Python交互環境,再導入hello模塊:

$ python3
Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 23 2015, 02:52:03)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>>
導入時,沒有打印Hello, word!,因為沒有執行test()函數。

調用hello.test()時,才能打印出Hello, word!:

>>> hello.test()
Hello, world!

3.作用域

在一個模塊中,我們可能會定義很多函數和變量,但有的函數和變量我們希望給別人使用,有的函數和變量我們希望僅僅在模塊內部使用。在Python中,是通過_前綴來實現的。
正常的函數和變量名是公開的(public),可以被直接引用,比如:abc,x123,PI等;
類似__xxx__這樣的變量是特殊變量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊變量,
hello模塊定義的文檔注釋也可以用特殊變量__doc__訪問,我們自己的變量一般不要用這種變量名;

類似_xxx和__xxx這樣的函數或變量就是非公開的(private),不應該被直接引用,比如_abc,__abc等;
之所以我們說,private函數和變量“不應該”被直接引用,而不是“不能”被直接引用,是因為Python並沒有一種方法可以完全限制訪問private函數或變量,但是,從編程習慣上不應該引用private函數或變量。

private函數或變量不應該被別人引用,那它們有什么用呢?請看例子:

def _private_1(name):
return 'Hello, %s' % name

def _private_2(name):
return 'Hi, %s' % name

def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
我們在模塊里公開greeting()函數,而把內部邏輯用private函數隱藏起來了,這樣,
調用greeting()函數不用關心內部的private函數細節,這也是一種非常有用的代碼封裝和抽象的方法,即:
外部不需要引用的函數全部定義成private,只有外部需要引用的函數才定義為public。

如果在命令行直接調用該程序文件,該文件作為主程序入口,name == 'main'理所當然啊。
如果在命令行調用其他程序文件,主程序入口name == 'main'自然不成立,因為main等於那個你在命令行輸入的程序名。
這個東西的好處就是,別人調用時(你並非是主程序入口)后面的東西不運行,自己命令行執行時(你是主程序入口)后面的東西運行。故可以作為測試用。

3.安裝第三方模塊
安裝第三方模塊,是通過包管理工具pip完成的
注意:Mac或Linux上有可能並存Python 3.x和Python 2.x,因此對應的pip命令是pip3。

一般來說,第三方庫都會在Python官方的pypi.python.org網站注冊,要安裝一個第三方庫,必須先知道該庫的名稱,
可以在官網或者pypi上搜索,比如Pillow的名稱叫Pillow,因此,安裝Pillow的命令就是:
pip install Pillow

有了Pillow,處理圖片易如反掌。隨便找個圖片生成縮略圖:
>>> from PIL import Image
>>> im = Image.open('test.png')
>>> print(im.format, im.size, im.mode)
PNG (400, 300) RGB
>>> im.thumbnail((200, 100))
>>> im.save('thumb.jpg', 'JPEG')

其他常用的第三方庫還有
MySQL的驅動:mysql-connector-python,
用於科學計算的NumPy庫:numpy,
用於生成文本的模板工具Jinja2,等等。
默認情況下,Python解釋器會搜索當前目錄、所有已安裝的內置模塊和第三方模塊,搜索路徑存放在sys模塊的path變量中:
>>> import sys
>>> sys.path


如果我們要添加自己的搜索目錄,有兩種方法:
一是直接修改sys.path,添加要搜索的目錄:
>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')
這種方法是在運行時修改,運行結束后失效。
第二種方法是設置環境變量PYTHONPATH,該環境變量的內容會被自動添加到模塊搜索路徑中。
設置方式與設置Path環境變量類似。注意只需要添加你自己的搜索路徑,Python自己本身的搜索路徑不受影響。

4.面向對象編程
所有數據類型都可以視為對象,當然也可以自定義對象。自定義的對象數據類型就是面向對象中的類(Class)的概念。

我們以一個例子來說明面向過程和面向對象在程序流程上的不同之處。

假設我們要處理學生的成績表,為了表示一個學生的成績,面向過程的程序可以用一個dict表示:

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
而處理學生成績可以通過函數實現,比如打印學生的成績:

def print_score(std):
print('%s: %s' % (std['name'], std['score']))
如果采用面向對象的程序設計思想,我們首選思考的不是程序的執行流程,而是Student這種數據類型應該被視為一個對象,這個對象擁有name和score這兩個屬性(Property)。如果要打印一個學生的成績,首先必須創建出這個學生對應的對象,然后,給對象發一個print_score消息,讓對象自己把自己的數據打印出來。

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))
給對象發消息實際上就是調用對象對應的關聯函數,我們稱之為對象的方法(Method)。面向對象的程序寫出來就像這樣:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
面向對象的設計思想是從自然界中來的,因為在自然界中,類(Class)和實例(Instance)的概念是很自然的。Class是一種抽象概念,比如我們定義的Class——Student,是指學生這個概念,而實例(Instance)則是一個個具體的Student,比如,Bart Simpson和Lisa Simpson是兩個具體的Student。

所以,面向對象的設計思想是抽象出Class,根據Class創建Instance。

面向對象的抽象程度又比函數要高,因為一個Class既包含數據,又包含操作數據的方法。

5.類和實例
面向對象最重要的概念就是類(Class)和實例(Instance),必須牢記類是抽象的模板,
比如Student類,而實例是根據類創建出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的數據可能不同。
仍以Student類為例,在Python中,定義類是通過class關鍵字:
class Student(object):
pass
class后面緊接着是類名,即Student,類名通常是大寫開頭的單詞,緊接着是(object),
表示該類是從哪個類繼承下來的,繼承的概念我們后面再講,通常,如果沒有合適的繼承類,
就使用object類,這是所有類最終都會繼承的類。

定義好了Student類,就可以根據Student類創建出Student的實例,創建實例是通過類名+()實現的:

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

可以看到,變量bart指向的就是一個Student的實例,后面的0x10a67a590是內存地址,每個object的地址都不一樣,
而Student本身則是一個類。可以自由地給一個實例變量綁定屬性,比如,給實例bart綁定一個name屬性:
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

由於類可以起到模板的作用,因此,可以在創建實例的時候,把一些我們認為必須綁定的屬性強制填寫進去。
通過定義一個特殊的__init__方法,在創建實例的時候,就把name,score等屬性綁上去:

class Student(object):

def __init__(self, name, score):
self.name = name
self.score = score

注意:特殊方法“init”前后有兩個下划線!!!


注意到__init__方法的第一個參數永遠是self,表示創建的實例本身,因此,在__init__方法內部,
就可以把各種屬性綁定到self,因為self就指向創建的實例本身。有了__init__方法,在創建實例的時候,
就不能傳入空的參數了,必須傳入與__init__方法匹配的參數,但self不需要傳,Python解釋器自己會把實例變量傳進去:
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
和普通的函數相比,在類中定義的函數只有一點不同,就是第一個參數永遠是實例變量self,並且,調用時,不用傳遞該參數。除此之外,類的方法和普通函數沒有什么區別,所以,你仍然可以用默認參數、可變參數、關鍵字參數和命名關鍵字參數。


數據封裝
面向對象編程的一個重要特點就是數據封裝。在上面的Student類中,每個實例就擁有各自的name和score這些數據。我們可以通過函數來訪問這些數據,比如打印一個學生的成績:

>>> def print_score(std):
... print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59
但是,既然Student實例本身就擁有這些數據,要訪問這些數據,就沒有必要從外面的函數去訪問,
可以直接在Student類的內部定義訪問數據的函數,這樣,就把“數據”給封裝起來了。這些封裝數據的函數是和Student類
本身是關聯起來的,我們稱之為類的方法:

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))
要定義一個方法,除了第一個參數是self外,其他和普通函數一樣。要調用一個方法,只需要在實例變量上直接調用,
除了self不用傳遞,其他參數正常傳入:

>>> bart.print_score()
Bart Simpson: 59
這樣一來,我們從外部看Student類,就只需要知道,創建實例需要給出name和score,而如何打印,
都是在Student類的內部定義的,這些數據和邏輯被“封裝”起來了,調用很容易,但卻不用知道內部實現的細節。

封裝的另一個好處是可以給Student類增加新的方法,比如get_grade:
class Student(object):
...

def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
同樣的,get_grade方法可以直接在實例變量上調用,不需要知道內部實現細節:

>>> bart.get_grade()
'C'

小結

類是創建實例的模板,而實例則是一個一個具體的對象,各個實例擁有的數據都互相獨立,互不影響;
方法就是與實例綁定的函數,和普通函數不同,方法可以直接訪問實例的數據;
通過在實例上調用方法,我們就直接操作了對象內部的數據,但無需知道方法內部的實現細節。
和靜態語言不同,Python允許對實例變量綁定任何數據,也就是說,對於兩個實例變量,雖然它們都是同一個類的不
同實例,但擁有的變量名稱都可能不同:

>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

6.訪問限制
在Class內部,可以有屬性和方法,而外部代碼可以通過直接調用實例變量的方法來操作數據,
這樣,就隱藏了內部的復雜邏輯。但是,從前面Student類的定義來看,外部代碼還是可以自由
地修改一個實例的name、score屬性:
>>> bart = Student('Bart Simpson', 98)
>>> bart.score
98
>>> bart.score = 59
>>> bart.score
59

如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下划線__,在Python中,
實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問,所以,我們把Student類改一改:
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))

確保了外部代碼不能隨意修改對象內部的狀態,這樣通過訪問限制的保護,代碼更加健壯。
但是如果外部代碼要獲取name和score怎么辦?可以給Student類增加get_name和get_score這樣的方法
class Student(object):
...

def get_name(self):
return self.__name

def get_score(self):
return self.__score
如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:
class Student(object):
...

def set_score(self, score):
self.__score = score

在方法中,可以對參數做檢查,避免傳入無效的參數:
class Student(object):
...

def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')

需要注意的是,在Python中,變量名類似__xxx__的,也就是以雙下划線開頭,並且以雙下划線結尾的,是特殊變量,
特殊變量是可以直接訪問的,不是private變量,所以,不能用__name__、__score__這樣的變量名。有些時候,你會看到
以一個下划線開頭的實例變量名,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到
這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。

雙下划線開頭的實例變量是不是一定不能從外部訪問呢?其實也不是。不能直接訪問__name是因為Python解釋器對外
把__name變量改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變量:
>>> bart._Student__name
'Bart Simpson'
但是強烈建議你不要這么干,因為不同版本的Python解釋器可能會把__name改成不同的變量名。

7.繼承和多態
判斷一個變量是否是某個類型可以用isinstance()判斷:
>>> isinstance(a, list)
True



class Animal(object):
def run(self):
print('Animal is running...')

class Dog(Animal):

def run(self):
print('Dog is running...')

def eat(self):
print('Eating meat...')


class Cat(Animal):

def run(self):
print('Cat is running...')

>>> isinstance(c, Animal)


def run_twice(animal):
animal.run()
animal.run()

>>> run_twice(Animal())
>>> run_twice(Dog())
>>> run_twice(Cat())

class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')


>>> run_twice(Tortoise())

新增一個Animal的子類,不必對run_twice()做任何修改,實際上,任何依賴Animal作為參數的函數或者方法都可以不加修改地正常運行,原因就在於多態。


多態的好處就是,當我們需要傳入Dog、Cat、Tortoise……時,我們只需要接收Animal類型就可以了,
因為Dog、Cat、Tortoise……都是Animal類型,然后,按照Animal類型進行操作即可。由於Animal類型有run()方法,
因此,傳入的任意類型,只要是Animal類或者子類,就會自動調用實際類型的run()方法,這就是多態的意思:

對於一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,
而具體調用的run()方法是作用在Animal、Dog、Cat還是Tortoise對象上,由運行時該對象的確切類型決定,
這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,
不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
對擴展開放:允許新增Animal子類;
對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。
繼承還可以一級一級地繼承下來,就好比從爺爺到爸爸、再到兒子這樣的關系。而任何類,最終都可以追溯到根類object,這些繼承關系看上去就像一顆倒着的樹。比如如下的繼承樹:


8.靜態語言 vs 動態語言
對於靜態語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調用run()方法。
對於Python這樣的動態語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:
class Timer(object):
def run(self):
print('Start...')
這就是動態語言的“鴨子類型”,它並不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。

Python的“file-like object“就是一種鴨子類型。對真正的文件對象,它有一個read()方法,返回其內容。但是,許多對象,
只要有read()方法,都被視為“file-like object“。許多函數接收的參數就是“file-like object“,你不一定要傳入真正
的文件對象,完全可以傳入任何實現了read()方法的對象。

小結

繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫。

動態語言的鴨子類型特點決定了繼承不像靜態語言那樣是必須的。

9.獲取對象信息
當我們拿到一個對象的引用時,如何知道這個對象是什么類型、有哪些方法呢?
使用type()
使用isinstance()
使用dir()


使用type()
基本類型都可以用type()判斷:
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
如果一個變量指向函數或者類,也可以用type()判斷:
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
但是type()函數返回的是什么類型呢?它返回對應的Class類型。如果我們要在if語句中判斷,就需要比較兩個變量的type類型是否相同:
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

判斷基本數據類型可以直接寫int,str等,但如果要判斷一個對象是否是函數怎么辦?可以使用types模塊中定義的常量:
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

使用isinstance()
isinstance()判斷的是一個對象是否是該類型本身,或者位於該類型的父繼承鏈上
對於class的繼承關系來說,使用type()就很不方便。我們要判斷class的類型,可以使用isinstance()函數。
我們回顧上次的例子,如果繼承關系是: object -> Animal -> Dog -> Husky
那么,isinstance()就可以告訴我們,一個對象是否是某種類型。先創建3種類型的對象:
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

然后,判斷:
>>> isinstance(h, Husky)
True

能用type()判斷的基本類型也可以用isinstance()判斷:
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
並且還可以判斷一個變量是否是某些類型中的一種,比如下面的代碼就可以判斷是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

使用dir()
如果要獲得一個對象的所有屬性和方法,可以使用dir()函數,它返回一個包含字符串的list,
比如,獲得一個str對象的所有屬性和方法:
>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__',
'__hash__', '__init__', '__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']

類似__xxx__的屬性和方法在Python中都是有特殊用途的,比如__len__方法返回長度。在Python中,
如果你調用len()函數試圖獲取一個對象的長度,實際上,在len()函數內部,它自動去調用該對象的__len__()方法,
所以,下面的代碼是等價的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3

我們自己寫的類,如果也想用len(myObj)的話,就自己寫一個__len__()方法:
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100

僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態:
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()

測試對象屬性:
>>> hasattr(obj, 'x') # 有屬性'x'嗎?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
False
>>> setattr(obj, 'y', 19) # 設置一個屬性'y'
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
True
>>> getattr(obj, 'y') # 獲取屬性'y'
19
>>> obj.y # 獲取屬性'y'
19
如果試圖獲取不存在的屬性,會拋出AttributeError的錯誤:
>>> getattr(obj, 'z') # 獲取屬性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'
以傳入一個default參數,如果屬性不存在,就返回默認值:
>>> getattr(obj, 'z', 404) # 獲取屬性'z',如果不存在,返回默認值404
404
也可以獲得對象的方法:
>>> hasattr(obj, 'power') # 有屬性'power'嗎?
True
>>> getattr(obj, 'power') # 獲取屬性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 獲取屬性'power'並賦值到變量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 調用fn()與調用obj.power()是一樣的
81

小結
通過內置的一系列函數,我們可以對任意一個Python對象進行剖析,拿到其內部的數據。要注意的是,只有在不知道對象信息的時候,我們才會去獲取對象信息。如果可以直接寫:
sum = obj.x + obj.y
就不要寫:
sum = getattr(obj, 'x') + getattr(obj, 'y')
一個正確的用法的例子如下:
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
假設我們希望從文件流fp中讀取圖像,我們首先要判斷該fp對象是否存在read方法,如果存在,則該對象是一個流,如果不存在,則無法讀取。hasattr()就派上了用場。

請注意,在Python這類動態語言中,根據鴨子類型,有read()方法,不代表該fp對象就是一個文件流,它也可能是網絡流,也可能是內存中的一個字節流,但只要read()方法返回的是有效的圖像數據,就不影響讀取圖像的功能。

10.實例屬性和類屬性
由於Python是動態語言,根據類創建的實例可以任意綁定屬性。給實例綁定屬性的方法是通過實例變量,或者通過self變量:
class Student(object):
def __init__(self, name):
self.name = name

s = Student('Bob')
s.score = 90

但是,如果Student類本身需要綁定一個屬性呢?可以直接在class中定義屬性,這種屬性是類屬性,歸Student類所有:
class Student(object):
name = 'Student

當我們定義了一個類屬性后,這個屬性雖然歸類所有,但類的所有實例都可以訪問到。來測試一下
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 創建實例s
>>> print(s.name) # 打印name屬性,因為實例並沒有name屬性,所以會繼續查找class的name屬性
Student
>>> print(Student.name) # 打印類的name屬性
Student
>>> s.name = 'Michael' # 給實例綁定name屬性
>>> print(s.name) # 由於實例屬性優先級比類屬性高,因此,它會屏蔽掉類的name屬性
Michael
>>> print(Student.name) # 但是類屬性並未消失,用Student.name仍然可以訪問
Student
>>> del s.name # 如果刪除實例的name屬性
>>> print(s.name) # 再次調用s.name,由於實例的name屬性沒有找到,類的name屬性就顯示出來了
Student

從上面的例子可以看出,在編寫程序的時候,千萬不要把實例屬性和類屬性使用相同的名字,因為相同名稱的實例屬性將屏蔽掉類屬性,
但是當你刪除實例屬性后,再使用相同的名稱,訪問到的將是類屬性。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM