《Python程序設計與算法基礎教程(第二版)》江紅 餘青松,第九章課後習題答案


例9.1~例9.53

補充:類名爲有效的標識符,一般爲多個單詞組成的名稱,每個單詞除第一個字母大寫外,其餘的字母均小寫

一:類對象和實例對象

例9.1(創建類對象和實例對象)

>>> class Person:
		pass

>>> p = Person()
>>> print(Person, type(Person), id(Person))
<class '__main__.Person'> <class 'type'> 2096437524072
>>> print(p, type(p), id(p))
<__main__.Person object at 0x000001E81DA88BC8> <class '__main__.Person'> 2096441625544

例9.2(實例對象的創建和使用)

  • Python創建實例對象的方法無須使用關鍵字new,而是直接像調用函數一樣調用類對象並傳遞參數,因此類對象是可調用對象(Callable)
  • 在Python內置函數中,bool、float、int、str、list、dict、set等均爲可調用內置類對象
>>> c = complex(1, 2)
>>> c.conjugate()
(1-2j)
>>> c.real
1.0

二:屬性

例9.3(定義實例屬性)

Python變量不需要聲明,可直接使用。所以建議用戶在類定義的開始位置初始化類屬性,或者在構造函數__init__()中初始化實例屬性

>>> class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age
	def say_hi(self):
		print("您好,我叫", self.name)

		
>>> p = Person('zgh', 18)
>>> p.say_hi()
您好,我叫 zgh
>>> print(p.age)
18

例9.4(定義類對象屬性)

類屬性如果通過obj.屬性名來訪問,則屬於該實例地實例屬性

>>> class Person:
	count = 0
	name = "Person"

	
>>> Person.count += 1
>>> print(Person.count)
1
>>> print(Person.name)
Person
>>> p1 = Person(); p2 = Person()
>>> print(p1.name, p2.name)
Person Person
>>> Person.name = "僱員"
>>> print(p1.name, p2.name)
僱員 僱員
>>> p1.name = '員工'
>>> print(p1.name, p2.name)
員工 僱員

例9.5(私有屬性)

Python類的成員沒有訪問控制限制,這與其他面嚮對象語言不同
通常約定以兩個下劃線開頭,但是不以兩個下劃線結束的屬性是私有的(private),其它爲公共的(public)

>>> class Person:
	__name = 'class Person'
	def get_name():
		print(Person.__name)

		
>>> Person.get_name()
class Person
>>> Person.__name
Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    Person.__name
AttributeError: type object 'Person' has no attribute '__name'

例9.6、9.7、9.8(property裝飾器)

  • 面向對象編程的封裝性原則要求不直接訪問類中的數據成員
  • 在Python中可以定義私有屬性,然後定義相應的訪問該私有屬性的函數,並使用@property裝飾器來裝飾這些函數
  • 程序可以把函數“當作”屬性訪問,從而提供更加友好的訪問方式
>>> class Person:
	def __init__(self, name):
		self.__name = name
	@property
	def name(self):
		return self.__name

	
>>> p = Person('zgh666')
>>> print(p.name)
zgh666

嘗試了一個想法(如果對象也有一個同名的屬性,會怎麼樣?):

>>> class Person:
        name = 'zgh'
        def __init__(self, name):
                self.__name = name
        @property
        def name(self):
                return self.__name

        
>>> p = Person('zgh666')
>>> print(p.name)
zgh666

很顯然,返回的是裝飾器修飾的name函數

@property裝飾器默認提供一個只讀屬性,如果需要,可以使用對應的getter、setter和deleter裝飾器實現其他訪問器函數

>>> class Person:
	def __init__(self, name):
		self.__name = name
	@property
	def name(self):
		return self.__name
	@name.setter
	def name(self, value):
		self.__name = value
	@name.deleter
	def name(self):
		del self.__name

		
>>> p = Person('zgh')
>>> p.name = 'Zhanggguohao'
>>> print(p.name)
Zhanggguohao

property(fget=None, fset=None, fdel=None, doc=None)

>>> class Person:
        def __init__(self, name):
                self.__name = name
        def getname(self):
                return self.__name
        def setname(self, value):
                self.__name = value
        def delname(self):
                del self.__name
        name = property(getname, setname,delname, "I'm the 'name' property.")

        
>>> p = Person('zgh')
>>> print(p.name)
zgh
>>> p.name = 'zgh666'
>>> print(p.name)
zgh666

例9.9(自定義屬性)

在Python中,可以賦予一個對象自定義的屬性,即類定義中不存在的屬性。對象通過特殊屬性__dict__存儲自定義屬性

>>> class Person:
	pass

>>> p = Person()
>>> p.name = 'custom name'
>>> p.name
'custom name'
>>> p.__dict__
{'name': 'custom name'}

通過重載__getattr____setattr__可以攔截對成員的訪問,從而自定義屬性的行爲

  • __getattr__()只有在訪問不存在的成員時纔會被調用
  • __getattribute__()攔截所有的(包括不存在)獲取操作
  • __setattr__()設置屬性
  • __delattr__()刪除屬性
>>> class CustomAttribute(object):
	def __init__(self):
		pass
	def __getattribute__(self, name):
		return str.upper(object.__getattribute__(self, name))
	def __setattr__(self, name, value):
		object.__setattr__(self, name, str.strip(value))

		
>>> p = CustomAttribute()
>>> p.firstname = '    mary'
>>> print(p.firstname)
MARY

三:方法

對象實例方法的第一個參數一般爲self,但是用戶調用時不需要也不能給該參數傳值

