1. 程式人生 > >python(五):面向對象--類和實例

python(五):面向對象--類和實例

括號 們的 apple 系統 method 不能 balance 類的創建 內置

一、類的基本概念

  類是用來創建數據結構和新類型對象的主要機制。一個類定義了一系列與其實例對象密切關聯的屬性.典型的屬性包括變量(也被稱為 類變量)和函數(又被稱為方法)。

  1、class上下文

  class語句用來定義一個類.類的主體語句在類定義同時執行。

class Account(object):
    """一個簡單的類"""
    print("hello")
    account_type = "Basic"
    def __init__(self, name, balance):
        """初始化一個新的Account實例
""" self.name = name self.balance = balance def deposit(self, amt): "存款" self.balance = self.balance + amt def withdraw(self, amt): """取款""" self.balance = self.balance - amt def inquiry(self): """返回當前余額""" return
self.balance # 執行上述語句,直接打印hello

  在當前Account類中,凡是不含有self的屬性和方法,都是直接用Account.屬性和Account.方法來訪問或執行的。它有些和匿名函數類似。再看下面代碼:

class Account(object):
    """一個簡單的類"""
    print("hello")
    account_type = "Basic"
    def sayHello():
        return "hello"
    
# 直接執行時,會打印hello
print(Account.account_type)
Account.sayHello()
# 打印結果為 # Basic # ‘hello‘

  結合兩個Account類,可以看出:

  1.能夠直接用對象.屬性和對象.方法執行的,都是類屬性和類方法;不能執行(含有self參數)的,都是實例對象的屬性和方法,需要實例化對象(或者類.方法傳入參數實例對象)才能執行。

  類方法有兩個含義:一是給類定義的,屬於類內存空間的方法,如Account.sayHello;二是該方法既然是類對象的方法,就能夠被類對象和所有實例對象調用。

class A:
    def __init__(self, *args, **kwargs):
        self.name, self.age, self.gender = args[:3]
    def sayHello(self):
        print("my name is %s, %s, %s." % (self.name, self.age, self.gender))
    
a = A("Li", 27, "male")
A.sayHello(a)
# my name is Li, 27, male.

  2.在class中直接寫func()(如print),會在代碼解析時直接執行,這說明:類屬性和類方法、實例方法(如上例account_type)是在類創建時就生成了。跟有沒有實例化對象無關。在類中引用一個類的屬性必須使用類的全名。
  3.代碼在解析Account類時,就為類對象開辟了內存空間。而此時還沒有實例化對象。

  4.類對象作為一個名字空間,存放在類定義語句運行時創建的對象。class語句並不創建類的實例,它用來定義所有實例都應該有的屬性。類的名字空間並不是為類主體中的代碼(而是實例)服務的。

  2、類裝飾器--@staticmethod

  sayHello()這個函數加上前綴@staticmethod,用以標識它屬於這個類(而不是普通函數)的方法,這被稱為靜態方法。

class AClass(object):
    @staticmethod # 靜態態方法修飾符,表示下面的方法是一個靜態態方法 
    def astatic(): 
        print(a static method)
anInstance = AClass()
AClass.astatic()                    # prints: a static method
anInstance.astatic()                # prints: a static method

  你完全可以將靜態方法當成一個用屬性引用方式調用的普通函數,靜態方法可以直接被類或類實例調用。它沒有常規方法那樣的特殊行為(綁定、非綁定、默認的第一個參數規則等等)。任何時候定義靜態方法都不是必須的(靜態方法能實現的功能都可以通過定義一個普通函數來實現)。

  3、類裝飾器--@classmethod

  @classmethod裝飾器來裝飾的通常稱為類方法,並且第一個固定不變的參數是cls,也就是該類對象自身。

class ABase(object):
    @classmethod #類方法修飾符
    def aclassmet(cls): 
        print(a class method for, cls.__name__)
class ADeriv(ABase): 
    pass
