1. 程式人生 > >Python中面向物件的概念

Python中面向物件的概念

1、語言的分類

1)面向機器

抽象成機器指令,機器容易理解。代表:組合語言。

2)面向過程

做一件事,排除步驟,第一步做什麼,第二步做什麼,如果出現A問題,做什麼處理,出現b問題,做什麼處理。問題規模小,步驟化,按部就班處理。  代表:c語言。

(按照步驟進行處理的。)

面向物件和麵向過程的差異(一步一步的走,都有誰做or抽象成為不同的類,誰能做。)

程式設計是多正規化的,面向物件只是一種正規化。

3)面向物件ocp

隨著計算機需要解決的問題規模擴大,情況越來越複雜,需要很多人,很多部門協作,面向過程程式設計太不適合了。代表:c++  java  python等

三要素:封裝,父類有的,子類直接繼承,多繼承少修改,

繼承是為了多複用。單一繼承、多繼承。Mixin技術

 

2、面向物件

1)定義    (是一種認識世界,分析世界的方法論,用類來表現實實在在的物件。)

c語言是面向過程的,Python和Java是面向物件的。(大型專案的話利用類抽象)面向物件一種是抽象的,一種是具體的。

一種認識世界、分析世界的方法論,將萬事萬物抽象為類。

2)類class

類是抽象的概念,是萬事萬物的抽象,是一類實物的共同特徵的集合。

用計算機語言來描述類,就是屬性和方法的集合。

3)物件instance、object

物件是類的巨象,是一個實體。

對於每個人這個個體,都是抽象概念人類的不同的實體。

(你吃魚,你就是物件。魚,也是物件;吃是動作。你是具體的人,是具體的動作。你屬於人類,人類是個抽象的概念,是無數具體的個體的抽象。魚也是具體的物件,就是說你吃的是一條具體的魚,這條魚屬於魚類,是無數的魚抽象出來的概念。)

(吃,是具體的動作,也是操作,也是方法,這個吃是你的動作,也就是人類具有的方法,如果說的是魚吃人,吃就是魚類的動作了)

(吃是個動作,許多動物都具有的動作,人類和魚類都屬於動物類,而動物類是抽象的概念,是動物都有吃的動作,但是吃法不同而已)

(駕駛車,這個車也是車類的具體的物件(例項),駕駛這個動作是魚類不具有的,是人類具有的方法。)

4)屬性

是物件狀態的抽象,用資料結構來描述。

5)操作

他是物件行為的抽象,用操作名和實現該操作方法的描述。

(每個人都有名字,身高,體重等資訊,這些資訊都是個人的屬性,但是,這些資訊不能儲存在人類中,因為他是抽象的概念,不能保留具體的值。)

(而人類的例項,就是具體的人,他可以儲存這些具體的特性,而且可以不同的人有不同的屬性)

6)哲學

一切皆物件

物件是資料和操作的封裝

物件是獨立的,但是物件之間可以相互作用。

目前oop是最接近人類認知的程式設計正規化。

3、面向物件3要素****

1)封裝

*組裝:將資料和操作組裝到一起。

*對外只是暴露一些介面,通過介面訪問物件。(可供操作的屬性,暴漏的屬性)

2)繼承

*多複用,繼承來的就不用自己寫了。(用到父類已經做好的實現和操作,在自己內部無需實現了,相同的就不寫了,寫自己獨有的特點。)

*多繼承少修改,ocp(open-closed princile),使用繼承來改變,來體現個性。(父類的基礎上少修改)。

3)多型

*面向物件程式設計最靈活的地方,動態繫結。

人類就是封裝:

人類繼承自動物類,孩子繼承父母的特徵,分為單一繼承、多繼承;

多型,繼承自動物類的人類,貓類的操作吃的不同。

 

(類物件)類的定義

(類的物件)類的例項。

4、Python的類

1)定義

Class  ClassName:

語句塊                    (放屬性和方法,例項的屬性。例項化過程。)

(1)必須使用class關鍵字。

(2)類名必須是用大駝峰命名。

(3)類定義完成後,就產生了一個類物件,綁定了標示符ClassName上。

(4)舉例

class MyClass:
    """A example class"""
   
x = 1  #  類屬性

    def foo(self):   #類屬性foo,也是方法
        return " My Class"

print(MyClass.x)
print(MyClass.foo)
print(MyClass.foo(1))
print(MyClass.__doc__)

 

No1 print:   1

No2 print:   <function MyClass.foo at 0x0000002E421852F0>  (說是個函式類在某個地址裡面)

No3 print:  My Class

No4 print:  A example class

 

類屬性

 

 

(標示符就是一個屬性)

5、類物件及類屬性

1)類物件

類的定義就會產生一個類物件。

2)類屬性

類定義中的變數和類中定義的方法都是類的屬性。

3)類變數

上類中的x是類MyClass的變數。

(加括號呼叫方法,不加是呼叫的屬性。)

4)總結

MyClass中  x、foo都是類的屬性。

