问答题
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。