Python--深入理解Python特性 第4章

第4章 类与面向对象

4.1 对象比较:is 与 ==

  is比较的是两个变量是否指向同一个对象,表示相同。

  ==比较的是两个变量所指向的对象是否具有相同的值(内容相同),表示相等。

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
'''
View Code

 

4.2 字符串转换(每个类都需要__repr__)

  python中内置的strrepr函数可以将对象转字符串,它们分别调用的是对象的__str____repr__方法,通常是使用str或者repr而不是直接调用对象的__str__和__repr__。

  在格式化字符串的时候如果指定了!r则是按__repr__输出字符串

  自定义类如果没有实现__str__或__repr__方法,默认转字符串输出的是包含类名和对象实列id的字符串。

  默认情况下,如果没有定义__str__,则__str__调用的是__repr__方法,因此定义了__repr__总是能正常的转换成字符串。

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')
'''
View Code

  通常情况下,__str__的结果侧重于可读性,面向用户;而__repr__的结果侧重于无歧义,面向开发人员。

import datetime


today = datetime.date.today()
print(str(today))  # 输出普通人可理解的字符串
print(repr(today))  # 输出更专业的字符串,并且此结果可直接创建对象,恢复对象的状态(通常情况我们自定义类不用做地这么麻烦)

''' 输出结果
2021-08-29
datetime.date(2021, 8, 29)
'''
View Code

  python2中__str__返回的是字节,而__unicode__返回的是字符串(它对应内置函数unicode)。

# --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')
'''
View Code

 

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 代码

 

posted @ 2021-08-29 16:39  liDB  阅读(50)  评论(0编辑  收藏  举报