foo方法都是類的屬性,如同吃是人類的方法。但是每個具體的人才能吃東西,也就說吃人的例項才能呼叫的方法。

 

foo 是方法物件method,不是普通的函式物件function了,他一般要求至少有一個引數。第一個引數可以是self(self是標示符),這個引數位置就留給了self

6、類的例項

呼叫MyClass().Show()     (new函式和init函式都呼叫了)  帶有self的都是方法。

呼叫 a = MyClass()

 

背後是要進行繫結的。

前後都有下劃線的稱為魔術方法。初始化方法。

 

 

7、例項化

    a = MyClass()

1)         定義方法:

使用上面的語法,在類物件名稱後面加上一個括號,就是呼叫類的例項化方法,完成例項化。例項化就是真正建立一個該類的物件(例項)。

tom = MyClass()

Jerry = MyClass()

tom /jerry都是MyClass的例項,通過例項化生成了2個例項。

 

每次例項化後獲得例項,是不同的例項,即使是使用同樣的引數例項化,也得到不同的物件。

Python類例項化後,會自動呼叫__init__,這個方法第一個引數都必須留給self,其它引數隨意。

2)__init__

3)MyClass()實際上呼叫的是__init__(self)方法,可以不定義,如果沒有定義會在例項化後隱式呼叫。

作用:對例項進行初始化(self當前物件的本身)。例項帶初始化。

 

初始化函式可以多個引數,第一個引數位置必須是self。

__init__()方法不能有返回值,也就是隻能是None。

class MyClass:
    """A example class"""
   
x = 1  #  類屬性

    def foo(self):   #類屬性foo,也是方法
        return " My Class"

# print(MyClass)    #不會被呼叫
print(MyClass())    #會呼叫
a = MyClass()     #  會被呼叫。。結果和2一樣。
print(a)

 

No1  print:不會被呼叫

No2 print :<__main__.MyClass object at 0x0000001101903908>

No2 print :<__main__.MyClass object at 0x0000001101903908>

 

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def showage(self):
        print('{} is {}'.format(self.name,self.age))

tom = Person('Tom',20)
cat = Person('cat',12)
tom.showage()                   #  Tom is 20
cat.showage()                   #   cat is 12
print(tom.name,cat.name)        #    Tom cat
cat.age += 1
print(cat.age)                   #   13
cat.showage()                   #   cat is 13

 

 

 

每次例項化後都會生成不同的物件。(id地址不同)

 

 

8、例項物件instance

1)類例項化後一定會獲得一個物件,就是例項物件。

tom  cat 就是Person類的例項。

__init__ 方法的第一引數self就是指代某一例項。

類例項化後,得到一個例項物件,例項物件都會繫結方法,呼叫時採用tom.showage()的方式。函式簽名是showage(self)。這個self就是tom,python中會把方法的呼叫者作為第一引數self作為實參傳入。

Self,name就是tom物件的name,name是儲存在tom物件上面,而不是Person的類上面,所有稱為例項變數。

2)         Self 例項化。

class MyClass:
    def __init__(self):
        print('self in init = {}'.format(id(self)))

c = MyClass()
print('c = {}'.format(id(c)))

 

self in init = 583263140104

c = 583263140104

Self就是呼叫者,就是c對應的例項物件。

Self這個名字只是一個慣例,可以修改,最好不要修改,影響可讀性。

 

 

 

 

 

9、例項變數和類變數

 

class Person:
    age = 3
    def __init__(self,name):
        self.name = name

tom = Person('tom')
cat = Person('cat')

print(tom.name,tom.age)
print(cat.name,cat.age)
print(Person.age)
Person.age = 30
print(Person.age,tom.age,cat.age)

 

tom 3

cat 3

3

30 30 30

 

 

例項變數是每一個例項自己的變數,是自己獨有的。類變數是累的變數,是類的所有例項共享和使用的方法。

特殊屬性

含義

__name__

物件名

__class__

物件的模型

__dict__

物件的屬性的字典

__qualname__

類的限定名

Python中每一種物件都擁有不同的屬性,函式、類、都是物件,類的例項也是物件。

 

class Person:
    age = 3
    def __init__(self,name):
        self.name = name

print('class-----------')
print(Person.__class__)
print(sorted(Person.__dict__.items()),end='\n\n')

tom = Person('tom')
print('----instance tom ----')
print(tom.__class__)
print(sorted(tom.__dict__.items()),end='\n\n')

print('-----tom class------')
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()),end= '\n\n')

 

 

class-----------

<class 'type'>

[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]

 

----instance tom ----

<class '__main__.Person'>

[('name', 'tom')]

 

-----tom class------

Person

[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]

 

 

 

class Person:
    age = 3
    hight = 170

    def __init__(self,name,age=18):
        self.name = name
        self.age = age

tom = Person('tom')
cat = Person('cat',20)

Person.age = 30
print(Person.age,tom.age,cat.age)

print(Person.hight,tom.hight,cat.hight)
cat.hight = 175
print(Person.hight,tom.hight,cat.hight)
tom.hight +=10
print(Person.hight,tom.hight,cat.hight)

