Python是面向对象的语言,也支持面向对象编程的三大特性:继承、封装(隐藏)、多态。
•封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将"细节封装起来",只
对外暴露"相关调用方法"。
通过前面学习的"私有属性、私有方法"的方式,实现"封装"。Python追求简洁的
语法,没有严格的语法级别的"访问控制符”,更多的是依靠程序员自觉实现。
•继承
继承可以让子类具有父类的特性,提高了代码的重用性。
从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进
已有的算法。
•多态
多态是指同一个方法调用由于对象不同会产生不同的行为。生活中这样的例子比比皆
是:同样是休息方法,人不同休息方法不同。张三休息是睡觉,李四休息是玩游戏,程序员 休息是"敲几行代码"。
继承是面向对象程序设计的重要特征,也是实现"代码复用”的重要手段。
如果一个新类继承自一个设计好的类,就直接具备了已有类的特征,就大大降低了工作 难度。已有的类,我们称为"父类或者基类",新的类,我们称为"子类或者派生类"。
Python支持多重继承,一个子类可以继承多个父类。继承的语法格式如下: class子类类名(父类1[,父类2,...]):
类体
如果在类定义中没有指定父类,则默认父类是object类。也就是说,object是所有类的父 类,里面定义了一些所有类共有的默认实现,比如:_new__()。
定义子类时,必须在其构造函数中调用父类的构造函数。调用格式如下:
父类名.__init_(self,参数列表)
class Person:
def __init__(self,name,age):
self.name = name
self.__age = age
def say_age(self):
print(self.name,"的年龄是:",self.__age)
class Student(Person):
def __init__(self,name,age,score):
self.score = score
Person.__init__(self,name,age) #构造函数中包含调用父类构 造函数。根据需要,不是必须。子类并不会自动调用父类的_init__(),我 们必须显式的调用它。
s1 = Student("张三",15,85) s1.say_age() print(dir(s1))
运行结果:
张三的年龄是:15
['_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']
【操作】继承和重写的案例
class Person:
def __init__(self/name/age):
self.name = name
self.age = age
def say_age(self): print(self.name,"的年龄是:二self.age)
def say_name(self):
print(”我是”,self.name)
class Student(Person):
def _init_(self,name,age,score):
self.score = score
Person._init_(self,name,age) #构造函数中包含调用父类构造函数
def say_score(self): print(self.name,"的分数是:二self.score)
def say_name(self): #重写父类的方法
print("报告老师,我是二self.name)
s1 = Student("张三",15,85)
s1.say_score()
s1.say_name()
s1.say age()
执行结果:
张三 的分数是: 85 报告老师,我是张三
张三 的年龄是: 15
通过类的方法mroO或者类的属性__mro__可以输出这个类的继承层次结构。
【操作】查看类的继承层次结构
class A:pass
class B(A):pass
class C(B):pass
print(C.mro())
执行结果:
[<class ,__main__.CI>/ <class '__main__.B‘>, <class '__main__A>, <class 'object'〉]
object类是所有类的父类,因此所有的类都有。bject类的属性和方法。我们显然有必要深 入研究一下。bject类的结构。对于我们继续深入学习Python很有好处。
dir()查看对象属性
为了深入学习对象,我们先学习内置函数dir。,他可以让我们方便的看到指定对象所有的 属性。
【测试】查看对象所有属性以及和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))
执行结果:
从上面我们可以发现这样几个要点:
__dict_ __module_ __weakref_ age name say_age
- object的所有属性,Person类作为object的子类,显然包含了所有的属性。
- 我们打印age、name、say_age,发现say_age虽然是方法,实际上也是属性。只不过, 这个属性的类型是"method”而已。
age <class 'int'>
name <class 'str'>
say_age <class 'method'>
【注】关于。bject这些属性的详细学习,会在后面学习中逐个涉及。在此,无法一一展开。
重写__st r__()方法
object有一个__str__()方法,用于返回一个对于"对象的描述",对应于内置函数str。 经常用于print。方法,帮助我们查看对象的信息。__str_0可以重写。
class Person:
def __init__(self,name,age):
self.name = name
self.__age = age
def __str__(self):
”将对象转化成一个字符串,—般用于print方法’
return "名字是:{0},年龄是{1}".format(self.name,self.__age)
p = Person("高淇",18)
print(p)
运行结果:
名字是:高淇,年龄是18
Python支持多重继承,一个子类可以有多个"直接父类"。这样,就具备了 "多个父 类”的特点。但是由于,这样会被"类的整体层次"搞的异常复杂,尽量避免使用。
#多重继承
class A:
def aa(self): print("aa")
class B:
def bb(self): print("bb")
class C(B,A):
def cc(self): print("cc")
c = C()
c.cc()
c.bb()
c.aa()
运算结果:
cc bb aa
Python支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将 "从左向右”按顺序搜索。
MRO( Method Resolution Order):方法解析顺序。我们可以通过mro()方法获得
"类的层次结构”,方法解析顺序也是按照这个"类的层次结构”寻找的。
#多重继承
class A:
def aa(self):
print("aa")
def say(self):
print("sayAAA!")
class B:
def bb(self): print("bb")
def say(self):
print("say BBB!")
class C(B,A):
def cc(self):
print("cc")
c = C0
print(C.mroO) #打印类的层次结构
c.say() #解释器寻找方法是"从左到右”的方式寻找,此时会执行B
类中的say()
在子类中,如果想要获得父类的方法时,我们可以通过super()来做。
super()代表父类的定义,不是父类对象。
#super()
class A:
def say(self):
print("A: ",self) print("sayAAA")
class B(A):
def say(self):
#A.say(self) 调用父类的say方法
super().say() #通过super。调用父类的方法
print("say BBB")
b= B()
b.say()
运行结果:
A: <__main__.B object at 0x007A5690>
say AAA
say BBB
多态(polymorphism )是指同一个方法调用由于对象不同可能会产生不同的行为。在现实 生活中,我们有很多例子。比如:同样是调用人的休息方法,张三的休息是睡觉,李四的休 息是玩游戏,高淇老师是敲代码。同样是吃饭的方法,中国人用筷子吃饭,英国人用刀叉吃 饭,印度人用手吃饭。
关于多态要注意以下2点:
#多态
(gBnnoqs 布 (()6oa)Jnoqs-a5E-uaJ 。匝 昏鱼牌S昌坦Enoqs、愕卷与业# ()冬OLISG X-O5IU-U<a5)a)uu3su-S-*: 球osnnoqsM-ap x-05lu-u<& ssep (鬲w由 ' sr^l 球osnnoqsM-ap x-a5lu-u<6oa ssep (--眼—lsm 昇伊-nuE 球osnnoqsM-ap Mlu-uv ssep |
*s SB |
Python的运算符实际上是通过调用对象的特殊方法实现的。比如: a = 20 b = 30 c = a+b
d = a.__add__(b) print("c=",c) print("d = ",d)
运算结果:
c= 50
d= 50
常见的特殊方法统计如下:
方法 |
说明 |
例子 |
__init__ | 构造方法 | 对象创建:p = Person() |
__del__ | 析构方法 | 对象回收 |
__repr__,__str__ | 打印,转换 | print(a) |
__call__ | 函数调用 | a() |
_getattr_ | 点号运算 | a.xxx |
_setattr_ | 属性赋值 | a.xxx = value |
_getitem_ | 索引运算 | a[key] |
_setitem_ | 索引赋值 | a[key]=value |
_len_ | 长度 | len(a) |
每个运算符实际上都对应了相应的方法,统计如下:
运算符 |
特殊方法 |
说明 |
运算符+ | __add__ | 加法 |
运算符- | __sub__ | 减法 |
<,<=,== | lt,le , eq | 比较运算符 |
>,>=,! = | __gt__,__ge__,__ne__ | |
|,气& | _o r__,_xor__,_a nd_ | 或、异或、与 |
< <,>> | __lshift__,__rs h ift__ | 左移、右移 |
*,/,%,// | _mul_,_truediv_,_mod_,_ _floordiv_ | 乘、浮点除、模运算 (取余)、整数除 |
** | _pow_ | 指数运算 |
我们可以重写上面的特殊方法,即实现了 "运算符的重载"。
#测试运算符的重载
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 ”不是同类对象,不能相乘”
pl 二 Person("高淇”)
p2 二 Person("高希希”)
x = pl + p2 print (x)
print (p1*3)
运算结果:
高淇-高希希
高淇高淇高淇
Python对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊用法。这 里我们列出常见的特殊属性:
特殊方法 | 含义 |
obj.dict | 对象的属性字典 |
obj.class | 对象所属的类 |
class._bases_ | 类的基类元组(多继承) |
class._base_ | 类的基类 |
class._mro_ | 类层次结构 |
class._subclasses_() | 子类列表 |
#测试特殊属性
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))
print (c. _dict ) print (c. _class_) print (C. _bases ) print (C. mro ())
print (A. _subclasses_())
运行结果:
「__class__', '__delattr__', '__ge_J,
'_getattribute_‘,'_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'>]
•变量的赋值操作
只是形成两个变量,实际还是指向同一个对象。
•浅拷贝
Python拷贝一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝。因此,源对象 和拷贝对象会弓I用同一个子对象。
•深拷贝
使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象。源对象和拷贝对象 所有的子对象也不同。
#测试对象的弓 I用赋值、浅拷贝、深拷贝 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("屏幕对象:",self)
c = CPU()
s = Screen()
m = MobilePhone(c,s)
m.cpu.calculate()
n = m #两个变量,但是指向了同一个对象
print(m,n)
m2 = copy.copy(m) #m2是新拷贝的另一个手机对象
print(m,m2)
m.cpu.calculate()
m2.cpu.calculate() #m2和 m拥有了一样的 cpu对象和screen对象
m3 = copy.deepcopy(m)
m3.cpu.calculate() #m3 和 m 拥有不一样的 cp u 对象和 scre en
对象
运算结果:
计算,算个12345
CPU 对象:<__main__.CPU object at 0x00685690>
<_main_.MobilePhone object at 0x00685B50> <_main_.MobilePhone object at
0x00685B50>
<__main__.MobilePhone object at 0x00685B50> <__main__.MobilePhone object at 0x0069B490>
计算,算个12345
CPU 对象:<__main__.CPU object at 0x00685690>
计算,算个12345
CPU 对象:<__main__.CPU object at 0x00685690>
计算,算个12345
CPU 对象:<__main__.CPU object at 0x006A5DB0>
"is-a”关系,我们可以使用"继承"。从而实现子类拥有的父类的方法和属性。"is-a” 关系指的是类似这样的关系:狗是动物,dog is animal。狗类就应该继承动物类。
“has-a”关系,我们可以使用"组合”,也能实现一个类拥有另一个类的方法和属性。” has-a”关系指的是这样的关系:手机拥有CPU。MobilePhone has a CPU。
#组合测试
class MobilePhone:
def __init__(self,cpu,screen): self.cpu = cpu self.screen = screen
class CPU:
def calculate(self): print("计算,算个 12345") class Screen:
def show(self):
print("显示一个好看的画面,亮瞎你的钛合金大眼")
c = CPU()
s = Screen()
m = MobilePhone(c,s)
m.cpu.calculate() #通过组合,我们也能调用cpu对象的方法。相
当于手机对象间接拥有了 "cpu的方法"
m.screen.show()
运算结果:
计算,算个12345
显示一个好看的画面,亮瞎你的钛合金大眼
设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法,设计 模式有很多种,比较流行的是:GOF( Goup Of Four)23种设计模式。当然,我们没有 必要全部学习,学习几个常用的即可。
对于初学者,我们学习两个最常用的模式:工厂模式和单例模式。
工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进 行统一的管理和控制。
#工厂模式
class CarFactory:
def createCar(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()
cl = factory.createCar("奔驰") c2 = factory.createCar("宝马") print(cl)
print(c2)
运行结果:
<__main__.Benz object at 0x021C5770> <__main__.BMW object at 0x021C5790>
单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一 个访问该实例的全局访问点。
单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较 多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个"单例对象",然后永久 驻留内存中,从而极大的降低开销。
单例模式有多种实现的方式,我们这里推荐重写__new__()的方法。
#单例模式
class MySingleton:
_obj = None
_init_flag = True
def _new (cis, *args, **kwargs):
if cis._obj == None: cis._obj = object._new (cis)
return cis._obj
def _init (seif, name):
if MySingieton._init_fiag: print ("init....") seif. name = name
MySingieton._init_fiag = Faise
a 二 MySingieton("aa") print (a)
b 二 MySingieton("bb") print (b)
运算结果:
init....
<__main__.MySingleton object at 0x01E15610>
<_main_.MySingleton object at 0x01E15610>
设计模式称之为"模式",就是一些固定的套路。我们很容易用到其他场景上,比如前面讲 的工厂模式,我们需要将工厂类定义成“单例",只需要简单的套用即可实现:
#测试工厂模式和单例模式的整合使用
class 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 = CarFactoryO
c1 = factory.create_car("奔驰")
c2 = factory.create_car("比亚迪")
print(c1)
print(c2)
factory2 = CarFactory() print(factory) print(factory2)
运算结果:
init CarFactory...
<_main_.Benz object at 0x01E36E90>
<__main__.BYD object at 0x01E36C30>
<_main_.CarFactory object at 0x01E36730> <_main_.CarFactory object at 0x01E36730>
#测试对象的引用赋值、浅拷贝、深拷贝
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("屏幕对象:",self)
c = CPU()
s = Screen()
m = MobilePhone(c,s)
m.cpu.calculate()
n = m print(m,n) | #两个变量,但是指向了同一个对象 |
m2 = copy.copy(m) print(m,m2) | #m2是新拷贝的另一个手机对象 |
m.cpu.calculate() m2.cpu.calculate() | #m2和m拥有了一样的cpu对象和screen对象 |
m3 = copy.deepcopy(m) m3.cpu.calculate() #m3 和 m 拥有不一样的 cp u 对象和 scre en 对象 |
定义汽车的run()方法,里面需要调用Motor类的work。方法,也需要调用座椅 类Seat的work。方法,也需要调用底盘类Chassis的work。方法。
(1)电脑工厂类ComputerFactory用于生产电脑Computer。工厂类使用单例模式, 也就是说只能有一个工厂对象。
⑵工厂类中可以生产各种品牌的电脑:联想、华硕、神舟
(3)各种品牌的电脑使用继承实现:
⑷ 父类是Computer类,定义了 calculate方法
(5)各品牌电脑类需要重写父类的calculate方法
(1)属性有:id、name、salary
⑵ 运算符重载+:实现两个对象相加时,默认返回他们的薪水和
(3)构造方法要求:输入name^ salary,不输入ido id采用自增的方式,从1000开 始自增,第一个新增对象是1001,第二个新增对象是1002。
⑷ 根据salary属性,使用@property设置属性的get和set方法。set方法要求输 入:1000-50000范围的数字。