bInstance = ABase()
dInstance = ADeriv()
ABase.aclassmet()
bInstance.aclassmet()
ADeriv.aclassmet()
dInstance.aclassmet()
# 打印結果為
# a class method for ABase
# a class method for ABase
# a class method for ADeriv
# a class method for ADeriv

  任何時候定義類方法都不是必須的(類方法能實現的功能都可以通過定義一個普通函數來實現,只要這個函數接受一個類對象做為參數就可以了)。避免在類方法中使用了帶有self參數的語句,以使cls和self產生混亂,這看起來不倫不類。

  4、類裝飾器--@property

  @property用於將一個實例方法變為屬性訪問。即調用方式由實例.方法()調用變為實例.方法。

class Goods:
    def __init__(self, price, discount):
        self.__price = price
        self.discount = discount
    
    @property
    def price(self):
        return self.__price * self.discount
    
    @price.setter
    def price(self, newprice):
        self.__price = newprice

    @price.deleter
    def price(self):
        del self.__price
        
apple = Goods(20, 0.8)
print(apple.price) # 16
apple.price = 30   # 看起來像是對self.price重新賦值,但是調用了self.price方法來設置
print(apple.price) # 24
# del apple.price

  5、類的名稱空間

  所有位於class語句中的代碼都在特殊的命名空間中執行--類命名空間(class namespace)。這個命名空間可由類內所有成員訪問。

class MemberCounter(object):
    members = 0
    def init(self):
        MemberCounter.members += 1
        print(MemberCounter.members)

m1 = m2 = m3 = MemberCounter()
m1.init()
m2.init()
m3.init()
# 打印結果為
# 1
# 2
# 3

  6、類是如何產生的

  當解釋器執行到class關鍵字時,會掃描class上下文的代碼語句(類名,類所屬類,內容),並交由解釋器底層,來通過object實現類的創建。這一過程在class上下文結束時已經完成了。

class_name = "Foo"    # 類名
class_parents = (object, )   # 基類
# 類主體
class_body = """
name = "Foo"
def __init__(self, x):
    self.x = x
def hello(self):
    print("Hello")
"""
class_dict = {}
# 在局部字典class_dict中執行類主體
exec(class_body, globals(), class_dict)
# 創建類對象Foo
Foo = type(class_name, class_parents, class_dict)  # type可以指定
Foo("X").hello()

  exec和Foo=type()兩行模擬了解釋器實現類的過程。當我們寫class時,解釋器會自動查找class_name,class_parents(默認是metaclass=type)和class_body,並在掃描到class上下文結束時,調用type類創建我們寫的Foo類。

二、實例的基本概念

  1、類和實例的關系

  技術分享圖片

  1.類對象通過Class()來實例化對象(如上面的MemberCounter(),即類對象加括號,以此為例)。

  2.類對象內存空間和實例對象的內存空間是相互獨立的,但實例對象保留了對類內存空間的引用和訪問。也即類內存空間、類屬性和方法能夠被其實例化的對象訪問。

class MemberCounter:
    members = 0
    # MemberCount.__init__
    # 寫__init__(self)只是對MemberCounter.__init__(類對象的特殊方法)的重寫
    # 不寫__init__(self),在實例化對象時直接調用MemberCounter.__init__
    # def __init__(self): 
    # pass
    def init(self):
        MemberCounter.members += 1
        print(MemberCounter.members)

m1 = m2 = m3 = MemberCounter()  # MemberCount()直接調用了MemberCount.__init__方法來實例化對象
m1.init()
m2.init()
m3.init()

  2、實例化的過程

class A(object):
def __init__(self, name, age):
self.name = name
self.age = age
print("__init__ has called.")

def __new__(cls, *args, **kwargs):
print("__new__ has called.")
return object.__new__(cls)

a = A("Li", 27)
# __new__ has called.
# __init__ has called.

  實例化至少分兩個步驟:

  1.調用父類的__new__方法來創建一個實例對象。__new__()始終是一個類方法,接受類對象作為第一個參數。盡管__new__()會創建一個實例,但它不會自動地調用__init__()。

    如果看到在類中定義了__new__(),通常表明這個類會做兩件事之一。
    首先,該類可能繼承自一個基類,該基類的實例是不變的。如果定義的對象繼承自不變的內置類型(如整數、字符串、元組),常常會遇到這種情況,因為__new__()是唯一在創建實例之前執行的方法,也是唯一可以修改值得的地方法。__new__()的另一個主要用途是在定義元類時使用。

class myStr(str):
    def __new__(cls, value=""):
        u1 = myStr.upper(value)
        print(u1)
        return str.__new__(cls, value.upper())
    @classmethod
    def upper(cls, value):
        return value.upper()
u2 = myStr("hello")
print(u2)

"""
HELLO
HELLO

"""

  2.調用自己(或父類)__init__方法來初始化一個實例對象。__init__方法主要用於初始化實例對象的屬性,也就是往self.__dict__裏添加鍵值對。在這一過程中,它會調用__setattr__方法。

class MemberCounter:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __setattr__(self, old, new):
        print("__setattr__ has called.")
        self.__dict__[old] = new  # 當這一句被隱藏掉時,會發現打印的字典裏沒有存儲任何值
member = MemberCounter("An", 24)   
member.gender = "female"
print(member.__dict__)

"""
__setattr__ has called.
__setattr__ has called.
__setattr__ has called.
{‘name‘: ‘An‘, ‘age‘: 24, ‘gender‘: ‘female‘}
"""

  3、實例屬性

  實例對象的主要作用是設置一些key:value,並調用類內存空間中定義好的實例方法來做一些事情。__dict__也是類的特殊方法,用以查看實例對象(或者類對象)的屬性。實例對象通常以字典的形式保存屬性。

class MemberCounter:
    members = 0
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
member = MemberCounter("Li", 27)
print(member.name, member.age) 

member.gender = "female"  #
print(member.gender)

member.name = "An" #
print(member.name)

print(member.__dict__) # 查,字典操作

del member.name  #
print(member.__dict__)

# Li 27
# female
# An
# {‘name‘: ‘An‘, ‘age‘: 27, ‘gender‘: ‘female‘}
# {‘age‘: 27, ‘gender‘: ‘female‘}

  4、實例方法

  實例方法的第一個參數必須為self。

class MemberCounter:
    members = 0
    records = {}
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def sayHello(self):
        print("Hello, my name is %s, %s." % (self.name, self.age))
        
member = MemberCounter("Li", 24)
member.sayHello()

  5、類和實例的私有屬性[數據隱藏]

  以__xx格式定義的實例屬性和方法被稱為私有屬性或方法。這樣系統會自動生成一個新的名字 _Classname__xx 並用於內部使用。所謂的私有(內部屬性),實際上都是公開的。

class Person:
    __country = "China"
    def __init__(self, name, age):
        self.name = name
        self.__age = age
        
print(Person.__dict__)
print(Person._Person__country)
print(Person.__dict__["_Person__country"])

Li = Person("Li", 27)
print(Li.__dict__)
print(Li.name)
print(Li._Person__age)
print(Li.__dict__["_Person__age"])

三、3個面試題

  1、請說出下面代碼打印結果並予以解釋

class Foo:
    def __init__(self):
        self.func()
    def func(self):
        print(in Foo)

class Son(Foo):
    def func(self):
        print(in son)

s = Son()
# in son

  解釋: 在實例化對象時,如果沒有定義__init__方法,則會查找並調用父類中的__init__方法。此時實例對象已經由object.__new__創建,命名為self。於是self.func()會調用s中的func()方法。

  2、請說出下面代碼打印結果並予以解釋

class Foo:
def __init__(self):
self.__func() # self._Foo__func
def __func(self):
print(‘in Foo‘)

class Son(Foo):
def __func(self): # _Son__func
print(‘in son‘)

s = Son()
# in Foo

  解釋: 私有方法和私有屬性,在其被訪問或執行時,會在當前的class上下文中被強制轉化成帶有當前classname的新屬性。因此,self.__func()在被執行前已被強制轉換成self._Foo__func。