Person.hight +=15
print(Person.hight,tom.hight,cat.hight)

Person.weight = 70
print(Person.weight,tom.weight,cat.weight)

print(tom.__dict__['hight'])
# print(tom.__dict__['weight'])  不可執行,因為後來定義的字典內沒有

 

 

30 18 20

170 170 170

170 170 175

170 180 175

185 180 175

70 70 70

180

 

總結:

是類的,也就是這個類的所有例項,其例項都可以訪問的到,是例項的,就是這個例項自己,通過類訪問不到。

類變數是屬於類的變數,這個類的所有例項可以共享這個變數。

例項可以動態的給自己增加一個屬性。例項.__dict__,如果沒有,然後通過屬性__class__找到自己的類,再去類的__dict__中找。

注意,如果例項使用__dict__[變數名]訪問變數,將不會按照上面的順序查詢變量了,這是指明使用字典的key查詢,不是屬性來查詢。

一般來說,類變數使用全部大寫來命名。

 

特殊屬性

 

 

例項的字典裡面放的是和self有關的資訊,類字典裡面放的是類的資訊,類的字典的items。

屬性訪問的:的找自己的找不到自己的找類的。

字典訪問的:指定的是自己的字典。

賦值即定義,定義屬性。

 

 

 

 

 

Person().normal()例項呼叫。必須傳引數。不用。

Person.normal()類呼叫。

10、裝飾一個類

 

def add_name(cls):
    cls.NAME = name
    return cls

@add_name('tom')
class Person:
    AGE = 3

 

 

def add_name(name):
    def wrapper(cls):
        cls.NAME = name
        return cls
    return wrapper

@add_name('tom')
class Person:
    AGE = 3

 

 

11、類方法和靜態方法。

__init__等方法,這些方法的本身都是類的屬性,第一個引數必須是self,而self必須指向一個物件,也就是類必須例項化以後,由例項來呼叫這個方法。

1)普通函式:

class Person:
    def normal_method():
        print('normal')
# Person.normal_method()   #可以呼叫
# Person().normal_method()   #不可以呼叫

print(Person.__dict__)

 

Person.normal_method()

可以被呼叫,因為這個方法只是被Person這個名詞空間管理的一個普通方法,normal只是一個屬性而已。

由於normal_method在定義的時候沒有指定self。所以不能完成例項物件的banding,不能用,Person().Normal_method()繫結。  雖然語法對的,但是禁止這麼寫。

2)類方法

class Person:
    @classmethod
    def class_method(cls):
        print('class={0.__name__}({0})'.format(cls))
        cls.HIGHT = 170

Person.class_method()
print(Person.__dict__)

 

 

class=Person(<class '__main__.Person'>)

{'__module__': '__main__', 'class_method': <classmethod object at 0x000000C59D4A3908>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}

 

 

(1)在類定義中,使用@clasmethod裝飾器修飾的方法,

(2)必須至少有一個引數,且第一個引數留給了cls,cls指代呼叫者即類物件自身。

(3)cls這個標示符可以是任意合法名稱,但是為了易讀,不建議修改。

(4)通過cls可以直接操作類的屬性。

 

(3)靜態方法。

class Person:
    @classmethod
    def class_method(cls):
        print('class={0.__name__}({0})'.format(cls))
        cls.HIGHT = 170

    @staticmethod
    def static_method():
        print(Person.HIGHT)

Person.class_method()
Person.static_method()
print(Person.__dict__)

 

 

class=Person(<class '__main__.Person'>)

170

{'__module__': '__main__', 'class_method': <classmethod object at 0x000000E7C4993908>, 'static_method': <staticmethod object at 0x000000E7C49938D0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}

 

 

(1)staticmethod 靜態方法

(2)不需要傳參。

(3)在類定義中,使用@staticmethod裝飾器修飾的方法。

(4)呼叫時不會隱身傳入引數,靜態方法,只是表明這個方法屬於這個名詞空間,函式歸在一起,方便管理。

12、方法的呼叫

class Person:
    def normal_method():
        print('normal')

    def method(self):
        print('{} method'.format(self))

    @classmethod
    def class_method(cls):
        print('class={0.__name__}({0})'.format(cls))
        cls.HIGHT = 170

    @staticmethod
    def static_method():
        print(Person.HIGHT)

print('---類訪問---')
print(1,Person.normal_method())    #  可以呼叫,返回None。
# print(2,Person.method())    #不可以被呼叫,必須傳入引數,一個self
print(3,Person.class_method())   ##返回None
print(4,Person.static_method())   #返回None
print(Person.__dict__)
print('例項訪問')
print('tom')
tom = Person()
# print(1,tom.normal_method())   #不能呼叫,報錯 需要引數
print(2,tom.method())      #可以
print(3,tom.class_method())  #可以
print(4,tom.static_method())   #可以

 

總結:

類幾乎可以呼叫所有內部定義的方法,但是呼叫普通方法的時候會報錯,原因是第一引數必須是類的例項。

