python中的類和對象


1.python中類和對象的概念

類(class):簡單來說就是某一類事物,它們具有相同的屬性,例如貓有各種顏色,各種顏色就屬於屬性(也被叫做變量)。

對象(object):黑貓,白貓這些都是對象,這個對象就是類的實例(instance)。對象/實例只有一種作用,即屬性引用。

對象內存空間里只存儲對象的屬性,而不存儲方法和靜態屬性,方法和靜態屬性存儲在類的內存空間中,這樣多個對象可以共享類中的資源,便於節省內存(見下圖)。

實例化:類到對象的過程(實例 = 類名(參數1,參數2))

字段(field):對象可以使用屬於它的普通變量來存儲數據,這種從屬於對象或者類的變量叫做字段。它們屬於某一類的各個實例或對象,或是從屬於某一類本身。它們被分別稱作實例變量(Instance Variables)與類變量(Class Variables)。有的地方稱他們為靜態屬性和動態屬性,靜態屬性針對的是類屬性,動態屬性針對的是定義在類中的方法。

方法(method):對象可以通過類的函數來實現相關功能,這個函數叫做類的方法。方法分為普通方法,類方法和靜態方法。三種方法在內存中都屬於類,區別在於調用方式不同,詳見后面例題。

__init__方法:它會在類的對象被實例化時立即運。

class Person:
    role = '路飛'  # 靜態屬性,對於靜態屬性的修改,必須使用類名直接修改,即類名.靜態變量名
    
    def eat(self):  # 動態屬性(方法)
        print('不夠吃')


print(Person.__dict__)  # 查看當中的靜態變量的所有字典方法
# {'role': '路飛', '__dict__': <attribute '__dict__' of 'Person' objects>, '__module__': '__main__', '__doc__': None, 'eat': <function Person.eat at 0x0292BB70>, '__weakref__': <attribute '__weakref__' of 'Person' objects>}
print(Person.__dict__['role'])  # 查看靜態變量,注意這里的role必須有引號
# 路飛
print(Person.role)  # 查看靜態變量的第二種方式
# 路飛
print(Person.eat)  # 查看這個方法的內存地址
# <function Person.eat at 0x0509BB70>
Person.eat('s')  # 調用這個方法,給self傳遞一個實參
# 不夠吃
ACE = Person()  # 創造一個對象(即實例),對象 = 類名()

# 引用靜態變量:
# Person.__dict__['role'],這種方式可以直接查看,但不能對其進行修改
# Person.role,這種方法可以查看,也可以修改,還能刪除(直接del Person.role)
# 引用動態變量:
# Person.eat, 類名.方法名,查詢這個方法的地址
# Person.eat('s'), 類名.方法名(參數), 調用這個方法


  
class Person:
    role = 'person'
    def __init__(self, name, sex, hp):
        print(self.__dict__)  # {}  相當於告訴用戶self里面內置了一個空字典
        self.name = name      # self.__dict__對象中存儲的屬性,被稱為對象屬性,一般被稱為屬性
        self.sex = sex
        self.hp = hp
        print(self)  # <__main__.Person object at 0x0572D790>
        print(self.__dict__)   # {'hp': 120, 'name': 'zoro', 'sex': 'man'}
    def beat(self):
        print('%s beated' % self.name)


ACE = Person('zoro', 'man', 120)
   # 實例化的過程:
# 1.創建一個實例/對象,將會作為一個實際參數(ACE)
# 2.自動觸發一個__init__的方法,通過它給對象添加一些屬性,對象默認名字是self
# 3.執行完__init__方法之后,會將self所指向的內存空間返回給實例化它的地方
print(ACE.__dict__['name'])
print(ACE.name)
# zoro
ACE.__dict__['name'] = 'sanzhi'  # 對輸入的實參可以進行修改
print(ACE.__dict__['name'])      # 對象名.__dict__['屬性名']
print(ACE.name)                  # 對象名.屬性名  →推薦使用這種寫法
# sanzhi
Person.beat(ACE)                 # 類名.方法名(對象名)
ACE.beat()                       # 對象名.方法名(),這樣寫相當於方法中的self參數直接指向這個對象
# zoro beated
# 這里對象ACE先找間自己的內存空間,再找見類對象指針,根據類對象指針找見person類,再通過類找見beat方法
print(ACE) # 可以看出,self和ACE所用是同一內存地址 # <__main__.Person object at 0x0572D790> print(ACE.__dict__) # 可以看出和上面self.__dict__值是一樣的 # {'name': 'sanzhi', 'sex': 'man', 'hp': 120}

1.對象的內存空間中只存儲對象的屬性

而方法和靜態屬性存儲在類的內存空間中,這樣即使存在多個方法也只占用類開辟的空間,這樣便於節省內存空間。

2.對象屬性是獨有的,靜態屬性的方法是共享的