print(Foo.__dict__)
"""
{
    ‘__module__‘: ‘__main__‘, 
    ‘__init__‘: <function Foo.__init__ at 0x110523510>, 
    ‘_Foo__func‘: <function Foo.__func at 0x10d022730>, 
    ‘__dict__‘: <attribute ‘__dict__‘ of ‘Foo‘ objects>, 
    ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Foo‘ objects>, 
    ‘__doc__‘: None
}
"""

  3、請用__new__方法實現單例模式

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

def __new__(cls, *args, **kargs):
if not cls.__isinstance:
obj = object.__new__(cls)
cls.__isinstance = obj
return cls.__isinstance

alex = Person("alex")
egon = Person("egon")
print(id(egon))
print(id(alex))
print(alex.__dict__)
print(egon.__dict__)
"""
4514525024
4514525024
{‘name‘: ‘egon‘}
{‘name‘: ‘egon‘}
"""

  說明,單例模式只會開辟一個實例內存,不管創建多少個實例,都會覆蓋這個內存空間。

四、python3的繼承和組合

  1、多繼承問題

  python3的多繼承遵循廣度優先算法。它會保證每個節點從左到右,從下到上都只訪問一次,並找到最近的父類進行繼承。所有的節點都必須訪問並且都只訪問一次。

    技術分享圖片

技術分享圖片
class A:
    def f(self):
        print(in A)

class B(A):
    pass
    # def f(self):
    #     print(‘in B‘)

class C(A):
    pass
    # def f(self):
    #     print(‘in C‘)


class D(B,C):
    pass
    # def f(self):
    #     print(‘in D‘)

class E(C):
    # pass
    def f(self):
        print(in E)

class F(D,E):
    pass
    # def f(self):
    #     print(‘in F‘)

d = D()
d.f()
print(F.mro())
mro

  2、super繼承

  self是子類實例化的對象,在對父類不初始化時,調用父類的實例方法只是"借殼生蛋"。當一個子類實例被創建時, 基類的__init__()方法並不會被自動調用。

  super繼承:super用來解決python鉆石多重繼承出現的基類重復調用的問題。在Python3中,直接寫super().__init__(*args, **kwargs)。

class B:
    varB = 42
    def method1(self):
        print("Class B : method1")
    def method2(self):
        return B.varB * 2
    
class A(B):
    varA = 3.3
    def method3(self):
        print("Class A : method3")
        B.method1(self)  # 註意,這裏的self是A()初始化後的a,B類沒有初始化,直接把a當做self傳遞進去了
        return B.method2(self)

a = A()
print(a.method3())

"""
Class A : method3
Class B : method1
84
"""
class B:
    def __init__(self, name, age, *args, **kwargs):
        self.name = name
        self.age = age 
        self.salary = 20000
    def method1(self):
        print("My name is {}, {}, salary {}.".format(self.name, self.age, self.salary))
    
class A(B):
    def __init__(self, *args, **kwargs):
        self.name, self.age, self.gender = args
        super().__init__(*args, **kwargs)   # 第一種繼承方法super().__init__(*args, **kwargs)
        # B.__init__(self, *args, **kwargs)   # 第二種寫法

a = A("Li", 27, "male")
a.method1()

"""
My name is Li, 27, salary 20000.
"""

  3、調查繼承

  如果想要查看一個類是否是另一個類的子類,可以使用內建的issubcalss函數。如果想要知道已知類的基類,可以直接使用它的特殊屬性__bases__。

print(issubclass(A, B))  # True
print(A.__bases__)  # (<class ‘__main__.B‘>,)

  可以使用isinstance方法檢查和一個實例對象是否是一個類的實例。使用__class__特性查找一個實例對象屬於哪個類。

print(isinstance(a, A))  # True
print(isinstance(a, B))  # True

  實例被特殊屬性__class__鏈接回它們的類,所屬類名可以用__name.__訪問類特殊屬性__bases__中將類鏈接到它們的基類,該屬性是一個基類元組。這種底層結構是獲取、設置和刪除對象屬性的所有操作的基礎。

class A:
    pass
class B(A):
    pass

b = B()

print(b.__class__)
print(b.__class__.__name__)
print(B.__bases__)

"""
<class ‘__main__.B‘>
B
(<class ‘__main__.A‘>,)
"""

python(五):面向對象--類和實例