Python3 與 C# 面向對象之~封裝

 

最新彩版: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

1.定義一個類

類的組成:類名、屬性(沒有字段)、方法github

1.1建立一個類

In [1]:
# 類名首字母大寫
class Student(object):
    """建立一個學生類"""
    # 沒有屬性定義,直接使用便可
    # 定義一個方法,方法裏面必須有self(至關於C#的this)
    def show(self):
        print("name:%s age:%d"%(self.name,self.age))
In [2]:
# 實例化一個張三
zhangsan=Student()
# 給name,age屬性賦值
zhangsan.name="張三"
zhangsan.age=22
# 調用show方法
zhangsan.show()
 
name:張三 age:22
In [3]:
# 打印一下類和類的實例
print(Student)
print(zhangsan) #張三實例的內存地址:0x7fb6e8502d30
 
<class '__main__.Student'>
<__main__.Student object at 0x7fe961195b70>
 

和靜態語言不一樣,Python容許對實例變量綁定任何數據 ==> 對於兩個實例變量,雖然它們都是同一個類的不一樣實例,但擁有的變量名稱可能都不一樣編程

說的比較抽象,舉個例子就明瞭了:安全

In [4]:
xiaoming=Student("小明",22)
xiaoming.mmd="mmd"
print(xiaoming.mmd)

# 小明和小潘都是Student類,可是小明有的mmd屬性,小潘卻沒有
xiaopan=Student("小潘",22)
print(xiaopan.mmd)
 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-500940527165> in <module>()
----> 1xiaoming=Student("小明",22)
      2 xiaoming.mmd="mmd"
      3 print(xiaoming.mmd)
      4 
      5 # 小明和小潘都是Student類,可是小明有的mmd屬性,小潘卻沒有

TypeError: object() takes no parameters
 

1.2使用__init__初始化賦值

建立對象後,python解釋器默認調用__init__方法,對必要字段進行初始化賦值app

須要注意的是:__init__並非C#中的構造函數,__new__ (後面會說) + __init__ 等價於構造函數less

第一個參數和類的其餘方法同樣,都是self(至關於C#裏面的this,表示建立的實例自己)調用的時候直接忽略它編輯器

In [5]:
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))
In [6]:
# 有了__init__方法,在建立實例的時候,就不能傳入空的參數了
lisi=Student()
 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-1ba88e24910b> in <module>()
      1 # 有了__init__方法,在建立實例的時候,就不能傳入空的參數了
----> 2lisi=Student()

TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
In [7]:
# 建立一個正確的實例
xiaowang=Student("小王",22)
xiaowang.show()
 
name:小王 age:22
 

1.3使用魔法方法__str__

在print(類名)的時候自定義輸出ide

這個有點像C#類裏面重寫ToString,eg:

public override string ToString()
{
    return "Name:" + this.Name + " Age:" + this.Age;
}
In [8]:
# 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)
In [9]:
lisi = Student("李四", 22)
print(lisi) #如今打印就是你DIV的輸出了
 
姓名:李四,年齡:22
 

1.4 私有屬性、方法

C#、Java裏面都是有訪問修飾符的,Python呢?

Python規定,若是以雙下劃線__開頭的屬性或者方法就是私有的

變量名相似xxx的,也就是以雙下劃線開頭,而且以雙下劃線結尾的,是特殊變量。特殊變量是能夠直接訪問的,不是private變量

在說私有屬性前,咱們來個案例說說屬性不私有的弊端,eg:

小明同窗學了點C#,而後學習了上面的知識,心想 ~ Python這麼搞安全性呢?不行,我得構造構造,因而有了下面的代碼:

In [10]:
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我就能夠判斷了哇,仍是本寶寶聰明

這時候小潘過來了,淡淡的一笑,看我怎麼破了你 ~ 看代碼:

In [11]:
zhangsan = Student("張三", -20)
zhangsan.show()  # name:張三,age:-20
zhangsan.age = -1  # set_age方法形同虛設,我徹底能夠直接訪問字段了
zhangsan.show()  # name:張三,age:-1
 
name:張三,age:-20
name:張三,age:-1
 

小潘傲氣的說道~大叔,給你臉呢。我就是不去訪問你設定的方法怎麼滴呢?

小明急的啊,趕忙去找偉哥求經。不一會,傲氣的貼出本身的New Code,心想着我私有屬性都用上了還怕個毛毛:

In [12]:
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))
 

小潘冷笑道~呵呵,而後使用了上次的絕招:

In [13]:
zhangsan = Student("張三", -20)
zhangsan.__age = -1  # 一樣的代碼,只是屬性前面加了下劃線
zhangsan.show()
 
age must > 0
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-82c41ff46846> in <module>()
      1 zhangsan = Student("張三", -20)
      2 zhangsan.__age = -1  # 一樣的代碼,只是屬性前面加了下劃線
----> 3zhangsan.show()

<ipython-input-12-1dec32486a19> in show(self)
     22 
     23     def show(self):
---> 24print("name:%s,age:%s" % (self.__name, self.__age))

AttributeError: 'Student' object has no attribute '_Student__age'
 

此次小潘同志傻眼了,徹底不能訪問了啊?不行,怎麼能被小明大叔笑話呢?

因而上網翻資料,國內不行就國外,外文很差就翻譯,終於找到一個新破解方式:

雙下劃線開頭的實例變量不能直接訪問,是由於Python解釋器對外把__age變量改爲了_Studentage,因此,仍然能夠經過**_Studentage**來訪問:

In [14]:
# 搞事情
zhangsan._Student__age = -1
zhangsan.show()
 
name:張三,age:-1
 

建議你不要這麼幹,不一樣版本的Python解釋器可能會把__age改爲不一樣的變量名

有些時候,你會看到以一個下劃線開頭的實例變量名,好比_age這樣的實例變量,外部是能夠訪問的。

可是,請把它視爲私有變量,不要隨意訪問(Python不少東西全憑自覺~捂臉@_@)

小潘終於長嘆一口氣,而後還不忘取笑小明同窗~你這屬性搞的,真麻煩,老是經過方法調用,太累了 <_> 鄙視!

這可把小明急的啊,學習的積極性都沒有了,吃了碗牛肉麪就去偉哥那邊好好取經了~

In [15]:
# 私有方法一筆帶過
class Student(object):
    """私有方法"""
    def __go_home(self):
        pass


zhangsan = Student()
zhangsan.__go_home() # 訪問不到
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-45c76191b808> in <module>()
      7 
      8 zhangsan = Student()
----> 9zhangsan.__go_home() # 訪問不到

AttributeError: 'Student' object has no attribute '__go_home'
 

1.5 裝飾器,讓方法像屬性那樣便利

Python內置的@property裝飾器就是負責把一個方法變成屬性調用的,來個例子

In [16]:
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))
In [17]:
xiaoming = Student("小明", 22)
xiaoming.name = "小潘"
xiaoming.age = -2
xiaoming.show()
 
age must > 0
name:小潘,age:22
 

把一個getter方法變成屬性,只須要加上@property就能夠了

@方法名.setter,負責把一個setter方法變成屬性賦值

固然了,若是隻想讀 ==> 就只打上@property標籤

小明同窗高興壞了,趕忙大吃了一頓~


1.6 __del__ and __new__

建立對象後,python解釋器默認調用__init__() 方法

當刪除一個對象時,python解釋器也會默認調用__del__() 方法(有點析構函數的味道)

當有1個變量保存了對象的引用時,此對象的引用計數就會加1

當使用del刪除變量指向的對象時,若是對象的引用計數不爲1,那麼每次刪除計數減1,當計數爲1的時候再調del就真把對象刪了

這個能夠結合我以前說過的連接來理解:於連接文件的探討

看着老師誇誇其談,小明愣了楞,摸摸肚子想到,真BB,我先搞個例子練練:

In [1]:
# 定義一個臨時類
class Temp(object):
    def __del__(self):
        print("你被幹掉了")
 

驗證方面用編輯器比較合適,交互模式下可能不是真正的結果

# 對象被s1和s2引用
s1 = Temp()
s2 = s1

del s1  # 只刪除s1,新建立的對象並無被刪除
print("-" * 10)

輸出:(最後的被幹掉是程序退出了)

# ----------
# 你被幹掉了

若是用連接來解釋就是這樣的: ln來解釋

此次兩個都刪掉:

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__方法中必需要有返回值(返回實例化出來的實例)

小明翻閱了官方文檔,淡定的打下了以下標準格式的代碼:

In [3]:
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()
 
建立對象完畢
初始化完畢
Dog的名字叫:Happy
 

關於__name__在模塊調用的時候會詳細說,你能夠先這樣理解:若是直接運行py文件就執行,別人調用那麼你的main就不執行了

標準寫法:

# 1.導入的模塊
# 2.class的定義
# 3.其餘方法定義

def main():
    pass

if __name__ == '__main__':
    main()

其餘內容後面會繼續說,封裝部分再說說靜態方法類方法之類的就結束了(和C#仍是有很大區別的)


1.7 類屬性、實例屬性

小明問老師:「老師老師,怎麼沒有靜態類,靜態屬性之類的東西呢?」

老師笑而不語道:「小傢伙原來不只僅是體重增長啊,這求知慾也是大大的增長呢 ~ 且聽我慢慢道來」


類在程序裏面也是對象(你姑且能夠認爲全部的類都相似於C#裏面的靜態類),而經過類實例化的對象,叫實例化對象

實例屬性 --> 實例對象相互之間不共享 通常咱們都是在__init__中定義

類屬性(相似於C#裏面的靜態字段) --> 屬於類對象,多個實例對象之間共享

注意一下:相同名稱的實例屬性將屏蔽掉類屬性(儘可能別同名)

類屬性除了能夠經過 類名.類屬性 訪問外,還能夠直接 實例對象.類屬性 (C#中抽象類和靜態類是不能被實例化的)

來個案例更直觀點:

In [1]:
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()
 
1
1
 

若是須要在類外 修改類屬性,必須經過類對象去引用而後進行修改

若是經過實例對象去引用會產生一個同名的實例屬性,這種方式修改的是實例屬性,不會影響到類屬性

若是經過實例對象去引用該名稱的屬性,實例屬性會強制 屏蔽掉類屬性,即引用的是實例屬性,除非del了該實例屬性才能正常訪問類屬性

你能夠理解爲,Python這麼作只是爲了方便你獲取,該怎麼修改還得怎麼作。來看個案例:

In [3]:
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()
 
1
100
1
100
22
22
22
 

1.8 實例方法、類方法、靜態方法

先說說 實例方法,實例方法第一個定義的參數只能是實例自己引用self,只能經過實例調用(就是咱們以前用的 def func_name(self,xxx):

類方法:是類對象所擁有的方法,須要用修飾器@classmethod來標識,第一個參數必須是類對象cls,能夠經過類或者實例直用

靜態方法:定義靜態方法使用裝飾器@staticmethod,沒有默認的必須參數,經過類和實例直接調用

靜態方法中不須要額外定義參數,所以在靜態方法中引用類屬性的話,必須經過 類對象來引用(訪問)

小明眼花繚亂的對老師說道,老師給我看幾個案例吧:

In [1]:
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()
 
我叫小汪
cls id:94310818174200
汪汪汪
self id:140392216464016
我叫小汪
cls id:94310818174200
汪汪汪
 

通常都是這樣用的(供參考):

實例方法:通常平時用的都是它

類方法:類方法用在模擬C#多個構造函數(Python裏面不能有同名函數) or 你須要 對類屬性、類方法操做之類的

靜態方法:通常 都是獨立功能,相似於函數,只不過在面向對象裏面通常這麼用

 

1.9 C#封裝案例

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);
        }
    }
}
相關文章
相關標籤/搜索