Task 03 從函數(shù)到高級魔法方法(Day 09 魔法方法)

一、學(xué)習(xí)內(nèi)容概括

二、具體學(xué)習(xí)內(nèi)容

魔法方法總是被雙下劃線包圍,例如__init__

魔法方法是面向?qū)ο蟮?Python 的一切,如果你不知道魔法方法,說明你還沒能意識到面向?qū)ο蟮?Python 的強大。

魔法方法的“魔力”體現(xiàn)在它們總能夠在適當(dāng)?shù)臅r候被自動調(diào)用。

魔法方法的第一個參數(shù)應(yīng)為cls(類方法) 或者self(實例方法)。

  • cls:代表一個類的名稱
  • self:代表一個實例對象的名稱

1、基本魔法方法

  • __init__(self[, ...]) 構(gòu)造器,當(dāng)一個實例被創(chuàng)建的時候調(diào)用的初始化方法
class Rectangle:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def getPeri(self):
        return (self.x + self.y) * 2

    def getArea(self):
        return self.x * self.y


rect = Rectangle(4, 5)
print(rect.getPeri())  # 18
print(rect.getArea())  # 20
  • __new__(cls[, ...]) 在一個對象實例化的時候所調(diào)用的第一個方法,在調(diào)用__init__初始化前,先調(diào)用__new__。
    • __new__至少要有一個參數(shù)cls,代表要實例化的類,此參數(shù)在實例化時由 Python 解釋器自動提供,后面的參數(shù)直接傳遞給__init__。
    • __new__對當(dāng)前類進行了實例化,并將實例返回,傳給__init__self。但是,執(zhí)行了__new__,并不一定會進入__init__,只有__new__返回了,當(dāng)前類cls的實例,當(dāng)前類的__init__才會進入。
class A(object):
    def __init__(self, value):
        print("into A __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into A __new__")
        print(cls)
        return object.__new__(cls)


class B(A):
    def __init__(self, value):
        print("into B __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into B __new__")
        print(cls)
        return super().__new__(cls, *args, **kwargs)


b = B(10)

# 結(jié)果:
# into B __new__
# <class '__main__.B'>
# into A __new__
# <class '__main__.B'>
# into B __init__

class A(object):
    def __init__(self, value):
        print("into A __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into A __new__")
        print(cls)
        return object.__new__(cls)


class B(A):
    def __init__(self, value):
        print("into B __init__")
        self.value = value

    def __new__(cls, *args, **kwargs):
        print("into B __new__")
        print(cls)
        return super().__new__(A, *args, **kwargs)  # 改動了cls變?yōu)锳


b = B(10)

# 結(jié)果:
# into B __new__
# <class '__main__.B'>
# into A __new__
# <class '__main__.A'>

注意:__new__沒有正確返回當(dāng)前類cls的實例,那__init__是不會被調(diào)用的,即使是父類的實例也不行,將沒有__init__被調(diào)用。

利用__new__實現(xiàn)單例模式

class Earth:
    pass


a = Earth()
print(id(a))  # 260728291456
b = Earth()
print(id(b))  # 260728291624

class Earth:
    __instance = None  # 定義一個類屬性做判斷

    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = object.__new__(cls)
            return cls.__instance
        else:
            return cls.__instance


a = Earth()
print(id(a))  # 512320401648
b = Earth()
print(id(b))  # 512320401648
  • __new__方法主要是當(dāng)你繼承一些不可變的 class 時(比如int, str, tuple), 提供給你一個自定義這些類的實例化過程的途徑。
class CapStr(str):
    def __new__(cls, string):
        string = string.upper()
        return str.__new__(cls, string)


a = CapStr("i love lsgogroup")
print(a)  # I LOVE LSGOGROUP
  • __del__(self) 析構(gòu)器,當(dāng)一個對象將要被系統(tǒng)回收之時調(diào)用的方法。

Python 采用自動引用計數(shù)(ARC)方式來回收對象所占用的空間,當(dāng)程序中有一個變量引用該 Python 對象時,Python 會自動保證該對象引用計數(shù)為 1;當(dāng)程序中有兩個變量引用該 Python 對象時,Python 會自動保證該對象引用計數(shù)為 2,依此類推,如果一個對象的引用計數(shù)變成了 0,則說明程序中不再有變量引用該對象,表明程序不再需要該對象,因此 Python 就會回收該對象。

