【Python】小甲鱼课后习题第38、39、40讲--类和对象(下)

问答题


1*. 如果按以下方式重写魔法方法 init,结果会怎样?

class MyClass:
    def __init__(self):
        return "I love FishC.com!"

答:会报错,因为 __ init __ 特殊方法不应当返回除了 None 以外的任何对象。

>>> myClass = MyClass()
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    myClass = MyClass()
TypeError: __init__() should return None, not 'str'

2. 当子类定义了与相同名字的属性或方法时,Python 是否会自动删除父类的相关属性或方法?

答:不会删除!Python 的做法跟其他大部分面向对象编程语言一样,都是将父类属性或方法覆盖,子类对象调用的时候会调用到覆盖后的新属性或方法,但父类的仍然还在,只是子类对象“看不到”。

3. 假设已经有鸟类的定义,现在我要定义企鹅类继承于鸟类,但我们都知道企鹅是不会飞的,我们应该如何屏蔽父类(鸟类)中飞的方法?

答:覆盖父类方法,例如将函数体内容写 pass,这样调用 fly 方法就没有任何反应了。

class Bird:
        def fly(self):
                print("Fly away!")

class Penguin(Bird):
        def fly(self):
                pass

>>> bird = Bird()
>>> penguin = Penguin()
>>> bird.fly()
Fly away!
>>> penguin.fly()

4. super 函数有什么“超级”的地方?

答:super 函数超级之处在于你不需要明确给出任何基类的名字,它会自动帮您找出所有基类以及对应的方法。由于你不用给出基类的名字,这就意味着你如果需要改变了类继承关系,你只要改变 class 语句里的父类即可,而不必在大量代码中去修改所有被继承的方法。

5. 多重继承使用不当会导致重复调用(也叫钻石继承、菱形继承)的问题,请分析以下代码在实际编程中有可能导致什么问题?

class A():
    def __init__(self):
        print("进入A…")
        print("离开A…")

class B(A):
    def __init__(self):
        print("进入B…")
        A.__init__(self)
        print("离开B…")
        
class C(A):
    def __init__(self):
        print("进入C…")
        A.__init__(self)
        print("离开C…")

class D(B, C):
    def __init__(self):
        print("进入D…")
        B.__init__(self)
        C.__init__(self)
        print("离开D…")

答:多重继承容易导致重复调用问题,下边实例化 D 类后我们发现 A 被前后进入了两次。

这有什么危害?我举个例子,假设 A 的初始化方法里有一个计数器,那这样 D 一实例化,A 的计数器就跑两次(如果遭遇多个钻石结构重叠还要更多),很明显是不符合程序设计的初衷的(程序应该可控,而不能受到继承关系影响)。

>>> d = D()
进入D…
进入B…
进入A…
离开A…
离开B…
进入C…
进入A…
离开A…
离开C…
离开D…

为了让大家都明白,这里只是举例最简单的钻石继承问题,在实际编程中,如果不注意多重继承的使用,会导致比这个复杂N倍的现象,调试起来不是一般的痛苦……所以一定要尽量避免使用多重继承。

6. 如何解决上一题中出现的问题?

答:super 函数再次大显神威。

class A():
    def __init__(self):
        print("进入A…")
        print("离开A…")

class B(A):
    def __init__(self):
        print("进入B…")
        super().__init__()
        print("离开B…")
        
class C(A):
    def __init__(self):
        print("进入C…")
        super().__init__()
        print("离开C…")

class D(B, C):
    def __init__(self):
        print("进入D…")
        super().__init__()
        print("离开D…")

>>> d = D()
进入D…
进入B…
进入C…
进入A…
离开A…
离开C…
离开B…
离开D…

2*. 类对象是在什么时候产生?

答:当你这个类定义完的时候,类定义就变成类对象,可以直接通过“类名.属性”或者“类名.方法名()”引用或使用相关的属性或方法。

3*. 如果对象的属性跟方法名字相同,会怎样?

答:如果对象的属性跟方法名相同,属性会覆盖方法。

class C:
        def x(self):
                print('Xman')

>>> c = C()
>>> c.x()
Xman
>>> c.x = 1
>>> c.x
1
>>> c.x()
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    c.x()
TypeError: 'int' object is not callable

4*. 请问以下类定义中哪些是类属性,哪些是实例属性?

class C:
        num = 0
        def __init__(self):
                self.x = 4
                self.y = 5
                C.count = 6

答:num 和 count 是类属性(静态变量),x 和 y 是实例属性。大多数情况下,你应该考虑使用实例属性,而不是类属性(类属性通常仅用来跟踪与类相关的值)。

5. 请问以下代码中,bb 对象为什么调用 printBB() 方法失败?

class BB:
        def printBB():
                print("no zuo no die")

>>> bb = BB()
>>> bb.printBB()
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    bb.printBB()
TypeError: printBB() takes 0 positional arguments but 1 was given

答:因为 Python 严格要求方法需要有实例才能被调用,这种限制其实就是 Python 所谓的绑定概念。所以 Python 会自动把 bb 对象作为第一个参数传入,所以才会出现 TypeError:“需要 0 个参数,但实际传入了 1 个参数“。

正确的做法应该是(即 printBB 函数中必须要有 self 参数):

class BB:
        def printBB(self):
                print("no zuo no die")

>>> bb = BB()
>>> bb.printBB()
no zuo no die

0*. 如何判断一个类是否为另一个类的子类?

答:使用 issubclass(class, classinfo) 函数,如果第一个参数(class)是第二个参数(classinfo)的一个子类,则返回 True,否则返回 False。