3.對象使用名字,先找到自己的命名空間,如果當中存在,則優先執行它,而不會通過類對象指針去person類找beat方法

4.對於靜態屬性的修改,必須使用類名修改(如Person.role = '索隆')

class Foo:
    count = 0
    def __init__(self):
        Foo.count += 1
        
f1 = Foo()
f2 = Foo()
---
print(Foo.count)
寫一個類,統計這個類有幾個對象

2.類的組合使用

一個類的對象作為另一個類的對象的屬性

創建一個圓,以及圓環,通過組合,計算圓環的周長與面積(對象名.屬性名,它表示另一個對象)

from math import pi

class Ricle:
    def __init__(self, r):
        self.r = r
    def perimeter(self):
        return 2*pi*self.r
    def area(self):
        return pi*self.r**2
    
class Ring:
    def __init__(self, in_r, out_r):
        self.in_circle = Ricle(in_r)   # 把小圓對象給了左邊
        self.out_circle = Ricle(out_r)
        # 上面這兩步相當於在字典中添加了兩個鍵值對
        print(self.__dict__)
        # {'out_circle': <__main__.Ricle object at 0x04EDD3D0>, 'in_circle': <__main__.Ricle object at 0x04EDD830>}
    def Ring_perimeter(self):
        return self.in_circle.perimeter() + self.out_circle.perimeter()
    def Ring_area(self):
        return self.out_circle.area() - self.in_circle.area()

s = Ring(5,10)
print(s.Ring_perimeter())
View Code

3.類屬性的當中調用

類名.__name__# 類的名字(字符串)
類名.__doc__# 類的文檔字符串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類所有父類構成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性,key為屬性名,value為屬性值
類名.__module__# 類定義所在的模塊
類名.__class__# 實例對應的類 

經典例題:

class Robot:
    '''表示有一個帶有名字的機器人'''
    # 一個類變量,用來計數機器人數量
    population = 0

    def __init__(self, name):
        '''初始化數據'''
        self.name = name    # name變量通過使用self分配,屬於一個對象變量
        print('initializng {}'.format(self.name))
        # 當有人被創建時,機器人增加數量
        Robot.population += 1

    def die(self):
        '''掛了'''
        print('{} is being destroyed'.format(self.name))
        Robot.population -= 1

        if Robot.population == 0:
            print('{} was the last one.'.format(self.name))
        else:
            print('there are still {:d} robots working.'.format(Robot.population))

    def say_hi(self):
        '''來自機器人的問候'''
        print('greeting, my masters call me{}'.format(self.name))

    @classmethod
    # 等價於調用how_many = classmethod(how_many)
    def how_many(cls):
        '''打印當前數量'''
        print('we have {:d} robots.'.format(Robot.population))


droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()

# droid2 = Robot("C-3PO")
# droid2.say_hi()
# Robot.how_many()

print("\nRobots can do some work here.\n")
print("Robots have finished their work. So let's destroy them.")
droid1.die()
# droid2.die()

Robot.how_many()


# 我們通過 Robot.population 而非 self.population 引用 population 類變量。
# 對於 name 對象變量采用 self.name 標記法加以稱呼,這是這個對象中所具有的方法。
# 要記住這個類變量與對象變量之間的簡單區別。
# how_many 實際上是一個屬於類而非屬於對象的方法。這就意味着我們可以將它定義為一個
# classmethod(類方法) 或是一個 staticmethod(靜態方法) ,這取決於我們是否知道我們需不需
# 要知道我們屬於哪個類.

 說出打印順序並解釋原因:

class Foo:
	country = '中國'
 	print(country)     # 這里也有一個打印
	
	def __init__(self,name,country):
		self.name = name
		self.country = country
		
learning = Foo('learning','印度')  
Foo.country = '泰國'    
print(Foo.country)      
print(learning.country)

4.面向對象的三大特性:繼承,多態,封裝

4.1繼承:python中,一個新建的類可以繼承一個或多個父類

繼承的作用:

  • 減少代碼的重用
  • 提高代碼的可讀性
  • 規范編程模式

python內繼承分為單繼承和多繼承,基本形式:

class Parent1:  # 定義父類
    pass

class Parent2:  # 定義父類
    pass

class Son1(Parent1):  # 單繼承,基類(父類,超類)是Parent1,派生類(子類)是Son
    pass

class Son2(Parent1, Parent2):  # 多繼承,用逗號分隔開多個繼承的類
    pass
 
print(Son1.__bases__)   # (<class '__main__.Parent1'>,)
print(Son2.__bases__)   #查看所有繼承的父類 (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
print(Parent1.__base__)  # 查看單個繼承的父類<class 'object'>

# 在python3中,所有的類都會默認繼承object類
# 繼承了object類的所有類都是新式類
# 如果一個類沒有繼承任何父類,那么__base__就會顯示<class 'object'>