大部分時候,Python 的 ARC 都能準確、高效地回收系統(tǒng)中的每個對象。但如果系統(tǒng)中出現(xiàn)循環(huán)引用的情況,比如對象 a 持有一個實例變量引用對象 b,而對象 b 又持有一個實例變量引用對象 a,此時兩個對象的引用計數(shù)都是 1,而實際上程序已經(jīng)不再有變量引用它們,系統(tǒng)應(yīng)該回收它們,此時 Python 的垃圾回收器就可能沒那么快,要等專門的循環(huán)垃圾回收器(Cyclic Garbage Collector)來檢測并回收這種引用循環(huán)。

class C(object):
    def __init__(self):
        print('into C __init__')

    def __del__(self):
        print('into C __del__')


c1 = C()
# into C __init__
c2 = c1
c3 = c2
del c3
del c2
del c1
# into C __del__
  • __str__(self):

    • 當(dāng)你打印一個對象的時候,觸發(fā)__str__
    • 當(dāng)你使用%s格式化的時候,觸發(fā)__str__
    • str強轉(zhuǎn)數(shù)據(jù)類型的時候,觸發(fā)__str__
  • __repr__(self)

    • reprstr的備胎
    • __str__的時候執(zhí)行__str__,沒有實現(xiàn)__str__的時候,執(zhí)行__repr__
    • repr(obj)內(nèi)置函數(shù)對應(yīng)的結(jié)果是__repr__的返回值
    • 當(dāng)你使用%r格式化的時候 觸發(fā)__repr__
class Cat:
    """定義一個貓類"""

    def __init__(self, new_name, new_age):
        """在創(chuàng)建完對象之后 會自動調(diào)用, 它完成對象的初始化的功能"""
        self.name = new_name
        self.age = new_age

    def __str__(self):
        """返回一個對象的描述信息"""
        return "名字是:%s , 年齡是:%d" % (self.name, self.age)
        
    def __repr__(self):
        """返回一個對象的描述信息"""
        return "Cat:(%s,%d)" % (self.name, self.age)

    def eat(self):
        print("%s在吃魚...." % self.name)

    def drink(self):
        print("%s在喝可樂..." % self.name)

    def introduce(self):
        print("名字是:%s, 年齡是:%d" % (self.name, self.age))


# 創(chuàng)建了一個對象
tom = Cat("湯姆", 30)
print(tom)  # 名字是:湯姆 , 年齡是:30
print(str(tom)) # 名字是:湯姆 , 年齡是:30
print(repr(tom))  # Cat:(湯姆,30)
tom.eat()  # 湯姆在吃魚....
tom.introduce()  # 名字是:湯姆, 年齡是:30

__str__(self) 的返回結(jié)果可讀性強。也就是說,__str__ 的意義是得到便于人們閱讀的信息,就像下面的 '2019-10-11' 一樣。

__repr__(self) 的返回結(jié)果應(yīng)更準確。怎么說,__repr__ 存在的目的在于調(diào)試,便于開發(fā)者使用。

import datetime

today = datetime.date.today()
print(str(today))  # 2019-10-11
print(repr(today))  # datetime.date(2019, 10, 11)
print('%s' %today)  # 2019-10-11
print('%r' %today)  # datetime.date(2019, 10, 11)

2、算數(shù)運算符

類型工廠函數(shù),指的是“不通過類而是通過函數(shù)來創(chuàng)建對象”。

class C:
    pass


print(type(len))  # <class 'builtin_function_or_method'>
print(type(dir))  # <class 'builtin_function_or_method'>
print(type(int))  # <class 'type'>
print(type(list))  # <class 'type'>
print(type(tuple))  # <class 'type'>
print(type(C))  # <class 'type'>
print(int('123'))  # 123

# 這個例子中l(wèi)ist工廠函數(shù)把一個元祖對象加工成了一個列表對象。
print(list((1, 2, 3)))  # [1, 2, 3]
  • __add__(self, other)定義加法的行為:+
  • __sub__(self, other)定義減法的行為:-
class MyClass:

    def __init__(self, height, weight):
        self.height = height
        self.weight = weight

    # 兩個對象的長相加,寬不變.返回一個新的類
    def __add__(self, others):
        return MyClass(self.height + others.height, self.weight + others.weight)

    # 兩個對象的寬相減,長不變.返回一個新的類
    def __sub__(self, others):
        return MyClass(self.height - others.height, self.weight - others.weight)

    # 說一下自己的參數(shù)
    def intro(self):
        print("高為", self.height, " 重為", self.weight)