例項幾乎可以呼叫所有,普通的函式的呼叫一般不可能出現,因為不允許定義。

 

類除了普通方法都可以呼叫,普通方法需要物件的例項作為第一引數。

例項可以呼叫所有類中定義的方法,(靜態,類方法),普通方法傳入例項本身,靜態方法和類方法需要找到例項的類。

 

 

 

 

 

 

 

13、訪問控制

1)私有屬性(private)

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.age = age

    def growup(self,i=1):   #控制邏輯
        if i>0 and i <150:
            self.age +=1
p1 = Person('tom')
p1.growup(20)
p1.age =160
print(p1.age)

 

想通過方法來控制屬性,但是由於屬性在外部可以訪問,就直接繞過方法,直接修改屬性。私有屬性則解決了這個問題。

 

私有屬性:

使用雙下劃線開頭的屬性名。就是私有屬性。

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def growup(self,i=1):   #控制邏輯
        if i>0 and i <150:
            self.__age +=1
p1 = Person('tom')
p1.growup(20)
# p1.age =160
print(p1.__age)

 

 print(p1.__age)

AttributeError: 'Person' object has no attribute '__age'

訪問不到 __age ..

 

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def growup(self,i=1):   #控制邏輯
        if i>0 and i <150:
            self.__age += 1

    def getage(self):
        return self.__age
p1 = Person('tom')
print(p1.getage())

 Print 19

2)私有屬性的本質,

外部訪問不到,能夠動態增加一個__age?

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def growup(self,i=1):   #控制邏輯
        if i>0 and i <150:
            self.__age += 1

    def getage(self):
        return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())

 

年齡沒有被覆蓋,全部存在__dict__的字典裡面了。

 

私有變數本質:

類定義的是,如果生命一個例項變數的時候,使用雙下劃線,python的直譯器就會將其改名,轉換為_類名__變數名 的名稱了,所以用原來的名字訪問不到了。

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def growup(self,i=1):   #控制邏輯
        if i>0 and i <150:
            self.__age += 1

    def getage(self):
        return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())

p1._Person__age =15
print(p1.getage())
print(p1.__dict__)

 

 

28

15

{'name': 'tom', '_Person__age': 15, '__age': 28}

##

3)保護變數

在變數名前面使用一個下劃線,稱為保護變數。

class Person:
    def __init__(self,name,age = 18):
        self.name = name
        self._age = age

       
tom = Person('tom')
print(tom._age)
print(tom.__dict__)

 

18

{'name': 'tom', '_age': 18}

_age屬性沒有改變名稱,和普通屬性一樣,直譯器不做任何處理,只是開發者共同的約定,看見這樣的變數,就如同私有變數,不直接使用。

4)私有方法

參展保護變數,私有變數,使用單下劃線,雙下劃線命名方法。

class Person:
    def __init__(self,name,age =18):
        self.name = name
        self._age = age

    def _getname(self):
        return self.name

    def __getage(self):
        return self._age

tom = Person('tom')
print(tom._getname())    ##沒改名
# print(tom.__getage())    ##m沒有次屬性
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person__getage())   #改名了

 

tom

{'name': 'tom', '_age': 18}

{'__module__': '__main__', '__init__': <function Person.__init__ at 0x000000F1ECDB52F0>, '_getname': <function Person._getname at 0x000000F1ECDB5620>, '_Person__getage': <function Person.__getage at 0x000000F1ECDB56A8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}

18

 

5)私有方法的本質

當下劃線的方法只是開發者之間的約定,直譯器不做任何改變

雙下劃線的方法,是私有方法,直譯器會改名,改名策略和私有變數相同,_類名__昂發名。

方法變數都在類的 __dict__ 裡面可以找到。

6)私有成員總結;

在python中使用_單下劃線或者_雙下劃線來標示一個成員被保護或者被私有化隱藏起來,但是不管使用什麼樣的訪問控制,都不能真正的組織使用者修改類的成員,python中沒有決定的安全環保成員或者私有成員。

因此,前道的下劃線只是一種警告或者提醒,要遵守這個約定,除非有必要,否則不能修好或者使用保護成員或者私有成員,更不能修改。

 

 

 

 

14、屬性裝飾器

一般很好的設計是:把例項的屬性保護起來,不讓外部直接訪問,外部使用getter讀取屬性和setter方法設定屬性。

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age =age

    @property
    def age(self):
        return self.__age
    @age.setter
    def age(self,age):
        self.__age = age

tom = Person('tom')
print(tom.age)
tom.age = 20
print(tom.age)

 

 

使用property裝飾器的時候,三個方法必須同名。

Property裝飾器:

 

後面跟的函式名就是以後的屬性名,他就是getter,這個必須有,有了它至少有了只讀屬性。

 

Setter裝飾器

與屬性名同名,且接受兩個引數,第一個是self,第二個是將要賦值的值,有了它,屬性可寫。

 

Deleter裝飾器

可以控制是否刪除屬性,很少用。

 

Property裝飾器必須在前,setter  和  delter裝飾器在後面。