如果在開發程序過程中,我們已經定義一個類A,現在又定義了一個類B,如果B的類的大部分內容與A都相同,則可以使用繼承來實現,實現代碼的重用。

繼承中的派生:子類在繼承父類的屬性基礎下,也可以重新定義一些屬性,如果重新定義的屬性(或方法)與父類的相同,則會覆蓋父類的屬性(或方法),優先執行自己定義的。

class Animal:
    def __init__(self, name, hp, ad):
        self.name = name    # 三個屬性
        self.hp = hp  # 血量
        self.ad = ad  # 攻擊力

    def eat(self):   # 方法
        print('eating in Animal')
        self.hp += 20

class Person(Animal):
    def __init__(self, name, hp, ad, sex):
        # Animal.__init__(self, name, hp, ad)  
        #super(Person, self).__init__(name, hp, ad) 
        # 在單繼承中,super負責找到當前類所在的父類,這個時候就不需要再手動傳self
        super().__init__(name, hp, ad)    # 這個比較常用
        self.sex = sex     # 派生屬性
        self.money = 100   # 給屬性中再增添一個新技能
    
    def attack(self, dog):   # 派生方法
        print("%s攻擊了%s" % (self.name, dog.name))
        
    def eat(self):     # 重寫
        # super().eat()
        # 在類內使用super()方法找父類的方法,這樣調用子類時可以同時實現父類eat方法
        print('eating in Person')
        self.money -= 50

class Dog(Animal):
    def __init__(self, name, hp, ad, kind):
        super().__init__(name, hp,ad)
        self.kind = kind    # 派生屬性
    def bite(self, person):  # 派生方法
        print("%s咬了%s" % (self.name, person.name))

       
mark = Person('alex', 100, 10, 'female')   # 實例化
tg = Dog('到哥', 100, 50, '藏獒')
tg.bite(mark)   # 到哥咬了alex
print(mark.__dict__)
# {'money': 100, 'name': 'alex', 'hp': 100, 'sex': 'female', 'ad': 10}
mark.eat()    # eating in Person
super(Person, mark).eat()  # 在外部通過調用super查看person父類中的方法,這時候不能用上面的簡便方法了,必須寫類名,對象名,明確調用者
# eating in Animal 

# 這里使用super方法,實現了子類可以調用父類中的屬性,通過super可以直接在子類執行父類的方法

看看super的相關用法(很經典)

class Fooparent(object):
    def bar(self, message):
        print("%s from parent" % message)
    def __init__(self):
        self.parent = "I\'m the parent."
        print("parent")


class Foochild(Fooparent):
    def __init__(self):
        super(Foochild, self).__init__()   # 這里也可以直接寫成super().__init__()
        print("child")

    def bar(self, message):
        super(Foochild, self).bar(message)   # 這里也是可以簡寫
        print("child bar function")
        print(self.parent)


if __name__ == "__main__":
    foochild = Foochild()
    foochild.bar("hello world")
    
# 當創建出foochild對象時,它會先去Foochild的類中自上而下執行,當遇到init下的super方法時,去執行父類的__init__方法,此時打印出parent,
接着執行自己的方法。當對象調用bar方法時,先在foochild中查找,遇到了super方法,又返回父類執行父類的bar,接着執行自己的方法,以及子類print(self.parent)

看看這個例子(調用了子類方法的同時又嵌套了父類方法):

class Schoolmember:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        print('ininialized Schoolmember:{}'.format(self.name))
    
    def tell(self):
        print('name:{},age:{}'.format(self.name, self.age,end = ''))
        
class Teacher(Schoolmember):
    def __init__(self,name, age, salary):
        Schoolmember.__init__(self,name,age)
        self.salary = salary
        print('initialized teacher:{}'.format(self.name))
        
    def tell(self):
        Schoolmember.tell(self)
        print('salary:{}'.format(self.salary))
        
class Student(Schoolmember):
    def __init__(self,name, age,marks):
        Schoolmember.__init__(self, name, age)
        self.marks = marks
        print('initialized students:{}'.format(self.name))
        
    def tell(self):
        Schoolmember.tell(self)
        print('marks:{}'.format(self.marks))
        
# t = Teacher('Tom', 40, 300)
s = Student('egg',26, 80)
# t.tell()
s.tell()

再看看這個例子

class Parent:
    def func(self):
        print('in parent func')
    def __init__(self):
        self.func()

class Son(Parent):
    def func(self):
        print('in son func')

s = Son()

示意圖:

鑽石繼承問題 

多繼承:遵循廣度優先遍歷順序

例一:

class A:
    def func(self):
        print('A')
class B(A):
    pass
    def func(self):
        print('B')
class C(A):
    pass
    def func(self):
        print('C')
class D(B):
    pass
    def func(self):
        print('D')
class E(B,C):
    pass
    def func(self):
        print('E')