例9.11(靜態方法:攝氏溫度與華氏溫度之間的相互轉換)

class TemperatureConverter:
        @staticmethod
        def c2f(t_c):
                return (float(t_c) * 9/5) + 32
        @staticmethod
        def f2c(t_f):
                return ((float(t_f) - 32) * 5/9)

print("1. 從攝氏溫度到華氏溫度.")
print("2. 從華氏溫度到攝氏溫度.")
                
choice = int(input("請選擇轉換方向:"))

if choice == 1:
        t_c = float(input("請輸入攝氏溫度:"))
        t_f = TemperatureConverter.c2f(t_c)
        print("華氏溫度爲:{0:.2f}".format(t_f))
elif choice == 2:
        t_f = float(input("請輸入華氏溫度:"))
        t_c = TemperatureConverter.f2c(t_f)
        print("攝氏溫度爲:{0:.2f}".format(t_c))
else:
        print("無此選項,只能選擇1或2")          

輸出:

====================== RESTART: D:\zgh\desktop\test.py ======================
1. 從攝氏溫度到華氏溫度.
2. 從華氏溫度到攝氏溫度.
請選擇轉換方向:1
請輸入攝氏溫度:30
華氏溫度爲:86.00
>>> 
====================== RESTART: D:\zgh\desktop\test.py ======================
1. 從攝氏溫度到華氏溫度.
2. 從華氏溫度到攝氏溫度.
請選擇轉換方向:2
請輸入華氏溫度:70
攝氏溫度爲:21.11
>>> 

補充一個很有意思的代碼:
(包含了很多知識點)

TempStr = input("請輸入帶有符號的溫度值:")

if TempStr[-1] in ['F', 'f']:
    C = (eval(TempStr[0:-1]) - 32) / 1.8
    print("轉化後的溫度是{:.2f}C".format(C))
elif TempStr[-1] in ['C', 'c']:
    F = 1.8 * eval(TempStr[0:-1]) + 32
    print("轉化後的溫度是{:.2f}F".format(F))
else:
    print("輸入格式錯誤")

例9.12(類方法)

類方法的第一個參數爲cls,但是調用時用戶不需要也不能給該參數傳值

>>> class Person:
	classname = "zgh"
	def __init__(self, name):
		self.name = name
	#實例方法
	def f1(self):
		print(self.name)
	#靜態方法
	@staticmethod
	def f2():
		print("static")
	#類方法
	@classmethod
	def f3(cls):
		print(cls.classname)

		
>>> p = Person("666")
>>> p.f1()
666
>>> Person.f2()
static
>>> p.f2()
static
>>> Person.f3()
zgh
>>> p.f3()
zgh
>>> Person.f1()
Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    Person.f1()
TypeError: f1() missing 1 required positional argument: 'self'

類名不能訪問實例方法

補充:

  • 無論是靜態方法還是類方法一般通過類名來訪問,也可以通過對象實例來調用
  • 在靜態方法中訪問對象實例會導致錯誤
  • 在類方法中訪問對象實例屬性會導致錯誤

例9.15(__init__()方法、__new__()方法、__del__()方法)

  1. __init__()
    構造函數,用於執行類的實例的初始化工作。在創建完對象後調用,初始化當前對象的實例,無返回值
  2. __new__()
    是一個類方法,在創建對象時調用,返回當前對象的一個實例,一般無須重載該方法
  3. __del__()
    析構函數,用於實現銷燬類的實例所需的操作,如釋放對象佔用的非託管資源(打開的文件、網絡連接等)
    在默認情況下,當對象不再被使用時,__del__()方法運行。由於Python解釋器實現自動垃圾回收,所以無法保證這個方法究竟在什麼時候運行
    但通過del語句可以強制銷燬一個對象實例,從而保證調用對象實例的__del__()方法
>>> class Person:
	count = 0
	def __init__(self, name, age):
		self.name = name
		self.age = age
		Person.count += 1
	def __del__(self):
		Person.count -= 1
	def say_hi(self):
		print("hello, i'm ", self.name)
	def get_count():
		print("count: ",Person.count)

	
>>> print("count: ", Person.count)
count:  0
>>> p1 = Person('zhangsan', 18)
>>> p1.say_hi()
hello, i'm  zhangsan
>>> Person.get_count()
count:  1
>>> p2 = Person('lisi', 28)
>>> p2.say_hi()
hello, i'm  lisi
>>> Person.get_count()
count:  2
>>> del p1
>>> Person.get_count()
count:  1
>>> del p2
>>> Person.get_count()
count:  0

例9.16(私有方法)

  • 與私有屬性類似,Python約定以兩個下劃線開頭,但不以兩個下劃線結束的方法是私有的
  • 不能直接訪問私有的
>>> class Book:
	def __init__(self, name, author, price):
		self.name = name
		self.author = author
		self.price = price
	def __check_name(self):
		if self.name == '': return False
		else: return True
	def get_name(self):
		if self.__check_name(): print(self.name, self.author)
		else: print("No Value")

		
>>> b = Book("Python", 'zgh', 666)
>>> b.get_name()
Python zgh
>>> b.__check_name()#直接調用私有方法,非法
Traceback (most recent call last):
  File "<pyshell#66>", line 1, in <module>
    b.__check_name()
AttributeError: 'Book' object has no attribute '__check_name'

例9.17(方法的重載)

Python本身是動態語言,

  • 方法的參數沒有聲明類型(在調用傳值時確定參數的類型)
  • 參數的數量由可選參數和可變參數來控制
  • 故Python對象方法不需要重載,定義一個方法即可實現多種調用,從而實現相當於其他程序設計語言的重載功能
