python高级属性 用法 编程_python高级编程之面向对象高级编程
1 面向?qū)ο缶幊?/p>
面向?qū)ο筮@節(jié)比較簡(jiǎn)單,就稍微總結(jié)幾個(gè)特殊的點(diǎn)。
特殊方法__init__前后分別有兩個(gè)下劃線,__init__方法的第一個(gè)參數(shù)永遠(yuǎn)是self,表示創(chuàng)建的實(shí)例本身,因此,在__init__方法內(nèi)部,就可以把各種屬性綁定到self,因?yàn)閟elf就指向創(chuàng)建的實(shí)例本身。
有了__init__方法,在創(chuàng)建實(shí)例的時(shí)候,就不能傳入空的參數(shù)了,必須傳入與__init__方法匹配的參數(shù),但self不需要傳,Python解釋器自己會(huì)把實(shí)例變量傳進(jìn)去。
如果要讓內(nèi)部屬性不被外部訪問,可以把屬性的名稱前加上兩個(gè)下劃線__,在Python中,實(shí)例的變量名如果以__開頭,就變成了一個(gè)私有變量(private),只有內(nèi)部可以訪問,外部不能訪問。
需要注意的是,在Python中,變量名類似__xxx__的,也就是以雙下劃線開頭,并且以雙下劃線結(jié)尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用__name__、__score__這樣的變量名。
多態(tài):當(dāng)我們需要傳入Dog、Cat、Tortoise……時(shí),我們只需要接收Animal類型就可以了,因?yàn)镈og、Cat、Tortoise……都是Animal類型,然后,按照Animal類型進(jìn)行操作即可。由于Animal類型有run()方法,因此,傳入的任意類型,只要是Animal類或者子類,就會(huì)自動(dòng)調(diào)用實(shí)際類型的run()方法,這就是多態(tài)。
“鴨子特性”:對(duì)于靜態(tài)語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對(duì)象必須是Animal類型或者它的子類,否則,將無法調(diào)用run()方法。
對(duì)于Python這樣的動(dòng)態(tài)語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對(duì)象有一個(gè)run()方法就可以了。
2 面向?qū)ο蟾呒?jí)編程
2.1 獲取對(duì)象信息
2.1.1 type()
判斷對(duì)象類型,使用type()函數(shù):
>>> type(123)
>>> type('str')
>>> type(None)
如果要判斷一個(gè)對(duì)象是否是函數(shù)可以使用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
對(duì)于class的繼承關(guān)系來說,使用type()就很不方便。我們要判斷class的類型,可以使用isinstance()函數(shù)。
2.1.2 dir()
如果要獲得一個(gè)對(duì)象的所有屬性和方法,可以使用dir()函數(shù),它返回一個(gè)包含字符串的list,比如,獲得一個(gè)str對(duì)象的所有屬性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
類似__xxx__的屬性和方法在Python中都是有特殊用途的,比如__len__方法返回長(zhǎng)度。在Python中,如果你調(diào)用len()函數(shù)試圖獲取一個(gè)對(duì)象的長(zhǎng)度,實(shí)際上,在len()函數(shù)內(nèi)部,它自動(dòng)去調(diào)用該對(duì)象的__len__()方法,所以,下面的代碼是等價(jià)的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個(gè)對(duì)象的狀態(tài):
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
緊接著,可以測(cè)試該對(duì)象的屬性:
>>> hasattr(obj, 'x') # 有屬性'x'嗎?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
False
>>> setattr(obj, 'y', 19) # 設(shè)置一個(gè)屬性'y'
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
True
>>> getattr(obj, 'y') # 獲取屬性'y'
19
>>> obj.y # 獲取屬性'y'
19
如果試圖獲取不存在的屬性,會(huì)拋出AttributeError的錯(cuò)誤:
>>> getattr(obj, 'z') # 獲取屬性'z'
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'MyObject' object has no attribute 'z'
可以傳入一個(gè)default參數(shù),如果屬性不存在,就返回默認(rèn)值:
>>> getattr(obj, 'z', 404) # 獲取屬性'z',如果不存在,返回默認(rèn)值404
404
也可以獲得對(duì)象的方法:
>>> hasattr(obj, 'power') # 有屬性'power'嗎?
True
>>> getattr(obj, 'power') # 獲取屬性'power'
>
>>> fn = getattr(obj, 'power') # 獲取屬性'power'并賦值到變量fn
>>> fn # fn指向obj.power
>
>>> fn() # 調(diào)用fn()與調(diào)用obj.power()是一樣的
81
2.2 使用__slots__
如果我們想要限制實(shí)例的屬性怎么辦?比如,只允許對(duì)Student實(shí)例添加name和age屬性。
為了達(dá)到限制的目的,Python允許在定義class的時(shí)候,定義一個(gè)特殊的__slots__變量,來限制該class實(shí)例能添加的屬性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
然后,我們?cè)囋?#xff1a;
>>> s = Student() # 創(chuàng)建新的實(shí)例
>>> s.name = 'Michael' # 綁定屬性'name'
>>> s.age = 25 # 綁定屬性'age'
>>> s.score = 99 # 綁定屬性'score'
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Student' object has no attribute 'score'
2.3 使用@property
在綁定屬性時(shí),如果我們直接把屬性暴露出去,雖然寫起來很簡(jiǎn)單,但是沒辦法檢查參數(shù),導(dǎo)致可以隨便給屬性賦值。
對(duì)于類的方法,裝飾器一樣起作用。Python內(nèi)置的@property裝飾器就是負(fù)責(zé)把一個(gè)方法變成屬性調(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的使用方法是:把一個(gè)getter方法變成屬性,只需要加上@property就可以了,此時(shí),@property本身又創(chuàng)建了另一個(gè)裝飾器@score.setter,負(fù)責(zé)把一個(gè)setter方法變成屬性賦值,于是,我們就擁有一個(gè)可控的屬性操作。
>>> s = Student()
>>> s.score = 60 # OK,實(shí)際轉(zhuǎn)化為s.set_score(60)
>>> s.score # OK,實(shí)際轉(zhuǎn)化為s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
注意到這個(gè)神奇的@property,我們?cè)趯?duì)實(shí)例屬性操作的時(shí)候,就知道該屬性很可能不是直接暴露的,而是通過getter和setter方法來實(shí)現(xiàn)的。
還可以定義只讀屬性,只定義getter方法,不定義setter方法就是一個(gè)只讀屬性。
2.4 多重繼承
在設(shè)計(jì)類的繼承關(guān)系時(shí),通常,主線都是單一繼承下來的,例如,Ostrich繼承自Bird。但是,如果需要“混入”額外的功能,通過多重繼承就可以實(shí)現(xiàn),比如,讓Ostrich除了繼承自Bird外,再同時(shí)繼承Runnable。Python允許使用多重繼承,這種設(shè)計(jì)通常稱之為MixIn。
MixIn的目的就是給一個(gè)類增加多個(gè)功能,這樣,在設(shè)計(jì)類的時(shí)候,我們優(yōu)先考慮通過多重繼承來組合多個(gè)MixIn的功能,而不是設(shè)計(jì)多層次的復(fù)雜的繼承關(guān)系。
2.5 定制類
后面用到實(shí)際例子時(shí)再補(bǔ)充。
2.6 使用枚舉類
當(dāng)我們需要定義常量時(shí),一個(gè)辦法是用大寫變量通過整數(shù)來定義,例如月份:
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
好處是簡(jiǎn)單,缺點(diǎn)是類型是int,并且仍然是變量。
更好的方法是為這樣的枚舉類型定義一個(gè)class類型,然后,每個(gè)常量都是class的一個(gè)唯一實(shí)例。Python提供了Enum類來實(shí)現(xiàn)這個(gè)功能:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
這樣我們就獲得了Month類型的枚舉類,可以直接使用Month.Jan來引用一個(gè)常量,或者枚舉它的所有成員:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
value屬性則是自動(dòng)賦給成員的int常量,默認(rèn)從1開始計(jì)數(shù)。
如果需要更精確地控制枚舉類型,可以從Enum派生出自定義類:
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ù)值。
2.7 使用元類
type()函數(shù)可以查看一個(gè)類型或變量的類型,例如Hello是一個(gè)class,它的類型就是type,而h是一個(gè)實(shí)例,它的類型就是class Hello。type()函數(shù)既可以返回一個(gè)對(duì)象的類型,又可以創(chuàng)建出新的類型,比如,我們可以通過type()函數(shù)創(chuàng)建出Hello類,而無需通過class Hello(object)...的定義。
我們說class的定義是運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的,而創(chuàng)建class的方法就是使用type()函數(shù)。通過type()函數(shù)創(chuàng)建的類和直接寫class是完全一樣的,因?yàn)镻ython解釋器遇到class定義時(shí),僅僅是掃描一下class定義的語法,然后調(diào)用type()函數(shù)創(chuàng)建出class:
>>> 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()
Hello, world.
>>> print(type(Hello))
>>> print(type(h))
要?jiǎng)?chuàng)建一個(gè)class對(duì)象,type()函數(shù)依次傳入3個(gè)參數(shù):
class的名稱;
繼承的父類集合,注意Python支持多重繼承,如果只有一個(gè)父類,別忘了tuple的單元素寫法;
class的方法名稱與函數(shù)綁定,這里我們把函數(shù)fn綁定到方法名hello上。
2.7.1 metaclass
除了使用type()動(dòng)態(tài)創(chuàng)建類以外,要控制類的創(chuàng)建行為,還可以使用metaclass。metaclass,直譯為元類,簡(jiǎn)單的解釋就是:當(dāng)我們定義了類以后,就可以根據(jù)這個(gè)類創(chuàng)建出實(shí)例,所以:先定義類,然后創(chuàng)建實(shí)例。
但是如果我們想創(chuàng)建出類呢?那就必須根據(jù)metaclass創(chuàng)建出類,所以:先定義metaclass,然后創(chuàng)建類。連接起來就是:先定義metaclass,就可以創(chuàng)建類,最后創(chuàng)建實(shí)例。
所以,metaclass允許你創(chuàng)建類或者修改類。換句話說,你可以把類看成是metaclass創(chuàng)建出來的“實(shí)例”。
先看一個(gè)簡(jiǎn)單的例子,這個(gè)metaclass可以給我們自定義的MyList增加一個(gè)add方法。定義ListMetaclass,按照默認(rèn)習(xí)慣,metaclass的類名總是以Metaclass結(jié)尾,以便清楚地表示這是一個(gè)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,我們?cè)诙x類的時(shí)候還要指示使用ListMetaclass來定制類,傳入關(guān)鍵字參數(shù)metaclass:
class MyList(list, metaclass=ListMetaclass):
pass
當(dāng)我們傳入關(guān)鍵字參數(shù)metaclass時(shí),魔術(shù)就生效了,它指示Python解釋器在創(chuàng)建MyList時(shí),要通過ListMetaclass.__new__()來創(chuàng)建,在此,我們可以修改類的定義,比如,加上新的方法,然后,返回修改后的定義。__new__()方法接收到的參數(shù)依次是:
當(dāng)前準(zhǔn)備創(chuàng)建的類的對(duì)象;
類的名字;
類繼承的父類集合;
類的方法集合。
測(cè)試一下MyList是否可以調(diào)用add()方法:
>>> L = MyList()
>>> L.add(1)
>> L
[1]
而普通的list沒有add()方法:
>>> L2 = list()
>>> L2.add(1)
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'list' object has no attribute 'add'
使用元類這塊比較復(fù)雜,等樓主后面涉及到這塊的實(shí)際例子再來補(bǔ)充這塊。
總結(jié)
以上是生活随笔為你收集整理的python高级属性 用法 编程_python高级编程之面向对象高级编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面积积分_袁颖妍:用定理积分求平面区域面
- 下一篇: 域名带后缀_[Python 爬虫]获取顶