def main():
    a = MyClass(height=10, weight=5)
    a.intro()

    b = MyClass(height=20, weight=10)
    b.intro()

    c = b - a
    c.intro()

    d = a + b
    d.intro()


if __name__ == '__main__':
    main()

# 高為 10  重為 5
# 高為 20  重為 10
# 高為 10  重為 5
# 高為 30  重為 15
  • __mul__(self, other)定義乘法的行為:*
  • __truediv__(self, other)定義真除法的行為:/
  • __floordiv__(self, other)定義整數(shù)除法的行為://
  • __mod__(self, other) 定義取模算法的行為:%
  • __divmod__(self, other)定義當(dāng)被 divmod() 調(diào)用時的行為
  • divmod(a, b)把除數(shù)和余數(shù)運算結(jié)果結(jié)合起來,返回一個包含商和余數(shù)的元組(a // b, a % b)。
print(divmod(7, 2))  # (3, 1)
print(divmod(8, 2))  # (4, 0)
  • __pow__(self, other[, module])定義當(dāng)被 power() 調(diào)用或 ** 運算時的行為
  • __lshift__(self, other)定義按位左移位的行為:<<
  • __rshift__(self, other)定義按位右移位的行為:>>
  • __and__(self, other)定義按位與操作的行為:&
  • __xor__(self, other)定義按位異或操作的行為:^
  • __or__(self, other)定義按位或操作的行為:|

3、反算數(shù)運算符

反運算魔方方法,與算術(shù)運算符保持一一對應(yīng),不同之處就是反運算的魔法方法多了一個“r”。當(dāng)文件左操作不支持相應(yīng)的操作時被調(diào)用。

  • __radd__(self, other)定義加法的行為:+
  • __rsub__(self, other)定義減法的行為:-
  • __rmul__(self, other)定義乘法的行為:*
  • __rtruediv__(self, other)定義真除法的行為:/
  • __rfloordiv__(self, other)定義整數(shù)除法的行為://
  • __rmod__(self, other) 定義取模算法的行為:%
  • __rdivmod__(self, other)定義當(dāng)被 divmod() 調(diào)用時的行為
  • __rpow__(self, other[, module])定義當(dāng)被 power() 調(diào)用或 ** 運算時的行為
  • __rlshift__(self, other)定義按位左移位的行為:<<
  • __rrshift__(self, other)定義按位右移位的行為:>>
  • __rand__(self, other)定義按位與操作的行為:&
  • __rxor__(self, other)定義按位異或操作的行為:^
  • __ror__(self, other)定義按位或操作的行為:|

a + b

這里加數(shù)是a,被加數(shù)是b,因此是a主動,反運算就是如果a對象的__add__()方法沒有實現(xiàn)或者不支持相應(yīng)的操作,那么 Python 就會調(diào)用b__radd__()方法。

class Nint(int):
    def __radd__(self, other):
        return int.__sub__(other, self) # 注意 self 在后面


a = Nint(5)
b = Nint(3)
print(a + b)  # 8
print(1 + b)  # -2

4、增量賦值運算

  • __iadd__(self, other)定義賦值加法的行為:+=
  • __isub__(self, other)定義賦值減法的行為:-=
  • __imul__(self, other)定義賦值乘法的行為:*=
  • __itruediv__(self, other)定義賦值真除法的行為:/=
  • __ifloordiv__(self, other)定義賦值整數(shù)除法的行為://=
  • __imod__(self, other)定義賦值取模算法的行為:%=
  • __ipow__(self, other[, modulo])定義賦值冪運算的行為:**=
  • __ilshift__(self, other)定義賦值按位左移位的行為:<<=
  • __irshift__(self, other)定義賦值按位右移位的行為:>>=
  • __iand__(self, other)定義賦值按位與操作的行為:&=
  • __ixor__(self, other)定義賦值按位異或操作的行為:^=
  • __ior__(self, other)定義賦值按位或操作的行為:|=

5、一元運算符

  • __neg__(self)定義正號的行為:+x
  • __pos__(self)定義負號的行為:-x
  • __abs__(self)定義當(dāng)被abs()調(diào)用時的行為
  • __invert__(self)定義按位求反的行為:~x

6、屬性訪問

  • __getattr__(self, name): 定義當(dāng)用戶試圖獲取一個不存在的屬性時的行為。
  • __getattribute__(self, name):定義當(dāng)該類的屬性被訪問時的行為(先調(diào)用該方法,查看是否存在該屬性,若不存在,接著去調(diào)用__getattr__)。
  • __setattr__(self, name, value):定義當(dāng)一個屬性被設(shè)置時的行為。
  • __delattr__(self, name):定義當(dāng)一個屬性被刪除時的行為。
class C:
    def __getattribute__(self, item):
        print('__getattribute__')
        return super().__getattribute__(item)

    def __getattr__(self, item):
        print('__getattr__')

    def __setattr__(self, key, value):
        print('__setattr__')
        super().__setattr__(key, value)

    def __delattr__(self, item):
        print('__delattr__')
        super().__delattr__(item)


c = C()
c.x
# __getattribute__
# __getattr__

c.x = 1
# __setattr__

del c.x
# __delattr__

7、描述符

描述符就是將某種特殊類型的類的實例指派給另一個類的屬性。

  • __get__(self, instance, owner)用于訪問屬性,它返回屬性的值。
  • __set__(self, instance, value)將在屬性分配操作中調(diào)用,不返回任何內(nèi)容。
  • __del__(self, instance)控制刪除操作,不返回任何內(nèi)容。
class MyDecriptor:
    def __get__(self, instance, owner):
        print('__get__', self, instance, owner)

    def __set__(self, instance, value):
        print('__set__', self, instance, value)

    def __delete__(self, instance):
        print('__delete__', self, instance)


class Test:
    x = MyDecriptor()


t = Test()
t.x
# __get__ <__main__.MyDecriptor object at 0x000000CEAAEB6B00> <__main__.Test object at 0x000000CEABDC0898> <class '__main__.Test'>

t.x = 'x-man'
# __set__ <__main__.MyDecriptor object at 0x00000023687C6B00> <__main__.Test object at 0x00000023696B0940> x-man

del t.x
# __delete__ <__main__.MyDecriptor object at 0x000000EC9B160A90> <__main__.Test object at 0x000000EC9B160B38>

8、定制序列

協(xié)議(Protocols)與其它編程語言中的接口很相似,它規(guī)定你哪些方法必須要定義。然而,在 Python 中的協(xié)議就顯得不那么正式。事實上,在 Python 中,協(xié)議更像是一種指南。

容器類型的協(xié)議

  • 如果說你希望定制的容器是不可變的話,你只需要定義__len__()__getitem__()方法。
  • 如果你希望定制的容器是可變的話,除了__len__()__getitem__()方法,你還需要定義__setitem__()__delitem__()兩個方法。

【例子】編寫一個不可改變的自定義列表,要求記錄列表中每個元素被訪問的次數(shù)。

class CountList:
    def __init__(self, *args):
        self.values = [x for x in args]
        self.count = {}.fromkeys(range(len(self.values)), 0)

    def __len__(self):
        return len(self.values)

    def __getitem__(self, item):
        self.count[item] += 1
        return self.values[item]


c1 = CountList(1, 3, 5, 7, 9)
c2 = CountList(2, 4, 6, 8, 10)
print(c1[1])  # 3
print(c2[2])  # 6
print(c1[1] + c2[1])  # 7

print(c1.count)
# {0: 0, 1: 2, 2: 0, 3: 0, 4: 0}

print(c2.count)
# {0: 0, 1: 1, 2: 1, 3: 0, 4: 0}
  • __len__(self)定義當(dāng)被len()調(diào)用時的行為(返回容器中元素的個數(shù))。
  • __getitem__(self, key)定義獲取容器中元素的行為,相當(dāng)于self[key]。
  • __setitem__(self, key, value)定義設(shè)置容器中指定元素的行為,相當(dāng)于self[key] = value。
  • __delitem__(self, key)定義刪除容器中指定元素的行為,相當(dāng)于del self[key]。

【例子】編寫一個可改變的自定義列表,要求記錄列表中每個元素被訪問的次數(shù)。

class CountList:
    def __init__(self, *args):
        self.values = [x for x in args]
        self.count = {}.fromkeys(range(len(self.values)), 0)

    def __len__(self):
        return len(self.values)

    def __getitem__(self, item):
        self.count[item] += 1
        return self.values[item]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]
        for i in range(0, len(self.values)):
            if i >= key:
                self.count[i] = self.count[i + 1]
        self.count.pop(len(self.values))