>>> class Person:
	def say_hi(self, name=None):
		self.name = name
		if name ==None: print("hello!")
		else: print("hello, i'm ", self.name)

	
>>> p = Person()
>>> p.say_hi()
hello!
>>> p.say_hi('zgh')
hello, i'm  zgh
>>> 

注意:在Python類體中定義多個重名的方法雖然不會報錯,但只有最後一個方法有效,所以建議不要定義重名的方法

四:繼承

  • Pyhon支持多重繼承,即一個派生類可以繼承多個基類
  • 如果在類定義中沒有指定基類,則默認其基類爲object
  • 在聲明派生類時,必須在其構造函數中調用基類的構造函數

例9.19(派生類)

>>> class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age
	def say_hi(self):
		print("hello, i'm {0}, {1} olds".format(self.name, self.age))

>>> class Student(Person):
	def __init__(self, name, age, stu_id):
		Person.__init__(self, name, age)
		self.stu_id = stu_id
	def say_hi(self):
		Person.say_hi(self)
		print("i'm student, my student_number is ", self.stu_id)

		
>>> p =Person('zhangsan',18)
>>> p.say_hi()
hello, i'm zhangsan, 18 olds
>>> s = Student('lisi', 5, '17202030118')
>>> s.say_hi()
hello, i'm lisi, 5 olds
i'm student, my student_number is  17202030118

例9.20(查看類的繼承關係)

多個類的繼承可以形成層次關係,通過類的方法mro()或類的屬性__mro__可以輸出其繼承的層次關係

>>> class A: pass

>>> class B(A): pass

>>> class C(B): pass

>>> class D(A): pass

>>> class E(B,D): pass

>>> A.mro()
[<class '__main__.A'>, <class 'object'>]
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
>>> C.mro()
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
>>> 
>>> D.mro()
[<class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
>>> 
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)

例9.21(類成員的繼承和重寫)

>>> class Dimension:
	def __init__(self, x, y):
		self.x = x
		self.y = y
	def area(self):
		pass

	
>>> class Circle(Dimension):
	def __init__(self, r):
		Dimension.__init__(self, r, 0)
	def area(self):
		return 3.14 * self.x * self.x

	
>>> class Rectangle(Dimension):
	def __init__(self, w, h):
		Dimension.__init__(self, w, h)
	def area(self):
		return self.x * self.y

	
>>> d1 = Circle(2.0)
>>> d2 = Rectangle(3.0, 4.0)
>>> print(d1.area(), d2.area())
12.56 12.0

五:對象的特殊方法

  • 在Python對象中包含許多以雙下劃線開始和結束的方法,稱之爲特殊方法。
  • 特殊方法通常在針對對象的某種操作時自動調用

例9.22(重寫對象的特殊方法)

特殊方法 含義
__init__()__del__() 創建或銷燬對象時調用
__setitem__()__getitem__() 按索引賦值、取值
__len__() 對應於內置函數len()
__repr__(self) 對應於內置函數repr()
__str__(self) 對應於內置函數str()
__bytes__(self) 對應於內置函數bytes()
__format__(self, format_spec) 對應於內置函數format()
__bool__(self) 對應於內置函數bool()
__hash__(self) 對應於內置函數hash()
__dir__(self) 對應於內置函數dir()
>>> class Person:
	def __init__(self, name, age):
		self.name = name
		self.age = age
	def __str__(self):
		return '{0}, {1}'.format(self.name, self.age)

	
>>> p = Person('zgh', '18')
>>> print(p)
zgh, 18

例9.23(運算符重載)

特殊方法 含義
__lt__()__le__()__eq__() 對應於運算符<,<=,==
__gt__()__ge__()__ne__() 對應於運算符>,>=,!=
__or__()__ror__()__xor__()__rxor__()__and__()__rand__() 對應於運算符|,^,&
__ior__()__ixor__()__iand__() 對應於運算符|=,^=,&=
__lshift__()__rlshift__()__rshift__()__rrshift__() 對應於運算符<<,>>
__ilshift__()__irlshift__()__irshift__()__irrshift__() 對應於運算符<<=,>>=
__add__()__radd__()__sub__()__rsub__() 對應於運算符+,-
__iaddr__()__isub__() 對應於運算符+=,-=
__mul__()__rmul__()__truediv__()__rtruediv__() 對應於運算符*,/
__mod__()__rmod__()__floordiv__()__rfloordiv__() 對應於運算符%,//
__imul__()__idiv__()__itruediv__()__imod__()__ifloordiv__() 對應於運算符*=,/=,%=,//=
__pos__()__neg__() 正負號
__invert__() 按位翻轉
__pow__()__rpow__()__ipow__() 指數運算

看了上這麼多特殊方法和運算符的對應,你是否會感到一些奇怪的地方:

  1. r,某些運算符爲啥子有兩個,然後其中一個的最前面加了一個r
    舉個例子就懂了:x.__mul__(y) == x / y;而x.__rmul__(y) == y / x
  2. 如果上手去嘗試的話,發現複合賦值運算對應的特殊方法都使用不了(未找到原因,暫過,哪位大佬知道啊,評論區留言)