Property裝飾器能通過簡單的方式,吧對方法的操作變成對屬性的訪問,並起到了一定的隱藏效果。

 

其它寫法:

class Person:
    def __init__(self,name,age =20):
        self.name = name
        self.__age = age

    def getage(self):
        return self.__age

    def setage(self,age):
        self.__age =age

    def delage(self):
        del  self.__age
        print('del')

    age = property(getage,setage,delage,'age property')

tom = Person('tom')
print(tom.age)
tom.age = 25
del tom.age

 

 

class Person:
    def __init__(self,name,age=20):
        self.name =name
        self.__age = age

    age = property(lambda self:self.__age)

tom = Person('tom')
print(tom.age)

 

 

可讀、可寫。

15、補丁。

可以通過修改或者替換類的成員。使用著呼叫的方式沒有改變,但是,類提供的功能可以已經改變。

 

猴子補丁:

在執行時,對屬性,方法,函式等進行動態替換。

其目的是為了替換,修改來增強,擴充套件原有的程式碼的能力。

慎重使用。

 

 

上例中,假設Person類和get_score方法是從資料庫中拿資料,但是測試的時候不方便。

使用猴子補丁的方法,替換了get_score方法,返回模擬的資料。

 

16、物件的銷燬。

__del__ 方法,稱為解構函式(方法)

 

作用:銷燬類的時候呼叫,以釋放佔用的資源,其中就放些清理資源的程式碼。比如釋放連結。

這個方法不能讓物件真正的小虎,只是物件銷燬的時候會自動呼叫它。

使用del的語句刪除例項,引用計數減1,當引用計數為0時候,會idong呼叫__del__方法。

import time

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def __del__(self):
        print('delete{}'.format(self.name))


def test():
    tom = Person('tom')
    tom.__del__()
    tom.__del__()
    tom.__del__()
    print('=======')
    tom1 = tom
    tom3 = tom1
    print(1,'del')
    del tom
    time.sleep(3)

    print(2,'del')
    del tom3
    time.sleep(3)
    print('-----')

    del tom3
    time.sleep(3)
    print('........')
test()

 

 

由於垃圾回收物件銷燬時候,才會真正清理物件,還會再之間自動呼叫__del__ 方法。除非知道自己的目的,否則不會手動清理;不會手動呼叫這個方法。

 

17、方法過載。

Python沒有過載,不需要過載

Python中,方法(函式)定義中,形參非常靈活,不需要指定型別(就算指定了也只是說明一個說明而非約束,),形參個數也不固定(可變引數),一個函式的定義可以實現很多種不同形式實參的呼叫,所喲python不需要方法過載。

或者說python本身就是實現了其他語言的過載。

 

18、封裝:

面向物件的三要素之一,封裝。

封裝:

將資料和操作組織到類中,即屬性和方法。

將資料隱藏起來,給使用者提供操作(方法),使用者通過操作就可以獲取或者修改資料。

Getter和setter。

通過控制訪問,暴漏給適當的資料和操作給使用者,該隱藏的隱藏起來,例如保護成員或者私有成員。

 

19、習題:

 

1)隨機整數生成類

##初步程式碼:

import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length

    def array(self):
        lst=  [random.randint(self.start,self.end)for i in range(self.length)]
        print(lst)

j = Ran()
j.length=5
j.array()

 

 

###第二步改進控制列印長度的

import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length

    def array(self,length=0):
        length = self.length if length<=0 else length
        lst=  [random.randint(self.start,self.end)for i in range(length)]
        print(lst)

j = Ran()
j.array(20)

####三,利用工具實現的方式

import random
class Ran:
    @classmethod
    def array(cls,start = 1,end =100,length =10):
        return [random.randint(start,end)for _ in range(length)]

a = Ran()
print(a.array())

###利用生成器

import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length
        self._gen = self._array()

    def _array(self):
        while True:
            yield random.randint(self.start,self.end)

    def array(self,length=0):
        if length <= 0:
            return [next(self._gen)for _ in range(self.length)]
        else:
            return [next(self._gen)for i in range(length)]

j = Ran()
print(j.array(20))
print(j.array())
import random


class Ran:

    def __init__(self,start=0,end=100,length=10):
        self.start = start
        self.end  = end
        self.length = length
        self._gen = self._array()

    def _array(self):
        while True:
            yield [random.randint(self.start,self.end)for _ in range(self.length)]

    def array(self,length=0):
        if length > 0:
            self.length = length
        return next(self._gen)

j = Ran()
print(j.array(20))
print(j.array())

 

2)利用上次的隨機數生成20次,兩兩組和,列印座標。

import random


class Ran:
    def __init__(self,start=0,end=100,count=20):
        self.start = start
        self.end= end
        self.count = count


    def array(self,count=0):
        count = self.count if count<=0 else count
        lst = [random.randint(self.start,self.end)for i in range(count)]
        return lst

a = Ran()
a.array()

class Point:
    def __init__(self,x,y):
        self.x = x
        self.y = y

