《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

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

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