>>> class MyList:
	def __init__(self, *args):
		self.__mylist = []
		for arg in args:
			self.__mylist.append(arg)
	def __add__(self, n):
		for i in range(0, len(self.__mylist)):
			self.__mylist[i] += n
	def __sub__(self, n):
		for i in range(0, len(self.__mylist)):
			self.__mylist[i] -= n
	def __mul__(self, n):
		for i in range(0, len(self.__mylist)):
			self.__mylist[i] *= n
	def __truediv__(self, n):
		for i in range(0, len(self.__mylist)):
			self.__mylist[i] /= n
	def __len__(self):
		return (len(self.__mylist))
	def __repr__(self):
		str1 = ''
		for i in range(0, len(self.__mylist)):
			str1 += str(self.__mylist[i]) + " "
		return str1

>>> m = MyList(1, 2, 3, 4, 5)
>>> m+2
>>> m
3 4 5 6 7 
>>> m -1
>>> m
2 3 4 5 6 
>>> m*4
>>> m
8 12 16 20 24 
>>> m/2
>>> m
4.0 6.0 8.0 10.0 12.0 
>>> len(m)
5

例9.24(@functools.total_ordering裝飾器)

實現了total_ordering裝飾器後,則是需要實現__eq__()以及__lt__()__le__()__gt__()__ge__()中的任意一個,那麼實現其他比較運算符能簡化代碼量

import functools

@functools.total_ordering
class Student:
        def __init__(self, firstname, lastname):
                self.firstname = firstname
                self.lastname = lastname
        def __eq__(self, other):
                return ((self.lastname.lower(), self.firstname.lower()) ==
                        (other.lastname.lower(), other.firstname.lower()))
        def __lt__(self, other):
                return ((self.lastname.lower(), self.firstname.lower()) <
                        (other.lastname.lower(), other.firstname.lower()))

if __name__ == '__main__':
        s1 = Student('Mary', 'Clinton')
        s2 = Student('Mary', 'Clinton')
        s3 = Student('Charlie', 'Clinton')
        print(s1 == s2)
        print(s1 > s3)
        print(s1 < s3)

輸出:

True
True
False

例9.25(定義了__call__()方法的對象稱爲可調用對象)

class GDistance:
        def __init__(self, g):
                self.g = g
        def __call__(self, t):
                return (self.g * t ** 2)/2

if __name__ == '__main__':
        e_gdist = GDistance(9.8)
        for t in range(11):
                print(format(e_gdist(t), "0.2f"), end = ' ')

輸出:

0.00 4.90 19.60 44.10 78.40 122.50 176.40 240.10 313.60 396.90 490.00 

六:對象的引用、淺拷貝和深拷貝

例9.26(對象的引用)

對象的賦值實際上是對象的引用,在創建一個對象並把它賦值給一個變量時,該變量是指向該對象的引用,其id()返回值保持一致

>>> acc10 = ['zgh', ['zgh666', 666]]
>>> acc11 = acc10
>>> id(acc10) == id(acc11)
True

例9.27(對象的淺拷貝)

對象的賦值引用同一個對象(即:不復制對象)
如果要複製對象,可以使用下列方法之一:

  • 切片操作:例如acc11[:]
  • 對象實例化:例如list(acc11)
  • copy模塊的copy()函數:例如copy.copy(acc11)
>>> import copy
>>> acc1 = ['Charlie', ['credit', 0.0]]
>>> acc2 = acc1[:]
>>> acc3 = list(acc1)
>>> acc4 = copy.copy(acc1)
>>> id(acc1), id(acc2), id(acc3), id(acc4)
(2387738921800, 2387738921544, 2387735787656, 2387738922888)

Python複製一般是淺拷貝,即複製對象時,對象內包含的子對象並不複製,而是引用同一個子對象

>>> acc2[0] = 'zgh666'
>>> acc2[1][1] = 100.0
>>> acc1
['Charlie', ['credit', 100.0]]
>>> acc2
['zgh666', ['credit', 100.0]]
>>> id(acc1[1]) == id(acc2[1])
True

例9.28(對象的深拷貝)

使用copy模塊的deepcopy()函數

>>> import copy
>>> acc1 = ['Charlie', ['credit', 0.0]]
>>> acc5 = copy.deepcopy(acc1)
>>> acc5[0] = 'zgh666'
>>> acc5[1][1] = 666
>>> acc1
['Charlie', ['credit', 100.0]]
>>> acc5
['zgh666', ['credit', 666]]
>>> id(acc1) == id(acc5)
False
>>> id(acc1[1]) == id(acc5[1])
False

七:可迭代對象:迭代器和生成器

相對於序列,可迭代對象僅在迭代時產生數據,故可以節省內存空間

  1. Python語言提供了若干內置可迭代對象,例如:range、map、enumerate、filter、zip
  2. 在標準庫itertools模塊中包含各種迭代器,這些迭代器非常高效,且內存消耗小
  • 在Python中,實現了__iter__()的對象是可迭代對象
  • 在collections.abc模塊中定義了抽象基類Iterable,使用內置的isinstance()可以判斷一個對象是否爲可迭代對象
>>> import collections.abc
>>> isinstance((1, 2, 3), collections.abc.Iterable)
True
>>> isinstance('Python33', collections.abc.Iterable)
True
>>> isinstance(123, collections.abc.Iterable)
False
  • 實現了__next__()的對象是迭代器
  • 使用迭代器可以實現對象的迭代循環,迭代器讓程序更加通用、優雅、高效,更加Python化。對於大量項目的迭代,使用列表會佔用更多的內存,而使用迭代器可以避免之
>>> [i**2 for i in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> import collections.abc
>>> i1 = (i**2 for i in range(10))
>>> isinstance(i1, collections.abc.Iterator)
True
  • 迭代器對象必須實現兩個方法,即__iter__()__next__(),二者合稱爲迭代器協議
  • __iter__()用於返回對象本身,以方便for語句進行迭代
  • __next__()用於返回下一元素
  • 內置函數iter(iterable)可以返回可迭代對象iterable的迭代器
  • 內置函數next()調用迭代器__next__()方法依次返回下一個項目值,如果沒有新項目,則將導致StopIteration
>>> t = ('zgh', '666')
>>> i = iter(t)
>>> next(i)
'zgh'
>>> next(i)
'666'
>>> next(i)
Traceback (most recent call last):
  File "<pyshell#72>", line 1, in <module>
    next(i)
StopIteration
  • 例9.29(使用while循環迭代可迭代對象)
>>> t = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> fetch = iter(t)
>>> while True:
	try: i = next(fetch)
	except StopIteration: break
	print(i, end = ' ')

	
0 1 2 3 4 5 6 7 8 9 

聲明一個類,定義__iter__()__next__()方法。創建該類的對象既是可迭代對象,也是迭代器

  • 例9.30(定義Fib,實現Fibonacci數列)
>>> class Fib:
	def __init__(self):
		self.a, self.b = 0, 1
	def __next__(self):
		self.a, self.b = self.b, self.a+self.b
		return self.a
	def __iter__(self):
		return self

	
>>> fibs = Fib()
>>> for i in fibs:
	if i < 1000: print(i, end =', ')
	else: break

	
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 
  • 在函數定義中,如果使用yield語句代替return返回一個值,則定義了一個生成器函數(generator)
  • 生成器函數是一個迭代器,是可迭代對象,支持迭代
>>> def triples(n):
	for i in range(n):
		yield i*3

		
>>> f = triples(10)
>>> f
<generator object triples at 0x0000022BF0563DC8>
>>> i = iter(f)
>>> next(i)
0
>>> next(i)
3
>>> for t in f: print(t, end = ', ')

6, 9, 12, 15, 18, 21, 24, 27, 
  • 例9.31(利用生成器函數創建Fibonacci數列)
>>> def fib():
	a,b = 0,1
	while True:
		a,b = b,a+b
		yield a

		
>>> if __name__ == '__main__':
	fibs = fib()
	for f in fibs:
		if f < 1000: print(f, end = ' ')
		else: break

		
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 
  • 例9.32(利用生成器函數創建返回m到n之間素數的生成器)
>>> import math
>>> def is_prime(n):
	if n < 2: return False
	if n == 2: return True
	if n % 2 == 0: return False
	sqrt_n = int(math.floor(math.sqrt(n)))
	for i in range(3, sqrt_n+1, 2):
		if n % i == 0: return False
	return True

>>> def primes(m, n):
	for i in range(m, n+1):
		if is_prime(i):
			yield i

>>> if __name__ == '__main__':
	primes1 = primes(50_0000_0000, 50_0000_0090)
	for p in primes1:
		print(p, end = ' ')

		
5000000029 5000000039 5000000059 5000000063 

只有長度有限的序列或者實現了__reversed__()方法的可迭代對象纔可以使用內置函數reversed()

>>> reversed([1, 2, 3, 4, 5])
<list_reverseiterator object at 0x0000022BF04FE608>
>>> for i in reversed([1, 2, 3, 4, 5]): print(i, end = ' ')

5 4 3 2 1 
  • 只有長度有限的序列或者實現了__reversed__()方法的可迭代對象纔可以使用內置函數reversed()
>>> class Countdown:
	def __init__(self, start):
		self.start = start
	def __iter__(self):
		n = self.start
		while n > 0:
			yield n
			n -= 1
	def __reversed__(self):
		n = 1
		while n <= self.start:
			yield n
			n += 1

			
>>> if __name__ == '__main__':
	for i in Countdown(10): print(i, end = ' ')
	for i in reversed(Countdown(10)): print(i, end = ' ')

	
10 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 10 
  • 生成器表達式
    生成器表達式的語法和列表解析基本一樣,只不過生成器表達式使用()代替[]
>>> (i **2 for i in range(10))
<generator object <genexpr> at 0x0000022BF0563F48>
>>> for j in (i **2 for i in range(10)):
	print(j, end = ' ')

	
0 1 4 9 16 25 36 49 64 81 
>>> for j in (i **2 for i in range(10) if i %3 == 0):
	print(j, end = ' ')

	
0 9 36 81
  • map迭代器和itertools.starmap迭代器
  • 我們已經學過了map(function, iterable, ...)函數
  • 如果函數的參數爲元組,則需要使用itertools.starmap()迭代器
>>> import itertools
>>> list(itertools.starmap(pow, [(2,5), (3,2), (10,3)]))
[32, 9, 1000]
  • filter迭代器和itertools.filterfalse迭代器
  • 我們已經學過了filter(function, iterable)函數,若結果爲True,則返回該元素。如果function爲None,則返回元素爲True的元素
  • itertools.filterfalse(predicate, iterable)則反之
>>> filter
<class 'filter'>
>>> list(filter(lambda x : x > 0, (-1, 2, -3, 0, 5)))
[2, 5]
>>> list(filter(None, (1, 2, 3, 0, 5)))
[1, 2, 3, 5]
>>> 
>>> import itertools
>>> list(itertools.filterfalse(lambda x : x%2, range(10)))
[0, 2, 4, 6, 8]
  • zip迭代器和itertools.zip_longest迭代器
  • zip(*iterables)用於拼接多個可迭代對象的元素,返回新的可迭代對象。如果各序列的長度不一致,則截取至最小序列長度
  • 如果需要截取最長的長度,可以使用itertools.zip_longest(*iterables, fillvalue = None)迭代器
>>> zip
<class 'zip'>
>>> zip((1,2,3), 'abc', range(3))
<zip object at 0x000001B3CDD758C8>
>>> list(zip((1,2,3), 'abc', range(3)))
[(1, 'a', 0), (2, 'b', 1), (3, 'c', 2)]
>>> list(zip('zgh', range(6,10)))
[('z', 6), ('g', 7), ('h', 8)]
>>> 
>>> 
>>> import itertools
>>> list(itertools.zip_longest('zgh', range(6,10), fillvalue = '-'))
[('z', 6), ('g', 7), ('h', 8), ('-', 9)]
>>> list(itertools.zip_longest('zgh', range(6,10)))
[('z', 6), ('g', 7), ('h', 8), (None, 9)]
  • enumerate(iterable, start = 0)可迭代對象
  • 用於枚舉可迭代對象iterable中的元素,返回元素爲元組(計數,元素)的可迭代對象
>>> enumerate
<class 'enumerate'>
>>> list(enumerate('zgh666', start = 666))
[(666, 'z'), (667, 'g'), (668, 'h'), (669, '6'), (670, '6'), (671, '6')]
  • 無窮序列迭代器itertools.count、itertools.cycle和itertools.repeat
  • count(start = 0, step = 1)
    從start開始,步長爲step的無窮序列
  • cycle(iterable)
    可迭代對象iterable元素的無限重複
  • repeat(object[, times])
    重複對象object無數次(若指定times,則重複times次)
>>> from itertools import *
>>> list(zip(count(1), 'zgh666'))
[(1, 'z'), (2, 'g'), (3, 'h'), (4, '6'), (5, '6'), (6, '6')]
>>> list(zip(range(10), cycle('zgh')))
[(0, 'z'), (1, 'g'), (2, 'h'), (3, 'z'), (4, 'g'), (5, 'h'), (6, 'z'), (7, 'g'), (8, 'h'), (9, 'z')]
>>> list(repeat('zgh', 6))
['zgh', 'zgh', 'zgh', 'zgh', 'zgh', 'zgh']
  • 累計迭代器itertools.accumulate
  • accumulate(iterable[, func])
  • 如果指定了帶兩個參數的func,則func代替默認的加法運算
>>> import itertools
>>> list(itertools.accumulate(range(1,11)))
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
>>> import operator
>>> list(itertools.accumulate(range(1,11), operator.mul))
[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
  • 級聯迭代器itertools.chain
  • chain(*iterables)
  • 由於連接所有的可迭代函數,及連接多個可迭代對象的元素,作爲一個序列
  • chain的類工廠函數chain.from_iterable(iterable)也可以用於連接多個序列
>>> import itertools
>>> list(itertools.chain('zgh',(6,6,6),range(5)))
['z', 'g', 'h', 6, 6, 6, 0, 1, 2, 3, 4]
>>> 
>>> list(itertools.chain.from_iterable(['zgh', '666']))
['z', 'g', 'h', '6', '6', '6']
  • 選擇壓縮迭代器itertools.compress
  • compress(data, selectors)
  • 根據selectors的元素(True/False),返回True對應的data序列中的元素。當data序列或者selectors終止時停止判斷
>>> import itertools
>>> list(itertools.compress('abcdef', [1,0,1,0,1,1]))
['a', 'c', 'e', 'f']
  • 截取迭代器itertools.dropwhile和itertools.takewhile
  • dropwhile(predicate, iterable)
    根據條件函數predicate處理可迭代對象的每個元素,丟棄iterable的元素,直到條件函數的結果爲False(補充:這裏不應該是True,書上有誤)
  • takewhile(predicate, iterable)
    根據條件函數predicate處理可迭代對象的每個元素,返回iterable的元素,直到條件函數的結果爲False
>>> import itertools
>>> list(itertools.dropwhile(lambda x : x < 5, [1, 4, 6, 4, 1]))
[6, 4, 1]
>>> list(itertools.takewhile(lambda x : x < 5, [1, 4, 6, 4, 1]))
[1, 4]
  • 切片迭代器itertools.islice
  • islice(iterable, stop)
  • islice(iterable, start, stop[, step])
  • 返回可迭代對象iterable的切片,從索引位置start(第一個元素爲0)開始到stop(不包括)結束,步長爲step(默認爲1)。如果stop爲None,則操作直到結束
>>> import itertools
>>> list(itertools.islice('ABCDEF', 2))
['A', 'B']
>>> list(itertools.islice('ABCDEF', 2, 4))
['C', 'D']
>>> list(itertools.islice('ABCDEF', 2, None))
['C', 'D', 'E', 'F']
>>> list(itertools.islice('ABCDEF', 0, None, 2))
['A', 'C', 'E']
  • 分組迭代器itertools.groupby
  • groupby(iterable, key = None)
  • iterable爲待分組的可迭代對象
  • 可選的key爲用於計算鍵值的函數,默認爲None,即鍵值爲元素本身值
  • 返回的結果爲迭代器,其元素爲(key, group),其中key爲分組的鍵值,group爲iterable中具有相同key值的元素的集合的子迭代器
  • 一般與排序聯合使用
>>> import itertools
>>> data = [1, -2, 0, 0, -1, 2, 1, -1, 2, 0, 0]
>>> data1 = sorted(data, key = abs)
>>> for k, g in itertools.groupby(data, key = abs):
	print(k, list(g))

	
1 [1]
2 [-2]
0 [0, 0]
1 [-1]
2 [2]
1 [1, -1]
2 [2]
0 [0, 0]
>>> for k, g in itertools.groupby(data1, key = abs):
	print(k, list(g))

	
0 [0, 0, 0, 0]
1 [1, -1, 1, -1]
2 [-2, 2, 2]
  • 返回多個迭代器itertools.tee
  • tee(iterable, n = 2)
  • 其返回可迭代對象iterable的nge(默認爲2)迭代器
>>> import itertools
>>> for i in itertools.tee(range(10), 3): print(list(i))

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 組合迭代器itertools.combinations和itertools.combinations_with_replacement
  • combinations(iterable, r)
    元素不重複
  • combinations_with_replacement(iterable, r)
    元素可重複
  • 其返回可迭代對象iterable的元素的組合,組合的長度爲r
  • 可以把它理解爲數學中的組合
>>> import itertools
>>> list(itertools.combinations([1, 2, 3], 2))
[(1, 2), (1, 3), (2, 3)]
>>> list(itertools.combinations([1, 2, 3, 4], 2))
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
>>> list(itertools.combinations([1, 2, 3, 4], 3))
[(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
>>> 
>>> 
list(itertools.combinations_with_replacement([1, 2, 3], 2))
[(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]
  • 排列迭代器itertools.permutations
  • permutations(iterable, r = None)
  • 排列長度爲r(默認爲序列長度)
>>> import itertools
>>> list(itertools.permutations([1, 2, 3], 2))
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
>>> list(itertools.permutations([1, 2, 3]))
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
  • 笛卡爾積迭代器itertools.product
  • product(*iterables, repeat = 1)
  • 其返回可迭代對象的元素的笛卡爾積,repeat爲可迭代對象的重複次數(默認爲1)
>>> import itertools
>>> list(itertools.product([1, 2], 'abc'))
[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c')]
>>> list(itertools.product([1, 2], repeat = 3))
[(1, 1, 1), (1, 1, 2), (1, 2, 1), (1, 2, 2), (2, 1, 1), (2, 1, 2), (2, 2, 1), (2, 2, 2)]

八:自定義類應用舉例

用戶可以通過自定義類創建和使用新的數據結構

例9.52 實現RGB顏色模型的Color類

class Color:
    def __init__(self, r = 0, g = 0, b = 0):
        self.__r = r
        self.__g = g
        self.__b = b
    @property
    def r(self):
        return self.__r
    @property
    def g(self):
        return self.__g
    @property
    def b(self):
        return self.__b
    def luminance(self):
        #計算並返回顏色的亮度
        return self.__r * .299  + .587 * self.__g + .114 * self.__b
    def toGray(self):
        #轉換爲灰度顏色
        y = int(round(self.luminance()))
        return Color(y, y, y)
    def isCompatible(self, c):
        #比較前景色和背景色是否匹配
        return abs(self.luminance() - c.luminance()) >= 128.0    
    def __str__(self):
        #重載方法,輸出:(r, g, b)
        return '({}, {}, {})'.format(self.__r, self.__g, self.__b)

#常用顏色
WHITE = Color(255, 255, 255)
BLACK = Color(0, 0, 0)
RED = Color(255, 0, 0)
GREEN = Color(0, 255, 0)
BLUE = Color(0, 0, 255)
CYAN = Color(0, 255, 255)
MAGENTA = Color(255, 0, 255)
YELLOW = Color(255, 255, 0)

#測試代碼
if __name__ == '__main__':
    c = Color(255, 200, 0)
    print('顏色字符串:{0}'.format(c))
    print('顏色分量:r = {0}, g = {1}, b = {1}'.format(c.r, c.g, c.b))
    print('顏色亮度:{0}'.format(c.luminance()))
    print('轉換爲灰度顏色:{0}'.format(c.toGray()))
    print('{0} 和 {1} 是否匹配:{2}'.format(c, RED, c.isCompatible(RED)))
    

例9.53 實現直方圖類Histogram

import random
import math

class Stat:
    def __init__(self, n):
        self.__data = []
        for i in range(n):
            self.__data.append(0)
    def addDataPoint(self, i):
        """增加數據點"""
        self.__data[i] += 1
    def count(self):
        """計算數據點個數之和(統計數據點個數)"""
        return sum(self.__data)
    def mean(self):
        """平均值"""
        return sum(self.__data)/len(self.__data)
    def max(self):
        return max(self.__data)
    def min(self):
        return min(self.__data)
    def draw(self):
        """繪製簡易直方圖"""
        for i in self.__data:
            print(' # ' * i)

if __name__ == '__main__':
    st = Stat(10)
    for i in range(100):
        score = random.randrange(0, 10)
        st.addDataPoint(math.floor(score))
    print('數據點個數:{}'.format(st.count()))
    print('數據點個數的平均值:{}'.format(st.mean()))
    print('數據點個數的最大值:{}'.format(st.max()))
    print('數據點個數的最小值:{}'.format(st.min()))
    st.draw()

輸出:

數據點個數:100
數據點個數的平均值:10.0
數據點個數的最大值:14
數據點個數的最小值:7
 #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  #  #  #  #  #  #  #  # 
 #  #  #  #  #  #  # 

填空題:2

2

>>> x = '123'
>>> print(isinstance(x, int))
False

思考題:3~11

3

在聲明派生類時,必須在其構造函數中調用基類的構造函數

>>> class Parent:
	def __init__(self, param):
		self.v1 = param

	
>>> class Child(Parent):
	def __init__(self, param):
		Parent.__init__(self, param)
		self.v2 = param

		
>>> obj = Child(100)
>>> print("%d %d" % (obj.v1, obj.v2))
100 100

4
注意區分實例對象屬性和局部變量

>>> class Account:
	def __init__(self, id):
		self.id = id
		id = 888

		
>>> acc = Account(100)
>>> print(acc.id)
100

5

>>> #5
>>> class Account:
	def __init__(self, id, balance):
		self.id = id
		self.balance = balance
	def deposit(self, amount):
		self.balance += amount
	def withdraw(self, amount):
		self.balance -= amount

		
>>> acc1 = Account('1234', 100)
>>> acc1.deposit(500)
>>> acc1.withdraw(200)
>>> print(acc1.balance)
400

6

  • getattr(object, name[, default])
    獲取object對象的屬性的值,如果存在則返回屬性值
  • setattr(object, name, value)
    給object對象的name屬性賦值value,如果對象原本存在給定的屬性name,則setattr會更改屬性的值爲給定的value;如果對象原本不存在屬性name,setattr會在對象中創建屬性,並賦值爲給定的value;
>>> class A:
	def __init__(self, a, b, c):
		self.x = a+b+c

	
>>> a = A(6, 2, 3)
>>> b = getattr(a, 'x')
>>> setattr(a, 'x', b+1)
>>> print(a.x)
12

7
淺拷貝,複製對象時對象中包含的子對象並不複製,而是引用同一個子對象

>>> import copy
>>> d1 = {'a' : [1, 2], 'b' : 2}
>>> d2 = copy.copy(d1)
>>> d1['a'][0] = 6
>>> sum = d1['a'][0] + d2['a'][0]
>>> print(sum)
12

8
深拷貝,可以遞歸複製對象中包含的子對象

>>> from copy import *
>>> d1 = {'a' : [1, 2], 'b' : 2}
>>> d2 = deepcopy(d1)
>>> d1['a'][0] = 6
>>> sum = d1['a'][0] + d2['a'][0]
>>> print(sum)
7

9
淺拷貝,複製對象時對象中包含的子對象並不複製,而是引用同一個子對象

>>> from copy import *
>>> list1 = [1, 2, 3]
>>> list2 = [3, 4, 5]
>>> dict1 = {"1" : list1, '2' : list2}
>>> dict2 = dict1.copy()
>>> dict1['1'][0] = 15
>>> print(dict1['1'][0] + dict2['1'][0])
30

10
深拷貝,可以遞歸複製對象中包含的子對象

>>> from copy import *
>>> list1 = [1, 2, 3]
>>> list2 = [3, 4, 5]
>>> dict1 = {"1" : list1, '2' : list2}
>>> dict2 = deepcopy(dict1)
>>> dict1['1'][0] = 15
>>> print(dict1['1'][0] + dict2['1'][0])
16

11
對象通過特殊屬性__dict__存儲屬性,包括自定義屬性

>>> class Person:
	def __init__(self, id):
		self.id = id

	
>>> mary = Person(123)
>>> mary.__dict__['age'] = 18
>>> mary.__dict__['gender'] = 'female'
>>> print(mary.age + len(mary.__dict__))
21
>>> mary.__dict__
{'id': 123, 'age': 18, 'gender': 'female'}

上機實踐:2~3

2. 編寫程序,創建類MyMath,計算圓的周長和麪積以及球的表面積和體積,並編寫測試代碼,結果均保留兩位小數

import math

class MyMath:
    def __init__(self, r):
        self.r = r
    def perimeter_round(self):
        return 2 * math.pi * self.r
    def area_round(self):
        return math.pi * self.r * self.r
    def area_ball(self):
        return 4 * math.pi * self.r ** 2
    def volume_ball(self):
        return 4 / 3 * math.pi *self.r ** 3

if __name__ == '__main__':
    n = float(input("請輸入半徑:"))
    m = MyMath(n)
    print("圓的周長 = {0:.2f}\n圓的面積 = {1:.2f}\n球的表面積 = {2:.2f}\n球的體積 = {3:.2f}".\
          format(m.perimeter_round(), m.area_round(), m.area_ball(), m.volume_ball()))

輸出:

請輸入半徑:5
圓的周長 = 31.42
圓的面積 = 78.54
球的表面積 = 62.83
球的體積 = 523.60

3. 編寫程序,創建類Temperature,其包含成員變量degree(表示溫度)以及實例方法ToFahrenheit(將攝氏溫度轉換爲華氏溫度)和ToCelsius(將華氏溫度轉換爲攝氏溫度),並編寫測試代碼

class Temperature:
    def __init__(self, degree):
        self.degree = degree
    def toFahrenheit(self):
        return self.degree*9/5 + 32
    def toCelsius(self):
        return (self.degree -32) * 5/9

if __name__ == '__main__':
    n1 = float(input("請輸入攝氏溫度:"))
    t1 = Temperature(n1)
    print("攝氏溫度 = {0:.2f}, 華氏溫度 = {1:.2f}".format(n1, t1.toFahrenheit()))
    n2 = float(input("請輸入華氏溫度:"))
    t2 = Temperature(n2)
    print("攝氏溫度 = {0:.2f}, 華氏溫度 = {1:.2f}".format(t2.toCelsius(), n2))

輸出:

請輸入攝氏溫度:30
攝氏溫度 = 30.00, 華氏溫度 = 86.00
請輸入華氏溫度:70
攝氏溫度 = 21.11, 華氏溫度 = 70.00

案例研究:文本相似度比較分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章