# points = zip(a.array(),a.array())
# for x in points:
#     print(x)
points = [Point(x,y)for x,y in zip(a.array(),a.array())]
for p in points:
    print('{}:{}'.format(p.x,p.y))

 

3)車輛資訊

class Car:

    def __init__(self, mark, color, price, speed, **kwargs):

        # self.id = genid
        self.mark = mark
        self.color = color
        self.price = price
        self.speed = speed
        self.__dict__.update(kwargs)

class CarInfo:
    cars = []
    def addcar(self,car:Car):
        self.cars.append(car)

    def getall(self):
        return self.cars

c1 = CarInfo()
car = Car('audi','red',100,100)
c1.addcar(car)
print(c1.getall())

 

4、實現溫度的處理。

class Temp:
    def __init__(self,t,unit='c'):
        self._c = None
        self._f = None
        self._k =None
        #都先轉換為攝氏溫度,然後以後訪問呼叫計算其他的溫度的值
        if unit == 'k':
            self._k = t
            self._c = self.k2c(t)
        elif unit == 'f':
            pass
        else:
            self._c = t

    @property
    def c(self):
        return self._c

    @property
    def k(self):
        pass

    @property
    def f(self):
        pass
    #溫度轉換
    @classmethod
    def c2f(cls,c):
        return 9*c/5 +32

    @classmethod
    def f2c(cls,f):
        return 5*(f-32)/9

    @classmethod
    def c2k(cls,c):
        return c +273.15

    @classmethod
    def k2c(cls,k):
        return k -273.15

    @classmethod
    def f2k(cls,f):
        return cls.c2k(cls.f2c(f))

    @classmethod
    def k2f(cls,k):
        return cls.c2f(cls.k2c(k))

print(Temp.c2f(40))
print(Temp.c2k(40))
print(Temp.f2c(40))
print(Temp.k2c(40))
print(Temp.k2f(40))
print(Temp.f2k(40))

t = Temp(37)
print(t.c,t.k,t.f)

5、模擬購物車購物

class Item:
    def __init__(self,**kwargs):
        self.__spec = kwargs

    def __repr__(self):
        return str(sorted(self.__spec.items()))

class Cart:
    def __init__(self):
        self.items = []

    def additem(self,item:Item):
        self.items.append(item)

    def getallitems(self):
        return self.items

mycart = Cart()
myphone = Item(mark='apple',menory='4G')
mycart.additem(myphone)
print(mycart.getallitems())

 

 

一、類的繼承

1、概念;

1)面向物件的三要素之一,繼承inheritance

 

人類和貓類都是繼承動物類。

個體繼承自父母,繼承了父母的一部分特徵,但也可以有自己的個性。

在面向物件的世界中,從父類中繼承,就可以直接擁有父類的屬性和方法,這個可以減少程式碼,增加多複用,子類可以定義自己的屬性和方法。

 

class Animal:
    def shout(self):
        print('Animal shouts')

class Cat:
    def shout(self):
        print('Cat shouts')

class Dog:
    def shout(self):
        print('Dog shouts')

a = Animal()
a.shout()

c = Cat()
c.shout()

d = Dog()
d.shout()

 

Animal shouts

Cat shouts

Dog shouts

 

上面的例子的類雖然有關係,但是定義時候並沒有建立這種關係,而是各自完成定義。。

 

class Animal:
    def __init__(self,name):
        self._name = name

    def shout(self):
        print('{} shouts'.format(self.__class__.__name__))

    @property
    def name(self):
        return self._name

a = Animal('monster')
a.shout()

class Cat(Animal):
    pass

cat = Cat('garfield')
cat.shout()

class Dog(Animal):
    pass

dog = Dog('ahuang')
dog.shout()
print(dog.name)

 

 

上例可以看出,通過繼承,貓類,狗類,直接繼承了父類的屬性和方法。

2)繼承

Class cat(Animal)這種形式就是從父類繼承,括號中寫繼承的類的列表。

繼承可以讓子類從父類獲取特徵(屬性和方法)

 

父類Animal 就是cat的父類,也稱為基類,超類。

 

子類

Cat 就是Animal的子類,也成為派生類。

 

2、定義

格式:

Class 子類名(基類1,[基類2,...........]):

語句塊

如果類定義的時候,沒有基類列表,等同於繼承自object,在python3種,object類是所有物件的根基類。

Class  A:

Pass

   #等價於

Class  A(object):

Pass

Python支援多繼承,繼承也可以多級。

 

檢視繼承的特殊屬性和方法:

特殊屬性和方法

含義

舉例

__base__

類的基類

 

__bases__

類的基類元組

 

__mro__

顯示方法查詢順序,基類的元組

 

mro()方法

同上

Int.mro()

__subclasses__()

類的子類列表

Int.__subclasses__()

3、繼承中的訪問控制

class Animal:
    __COUNT = 100
    HEIGHT = 0

    def __init__(self,age,weight,height):
        self.__COUNT += 1
        self.age = age
        self.__weight = weight
        self.HEIGHT = height

    def eat(self):
        print('{}eat'.format(self.__class__.__name__))

    def __getweight(self):
        print(self.__weight)

    @classmethod
    def showcount1(cls):
        print(cls.__COUNT)

    @classmethod
    def __showcount2(cls):
        print(cls.__COUNT)

    def showcount3(self):
        print(self.__COUNT)