class F(D,E):
    pass
    def func(self):
        print('F')
f = F()
f.func()
print(F.mro())    # 廣度優先的遍歷順序

圖例

 例二:

class A:
    def func(self):
        print('A')
class B(A):
    def func(self):
        super().func()
        print('B')
class C(A):
    def func(self):
        super().func()
        print('C')
class D(B,C):
    def func(self):
        super().func()   
        print('D')

d = D()
d.func()

圖例

 

這里的多繼承是根據子節點去找的,執行d.func()的時候遇到super,去B中去找,B中也有Super,緊接着會去C,發現C也有super,由於廣度有限,此時它會找到A,執行A,再返回C,執行C,再到B,再到D

# 這里你可以分別把B和C中的super注掉再看看它的打印

 例三:

class A:
   def test(self):
        print('from A')
class B(A):
    def test(self):
        print('from B')
class C(A):
    def test(self):
        print('from C')
class D(A):
    def test(self):
        print('from D')
class E(B):
    def test(self):
        print('from E')

class F(E,D,C):
    def test(self):
       print('from F')

b= B()
b.test()
d = D()
d.test()

f = F()
f.test()
print(F.mro())

圖例

 

例題:

class F3(object):
	def f1(self):
		ret = super().f1()
		print(ret)
		return 123


class F2(object):
	def f1(self):
		print('123')


class F1(F3, F2):
	pass


obj = F1()
obj.f1()

注意:這里會先去F3下,因為存在super,會到F2下打印f1,再返回F3去執行f1。這個跟上面一樣,它是根據節點去查找的

例題:

class Init(object):
    def __init__(self,value):
        self.val = value

class Add2(Init):
    def __init__(self,val):
        super(Add2,self).__init__(val)
        self.val += 2

class Mul5(Init):
    def __init__(self,val):
        super(Mul5,self).__init__(val)
        self.val *=5

class Pro(Mul5,Add2):
    pass

class Incr(Pro):
    csup = super(Pro)

    def __init__(self,val):
        self.csup.__init__(val)
        self.val += 1

p = Incr(5)
print(p.val)

例題:

class F1(object):

	def __init__(self, num):
		self.num = num

	def func(self, request):
		print(self.num, request)

	def run(self):
		self.func(999)


class F2(F1):

	def func(self, request):
		print(666, self.num)


objs = [F1(1), F2(2), F2(3)]
objs[1].run()
objs[2].run()

總結:(py3中全是新式類(默認都帶有object),遵循廣度優先。py2中不帶object的屬於經典類,遵循深度優先)

# 單繼承中
   在單繼承中就是單純的尋找父類
   在多繼承中就是根據子節點 所在圖 的 mro順序找尋下一個類

# 遇到多繼承和super
    # 對象.方法
        # 找到這個對象對應的類
        # 將這個類的所有父類都找到畫成一個圖
        # 根據圖寫出廣度優先的順序
        # 再看代碼,看代碼的時候要根據廣度優先順序圖來找對應的super

