面向对象(下)
文章目录
1 继承与重写
继承是一项代码复用技术,就是新建一个类的时候,使其能够保留已有类的属性和方法。Python 支持多重继承,一个子类可以继承多个父类。
重写是子类中的属性名或方法名与父类一致,使得父类的属性或方法被覆盖,调用和访问的时候,只是用子类的属性或方法。
继承的语法格式如下:
class 子类类名(父类 1[,父类 2,...]):
类体
如果在类定义中没有指定父类,则默认父类是 object 类。也就是说,object 是所有类的父类,里面定义了一些所有类共有的默认实现,比如__new__()。
例:
class Person: # 没有说继承什么类,那么默认Person继承的是object类
def __init__(self,name,age):
self.name = name
self.__age = age # 私有属性
def say_age(self):
print("年龄,年龄,我也不知道")
class Student(Person):
def __init__(self,name,age,score):
Person.__init__(self,name,age)
# 必须显式的调用父类初始化方法,不然解释器不会去调用,如果不调父类的构造器,也不会报错
self.score = score
# Student-->Person-->object类
print(Student.mro()) # 打印继承层次结构
s = Student("高淇",18,60)
s.say_age()
print(s.name)
print(dir(s)) # 查看 s 中的所有属性与方法
# print(s.__age) # 这句会报错,在父类中是私有属性,在被继承的子类中也是私有
print(s._Person__age) # 访问父类的私有属性
输出
[<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>]
年龄,年龄,我也不知道
高淇
['_Person__age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_age', 'score']
18
从上面的程序可以看到,子类继承父类的私有属性,私有属性的名称前面,加的类名是父类的名,也就是说,子类得到的私有属性是_Person__age,而非_Student__age。
子类在继承父类的时候,**如果希望使用父类的属性,那么必须调用父类的构造函数对父类的属性进行初始化。**当然,如果不想使用父类的属性,那么可以不调用父类的构造方法,这跟C++区别很大。父类的属性,子类要么全收,要么一个都不收。
没有调用父类的构造方法,也可以使用父类的其他函数,前提是这些函数要么不涉及父类的属性,要么涉及的属性已被子类覆盖(同名重写),如
class Person:
def __init__(self,name,age):
self.name = name
self.__age = age # 私有属性
def say_age(self):
print("年龄", self.__age)
def say_name(self):
print("姓名", self.name)
class Student(Person):
def __init__(self,name,age,score):
# 没有调用父类的构造函数,不想使用父类的属性
self.name = name
# 这里的 name 不是继承自Person,而是Student自己又定义了一个,把Person中的name属性给覆盖了
self.score = score
s = Student("高淇",18,60)
s.say_name() # 虽然没有给父类的name属性初始化,但是子类有name属性,仍然可以调用say_name()方法
# s.say_age() # 这里会报错,因为子类的构造函数中没有调用父类的构造函数
print(dir(s))
输出
姓名 高淇
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_age', 'say_name', 'score']
如果子类使用了父类的构造方法,那么子类中,也无法直接访问父类的私有属性,必须用“_父类名__属性”的方式使用父类的私有属性,否则报错
class Person:
def __init__(self,name,age):
self.name = name
self.__age = age # 私有属性
class Student(Person):
def __init__(self,name,age,score):
Person.__init__(self,name,age)
self.score = score
def say_age(self):
print(self._Person__age) # 如果括号中写成“self.__age”,则会报错
s = Student("高淇",18,60)
s.say_age()
输出
18
继承总结:
(1)父类的属性,子类要么全收,要么一个都不收,全收的话必须调用父类的构造方法;
(2)子类中可以不使用父类的构造函数,但仍可以访问父类的其他某些方法(要么不涉及父类的属性,要么涉及的父类属性被覆盖);
(3)无论是属性还是方法,无论私有还是公有,只要同名就覆盖;
(4)在子类中使用父类的私有属性和私有方法的方式,与类外访问无区别。
2 打印类的层次结构示意图
通过类的方法 mro()或者类的属性__mro__可以输出这个类的继承层次结构。
class A:pass
class B(A):pass
class C(B):pass
print(C.mro())
print('#'*30)
print(C.__mro__)
输出
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
##############################
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
3 object根类
object 类是所有类的父类,因此所有的类都有 object 类的属性和方法。
可以使用内置函数查看object的所有属性和方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_age(self):
print(self.name, "的年龄是:", self.age)
obj = object()
print(dir(obj))
s2 = Person("高淇",18)
print(dir(s2))
从上面我们可以发现这样几个要点:
1.Person 对象增加了六个属性:
dict,module ,weakref,age,name,say_age
2.object 的所有属性,Person 类作为 object 的子类,显然包含了所有的属性。
3.我们打印 age、name、say_age,发现 say_age 虽然是方法,实际上也是属性。只不过这个属性的类型是“method”而已。
age <class ‘int’>
name <class ‘str’>
say_age <class ‘method’>
4 重写__str__()方法
object 有一个__str__()方法,用于返回一个对于“对象的描述”。
假如 p 是 Person的一个对象,那么print§就默认调用了__str__()方法,即print(p.str())
class Person: #默认继承object类
def __init__(self,name):
self.name = name
p = Person("高淇")
print(p)
print(p.__str__())
输出
<__main__.Person object at 0x0000000002605490>
<__main__.Person object at 0x0000000002605490>
str()可以重写
class Person: #默认继承object类
def __init__(self,name):
self.name = name
def __str__(self):
return "名字是:{0}".format(self.name)
p = Person("高淇")
print(p)
输出
名字是:高淇
5 多继承
Python 支持多重继承,一个子类可以有多个“直接父类”,但是由于,这样会被“类的整体层次”搞的异常复杂,尽量避免使用。
6 子类中调用父类方法
可以用super().方法名,也可以用 父类名.方法名,两者效果等价。
#测试super(),代表父类的定义,而不是父类的对象
class A:
def say(self):
print("A:",self)
class B(A):
def say(self):
super().say()
A.say(self) # 类外就不能这么写了,类外必须通过对象调用
print("B:",self)
B().say()
# 建立了一个B类对象,这个对象没有名字,是匿名对象,通过匿名对象调用B类的方法
输出
A: <__main__.B object at 0x00000000025F5FD0>
A: <__main__.B object at 0x00000000025F5FD0>
B: <__main__.B object at 0x00000000025F5FD0>
7 多态
多态(polymorphism)是指同一个方法调用由于对象不同可能会产生不同的行为。
例如,同样是吃饭的方法,中国人用筷子吃饭,英国人用刀叉吃饭,印度人用手吃饭。
class Man:
def eat(self):
print("饿了,吃饭啦!")
class Chinese(Man):
def eat(self):
print("中国人用筷子吃饭")
class English(Man):
def eat(self):
print("英国人用叉子吃饭")
class Indian(Man):
def eat(self):
print("印度人用右手吃饭")
def manEat(m):
if isinstance(m,Man): # 只要是 Man 的子类对象,都会返回True
m.eat() # 多态,一个方法调用,根据对象不同调用不同的方法!
else:
print("不能吃饭")
manEat(Chinese()) # 参数是匿名对象
manEat(English())
输出
中国人用筷子吃饭
英国人用叉子吃饭
关于多态要注意以下 2 点:
1.多态是方法的多态,属性没有多态。
2.多态的存在有 2 个必要条件:继承、方法重写。
8 运算符重载
python语言同C++语言一样,具有运算符重载的功能,所谓的运算符重载,其实就是扩展运算符的功能。python解释器遇到运算符的时候,都是调用函数,比如遇到加号(+)时,其实是调用__add__(),我们在学习字符串的时候说过,加号还有字符串拼接的功能,其实是因为__add__被重载了,使其能够处理两个操作数都是字符串的情况。
下面是重载加号(+)和乘号(*)的例子
class Person:
def __init__(self,name):
self.name = name
def __add__(self, other):
if isinstance(other,Person):
return "{0}--{1}".format(self.name,other.name)
else:
return "不是同类对象,不能相加"
def __mul__(self, other):
if isinstance(other,int):
return self.name*other
else:
return "不是同类对象,不能相乘"
p1 = Person("高淇")
p2 = Person("高希希")
x = p1 + p2
print(x)
print(p1*3)
输出
高淇--高希希
高淇高淇高淇
9 特殊属性
Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊用法,这里我们仅作简要介绍。
#测试特殊属性
class A:
pass
class B:
pass
class C(B,A):
def __init__(self,nn):
self.nn = nn
def cc(self):
print("cc")
c = C(3)
print(dir(c)) # 打印 c 的所有属性和方法
print(c.__dict__) # 打印子弟你的属性和方法
print(c.__class__) # 打印对象 c 所属类型
print(C.__bases__) # 打印 C 类的基类所有组成的元组(多继承)
print(C.__mro__) # 打印 C 类的层次结构
print(A.__subclasses__()) # 打印由 A 的子类构成的列表
输出
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'cc', 'nn']
{'nn': 3}
<class '__main__.C'>
(<class '__main__.B'>, <class '__main__.A'>)
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
[<class '__main__.C'>]
10 对象的浅拷贝与深拷贝
当实例对象的属性是某个其他对象的引用时,浅拷贝就是拷贝一个新的对象,但是不拷贝属性所指向的对象,换汤不换药,深拷贝是连属性所指向的对象一起拷贝(如果有多层子对象,则是递归复制),连汤带药一起换。
import copy
class MobilePhone:
def __init__(self,cpu,screen):
self.cpu = cpu
self.screen = screen
class CPU:
def calculate(self):
print("算你个12345")
print("cpu对象:",self)
class Screen:
def show(self):
print("显示一个好看的画面,亮瞎你的钛合金大眼")
print("screen对象:",self)
# 测试变量赋值
c1 = CPU()
c2 = c1
print(c1)
print(c2)
print("测试浅复制....")
# 测试浅复制
s1 = Screen()
m1 = MobilePhone(c1,s1)
m2 = copy.copy(m1)
print(m1,m1.cpu,m1.screen)
print(m2,m2.cpu,m2.screen)
# 测试深复制
print("测试深复制....")
m3 = copy.deepcopy(m1)
print(m1,m1.cpu,m1.screen)
print(m3,m3.cpu,m3.screen)
输出
<__main__.CPU object at 0x00000000025F5FD0>
<__main__.CPU object at 0x00000000025F5FD0>
测试浅复制....
<__main__.MobilePhone object at 0x0000000002087160> <__main__.CPU object at 0x00000000025F5FD0> <__main__.Screen object at 0x00000000020876A0>
<__main__.MobilePhone object at 0x0000000002602850> <__main__.CPU object at 0x00000000025F5FD0> <__main__.Screen object at 0x00000000020876A0>
测试深复制....
<__main__.MobilePhone object at 0x0000000002087160> <__main__.CPU object at 0x00000000025F5FD0> <__main__.Screen object at 0x00000000020876A0>
<__main__.MobilePhone object at 0x00000000025D9580> <__main__.CPU object at 0x0000000002643BE0> <__main__.Screen object at 0x0000000002643C10>
浅拷贝的话,两部手机共用CPU和屏幕,深拷贝的话,两部手机有各自的CPU和屏幕。
11 组合
与继承类似,组合(“has-a”关系)也是一种代码复用技术,我们可以使用“组合”,也能实现一个类拥有另一个类的方法和属性。
#使用继承实现代码的复用
class A1:
def say_a1(self):
print("a1,a1,a1")
class B1(A1):
pass
b1 = B1()
b1.say_a1()
#同样的效果,使用组合实现代码的复用
class A2:
def say_a2(self):
print("a2,a2,a2")
class B2:
def __init__(self,a):
self.a = a
a2 = A2()
b2 = B2(a2)
b2.a.say_a2()
输出
a1,a1,a1
a2,a2,a2
前面的手机例子,其实也是组合,CPU和屏幕作为手机的属性,也可以通过手机对象,间接调用CPU和屏幕的方法和属性,可以自己把程序修改然后跑一下试试。
12 设计模式
设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法。设计模式有很多种,对于初学者,我们学习两个最常用的模式:工厂模式和单例模式。
(1)工厂模式
工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制。
class CarFactory:
def create_car(self,brand): # 在工厂里造车
if brand =="奔驰":
return Benz()
elif brand =="宝马":
return BMW()
elif brand == "比亚迪":
return BYD()
else:
return "未知品牌,无法创建"
class Benz:
pass
class BMW:
pass
class BYD:
pass
factory = CarFactory()
c1 = factory.create_car("奔驰")
c2 = factory.create_car("比亚迪")
print(c1)
print(c2)
输出
<__main__.Benz object at 0x0000000002655FD0>
<__main__.BYD object at 0x000000000262F190>
(2)单例模式
单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一个访问该实例的全局访问点。
单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个“单例对象”,然后永久驻留内存中,从而极大的降低开销。
单例模式有多种实现的方式,仅介绍重写__new__()和__init__()的方法。
class MySingleton:
__obj = None # 类属性,用来指向创建好的单例
__init_flag = True # 检测参数能否初始化
def __new__(cls, *args, **kwargs): # 重写__new__()方法
if cls.__obj ==None: # 当__obj为空的时候,表示没有创建单例,可以进行创建
cls.__obj = object.__new__(cls)
return cls.__obj # 如果是第二次创建,这里返回的将是第一次创建对象的指针
def __init__(self,name): # 重写__init__()方法
if MySingleton.__init_flag:
print("init.....")
self.name = name
MySingleton.__init_flag = False
# 因为单例只能由一个例子,因此初始化方法被调用之后,必须修改标志,防止参数继续被初始化
a = MySingleton("aa")
b = MySingleton("bb")
# 这里__new__方法和__init__方法都被调用了两次,但是参数 name 仅仅被初始化一次,因此最终只生成一个对象
# 换句话说,创建了两个对象,但只有第一个被初始化,而且第二次创建时,返回的也是第一个对象的指针
# 因此第二次创建得到的对象,会马上被垃圾回收器回收
print(a)
print(b)
c = MySingleton("cc")
print(c)
输出
init.....
<__main__.MySingleton object at 0x00000000025D5FD0>
<__main__.MySingleton object at 0x00000000025D5FD0>
<__main__.MySingleton object at 0x00000000025D5FD0>
可以看到,上面的程序段试图三次创建对象,但是第二次和第三次得到的对象和第一次的完全一样,其原因是重写了__new__方法,使得不管是第几次创建,返回的都是第一次创建的引用。
(3)工厂模式与单例模式的整合使用
把上面两个模式整合,效果就是只有一个工程,但是可以造各种不同品牌的车。
lass CarFactory:
__obj = None #类属性
__init_flag = True
def create_car(self,brand):
if brand =="奔驰":
return Benz()
elif brand =="宝马":
return BMW()
elif brand == "比亚迪":
return BYD()
else:
return "未知品牌,无法创建"
def __new__(cls, *args, **kwargs):
if cls.__obj ==None:
cls.__obj = object.__new__(cls)
return cls.__obj
def __init__(self):
if CarFactory.__init_flag:
print("init CarFactory....")
CarFactory.__init_flag = False
class Benz:
pass
class BMW:
pass
class BYD:
pass
factory = CarFactory()
c1 = factory.create_car("奔驰")
c2 = factory.create_car("比亚迪")
print(c1)
print(c2)
factory2 = CarFactory()
print(factory)
print(factory2)
输出
init CarFactory....
<__main__.Benz object at 0x00000000025F9640>
<__main__.BYD object at 0x00000000025F9250>
<__main__.CarFactory object at 0x0000000002615FD0>
<__main__.CarFactory object at 0x0000000002615FD0>