class Cat(Animal):
    NAME = 'CAT'
    __COUNT = 200

# c = Cat()     函式引數錯誤,傳參錯誤。
c = Cat(3,5,15)
c.eat()    ##cat eat
print(c.HEIGHT)   ##15
# print(c.__COUNT) ##私有不可訪問
# c.__getweight()   #私有不可訪問
c.showcount1()    #100因為__是改名的,在例項的類找不到,所以利用繼承的。
# c.__showcount2()  #不可以,私有的不能訪問
c.showcount3()   #101
print(c.NAME)   # CAT

print('{}'.format(Animal.__dict__))
print('{}'.format(Cat.__dict__))
print(c.__dict__)
print(c.__class__.mro())

 

後四個print

 

1/{'_Animal__getweight': <function Animal.__getweight at 0x000000D34976B0D0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, 'HEIGHT': 0, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'showcount3': <function Animal.showcount3 at 0x000000D34976B268>, 'showcount1': <classmethod object at 0x000000D349769748>, 'eat': <function Animal.eat at 0x000000D349764BF8>, '_Animal__COUNT': 100, '__module__': '__main__', '__init__': <function Animal.__init__ at 0x000000D349764D08>, '_Animal__showcount2': <classmethod object at 0x000000D349769780>, '__doc__': None}

2/{'NAME': 'CAT', '__module__': '__main__', '_Cat__COUNT': 200, '__doc__': None}

3/{'_Animal__COUNT': 101, 'HEIGHT': 15, '_Animal__weight': 5, 'age': 3}

4/[<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]

 

從父類繼承,自己沒有的,就到父類中去找。

私有的都是不可以訪問的,但是本質上依然是改了名字放在這個屬性的類的__dict__ 中,知道這個新名稱就可以直接找到這個隱藏的變數。

 

(類外部是不會被訪問的,私有屬性,內部可以。)

繼承順序:例項,自己的類,父類。繼承類;

 

總結:

繼承時候,公有的,子類和例項都可以隨意訪問,私有成員被隱藏,子類和例項不可直接訪問,當私有變數所在的類內的方法都可以訪問這個私有變數。

 

屬性查詢順序:

例項的__dict__>>    類__dict__如果有繼承==〉父類__dict__.         

dir()來檢視繼承屬性。

4、方法的重寫、覆蓋override

class Animal:
    def shout(self):
        print('Animal shouts')

class Cat(Animal):
    #覆蓋了父類的方法
    def shout(self):
        print('miao')
a = Animal()
a.shout()
c =Cat()
c.shout()

print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)

 

 

{}

{}

{'__dict__': <attribute '__dict__' of 'Animal' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None, 'shout': <function Animal.shout at 0x000000E832DF4D08>}

{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x000000E832DF4BF8>}

 

 

class Animal:
    def shout(self):
        print('Animal shouts')

class Cat(Animal):
    #覆蓋了父類的方法
    def shout(self):
        print('miao')
    #覆蓋本身的方法,呼叫父類的方法
    def shout(self):
        print(super())
        print(super(Cat,self))
        super().shout()
        super(Cat,self).shout()   #等價於super()
        # self.__class__.__bases__.shout(self)
a = Animal()
a.shout()
c =Cat()
c.shout()

print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)

 

 

Animal shouts

<super: <class 'Cat'>, <Cat object>>

<super: <class 'Cat'>, <Cat object>>

Animal shouts

Animal shouts

{}

{}

{'__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'shout': <function Animal.shout at 0x0000001852BD4D08>, '__doc__': None, '__dict__': <attribute '__dict__' of 'Animal' objects>}

{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x0000001852BDB0D0>}

 

Super()可以訪問到父類的屬性。

 

class Animal:

    @classmethod
    def class_method(cls):
        print('class_method_animal')

    @staticmethod
    def static_method():
        print('statice_method_animal')

class Cat(Animal):
    @classmethod
    def class_method(cls):
        print('class_method_cat')

    @staticmethod
    def static_method():
        print('statice_method_animal')

c = Cat()
c.class_method()
c.static_method()

 

 

class_method_cat

statice_method_animal

 

這些都可以進行被覆蓋,原因是字典的搜尋順序。

 

5、繼承中的初始化

 

上圖中類B定義時候生命繼承自類A,則在類B中__base__中是可以看到A的

但是這是和呼叫類A的構造方法是兩回事。

如果B中呼叫了A的構造方法,就可以擁有父類的屬性了。

 

class A:
    def __init__(self,a,b=1):
        self.a = a
        self.__b = b

class B(A):
    def __init__(self,b,c):
        A.__init__(self,b + c,b -c )
        self.b = b
        self.c = c

    def printv(self):
        print(self.b)
        print(self.a)
f = B(4,5)
print(f.__dict__)
print(f.__class__.__bases__)
f.printv()

 

 

 

{'c': 5, 'a': 9, '_A__b': -1, 'b': 4}

(<class '__main__.A'>,)

4

9在B中呼叫了父類的__init__函式。  如果父類中定義了__init__,子類中就應該呼叫他。

 

如果利用自動呼叫父類的__init__方法呢。

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'

class B:
    pass
b =B()
print(b.__dict__)

 

 

子類沒有自己的屬性和方法,直接繼承。。。會自動呼叫的。

 

此種類型不會自動呼叫,需要手動處理呼叫:

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'
        print('Ainit')
class B(A):
    def __init__(self):
        self.b1 ='b1'
        print('B init')
b =B()
print(b.__dict__)

 

B init

{'b1': 'b1'}

正確初始化。

class A:
    def __init__(self,age):
        print('Ainit')
        self.age = age

    def show(self):
        print(self.age)

class B(A):
    def __init__(self,age,weight):

        print('Binit')
        self.age = age +1
        self.weight = weight
        super().__init__(age)
b = B(10,5)
b.show()

 

呼叫父類的__init__方法,出現在不同的位置,導致出現的結果不同。

 

class A:
    def __init__(self,age):
        print('Ainit')
        self.__age = age

    def show(self):
        print(self.__age)

class B(A):
    def __init__(self,age,weight):

        print('Binit')
        self.__age = age +1
        self.__weight = weight
        super().__init__(age)
b = B(10,5)
b.show()

 

打印出10,原因在__dict__,父類A的show方法中__age h會被解釋為_A__age因此顯示的就是10,而不是11.

自己私有的屬性,就該自己讀取和修改,不要藉助其他類的方法,即使是父類或者派生類。

       

 

 

繼承中的初始化

(繼承加覆蓋就是多型。)

 

 

二、多繼承

1、Python不同的版本的類

Python2.2之前是沒有共同祖先的,之後,引入object的類,他是所有類的共同 的祖先object。

Python2中為了相容,分為古典類(舊式類)和新式類。

Python3中全部都是新式類。

新式類都是繼承自object的,新式類可以使用super。

 

2、Ocp原則

多用繼承,少用修改。

繼承的用途,增強基類,實現多型。

 

多型:

在面向物件中,父類,子類通過繼承聯絡在一起,如果通過一套方法,就可以實現不同的表現,就是多型。

一個類繼承自多個類就是多繼承,他將會有多個類的特徵。

 

3、多繼承弊端

多繼承很好的模擬了世界,因為事物很少是單一繼承的,但是捨棄簡單,必然引入複雜性,帶來了衝突。

 

如同一個孩子繼承了來自父母雙方的特徵,那麼到底眼睛像爸爸還是媽媽呢,孩子究竟該誰多一點呢。

 

多繼承的實現會導致編譯器設計的複雜度增加,所以很多語言捨棄了類的多繼承。

 

C++支援多繼承,JAVA也捨棄了多繼承。

Java中,一個類可以實現多個介面,一個介面也可以繼承多個解耦,Java的介面很純粹,只是方法的生命。繼承者必須實現這些方法,就具有了這些能力,就能幹什麼。

 

多繼承可能會帶來二義性,例如,貓和狗都繼承自動物類,現在如果一個類多繼承了貓和狗類,貓和狗都有shout方法,子類究竟繼承誰的shout呢。

 

解決的方案。

實現多繼承的語言,要解決二義性,深度優先或者廣度優先。

 

4、Python多繼承實現。

Class ClassName(基類列表)

類體

 

左圖是多繼承,右圖是單一繼承。

 

多繼承帶來路徑選擇問題,究竟繼承哪個父類的特徵呢。

 

Python使用MRO(method resolution order)解決基類搜尋順序問題。

 歷史原因:MRO有三個搜尋演算法:

經典演算法,按照定義從左到右,深度優先策略,2.2之前左圖的MRO是MYclass dbaca

新式演算法,經典演算法的升級,重複的是隻保留最後一個,2.2左圖的mro是myclassd,b,c,a,object

C3演算法,在類被創建出來的時候,就計算出一個MRO有序列表,2.3之後,Python3唯一支援的演算法。

左圖中MRO是myclass,d,b,c,a,object的列表。

C3演算法解決了多繼承的二義性。

 

5、多繼承的缺點

當類很多,繼承複雜的情況下,繼承路徑太多,很難說清什麼樣的繼承路徑。

Python語法是允許很多繼承的,但Python程式碼是解釋執行的,只有執行到的時候,才會發現錯誤。

 

團隊協作開發,如果引入多繼承,name程式碼將會不可控。

不管程式語言是否支援多繼承,都應避免使用多繼承。

 

6、Mixin類                  (繼承加覆蓋等於多型。)

類有下面的繼承關係。

 

本質上是:多繼承的來實現,體現的是組合的設計模式。

 

文件Document類是其他所有文件類的抽象基類;

Word PDF類是Document的子類。

 

為document子類提供列印能力:

方法:

1)在Document中提供print方法。

 

<