第4章 类与面向对象
4.1 对象比较:is 与 ==
is比较的是两个变量是否指向同一个对象,表示相同。
==比较的是两个变量所指向的对象是否具有相同的值(内容相同),表示相等。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
a = [1, 2, 3] b = a c = [1, 2, 3] d = [a] e = [b] f = [c] print(f'a == b: {a == b}') print(f'a == c: {a == c}') print(f'a is b: {a is b}') print(f'a is c: {a is c}') print(f'd == e: {d == e}') print(f'd == f: {d == f}') print(f'd is e: {d is e}') print(f'd is f: {d is f}') ''' 输出结果 a == b: True a == c: True a is b: True a is c: False d == e: True d == f: True d is e: False d is f: False '''
4.2 字符串转换(每个类都需要__repr__)
python中内置的str和repr函数可以将对象转字符串,它们分别调用的是对象的__str__和__repr__方法,通常是使用str或者repr而不是直接调用对象的__str__和__repr__。
在格式化字符串的时候如果指定了!r则是按__repr__输出字符串
自定义类如果没有实现__str__或__repr__方法,默认转字符串输出的是包含类名和对象实列id的字符串。
默认情况下,如果没有定义__str__,则__str__调用的是__repr__方法,因此定义了__repr__总是能正常的转换成字符串。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Car: def __init__(self, color, mileage): self.color = color self.mileage = mileage def __repr__(self): return (f'{self.__class__.__name__}(' f'{self.color!r}, {self.mileage!r})') def __str__(self): return f'a {self.color} car' car = Car('red', '37281') print(car) # 按str输出 print('{!r}'.format(car)) # 按repr输出 ''' 默认的输出结果 <__main__.Car object at 0x000001CEB075C2E8> <__main__.Car object at 0x000001CEB075C2E8> ''' ''' 自定义了__str__和__repr__方法的输出结果 a red car Car('red', '37281') ''' ''' 把自定义的__str__注释后的输出结果 Car('red', '37281') Car('red', '37281') '''
通常情况下,__str__的结果侧重于可读性,面向用户;而__repr__的结果侧重于无歧义,面向开发人员。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import datetime today = datetime.date.today() print(str(today)) # 输出普通人可理解的字符串 print(repr(today)) # 输出更专业的字符串,并且此结果可直接创建对象,恢复对象的状态(通常情况我们自定义类不用做地这么麻烦) ''' 输出结果 2021-08-29 datetime.date(2021, 8, 29) '''
python2中__str__返回的是字节,而__unicode__返回的是字符串(它对应内置函数unicode)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# --encoding=utf8-- class Car(object): def __init__(self, color, mileage): self.color = color self.mileage = mileage def __repr__(self): return '{}({!r}, {!r})'.format( self.__class__.__name__, self.color, self.mileage) def __unicode__(self): return u'a {self.color} car'.format(self=self) def __str__(self): return unicode(self).encode('utf-8') car = Car('red', '37281') print(car) # 按str输出 print('{!r}'.format(car)) # 按repr输出 ''' 默认的输出结果 a red car Car('red', '37281') '''
4.3 自定义异常
定义自己的异常类型能让代码清楚地表达出自己的意图,并易于调试。
要从Python内置的Exception类或特定的异常类(如ValueError或KeyError)派生出自定义异常。
可以使用继承来根据逻辑对异常分组,组成层次结构。
4.4 克隆对象
创建的浅副本不会克隆子对象,因此副本和原对象并不完全独立。
对象的深副本将递归克隆子对象。副本完全独立于原对象,但创建深副本的速度较慢。
使用copy模块,copy.copy(x)进行浅复制,copy.deepycopy(x)进行深复制。
对于内置的容器,只需要使用list、dict、set这样的工厂函数就能创建浅副本,这样更据Python特色。
4.5 用抽象基类避免继承错误
抽象基类(ABC)能在派生类实例化时检查其是否实现了基类中某些特定的方法。
是用ABC可以帮助避免bug并使类层次易于理解和维护。
抽象基类在abc模块。
代码todo
4.6 namedtuple的优点
namedtuple和普通元组一样是不可变容器。
namedtuple就是具有名称的元组,可以使用标识符来访问,当然还是可以使用索引来访问的。
依旧可以使用*来对namedtuple进行解包。
collection.namedtuple能够方便地在Python中手动定义一个内存占用较小的不可变类。
是用namedtuple能够按照更易于理解的结构组织数据,进而简化了代码。
namedtuple提供了一些有用的辅助方法,虽然这些方法以单下划线开头,但实际上是公共接口的一部分,可以正常使用。
todo代码
4.7 类变量与实例变量的陷阱
类变量属于类,在类的所有实例对象之间共享数据。
实例变量是特定于每个实例的数据,属于单个对象实例。
实例变量可以在对象实例化之后动态添加???
实例变量能够覆盖同名的类变量,所以很容易(意外地)由于覆盖类变量而引入bug和奇怪的行为。
4.8 实例方法、类方法和静态方法揭秘
实例方法第一个参数self是实例对象本身,通过它可以访问实例对象的其他实例方法和实例变量。
在实例方法中可以通过self.__class__访问类本身。
类方法第一个参数cls是类本身,通过它可以访问其他的类方法和类变量,也可以通过cls实例化对象。
可以利用类方法定义额外的构造函数。
静态方法不接收self和cls参数,因此它改变不了实例对象的状态和类的状态,其作用与普通函数相同,但属于类的名称空间。
其实,实例对象可以调用实例方法,类方法和静态方法。那应该self也可以调用吧??
TODO 代码