4.2多態(指的是一類事物有多種形態。記住,python是自帶多態效果的,且崇尚鴨子類型

先來閑聊下python編程原則:

  • 開放封閉原則:對於擴展是開放的,對於修改是封閉的
  • 依賴倒置原則
  • 接口隔離原則(python里沒有接口類的概念,java類沒有多繼承,但是可以通過接口實現多繼承

python中接口類和抽象類:

  接口類(在抽象類的基礎上):python默認沒有接口類,接口類不能被實例化,這也意味着接口類中的方法不能被實現。

  抽象類:python中,默認是有的

      父類的方法,子類必須實現

     (抽象類)父類的方法也可以被實現

  相同:這兩者都是用來約束子類方法的,都不能被實例化

  區別:接口類不能實現方法,抽象類可以實現方法里面的內容

        只要是抽象類和接口類中被abstractmethod裝飾的方法,都需要被子類實現。

     當多個類之間有相同的功能也有不同的功能的時候,python應采用多個接口類來實現。

        如果工作中遇到接口類,記住按照抽象類中的規范去去一一實現對應的方法

多態(通過繼承實現):

  在一個類之下發展出來的多個類的對象都可以作為參數傳入一個函數或者方法,在python中不需要可以實現多態,因為它本身自帶多態效果

  Pyhon不支持Java和C#這一類強類型語言中多態的寫法,但支持原生多態,其Python崇尚“鴨子類型。所謂的鴨子類型,說白了就是兩個不同的類擁有相同的方法名,利用一個統一的接口來調用,不同的對象在接收時會產生不同的行為(即方法)

  鴨子類型:不是通過具體的繼承關系來約束某些類必須必須要有哪些方法名,是通過一種約定俗成的概念來保證多個類中相似的功能叫相同的名字

python的鴨子類型:

class A:
    pass

class B(A):
    def show(self):
        print(111)
        
class C(A):
    def show(self):
        print(222)
 
        
def func(obj):    # 在Java或C#中定義函數參數時,必須指定參數的類型,即def Func(F1 
    return obj.show()    # 定義一個統一的接口來使用

s1 = B()
func(s1)    # 111

s2 = C()
func(s2)    # 222

  # 抽象類和接口類做的事情就是創建規范,制定一個類的metaclass(元類)是ABCmeta,這個類就變成了一個接口類,這個類的功能主要就是建立一個規范,從而約束后面的子類

抽象接口:

from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):   
    @abstractmethod
    def pay(self):
        pass

class Alipay(Payment):
    def pay(self, money):
        print('使用支付寶支付了%s元' % money)

class QQpay(Payment):
    def pay(self, money):
        print('使用qq支付了%s元'%money)

class Wechatpay(Payment):
    def pay(self, money):
        print('使用微信支付了%s元'%money)
    def recharge(self):
        pass

def pay(a, money):
    a.pay(money)
# 歸一化設計:不管是哪一個類的對象,都調用了同一個函數去實現了相似的功能

a = Alipay()
a.pay(100)   # 使用支付寶支付了100元

b = QQpay()
b.pay(50)   # 使用qq支付了50元

c = Payment()
c.pay()      # 接口類不能被實例化,會報錯
# TypeError: Can't instantiate abstract class Payment with abstract methods pay 

4.3 封裝:顧名思義就是將內容封裝到某個地方,以后再去調用被封裝在某處的內容

封裝原則:

   1. 將不需要對外提供的內容都隱藏起來;

      2. 把屬性都隱藏,提供公共方法對其訪問。

封裝的好處:

  1.提高代碼的復用性

  2.提高安全性

  3.降低代碼的冗余度

4.3.1 私有變量和私有方法:

定義一個私有的名字:在私有的名字前面加兩條下划線 _ _N = 'name'。這樣就不能在類的外面調用它

 但是,一個私有的名字在存儲過程中仍然會出現在A.__dict__中,我們仍然可以調用到,只不過python對其名字進行了修改:_類名__名字

class A:
    __N = 666    # 靜態變量
    
    def func(self):
        print(A.__N)
        
print(A._A__N)   # 666
print(A.__dict__)  # 通過這個可以查看它的內部機制

在類里面,只要代碼遇到_ _名字,就會被python解釋器自動轉換成 _類_ _名字

#  在類中,靜態屬性,方法,對象屬性都可以變成私有的,只需要在這些名字之前加上__

# 私有屬性
class A:
    def __init__(self,name):
        self.__name = name
        
    def b(self):
        print('in b: %r' % self.__name)

a = A(6)
print(a._A__name)   # 6
a.b()    # in b: 6

例二 class F: def a(self): self.__name = 666 f = F() f.a() print(f.__name) # 報錯 print(f._F__name) # 666 # 私有方法 class A: def __a(self): print(111) def b(self): self.__a() c = A() c.b() # 私有方法的調用 # 繼承中的私有方法 class D: def __func(self): print(111) class E(D): def __init__(self): self.__func() # __func()是父類私有的方法 e = E() # 報錯,因為私有的名字不能被子類繼承 

#  繼承中的關於私有屬性的易錯點(一不小心就分析錯誤)   

###  子類不能繼承父類的私有方法和屬性

class A:
    def fa(self):
        print('from A')
    
    def test(self):
        self.fa()
        
class B(A):
    def fa(self):
        print('from B')
        
b = B()
b.test()   # from B(捋一捋這里為什么是B)


class A:
    def __fa(self):    # 此時這里變形為_A__fa
        print('from A')
    
    def test(self):
        self.__fa()   # 這里止與自己所在的類為准,及調用_A__fa


class B(A):
    def __fa(self):   # 此時這里變形為_B__fa
        print('from B')


b = B()
b.test()   # from A   (捋一捋這里為什么又是A)

這個題也是一樣

class C:
__name = "公有靜態字段"

def func(self):
print(C.__name)

class D(C):
def show(self):
print(C.__name) # 公有靜態字段

obj = C()
obj.func() #正常

obj_son = D() # 報錯
obj_son.show() 

4.3.2 封裝與擴展

  封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。

字典對象做封裝:

list_filter = [
	{'text':'董方方','gender':'男','color':'xx'}, 
	{'text':'黃曉雪','gender':'男','color':'xx'},
	{'text':'李貝貝','gender':'男','color':'xx'},
]


for item in list_filter:
	print(item['text'] + item['gender'])

  這里我們不要狹隘的認識對象,python中一切皆對象,字典也是對象,這里也是一種封裝的思想

接下來看看下面的例子:

class Option(object):
	def __init__(self,text,gender,color):
		self.text = text
		self.gender = gender
		self.color = color

	def get_t_g(self):
		return self.text +self.gender

list_filter = [
	Option(text='董方方',gender='男',color = 'xx'),
	Option(text='黃曉雪',gender='男',color = 'xx'), 
	Option(text='李貝貝',gender='男',color = 'xx'), 
]

for item in list_filter:
	print(item.get_t_g())

  該類也是一種封裝的思想,這里實例化了三次,開辟了三個內存空間,不過都是用同一個對象進行調用的

# 面試晉升題

class D:
    def __init__(self):
        self.__func()
    def __func(self):
        print('in D')

class E(D):
    def __func(self):
        print('in E')

e = E()

@property(實現了將方法偽裝成屬性)

  能夠將一個方法偽裝成屬性,從原來的obj.func()變成了obj.func

被property裝飾的方法仍然是一個方法,雖然代碼看上去邏輯上沒有什么變化,但是從調用者角度來看換了一種方式,使之更加合理。

# 人體BMI指數 ,體質指數(BMI)=體重(kg)÷身高^2(m),寫一個類 描述人體BMI指數

class Person:
    def __init__(self, tall, weight):
        self.__weight = weight
        self.tall = tall

    @property
    def BMI(self):
        return 'BMI指數:%s' % (self.__weight/(self.tall**2))


me = Person(1.79, 69)
print(me.BMI)     # 將方法偽裝成屬性

@property的進階之@funcname.setter(實現了對私有屬性的修改)

被@property裝飾的方法名必須和被@funcname.setter裝飾的方法同名

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

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

    @name.setter      # 這里的名字name必須與上面的方法名一致
    def name(self,new_name):   # 只能傳入一個形參
        print('one', new_name)

p = Person('luffy')
print(p.name)  # luffy
p.name = 'piece'  # one piece

@property的進階之@funcname.deleter(實現了對私有屬性的刪除)

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

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

    @name.deleter  # 這里的名字name必須與上面的方法名一致
    def name(self):
        del self.__name


p = Person('luffy')
print(p.name)  # luffy
del p.name
print(p.name)   # 報錯,因為已經刪除掉了

例:輸入原價和折扣價,查看價格的時候可以查看折扣價,同時實現可以更換價格,也能實現輸出折扣價

class Goods:
    def __init__(self, name, price, discount):
        self.name = name
        self.__price = price
        self.__discount = discount
    
    @property
    def now_price(self):
        return self.__price * self.__discount
    
    @now_price.setter
    def now_price(self, new_price):
        if type(new_price) is int or float:
            self.__price = new_price


apple = Goods('apple', 8, 0.8)
print(apple.now_price)     # 6.4
apple.now_price = 10             # 對價格進行修改
print(apple.now_price)     # 8.0     # 折扣價

@classmethod類方法(可以被類直接調用,不需要默認傳對象參數,需要傳入類參數cls,執行類方法時,自動將調用該方法的復制給cls

對於靜態屬性的修改

class Goods:
    __discount = 0.8
    def __init__(self, name, price):
        self.name = name
        self.__price = price
        
    @property
    def now_price(self):
        return self.__price*Goods.__discount
    
    @classmethod
    def change_discount(cls,new_discount):    # 類方法,可以被類直接調用,需要傳入一個類參數cls
        cls.__discount = new_discount
        
apple = Goods('apples', 6)
print(apple.now_price)    # 4.8
 
Goods.change_discount(1)    # 修改折扣后       
# 這里注意是在括號里傳值,不是賦值,不是
Goods.change_discount=1,不知道為啥自己很容易犯錯
banana = Goods('bananas', 6)
print(banana.now_price) # 6

@staticmethod靜態方法(由調用,無默認參數)

#  當一個方法要使用對象的屬性時 就是用普通的方法(不帶self)
#  當一個方法要使用類中的靜態屬性時 就是用類方法
當一個方法要既不使用對象的屬性也不使用類中的靜態屬性時,就可以使用staticmethod靜態方法

class A:
    def __init__(self,name):
        self.name = name
    
    @staticmethod
    def a():    # 這里不用再傳入self
        print(666)
        
A.a()   # 666

類中的三種方法總結:

注意:

  對於實例方法:由實例化對象調用

  對於類方法:由於不適用於對象內存空間的屬性,所以不會將對象和方法綁定到一起,而是將類和方法綁定在一起

  對於靜態方法:不是綁定方法,沒有和對象或者類方發生任何綁定關系

class Foo:

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

    def ord_func(self):
        """ 定義實例方法,至少有一個self參數 """

        # print self.name
        print '實例方法'

    @classmethod
    def class_func(cls):
        """ 定義類方法,至少有一個cls參數 """

        print '類方法'

    @staticmethod
    def static_func():
        """ 定義靜態方法 ,無默認參數"""

        print '靜態方法'


# 調用實例方法
f = Foo()
f.ord_func()

# 調用類方法
Foo.class_func()

# 調用靜態方法
Foo.static_func()

例題:(分析輸出的是什么)

class A:
    __role = 'CHINA'   
    @classmethod
    def show_role(cls):
        print(cls.__role)

    @staticmethod
    def get_role():
        return A.__role

    @property
    def role(self):
        return self.__role   
      
a = A()
print(a.role)       
print(a.get_role()) 
a.show_role()   

7.面向對象進階  

7.1.isinstance和issubclass

    isinstance(obj,cls)用來檢查obj是否是類cls的對象,之間是否存在血緣關系
  (type只能單純的判定,但是在繼承方面判定不出)
    issubclass(sub,super),必須先子類后父類,否則也會返回False,它用來檢查sub類是否是super類的派生類(之間存在繼承關系)

7.2.反射(高級知識點,重點)

    指程序可以訪問,監測和修改它本身狀態或者行為的一種能力(自省)
 概念:利用字符串的形式去(對象)模塊中操作(增刪改查)成員。python中一切皆對象,都可以使用反射

7.3.python面向對象中的反射

    通過字符串的形式操作對象相關的屬性
 python中都可以使用反射
     hasattr()判斷一個命名空間中有沒有這個名字
    getattr()從命名空間中獲取這個名字對應的值
    setattr()修改和新建
    delattr()刪除一個屬性
class F:
    a = 'luffy'
    def __init__(self,name):
        self.name =name
        
    def A(self):
        print('%s666' % self.name)
        
obj = F('Ronan')

# 檢查是否存在某屬性,也能檢查靜態屬性
print(hasattr(obj, 'name'))
print(hasattr(obj, 'A'))

# 獲取屬性,包含靜態屬性
print(getattr(obj, 'name'))
getattr(obj,'A')()

# 設置屬性,如果有就改,沒有就增
setattr(obj, 'ACE', '999')
print(obj.__dict__)    # {'name': 'Ronan', 'ACE': '999'}

# 刪除屬性(如果不存在該屬性則報錯),無法刪除靜態屬性
delattr(obj, 'name')
print(obj.__dict__)   # {'ACE': '999'}  

再看看詳細用法:

# 例一
class A:
    role = 'luffy'
    def func(self):
        print('*'*self)
        
print(getattr(A,'role'))         # 從A的命名空間找一個屬性,直接就可以找見這個屬性的值
f = getattr(A, 'func');f(5)      # 從A的命名空間找一個方法,找見的是這個方法的內存地址

# 在類中hasattr和gettar的組合使用
ret = input('>>>')
if hasattr(A, ret):
    print(getattr(A, ret))  # getattr從A的命名空間里找一個屬性

if hasattr(A, ret):
    func = getattr(A, ret)  # getattr從A的命名空間里找一個方法
    func(1)  # 給self傳參


# 例二
class A:
    role = 'Person'
    def __init__(self):
        self.money = 500
    def func(self):
        print('*'*10)

a = A()
print(a.func)
getattr(a,'func')()           # 對象使用對象能用的方法
print(getattr(a,'money'))     # 對象使用對象能用的屬性

# 對於模塊,模塊使用模塊中的名字,從自己所在的模塊中使用自己名字
import os
os.rename('userinfo','user')
getattr(os,'rename')('user','user_info')

# 總結
# 類使用類命名空間中的名字
#     getattr(類名,'名字')
# 對象使用對象能用的方法和屬性
#     getattr(對象名,'名字')
# 模塊使用模塊中的名字
#     import 模塊
#     getattr(模塊名,'名字')
# 從自己所在的模塊中使用自己名字
#     import sys
#     getattr(sys.modules['__main__'],名字)


# 關於setattar和delattr
class A:
    def __init__(self, name):
        self.name = name
    
    def wahaha(self):
        print('wahahahahaha')

a = A('alex')
print(a.__dict__)  # {'name': 'alex'}
setattr(a, 'age', 18)  # 給a對象新增一個屬性
print(a.__dict__)  # {'age': 18, 'name': 'alex'}
setattr(a, 'name', 'egon')
print(a.__dict__)  # {'age': 18, 'name': 'egon'}
delattr(a, 'age')
print(a.__dict__)  # {'name': 'egon'}

 類中帶下划線的內置方法:詳細介紹

__doc__            表示類的描述信息

__dict__             類或對象中的所有成員

__getitem__

__setitem__

__delitem__      用於字典索引操作

__iter__             用於迭代器,列表,字典,元祖進行for循環,是因為它們的內部都定義了__iter__

__module__     表示當前操作的對象在哪個模塊

__class__         表示當前操作的對象的類是什么

__init__             構造方法,通過類創建對象時,自動觸發執行

__len__            obj對應的類中含有__len__方法,len(obj)才能正常執行

__hash__        obj類自帶的,只有實現了__hash__方法,hash(obj)才能正常執行

__str__

__repr__         在格式化字符串的時候,repr可以作為str的替補,單反之不行

__call__           __call__ 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()

__del__          析構方法:在刪除一個對象時做一些收尾工作,對象在內存中被釋放時自動觸發執行,用來關閉在對象中打開的系統資源

          python內的垃圾回收機制

 __new__       單例模式:一個類只能創建一個實例。它在__init__之前執行,負責創建一個對象,控制生成一個新實例的過程

 __init__   初始化方法:用於初始化一個新實例,控制這個初始化過程,比如添加某些屬性

# __repr__
class Book:
    s = 'dsa'
    def __init__(self):
        self.name = 'alex'
        
    def __repr__(self):
        return 'zero'
        
    def __str__(self):
        return 'luffy'
    
f = Book()
print(f)   # luffy       # 優先執行__str__里的內容
print(str(f))   # luffy
print(repr(f))   # zero

# 不管是在字符串格式化還是打印對象的時候,__repr__方法都可以作為__str__方法的替補
# 如果__str__和__repr__方法你只能實現一個:先實現__repr__


# __call__
class A:
    s = 'ro'
    def __call__(self, *args, **kwargs):
        print('one')
    def call(self):
        print('two')

a = A()
a.call()  # two
a()       # one     # 通過對象名()直接調用類內置的__call__
A()()     # one

print(callable(a))   # True
print(callable(A))   # True
print(callable(A.s))  # False, 只能查看方法

# 一個對象是否可調用 完全取決於這個對象對應的類是否實現了__call__


# __eq__
class A:
    def __eq__(self, other):
        return True

a = A()
b = A()
print(a == b)  # True
print(a is b)  # False

# == 是由__eq__的返回值來決定的

# __new__     設計模式:單例模式,即一個類只能有一個實例

class B:
    __instance = None
    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = object.__new__(cls,*args,**kwargs)
        return cls.__instance
    def __init__(self,name,age):
        self.name = name
        self.age = age
    
a = B('alex',80)
b = B('egon',20)
print(a)  
print(b)
print(a.name)
print(b.name)

# <__main__.B object at 0x04F1D330>    # 結果相同
# <__main__.B object at 0x04F1D330>   
# egon
# egon

使用了__instance=None來存放實例,如果 _instance 為 None,則新建實例,否則直接返回 _instance 存放的實例

再看一個單例模式的例子:

class A:
	def __new__(cls, *args, **kwargs):
		if not hasattr(cls,'instance'):
			# 每次實例化,都返回這同一個對象
			cls.instance = super().__new__(cls)
		return cls.instance
obj1 = A()
obj2 = A()
obj1.attr = 'value'
print(obj1.attr)
print(obj2.attr)

# value
# value

其他實現單例模式方式:猛戳此處

__del__

class Foo:

    def __del__(self):
        print('run __del__')

f1=Foo()
# del f1
print('------->')

>>------->
run __del__
#程序中沒有調用del方法,在整個程序執行結束之后調用__del__方法



class Foo:

    def __del__(self):
        print('run __del__')

f1=Foo()
del f1
print('------->')

>>run __del__
------->
# 調用del方法則先執行析構方法,然后執行

 __call__:對象后面加括號,觸發執行。構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於__call__方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()

class Foo:

    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 執行 __init__
obj()       # 執行 __call__

__next__,__iter__用來實現迭代器

# 斐波那契數列
class Fib:
    def __init__(self):
        self.a = 0
        self.b = 1
    def __iter__(self):
        return self
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a
    
f = Fib()
for i in f:
    if i > 1000:
        break
    print(i,end='\t')

 例題:

有一個類的init方法如下:

    class Person:

        def __init__(self,name,age,sex,weight):

            self.name = name

            self.sex = sex

            self.age = age

            self.weight = weight

    假設有100個person的對象,

    若兩個對象的obj1,obj2的name和sex屬性相同

    即obj1.name==obj2.name and obj1.sex==obj2.sex

    我們認為兩個對象為同一個對象,已知一個列表中的100個對象,對這100個對象進行去重。

 ### 首先看到這個題,會考慮到使用內置函數__eq__方法 ,只要name和sex相同,返回True,用集合去重時會報錯,說對象時不可哈希類型

class Person:
    def __init__(self,name,age,sex,weight):
        self.name = name
        self.sex = sex
        self.age = age
        self.weight = weight
    def __eq__(self, other):
        if other.name == self.name and other.sex==self.sex:
            return True
    def __hash__(self):
        return hash(self.name + self.sex)

lis = [Person('luffy',19,'male',88),Person('luffy',19,'male',120),Person('ronan',19,'male',120)]

print(set(lis))   # {<__main__.Person object at 0x05306930>, <__main__.Person object at 0x05306910>}

  

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM