第四章 面向对象
1、简述面向对象的三大特性?
封装、继承和多态
封装:
python的封装特性体现在它可以把数据封装在对象中和把方法封装在类中。父类的私有(以左双下划线开始)属性和方法,子类无法对其进行使用和修改。
继承:
继承就是继承的类直接拥有被继承类的属性而不需要在自己的类体中重新再写一遍,其中被继承的类叫做父类或者基类,继承的类叫做子类或者派生类。python支持多继承,即一个子类可以拥有多个父类,这样做可以提高代码的重用性。
多态:
多态就是不同的对象可以调用相同的方法然后得到不同的结果,有点类似接口类的感觉,python原生支持多态,比如不管你是列表还是字符串还是数字都可以使用+和*。
2、什么是鸭子模型?
鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。
3、super的作用
当子类中的方法与父类中的方法重名时,子类中的方法会覆盖父类中的方法,那么,如果我们想实现同时调用父类和子类中的同名方法,就需要使用到super()这个函数,用法为super().函数名()
4、mro是什么?
- 对于支持继承的编程语言来说,其方法(属性)可能定义在当前类,也可能来自于基类,所以在方法调用时就需要对当前类和基类进行搜索以确定方法所在的位置。而搜索的顺序就是所谓的「方法解析顺序」(Method Resolution Order,或MRO)。
5、什么是c3算法?
- c3算法是python新式类中用来产生mro顺序的一套算法。即多继承的查找规则。
6、列举面向对象中带双下划线的特殊方法(列举几个常见的和几个不常见的)
__new__:可以调用其它类的构造方法或者直接返回别的对象来作为本类的实例。
__init__: 负责类的实例化。
__call__:对象后边加括号,触发执行。
__str__:print打印一个对象时。
__doc__:类的注释,该属性是无法继承的。
__getattr__:在使用调用属性(方式、属性)不存在的时候触发。
__setattr__:添加/修改属性会触发它的执行。
__delattr__:删除属性的时候会触发。
7、双下划线和单下划线的区别
- "单下划线" 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量
- "双下划线" 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个属性或方法
8、实例变量和类变量的区别
- 实例变量是对于每个实例都独有的属性
- 类变量是该类所有实例共享的属性
9、实例方法、静态方法和类方法的区别
- 实例方法:第一个参数必须是实例对象,通常为self。实例方法只能由实例对象调用。
- 类方法:使用装饰器@classmethod。第一个参数为当前类的对象,通常为cls。实例对象和类都可以调用类方法。但一般用类调用。
- 静态方法:使用装饰器@staticmethod。没有self和cls参数。方法中不能使用类或者实例的任何属性和方法。实例对象和类都可以调用。但一般用类调用,本质上静态方法就是在类中写函数。
10、isinstance和type的作用
- 两者都用来判断对象的类型
- 对于一个类的子类对象的类型判断,type就不行了,而isinstance可以。
- isinstance是判断某个对象是一个类的子类或者子孙类,而type是判断某个对象是否是某个类的实例对象
class A(object): pass class B(A): pass ba=B() ab=A() print(type(ba)==A) # False print(type(ab)==A) # True print(isinstance(ab,A)) # True print(isinstance(ba,A)) # True
11、使用with语句的好处是什么
- 使用with后不管with中的代码出现什么错误,都会对当前对象进行清理工作。例如file的file.close()方法,无论with中出现任何错误,都会执行file.close() 方法
- 只有支持上下文管理器的对象才能使用with,即在对象内实现了两个方法:__enter__()和__exit__()
写一个支持with语句的类:参考链接
class W(object): def __init__(self): pass def __enter__(self): print('进入with语句') return self def __exit__(self,*args,**kwargs): print('退出with语句') with W() as w: print('之前') print(w) print('之后')
13、实现一个单例模式。(尽可能多的方法)
# 方法一:使用__new__() import threading class Singleton(object): _instance_lock = threading.Lock() def __init__(self): pass def __new__(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): with Singleton._instance_lock: if not hasattr(Singleton, "_instance"): Singleton._instance = object.__new__(cls) return Singleton._instance obj1 = Singleton() obj2 = Singleton() print(obj1 is obj2) # 方法二:使用元类来创建 import threading class SingletonType(type): _instance_lock = threading.Lock() def __call__(cls, *args, **kwargs): if not hasattr(cls, "_instance"): with SingletonType._instance_lock: if not hasattr(cls, "_instance"): cls._instance = super().__call__(*args, **kwargs) return cls._instance class Singleton(metaclass=SingletonType): def __init__(self): pass obj1 = Singleton() obj2 = Singleton() print(obj1 is obj2)
14、请描述with的用法,如果自己的类需要支持with语句,应该如何书写?
参见第11题。
15、如何判断一个对象是否可调用?哪些对象是可调用对象?如何定义一个类,使其对象本身就是可调用对象?
- 使用 callable(func) 函数判断。
- 可调用对象有7类:
- 用户自定义函数
- 内置函数
- 内置方法
- 方法(定义在类中的函数)
- 类
- 类实例(如果类中定义了__call__方法,那么这个类的实例就是可调用对象)
- 生成器函数
- 在类中定义__call__方法,实例对象加()是即调用__call__的方法
16、手写一个栈
# 解法一: class StackFullError(Exception): pass class StackEmptyError(Exception): pass class Stack: def __init__(self, size): self.size = size self.lst = [] self.top = 0 def push(self, item): if self.top == self.size: raise StackFullError("Stack is Full Now~~~~~") self.lst.insert(self.top, item) # 放数据 self.top += 1 # 栈顶指针向上移动 def pop(self): if self.top == 0: raise StackEmptyError("Stack is Empty Now~~~~") self.top -= 1 item = self.lst[self.top] # 获取到元素 return item s = Stack(5) s.push("香肠1") s.push("香肠2") s.push("香肠3") s.push("香肠4") s.push("香肠5") # stackoverflow print(s.pop()) print(s.pop()) print(s.pop()) print(s.pop()) print(s.pop()) # 解法二: # 给一个点,我们能够根据这个点知道一些内容 class Node(object): def __init__(self,val): #定位的点的值和一个指向 self.val=val #指向元素的值,原队列第二元素 self.next=None #指向的指针 class stack(object): def __init__(self): self.top=None #初始化最开始的位置 def push(self,n):#添加到栈中 n=Node(n) #实例化节点 n.next=self.top #顶端元素传值给一个指针 self.top=n return n.val def pop(self): #退出栈 if self.top == None: return None else: tmp=self.top.val self.top=self.top.next #下移一位,进行 return tmp if __name__=="__main__": s=stack() print(s.pop()) s.push(1) print(s.pop()) s.push(2) s.push(3) print(s.pop()) s.push(3) s.push(3) s.push(3) print(s.pop()) print(s.pop()) print(s.pop()) print(s.pop())
18、实现一个hashtable(可以理解为字典)类,对外暴露的有add和get方法,满足以下测试代码:
def test(): import uuid names = {'name', 'web', 'python'} ht = HashTable() for key in names: value = uuid.uuid4() ht.add(key,value) print('add元素',key,value) for key in names: v = ht.get(key) print('get元素',key,v)
# 答案:其实就是手写一个字典,但是它没说不让用字典,所以可"投机",即类中可以用字典 class HashTable: def __init__(self): self.dict = {} def add(self, key, value): self.dict[key] = value def get(self, key): return self.dict.get(key)
19、使用两个队列实现一个栈
class Stack(object): def __init__(self): self.queueA = [] self.queueB = [] def push(self, node): self.queueA.append(node) def pop(self): if len(self.queueA) == 0: return None while len(self.queueA) != 1: self.queueB.append(self.queueA.pop(0)) ret = self.queueA.pop(0) self.queueA, self.queueB = self.queueB, self.queueA return ret st = Stack() print(st.pop()) st.push(1) print(st.pop()) st.push(2) st.push(3) st.push(4) print(st.pop()) print(st.pop()) print(st.pop())
注意上面两个栈的实现方法(16题和19题),第一种的效率高,队列的这种方法效率低
20、有如下链表类,请实现单链表逆置。
class ListNode: def __init__(self,val): self.val = val self.next = None class Solution: def reverseList(self,pHead): if not pHead or not pHead.next: return pHead last = None while pHead: tmp = pHead.next pHead.next = last last = pHead pHead = tmp return last
21、类的加载顺序(类中有继承,有构造,有静态)
先加载父类,后子类,先静态,后类,最后创建对象
22、参考下面代码片段
class Context: pass with Context() as ctx: ctx.do_something() # 请在Context类下添加代码完成该类的实现。
# 考察上下文管理器的__enter__和__exit__方法,参考12题
28、写代码(栈与队列)
编程实现一个先进先出的队列类,能指定初始化时的队列大小,以及enqueue,dequeue,is_empty,is_full四种方法,使用方法如下:
1、s=Queue(2) # 初始化一个大小为2的队列
2、s.is_empty() # 初始化后,队列为空,返回True
3、s.enqueue(1) # 将1加入队列
4、s.enqueue(2) # 将2加入队列
5、s.is_full() # 加入了两个元素,队列已满,返回True
6、s.dequeue() # 移除一个元素,返回1
7、s.dequeue() # 移除一个元素,返回2
8、s.is_empty() # 队列已经为空,返回True
class Queue(object): def __init__(self,size): self.queue=[] self.size=size def is_empty(self): return not bool(len(self.queue)) def is_full(self): return len(self.queue)==self.size def enqueue(self,val): if not self.is_full(): self.queue.insert(0,val) return True return False def dequeue(self): if not self.is_empty(): return self.queue.pop() return None s=Queue(2) print(s.is_empty) s.enqueue(1) s.enqueue(2) print(s.is_full()) print(s.dequeue()) print(s.dequeue()) print(s.is_empty())
29、两个栈实现一个队列
class Queue(object): def __init__(self): self.stackA=[] # stackA 入队 self.stackB=[] # stackB 出队 def rudui(self,node): self.stackA.append(node) def chudui(self): if len(self.stackB) == 0 and len(self.stackA) == 0: return None elif len(self.stackB) == 0 and len(self.stackA) != 0: while len(self.stackA) > 0: self.stackB.append(self.stackA.pop(0)) return self.stackB.pop(0) st = Queue() print(st.chudui()) st.rudui(12) st.rudui(132) st.rudui(16) print(st.chudui())
浙公网安备 33010602011771号