c1 = CountList(1, 3, 5, 7, 9)
c2 = CountList(2, 4, 6, 8, 10)
print(c1[1])  # 3
print(c2[2])  # 6
c2[2] = 12
print(c1[1] + c2[2])  # 15
print(c1.count)
# {0: 0, 1: 2, 2: 0, 3: 0, 4: 0}
print(c2.count)
# {0: 0, 1: 0, 2: 2, 3: 0, 4: 0}
del c1[1]
print(c1.count)
# {0: 0, 1: 0, 2: 0, 3: 0}

9、迭代器

  • 迭代是 Python 最強大的功能之一,是訪問集合元素的一種方式。
  • 迭代器是一個可以記住遍歷的位置的對象。
  • 迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結(jié)束。
  • 迭代器只能往前不會后退。
  • 字符串,列表或元組對象都可用于創(chuàng)建迭代器。
string = 'lsgogroup'
for c in string:
    print(c)

'''
l
s
g
o
g
r
o
u
p
'''

for c in iter(string):
    print(c)
links = {'B': '百度', 'A': '阿里', 'T': '騰訊'}
for each in links:
    print('%s -> %s' % (each, links[each]))
    
'''
B -> 百度
A -> 阿里
T -> 騰訊
'''

for each in iter(links):
    print('%s -> %s' % (each, links[each]))
  • 迭代器有兩個基本的方法:iter()next()。
  • iter(object) 函數(shù)用來生成迭代器。
  • next(iterator[, default]) 返回迭代器的下一個項目。
  • iterator -- 可迭代對象
  • default -- 可選,用于設(shè)置在沒有下一個元素時返回該默認值,如果不設(shè)置,又沒有下一個元素則會觸發(fā) StopIteration 異常。
links = {'B': '百度', 'A': '阿里', 'T': '騰訊'}

it = iter(links)
while True:
    try:
        each = next(it)
    except StopIteration:
        break
    print(each)

# B
# A
# T

it = iter(links)
print(next(it))  # B
print(next(it))  # A
print(next(it))  # T
print(next(it))  # StopIteration

把一個類作為一個迭代器使用需要在類中實現(xiàn)兩個魔法方法 __iter__()__next__() 。

  • __iter__(self)定義當(dāng)?shù)萜髦械脑氐男袨?,返回一個特殊的迭代器對象, 這個迭代器對象實現(xiàn)了 __next__() 方法并通過 StopIteration 異常標識迭代的完成。
  • __next__() 返回下一個迭代器對象。
  • StopIteration 異常用于標識迭代的完成,防止出現(xiàn)無限循環(huán)的情況,在 __next__() 方法中我們可以設(shè)置在完成指定循環(huán)次數(shù)后觸發(fā) StopIteration 異常來結(jié)束迭代。
class Fibs:
    def __init__(self, n=10):
        self.a = 0
        self.b = 1
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > self.n:
            raise StopIteration
        return self.a


fibs = Fibs(100)
for each in fibs:
    print(each, end=' ')

# 1 1 2 3 5 8 13 21 34 55 89

10、生成器

  • 在 Python 中,使用了 yield 的函數(shù)被稱為生成器(generator)。
  • 跟普通函數(shù)不同的是,生成器是一個返回迭代器的函數(shù),只能用于迭代操作,更簡單點理解生成器就是一個迭代器。
  • 在調(diào)用生成器運行的過程中,每次遇到 yield 時函數(shù)會暫停并保存當(dāng)前所有的運行信息,返回 yield 的值, 并在下一次執(zhí)行 next() 方法時從當(dāng)前位置繼續(xù)運行。
  • 調(diào)用一個生成器函數(shù),返回的是一個迭代器對象。
def myGen():
    print('生成器執(zhí)行!')
    yield 1
    yield 2
    
myG = myGen()
for each in myG:
    print(each)

'''
生成器執(zhí)行!
1
2
'''

myG = myGen()
print(next(myG))  
# 生成器執(zhí)行!
# 1

print(next(myG))  # 2
print(next(myG))  # StopIteration
def libs(n):
    a = 0
    b = 1
    while True:
        a, b = b, a + b
        if a > n:
            return
        yield a


for each in libs(100):
    print(each, end=' ')

# 1 1 2 3 5 8 13 21 34 55 89
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容