最新彩版:http://www.javashuo.com/article/p-purcxavn-d.htmlhtml
在線編程:https://mybinder.org/v2/gh/lotapp/BaseCode/master
在線預覽:http://github.lesschina.com/python/base/oop/1.封裝.html
python
此次儘可能用故事模式來說知識,上次剛說美化,此次算是第一篇了。步入正題:git
類的組成:類名、屬性(沒有字段)、方法github
# 類名首字母大寫
class Student(object):
"""建立一個學生類"""
# 沒有屬性定義,直接使用便可
# 定義一個方法,方法裏面必須有self(至關於C#的this)
def show(self):
print("name:%s age:%d"%(self.name,self.age))
# 實例化一個張三
zhangsan=Student()
# 給name,age屬性賦值
zhangsan.name="張三"
zhangsan.age=22
# 調用show方法
zhangsan.show()
# 打印一下類和類的實例
print(Student)
print(zhangsan) #張三實例的內存地址:0x7fb6e8502d30
和靜態語言不一樣,Python容許對實例變量綁定任何數據 ==> 對於兩個實例變量,雖然它們都是同一個類的不一樣實例,但擁有的變量名稱可能都不一樣編程
說的比較抽象,舉個例子就明瞭了:安全
xiaoming=Student("小明",22)
xiaoming.mmd="mmd"
print(xiaoming.mmd)
# 小明和小潘都是Student類,可是小明有的mmd屬性,小潘卻沒有
xiaopan=Student("小潘",22)
print(xiaopan.mmd)
建立對象後,python解釋器默認調用__init__方法,對必要字段進行初始化賦值app
須要注意的是:__init__並非C#中的構造函數,__new__ (後面會說) + __init__ 等價於構造函數less
第一個參數和類的其餘方法同樣,都是self(至關於C#裏面的this,表示建立的實例自己)調用的時候直接忽略它編輯器
class Student(object):
# 初始化賦值
def __init__(self,name,age):
self.name=name
self.age=age
def show(self):
print("name:%s age:%d"%(self.name,self.age))
# 有了__init__方法,在建立實例的時候,就不能傳入空的參數了
lisi=Student()
# 建立一個正確的實例
xiaowang=Student("小王",22)
xiaowang.show()
在print(類名)的時候自定義輸出ide
這個有點像C#類裏面重寫ToString,eg:
public override string ToString()
{
return "Name:" + this.Name + " Age:" + this.Age;
}
# Python的__str__()方法
class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age
# self別忘記寫了,return也別忘了
def __str__(self):
return "姓名:%s,年齡:%s" % (self.name, self.age)
lisi = Student("李四", 22)
print(lisi) #如今打印就是你DIV的輸出了
C#、Java裏面都是有訪問修飾符的,Python呢?
Python規定,若是以雙下劃線__開頭的屬性或者方法就是私有的
變量名相似xxx的,也就是以雙下劃線開頭,而且以雙下劃線結尾的,是特殊變量。特殊變量是能夠直接訪問的,不是private變量
在說私有屬性前,咱們來個案例說說屬性不私有的弊端,eg:
小明同窗學了點C#,而後學習了上面的知識,心想 ~ Python這麼搞安全性呢?不行,我得構造構造,因而有了下面的代碼:
class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
return self.name
def set_name(self, name):
self.name = name
def get_age(self):
return self.age
def set_age(self, age):
if age > 0:
self.age = age
else:
print("age must > 0")
def show(self):
print("name:%s,age:%d" % (self.name, self.age))
小明心想,想要修改age屬性,你經過set_age我就能夠判斷了哇,仍是本寶寶聰明
這時候小潘過來了,淡淡的一笑,看我怎麼破了你 ~ 看代碼:
zhangsan = Student("張三", -20)
zhangsan.show() # name:張三,age:-20
zhangsan.age = -1 # set_age方法形同虛設,我徹底能夠直接訪問字段了
zhangsan.show() # name:張三,age:-1
小潘傲氣的說道~大叔,給你臉呢。我就是不去訪問你設定的方法怎麼滴呢?
小明急的啊,趕忙去找偉哥求經。不一會,傲氣的貼出本身的New Code,心想着我私有屬性都用上了還怕個毛毛:
class Student(object):
def __init__(self, name, age):
self.__name = name
# 通常須要用到的屬性都直接放在__init__裏面了
# self.__age = age
self.set_age(age)
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
def get_age(self):
return self.__age
def set_age(self, age):
if age > 0:
self.__age = age
else:
print("age must > 0")
def show(self):
print("name:%s,age:%s" % (self.__name, self.__age))
小潘冷笑道~呵呵,而後使用了上次的絕招:
zhangsan = Student("張三", -20)
zhangsan.__age = -1 # 一樣的代碼,只是屬性前面加了下劃線
zhangsan.show()
此次小潘同志傻眼了,徹底不能訪問了啊?不行,怎麼能被小明大叔笑話呢?
因而上網翻資料,國內不行就國外,外文很差就翻譯,終於找到一個新破解方式:
雙下劃線開頭的實例變量不能直接訪問,是由於Python解釋器對外把__age變量改爲了_Studentage,因此,仍然能夠經過**_Studentage**來訪問:
# 搞事情
zhangsan._Student__age = -1
zhangsan.show()
建議你不要這麼幹,不一樣版本的Python解釋器可能會把__age改爲不一樣的變量名
有些時候,你會看到以一個下劃線開頭的實例變量名,好比_age這樣的實例變量,外部是能夠訪問的。
可是,請把它視爲私有變量,不要隨意訪問(Python不少東西全憑自覺~捂臉@_@)
小潘終於長嘆一口氣,而後還不忘取笑小明同窗~你這屬性搞的,真麻煩,老是經過方法調用,太累了 <_> 鄙視!
這可把小明急的啊,學習的積極性都沒有了,吃了碗牛肉麪就去偉哥那邊好好取經了~
# 私有方法一筆帶過
class Student(object):
"""私有方法"""
def __go_home(self):
pass
zhangsan = Student()
zhangsan.__go_home() # 訪問不到
Python內置的@property
裝飾器就是負責把一個方法變成屬性調用的,來個例子
class Student(object):
def __init__(self, name, age):
# 通常須要用到的屬性都直接放在__init__裏面了
self.name = name
self.age = age
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if age > 0:
self.__age = age
else:
print("age must > 0")
def show(self):
print("name:%s,age:%s" % (self.name, self.age))
xiaoming = Student("小明", 22)
xiaoming.name = "小潘"
xiaoming.age = -2
xiaoming.show()
把一個getter方法變成屬性,只須要加上@property
就能夠了
@方法名.setter
,負責把一個setter方法變成屬性賦值
固然了,若是隻想讀 ==> 就只打上@property
標籤
小明同窗高興壞了,趕忙大吃了一頓~
建立對象後,python解釋器默認調用__init__() 方法
當刪除一個對象時,python解釋器也會默認調用__del__() 方法(有點析構函數的味道)
當有1個變量保存了對象的引用時,此對象的引用計數就會加1
當使用del刪除變量指向的對象時,若是對象的引用計數不爲1,那麼每次刪除計數減1,當計數爲1的時候再調del就真把對象刪了
這個能夠結合我以前說過的連接來理解:於連接文件的探討
看着老師誇誇其談,小明愣了楞,摸摸肚子想到,真BB,我先搞個例子練練:
# 定義一個臨時類
class Temp(object):
def __del__(self):
print("你被幹掉了")
驗證方面用編輯器比較合適,交互模式下可能不是真正的結果
# 對象被s1和s2引用
s1 = Temp()
s2 = s1
del s1 # 只刪除s1,新建立的對象並無被刪除
print("-" * 10)
輸出:(最後的被幹掉是程序退出了)
# ---------- # 你被幹掉了
若是用連接來解釋就是這樣的:
此次兩個都刪掉:
t1 = Temp()
t2 = t1
del t1
del t2
print("-" * 10)
輸出:
# 你被幹掉了 # ----------
都刪了,天然就真刪掉了
這樣搞比較麻煩,咱們引入一下獲取引用個數:getrefcount(object也會佔1個引用計數)來個案例:
# 程序退出的時候,在他運行期間全部佔用資源歸還操做系統
# 引用計數
import sys
t1 = Temp()
print(sys.getrefcount(t1)) #(結果比實際引用大1)【object也會佔1個引用計數】
t2 = t1
print(sys.getrefcount(t1))
print(sys.getrefcount(t2))
del t1
print(sys.getrefcount(t2))
# sys.getrefcount(t1)#被刪掉天然沒有了
del t2
print("-" * 10)
運行結果:
2 3 3 2 你被幹掉了 ----------
我再貼一種狀況,你能夠思考下爲啥:
t1 = Temp()
t2 = Temp()
del t1
del t2
print("-" * 10)
輸出:
# 你被幹掉了 # 你被幹掉了 # ----------
小潘扭過頭瞅了一眼說道:「大叔,你__new__忘記寫案例了」
小明一愣神,立馬反應過來講:「我這叫謀然後動~」
當你實例化一個對象的時候,就會執行new 方法裏面的方法。new方法在類定義中不是必須寫的,若是沒定義,默認會調用object.new去建立一個對象
__new__方法中至少要有一個參數cls,表明要實例化的類,此參數在實例化時由Python解釋器自動提供
__new__方法中必需要有返回值(返回實例化出來的實例)
小明翻閱了官方文檔,淡定的打下了以下標準格式的代碼:
class Dog(object):
def __init__(self, name):
self.name = name
print("初始化完畢")
def __str__(self):
return "Dog的名字叫:%s" % self.name
def __new__(cls, name):
# 注意參數,是cls,而後其餘參數和init保持一致便可
print("建立對象完畢")
# 別忘記寫返回值哦
return object.__new__(cls)
def main():
happy = Dog("Happy")
print(happy)
if __name__ == '__main__':
main()
關於__name__在模塊調用的時候會詳細說,你能夠先這樣理解:若是直接運行py文件就執行,別人調用那麼你的main就不執行了
標準寫法:
# 1.導入的模塊
# 2.class的定義
# 3.其餘方法定義
def main():
pass
if __name__ == '__main__':
main()
其餘內容後面會繼續說,封裝部分再說說靜態方法和類方法之類的就結束了(和C#仍是有很大區別的)
小明問老師:「老師老師,怎麼沒有靜態類,靜態屬性之類的東西呢?」
老師笑而不語道:「小傢伙原來不只僅是體重增長啊,這求知慾也是大大的增長呢 ~ 且聽我慢慢道來」
類在程序裏面也是對象(你姑且能夠認爲全部的類都相似於C#裏面的靜態類),而經過類實例化的對象,叫實例化對象
實例屬性 --> 實例對象相互之間不共享 通常咱們都是在__init__
中定義
類屬性(相似於C#裏面的靜態字段) --> 屬於類對象,多個實例對象之間共享
注意一下:相同名稱的實例屬性將屏蔽掉類屬性(儘可能別同名)
類屬性除了能夠經過 類名.類屬性 訪問外,還能夠直接 實例對象.類屬性 (C#中抽象類和靜態類是不能被實例化的)
來個案例更直觀點:
class Person(object):
# age爲類屬性
age = 1
def __init__(self, name):
# name爲實例屬性
self.name = name
def main():
# 類名.類屬性
print(Person.age)
xiaoming = Person("小明")
# 對象.類屬性
print(xiaoming.age)
if __name__ == '__main__':
main()
若是須要在類外 修改類屬性,必須經過類對象去引用而後進行修改
若是經過實例對象去引用,會產生一個同名的實例屬性,這種方式修改的是實例屬性,不會影響到類屬性
若是經過實例對象去引用該名稱的屬性,實例屬性會強制 屏蔽掉類屬性,即引用的是實例屬性,除非del了該實例屬性才能正常訪問類屬性
你能夠理解爲,Python這麼作只是爲了方便你獲取,該怎麼修改還得怎麼作。來看個案例:
class Person(object):
# age爲類屬性
age = 1
def __init__(self, name):
# name爲實例屬性
self.name = name
def main():
# 類名.類屬性
print(Person.age)
# 經過對象.類屬性修改
xiaoming = Person("小明")
xiaoming.age = 100
print(xiaoming.age) # 其實,並無修改爲功,只是產生了一個同名age
print(Person.age) # 對吧,類屬性並無被修改
# 經過類名修改
Person.age = 22 # 若是須要在類外修改類屬性,必須經過類對象去引用而後進行修改
print(xiaoming.age) # 剛纔已經建立一個同名age,因此如今顯示的是剛纔的值
print(Person.age) # 經過類名.類屬性 就能夠看到值被修改了
# 若是你仍是不信,能夠建立一個新對象看看
xiaopan = Person("小潘")
print(xiaopan.age)
# xiaoming實例對象想訪問怎麼辦?
# 除非del了該實例屬性才能正常訪問類屬性
del xiaoming.age
print(xiaoming.age) # 這時候訪問的就是 類屬性 了
if __name__ == '__main__':
main()
先說說 實例方法,實例方法第一個定義的參數只能是實例自己引用self
,只能經過實例調用(就是咱們以前用的 def func_name(self,xxx):
)
類方法:是類對象所擁有的方法,須要用修飾器@classmethod
來標識,第一個參數必須是類對象cls
,能夠經過類或者實例直用
靜態方法:定義靜態方法使用裝飾器@staticmethod
,沒有默認的必須參數,經過類和實例直接調用
靜態方法中不須要額外定義參數,所以在靜態方法中引用類屬性的話,必須經過 類對象來引用(訪問)
小明眼花繚亂的對老師說道,老師給我看幾個案例吧:
class Dog(object):
# 類屬性
name = "小汪"
# 實例方法
def __init__(self, age):
# 實例屬性
self.age = age
# 打印看看
print("self id:%s" % id(self))
# 類方法
@classmethod
def show_name(cls):
# 訪問類屬性 cls.xxx
print("我叫%s" % cls.name)
# 打印看看
print("cls id:%s" % id(cls))
# 靜態方法
@staticmethod
def say_hello():
print("汪汪汪")
def main():
# 類名方式訪問
Dog.show_name()
Dog.say_hello() # 類名的方式能夠訪問靜態方法
# 實例對象方式訪問
dog = Dog(2)
dog.show_name()
dog.say_hello()
if __name__ == '__main__':
main()
通常都是這樣用的(供參考):
實例方法:通常平時用的都是它
類方法:類方法用在模擬C#多個構造函數(Python裏面不能有同名函數) or 你須要 對類屬性、類方法操做之類的
靜態方法:通常 都是獨立功能,相似於函數,只不過在面向對象裏面通常這麼用
C#面向對象比較優美,來個封裝的案例基本上就搞定了:
using System;
namespace _1Encapsulation
{
public class Student
{
/// <summary>
/// 字段
/// </summary>
private int _age;
/// <summary>
/// 屬性
/// </summary>
public int Age
{
get
{
return _age;
}
set
{
if (value > 1)
{
_age = value;
}
}
}
/// <summary>
/// 自動化屬性
/// </summary>
public string Name { get; set; }
/// <summary>
/// 自動屬性必需要有get訪問器
/// </summary>
public string SNum { get; }
private int _gender;
public int Gender
{
set
{
_gender = value;
}
}
/// <summary>
/// 構造函數的名字必須與類名一致
/// 構造函數沒有返回值也沒有viod
/// 默認自動生成一個無參構造函數,當有一個有參構造函數的時候無參構造函數便不會自動建立
/// </summary>
public Student() { }
/// <summary>
/// 有參構造函數
/// </summary>
/// <param name="name"></param>
/// <param name="age"></param>
public Student(string name, int age)
{
this.Name = name;
this.Age = age;
}
/// <summary>
/// this調用當前類的某個有參構造函數
/// </summary>
/// <param name="name"></param>
/// <param name="age"></param>
/// <param name="gender"></param>
public Student(string name, int age, int gender) : this(name, age)
{
this.Gender = gender;
}
/// <summary>
/// 某個方法
/// </summary>
public void Show()
{
Console.WriteLine("Name:" + this.Name + " Age:" + this.Age + "\n");
}
public override string ToString()
{
return "Name:" + this.Name + " Age:" + this.Age;
}
}
}
調用部分:
using System;
namespace _1Encapsulation
{
class Program
{
static void Main(string[] args)
{
Student s = new Student() { Name = "mmd", Age = 13, Gender = 1 };
s.Show();
Student s1 = new Student("dmm", 20);
s1.Show();
Console.WriteLine(s);
}
}
}