另外以下这些常识你应该知道:

  • 一个类被认为是其自身的子类
  • classinfo 可以是类对象组成的元组,只要 class 是其中任何一个候选类的子类,则返回 True
  • 在其他情况下,会抛出一个 TypeError 异常

1. 如何判断对象 a 是否为类 A 的实例对象?

答:使用 isinstance(object, classinfo) 函数,如果第一个参数(object)是第二个参数(classinfo)的实例对象,则返回 True,否则返回 False。

另外以下这些常识你应该知道:

  • 如果 object 是 classinfo 的子类的一个实例,也符合条件
  • 如果第一个参数不是对象,则永远返回False
  • classinfo 可以是类对象组成的元组,只要 object 是其中任何一个候选类的实例对象,则返回 True
  • 如果第二个参数不是类或者由类对象组成的元组,会抛出一个 TypeError 异常

2. 如何优雅地避免访问对象不存在的属性(不产生异常)?

答:有两种方法可以做到。

第一种先使用 hasattr(object, name) 函数判断属性是否存在,如果存在再访问(第一个参数(object)是对象,第二个参数(name)是属性名的字符串形式);

第二种方法是直接使用 getattr(object, name[, default]) 函数并设置 default 参数(返回对象指定的属性值,如果指定的属性不存在,返回default(可选参数)的值)。

3. Python 的一些 BIF 很奇怪,但却十分有用。请问 property() 函数的作用是什么?

答:property() 函数允许编程人员轻松、有效地 管理属性访问。

4. 请补充以下代码,使程序可以正常运行:

class C:
    def __init__(self, size=10):
        self.size = size

    def getXSize(self):
        return self.size

    def setXSize(self, value):
        self.size = value

    def delXSize(self):
        del self.size

        # 此处应该补充一句代码,程序才能正常运行

>>> c.x
10
>>> c.x = 12
>>> c.x
12

答:x = property(getXSize, setXSize, delXSize)


动动手


0. 定义一个点(Point)类和直线(Line)类,使用 getLen 方法可以获得直线的长度。)

提示:

  • 设点 A(X1,Y1)、点 B(X2,Y2),则两点构成的直线长度 |AB| = √((x1-x2)2+(y1-y2)2)
  • Python 中计算开根号可使用 math 模块中的 sqrt 函数
  • 直线需有两点构成,因此初始化时需有两个点(Point)对象作为参数
import math

class Point():
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def getX(self):
        return self.x

    def getY(self):
        return self.y

class Line():
    def __init__(self, p1, p2):
        self.x = p1.getX() - p2.getX()
        self.y = p1.getY() - p2.getY()
        self.len = math.sqrt(self.x*self.x + self.y*self.y)

    def getLen(self):
        return self.len

>>> p1 = Point(1, 1)
>>> p2 = Point(4, 5)
>>> line = Line(p1, p2)
>>> line.getLen()
5.0

0*. 请动手在一个类中定义一个变量,用于跟踪该类有多少个实例被创建(当实例化一个对象,这个变量+1,当销毁一个对象,这个变量自动-1)。

答:代码如下:

class C:
        count = 0
        
        def __init__(self):
                C.count += 1

        def __del__(self):
                C.count -= 1

>>> a = C()
>>> b = C()
>>> c = C()
>>> C.count
3
>>> del a
>>> C.count
2
>>> del b, c
>>> C.count
0

1. 定义一个栈(Stack)类,用于模拟一种具有后进先出(LIFO)特性的数据结构。至少需要有以下方法:

在这里插入图片描述

方法名含义
isEmpty()判断当前栈是否为空(返回 True 或 False)
push()往栈的顶部压入一个数据项
pop()从栈顶弹出一个数据项(并在栈中删除)
top()显示当前栈顶的一个数据项
bottom()显示当前栈底的一个数据项

答:我写的代码如下:

class Stack:
    def __init__(self):
        self.list1=[]
        self.topp=-1
        self.bott=0
        self.length=0
        self.max_len=100

    def isEmpty(self):
        self.length=len(self.list1)
        if self.length==0:
            print(True)
        else:
            print(False)

    def push(self,data):
        if self.length<self.max_len:
            self.topp+=1
            self.list1.append(data)
            print("入栈成功!")
        else:
            print("栈满,无法入栈!")

    def pop(self,data):
        if self.length>0:
            data=self.list1.pop()
            self.topp-=1
            print("出栈成功!")
        else:
            print("栈空,无法出栈!")

    def top(self):
        if self.topp==-1:
            print("栈空,请压入元素!")
        else:
            print(self.list1[self.topp])

    def bottom(self):
        if self.topp==-1:
            print("栈空,请压入元素!")
        else:
            print(self.list1[self.bott])

stack=Stack()
stack.isEmpty()
stack.top()
stack.push("Pig")
stack.isEmpty()
stack.top()
stack.bottom()

小甲鱼提供的代码如下:

class Stack:
    def __init__(self, start=[]):
        self.stack = []
        for x in start:
            self.push(x)

    def isEmpty(self):
        return not self.stack
    
    def push(self, obj):
        self.stack.append(obj)
 
    def pop(self):
        if not self.stack:
            print('警告:栈为空!')
        else:
            return self.stack.pop()
 
    def top(self):
        if not self.stack:
            print('警告:栈为空!')
        else:
            return self.stack[-1]
 
    def bottom(self):
        if not self.stack:
            print('警告:栈为空!')
        else:
            return self.stack[0]

我感觉可以学习小甲鱼判空函数中用的方法,直接 return not self.stack,这样效率比较高,我用的方法跟小甲鱼相比就麻烦了一些。因为 Python 会将空的集合或序列看做 False。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

早知晓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值