<!DOCTYPE html>
python裏有一個祖先類object的內置類型,他是全部內置類型的共同祖先,也是全部沒有指定父類的自定義類的祖先。當須要實現與某個內置類型具備類似行爲的類時,最好使用子類化內置類型。
例如,咱們想使用dict,但咱們不但願有相同的值存在字典中,這時咱們就自定義一個類
html
class MyDictError(ValueError):
""""有相同值時報錯"""
class Mydict(dict):
def setitem(self, key, value):
if value in self.values():
if(key in self and self[key]!=value) or key not in self:
raise MyDictError('不能有相同值')
super().__setitem__(key,value)
my=Mydict()
my['key']='value'
my['other_key']='value'
報錯:
raise MyDictError('不能有相同值')
main.MyDictError: 不能有相同值
正確的寫法
my=Mydict()
my['key']='value'
my['other_key']='value1'
print(my)
輸出
{'key': 'value', 'other_key': 'value1'}
super是一個內置類,可用於訪問屬於某個對象的超類的屬性,就是使用super能夠調用父類的方法 python
class Mama:
def says(self):
print('do your homework')
class Sister(Mama):
def says(self):
Mama.says(self)
print('and clean your bedroom')
s=Sister()
s.says()
class Sister(Mama):
def says(self):
super(Sister,self).says()
print('and clean your bedroom')
class Sister(Mama):
def says(self):
super().says()
print('and clean your bedroom')
若是super不在方法內部使用,那必須給出參數 git
s=Sister()
super(s.__class__,s).says()
當super只提供了一個參數,那麼super返回的是一個未綁定類型,與classmethod一塊兒使用特別合適,@classmethod類方法只能訪問類變量。不能訪問實例變量。 github
class Pizza:
def __init__(self,toppings):
self.toppints=toppings
def __repr__(self):
return "Pizza with "+" and ".join(self.toppints)
@classmethod
def recomend(cls):
return cls(['spam','ham','eggs'])
class VikingPizza(Pizza):
@classmethod
def recomend(cls):
recomended=super(VikingPizza,cls).recomend()
print(type(recomended))
recomended.toppints+=['spam']*5
return recomended
print(Pizza.recomend())
print(VikingPizza.recomend())
python方法解析順序是基於C3,C3是一個類的線性化(也叫優先級,即祖先的有序列表),這個列表用於屬性查找。C3序列化會挑選最接近的祖先的方法: web
class CommonBase:
def method(self):
print('CommonBase')
class Base1(CommonBase):
pass
class Base2(CommonBase):
def method(self):
print('Base2')
class MyClass(Base1,Base2):
pass
MyClass.__mro__
(<class '__main__.MyClass'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.CommonBase'>, <class 'object'>)
>>> MyClass.mro()
[<class '__main__.MyClass'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class '__main__.CommonBase'>, <class 'object'>]
類的mro屬性保存了線性化的計算結果 編程
class A(object):
def __init__(self):
print("A"," ")
super().__init__()
class B(object):
def __init__(self):
print("B"," ")
super().__init__()
class C(A,B):
def __init__(self):
print("C"," ")
A.__init__(self)
B.__init__(self)
C()
print(C.mro())
輸出結果
C
A
B
B
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
當C調用A.init(self)時,super(A,self).init()調用了B.init方法,產生了錯誤信息 canvas
這個問題就是在使用super初始化過程當中傳遞參數,若是沒有相同的簽名,就是傳遞的參數個數不一樣,會報錯 ruby
__author__ = 'Mr.Bool'
class CommonBase:
def __init__(self):
print('CommonBase')
super().__init__()
class Base1(CommonBase):
def __init__(self):
print('Base1')
super().__init__()
class Base2(CommonBase):
def __init__(self):
print('Base2')
super().__init__()
class MyClass(Base1,Base2):
def __init__(self,arg):
print('my base')
super().__init__(arg)
MyClass(10)
#使用super().__init__(arg)時,父類初始化沒有傳遞參數,觸發TypeError錯誤,解決方案就是使用魔法參數包裝構造方法
class CommonBase:
def __init__(self,*args,**kwargs):
print('CommonBase')
super().__init__()
class Base1(CommonBase):
def __init__(self,*args,**kwargs):
print('Base1')
super().__init__()
class Base2(CommonBase):
def __init__(self,*args,**kwargs):
print('Base2')
super().__init__()
class MyClass(Base1,Base2):
def __init__(self,arg):
print('my base')
super().__init__(arg)
MyClass(10)
#這樣作是解決了問題,但代碼變得太過脆弱,由於使得構造函數能夠接受任何參數,顯示使用特定類的__init__()調用能夠解決這個問題,但會引起混合super與顯示類的調用衝突
描述符容許自定義在一個引用對象的屬性身上,描述符是實現複雜屬性訪問的基礎,描述符自己是你自定義的一個類,定義了另外一個類的屬性的訪問方式
定義描述符類基於3個特殊方法,3個方法組成描述符協議
1. set(self,obj,type=None):在設置屬性時調用這一方法
2. get(self,obj,value):在讀取屬性時調用這一方法
3. delete(self,obj):對屬性調用del時將調用這一方法
實現了1,2兩個方法的描述符類被稱爲數據描述符,只實現了2的描述符類被稱爲非數據描述符
python中類對象的示例都有一個特殊方法getattribute(),每次調用示例對象的屬性或方法都會先調用這個方法,這個方法下的屬性查找順序是:
1. 屬性是否爲實例的類對象的數據描述符
2. 查看該屬性是否在對象的dict中
3. 查看該屬性是否爲實例的類對象的非數據描述符
優先級爲1,2,3 markdown
class RevealAccess(object):
"""一個數據描述符,正常設定值並返回值,同時打印記錄訪問的信息"""
def __init__(self,initval=None,name="var"):
self.val=initval
self.name=name
def __get__(self, instance, owner):
print('調用',self.name)
print(instance)
print(owner)
return self.val
def __set__(self, instance, value):
print('更新',self.name)
self.val=value
class MyClass(object):
x=RevealAccess(10,'var "x"')
y=5
m=MyClass()
print(m.x)
m.x=20
m.y=10
print(m.__dict__)
#輸出結果
調用 var "x"
<__main__.MyClass object at 0x000001D42CB11F60>
<class '__main__.MyClass'>
10
更新 var "x"
{'y': 10}
這樣看就明朗了,dict沒有x值,因此被數據描述符或非數據描述符修飾的屬性,不會在dict中顯示出來,每次查找屬性是都會調用描述符類的get()方法並返回它的值,每次對屬性賦值都會調用set()
描述符能夠將類屬性的初始化延遲到被實例訪問時,若是這些屬性的初始化依賴全局應用上下文或者初始化代價很大,可使用描述符解決
class InitOnAccess:
def __init__(self,klass,*args,**kwargs):
self.klass=klass
self.args=args
self.kwargs=kwargs
self._initialized=None
def __get__(self,instance,owner):
if self._initialized is None:
print("已初始化的")
self._initialized=self.klass(*self.args,**self.kwargs)
else:
print('內存中')
return self._initialized
class MyClass:
lazy_init=InitOnAccess(list,"argument")
m=MyClass()
m.lazy_init
m.lazy_init
#輸出結果
已初始化的
內存中
property是一個描述符類,它能夠將一個屬性連接到一組方法上,property接收4個可選參數:fget、fset、fdel和doc,函數參數順序就是這個,最後一個參數能夠用來連接到屬性的doc文檔
class Rectangle:
def __init__(self,x1,y1,x2,y2):
self.x1,self.x2=x1,x2
self.y1,self.y2=y1,y2
def _width_get(self):
return self.x2-self.x1
def _width_set(self,value):
self.x2=self.x1+value
def _height_get(self):
return self.y2-self.y1
def _height_set(self,value):
self.y2=self.y1+value
width=property(_width_get,_width_set,doc="矩形寬度")
height=property(_height_get,_height_set,doc='矩形高度')
def __repr__(self):
return "{}({},{},{},{})".format(self.__class__.__name__,self.x1,self.x2,self.y1,self.y2)
r=Rectangle(10,10,25,34)
print(r.width,r.height)
help(Rectangle)
r.width=100
print(r)
#輸出
15 24
Help on class Rectangle in module __main__:
class Rectangle(builtins.object)
| Methods defined here:
|
| __init__(self, x1, y1, x2, y2)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| height
| 矩形高度
|
| width
| 矩形寬度
Rectangle(10,110,10,34)
property在使用類的繼承時,所建立的屬性時利用當前類的方法實時建立,不會使用派生類中覆寫的方法
class MyRectangle(Rectangle):
def _width_get(self):
return "{} 米".format(self.x2-self.x1)
print(Rectangle(10,10,25,34).width)
print(MyRectangle(10,10,25,34).width)
#輸出結果
15
15
解決這個問題,須要在派生類中覆寫整個property
class MyRectangle(Rectangle):
def _width_get(self):
return "{} 米".format(self.x2-self.x1)
width=property(_width_get,Rectangle.width.fset,doc="矩形寬度")
print(Rectangle(10,10,25,34).width)
print(MyRectangle(10,10,25,34).width)
#輸出結果
15
15 米
以上存在一個問題就是寫派生類很麻煩容易出錯,因此使用property的最佳語法是使用property做裝飾器
class Rectangle:
def __init__(self,x1,y1,x2,y2):
self.x1,self.x2=x1,x2
self.y1,self.y2=y1,y2
@property
def width(self):
"矩形寬度"
return self.x2-self.x1
@width.setter
def width(self,value):
self.x2=self.x1+value
@property
def height(self):
"矩形高度"
return self.y2-self.y1
@height.setter
def height(self,value):
self.y2=self.y1+value
def __repr__(self):
return "{}({},{},{},{})".format(self.__class__.__name__,self.x1,self.x2,self.y1,self.y2)
class MyRectangle(Rectangle):
def width(self):
return "{} 米".format(self.x2-self.x1)
print(MyRectangle(0,0,10,10).width())
元編程是一種編寫計算機程序的技術,這些程序看做數據,你能夠在運行時進行修改,生成和內省
元編程的兩種主要方法:
1. 專一對基本元素內省的能力與實時創造和修改的能力,最簡單的工具是修飾器,容許向現有函數、方法或類中添加附加功能。
2. 類的特殊方法,容許修改類實例的建立過程,最強大的工具爲元類
這裏只說說類裝飾器,不大爲人熟知,請看以下實例
def short_repr(cls):
cls.__repr__=lambda self:super(cls,self).__repr__()[:12]
return cls
@short_repr
class ClassWithLongName:
pass
xcv
print(ClassWithLongName())
#輸出<__main__.Cl
上面實例展現出了類的好幾種特性
1. 在運行時能夠修改實例,也能夠修改類對象,ClassWithLongName的方法被修改了 2. 函數也是描述符,根據描述符協議,能夠在屬性查找時執行實際綁定的,添加到類中,repr添加到ClassWithLongName中
3. super能夠在類定義做用域外使用,傳入參數要正確
4. 類裝飾器能夠用於類的定義
修改上上面裝飾器
def short_repr(max_width=12):
"""縮短表示的參數化裝飾器"""
def short(cls):
"""內部包裝函數,是實際的裝飾器"""
class ShortName(cls):
"""提供裝飾器行爲的子類"""
def __repr__(self):
return super().__repr__()[:max_width]
return ShortName
return short
不過是用上面的裝飾器也會出現name,doc元數據發生變化的狀況,這個不能使用wrap裝飾器修改
由於new方法在init方法調用以前,因此使用new()方法能夠覆寫實例的建立過程,覆寫new()的實現將會使用合適的參數調用器超類的super().new(),並返回以前修改實例
class InstanceCountClass:
instance_created=0
def __new__(cls, *args, **kwargs):
print('__new__() 調用',cls,args,kwargs)
instance=super().__new__(cls)
instance.number=cls.instance_created
cls.instance_created+=1
return instance
def __init__(self,attr):
print('__init__()調用',self,attr)
self.attr=attr
i1=InstanceCountClass('abc')
i2=InstanceCountClass('xyz')
print(i1.number,i1.instance_created)
print(i2.number,i2.instance_created)
#輸出
__new__() 調用 <class '__main__.InstanceCountClass'> ('abc',) {}
__init__()調用 <__main__.InstanceCountClass object at 0x000001D9B29C1F98> abc
__new__() 調用 <class '__main__.InstanceCountClass'> ('xyz',) {}
__init__()調用 <__main__.InstanceCountClass object at 0x000001D9B29C8400> xyz
0 2
1 2
不調用init()方法
class NoInit(int): def __new__(cls,v): return super().__new__(cls,v) if v!=0 else None def __init__(self,value): print('調用__init__') super().__init__() print(type(NoInit(1))) print(type(NoInit(-1))) print(type(NoInit(0))) #輸出 調用__init__ <class '__main__.NoInit'> 調用__init__ <class '__main__.NoInit'> <class 'NoneType'>