面向对象编程
对象
对象是指现实中的物体或实体
面向对象
把一切看成对象(实例),让对象和对象之建立关联关系
对象的特征
-
属性
例如:人的姓名, 年龄, 性别等
-
行为
例如:人学习,吃饭,睡觉,踢球, 工作等
类
- 拥有相同属性和行为的对象分为一组,即为一个类
- 类是用来描述对象的工具,用类可以创建同类对象
类的创建
- 创建一个类
- 用于描述此类对象的行为和属性
- 类用于创建此类的一个或多个对象(实例)
class 类名(继承列表):
'''类的文档字符串'''
实例方法定义(类内的函数称为方法method)
类变量定义
类方法定义
静态方法定义
构造函数
创建这个类的实例对象,并返回此实例对象的引用关系
类名([创建传参列表])
实例(对象)说明
- 实例有自己的作用域和名字空间,可以为该实例添加实例变量(属性)
- 实例可以调用类方法和实例方法
- 实例可以访问类变量和实例变量
实例方法
- 用于描述一个对象的行为,让此类型的全部对象都拥有相同的行为
- 实例方法实质是函数,是定义在类内的函数
- 实例方法至少有一个形参,第一个形参代表调用这个方法的实例,一般命名为
self
# 实例方法的定义
class 类名(继承列表):
def 实例方法名(self, 参数1, 参数2, ...):
'''实例方法的文档字符串'''
语句块
# 实例方法的调用
实例.实例方法名(调用传参)
或
类名.实例方法名(实例, 调用传参)
实例方法示例
class Dog:
def play(self,ball):
print('小狗正在玩',ball)
def eat(self,food):
print('小狗正在吃',food)
dog1 = Dog()
dog1.play('baseball') # 小狗正在玩 baseball
dog1.eat('sandwich') # 小狗正在吃 sandwich
dog2 = Dog()
dog2.play('soccer') # 小狗正在玩 soccer
dog2.eat('noddles') # 小狗正在吃 noddles
dog3 = Dog()
Dog.play(dog3,'basketball') # 小狗正在玩 basketball
Dog.eat(dog3,'salad') # 小狗正在吃 salad
实例属性
实例.属性名
- 每个实例都可以有自己的变量,此变量称为实例变量(也叫属性)
- 用来记录对象自身的数据
- 实例方法和实例变量(属性)可以结合在一起用
- 首次为属性赋值则创建此属性
- 再次为属性赋值则改变属性的绑定关系
# 此示例示意为对象添加属性
class Dog:
pass
dog1 = Dog()
dog1.kinds = '京巴' # 添加属性kinds
dog1.color = '白色' # 添加属性color
dog1.color = '黃色' # 改变属性的绑定关系
print(dog1.color, '的', dog1.kinds) # 访问属性
dog2 = Dog()
dog2.kinds = '牧羊犬'
dog2.color = '灰色'
print(dog2.color, '的', dog2.kinds) # 访问属性
删除属性
del 对象.实例变量名
用del
语句可以删除一个对象的实例变量
class Cat:
pass
c1 = Cat() # 创建变量
c1.color = "白色" # 添加属性
print(c1.color) # 白色
del c1.color # 删除属性
print(c1.color) # 属性错误
初始化方法
对新创建的对象添加实例变量(属性)或相应的资源
class 类名(继承列表):
def __init__(self [,形参列表]):
语句块
- 初始化方法名必须为
__init__
不可改变 - 初始化方法会在构造函数创建实例后自动调用,且将实例自身通过第一个参数
self
传入__init__
方法 - 构造函数的实参将通过
__init__
方法的形参列表传入__init__
方法中 - 初始化方法内部如果需要返回则只能返回
None
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
def show_info(self):
print(self.name,'今年',self.age,'岁')
s1 = Student('小张',20)
s2 = Student('小李',22)
s3 = Student('wx1',23)
s3.__init__('wx',21) # 显式调用
s1.show_info() # 小张 今年 20 岁
s2.show_info() # 小李 今年 22 岁
s3.show_info() # wx 今年 21 岁
析构方法
class 类名(继承列表):
def __del__(self):
语句块
- 析构方法在对象销毁时被自动调用
- 清理此对象所占用的资源
- Python不建议在析构方法内做任何事情,因为对象销毁的时间难以确定
预置实例属性
-
__dict__
此属性绑定一个存储此实例自身实例变量(属性)的字典 -
__class__
此属性用来绑定创建此实例的类
用于类的函数
-
isinstance(obj, class_or_tuple)
返回这个对象obj
是否某个类class
或某些类的实例,如果是则返回True
, 否则返回False
-
type(obj)
返回对象的类型
class Dog:
def __init__(self,kind):
self.kind = kind
class Cat:
pass
dog1 = Dog('京巴')
print(dog1.__dict__) # {'kind': '京巴'}
dog1.age = 2
print(dog1.__dict__) # {'kind': '京巴', 'age': 2}
print(dog1.__class__) # <class '__main__.Dog'>
print(isinstance(dog1, Dog)) # True
print(isinstance(dog1, Cat)) # False
print(isinstance(dog1, (Cat,int,list))) # False
print(isinstance(dog1, (Cat, int, Dog))) # True
print(type(dog1)) # <class '__main__.Dog'>
-
issubclass(cls, class_or_tuple)
判断一个类是否继承自其它的类,如果此类cls
是class
或tuple
中的一个派生子类则返回True
,否则返回False
class A:
pass
class B(A):
pass
class C(B):
pass
issubclass(C, (A, B)) # True
issubclass(C, (int, str)) # False
类变量
- 类变量是类的属性,此属性属于类
- 用来记录类相关的数据
- 类变量可以通过类直接访问
- 类变量可以通过类的实例直接访问
- 类变量可以通过此类的实例的
__class__
属性间接访问 - 实例变量可以通过
__class__
属性间接修改类变量的值 - 可以用类变量来记录对象的个数
class Human:
count = 100
h1 = Human()
print('通过类直接访问:',Human.count)
# 通过类直接访问: 100
print('通过类的实例直接访问:',h1.count)
# 通过类的实例直接访问: 100
print('通过此类的实例的 __class__ 属性间接访问:',h1.__class__.count)
# 通过此类的实例的 __class__ 属性间接访问: 100
h1.count = 200 # 给h1对象添加属性
print('通过类直接访问:',Human.count)
# 通过类直接访问: 100
print('通过类的实例直接访问:',h1.count)
# 通过类的实例直接访问: 200
h1.__class__.count += 1
print('通过类直接访问:',Human.count)
# 通过类直接访问: 101
h1.count += 1
print('通过类直接访问:',Human.count)
# 通过类直接访问: 100
print('通过类的实例直接访问:',h1.count)
# 通过类的实例直接访问: 201
类的文档字符串
- 类内第一个没有赋值给任何变量的字符串是类的文档字符串
- 类的文档字符串用类的
__doc__
属性可以访问 - 类的文档字符串可以用
help()
函数查看
类的 __slots__
列表
- 限定一个类的实例只能有固定的属性(实例变量)
- 通常为防止错写属性名而发生运行时错误
- 含有
__slots__
列表的类创建的实例对象没有__dict__
属性,即此实例不用字典来保存对象的属性(实例变量)
类方法
- 类方法是描述类的行为的方法,类方法属于类
- 类方法需要用
@classmethod
装饰器定义 - 类方法至少有一个形参,第一个形参用于绑定类,约定写为
cls
- 类和该类的实例都可以调用类方法
- 类方法不能访问此类创建的实例的属性(只能访问类变量)
class Car:
count = 0 # 类变量
@classmethod # 类方法
def getTotalCount(cls):
return cls.count
@classmethod
def updateCount(cls,value):
cls.count += value
print(Car.getTotalCount()) # 调用类方法
Car.updateCount(1)
print(Car.getTotalCount()) # 调用类方法
c1 = Car()
c1.updateCount(100) # 类的实例都调用类方法
print(c1.getTotalCount())
print(Car.getTotalCount()) # 调用类方法
静态方法
- 静态方法不属于类,也不属于类的实例,它相当于定义在类内普通函数,只是它的作用域属于类
- 静态方法需要用
@staticmethod
来定义 - 静态方法可以通过类和类的实例来调用
class A:
@staticmethod
def myadd(x,y):
return x + y
print(A.myadd(1,2)) # 3
a = A()
print(a.myadd(3,4)) # 7
面向对象高级特性
继承与派生
- 继承是指从已有的类中衍生出新类,新类具有原类的行为,并能扩展新的行为
- 派生就是从一个已有类中衍生(创建)新类,在新类上可以添加新的属性和行为
继承和派生的目的
- 继承是延续旧类的功能
- 派生是为了在旧类的基础上添加新的功能
继承和派生的作用
- 用继承派生机制,可以将一些共有功能加在基类中,实现代码的共享
- 在不改变基类的基础上改变原有功能
继承/派生的名词
- 基类(base class) / 超类(super class) / 父类(father class)
- 派生类(derived class) / 子类(child class)
单继承
单继承是指派生类由一个基类衍生出来的类
class 类名(基类名):
语句块
- 任何类都直接或间接的继承自
object
类 -
object
类是一切类的超类(祖类) -
__base__
属性用来记录此类的基类
class Human: # 相当于 class Human(object):
def say(self,that):
print('说',that)
def walk(self,km):
print('走了',km,'km')
class Student(Human):
def study(self,subject):
print('学习',subject)
h1 = Human()
h1.say('今天天气真热')
h1.walk(5)
s1 = Student()
s1.say('学习很累') # 调用父类的方法
s1.walk(3) # 调用父类的方法
s1.study('数学')
print(Student.__bases__) # (<class '__main__.Human'>,)
print(Human.__bases__) # (<class 'object'>,)
覆盖
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类实例调用该方法时,实例调用的是子类中的覆盖版本的方法,这种现象叫做覆盖
- 子类对象显式调用基类方法:
基类名.方法名(实例, 实际调用传参)
class Human:
def eat(self):
print('Human类中的eat()方法')
class Student(Human):
def eat(self):
print('Student类中的eat()方法')
h1 = Human()
h1.eat() # Human类中的eat()方法
s1 = Student()
s1.eat() # Student类中的eat()方法
# 子类对象显式调用基类方法
Human.eat(s1) # Human类中的eat()方法
# 使用 super() 方法调用父类方法
super(Student,s1).eat()
super
函数
-
super(type, obj)
返回绑定超类的实例 -
super()
返回绑定超类的实例,必须在方法内调用 - 显式调用基类的初始化方法
当子类中实现了__init__
方法时,基类的__init__
方法并不会被自动调用,此时需要显式调用
class Human:
def __init__(self, n, a):
'''此方法为人的对象添加,姓名和年龄属性'''
self.name = n
self.age = a
def infos(self):
print("姓名:", self.name)
print("年龄:", self.age)
class Student(Human):
def __init__(self, n, a, s=0):
super().__init__(n, a) # 显式调用基类的初始化方法
self.score = s
def infos(self):
super().infos()
print("成绩:", self.score)
s1 = Student('小张', 18, 100)
s1.infos()
h1 = Human('小赵', 20)
h1.infos()
封装
- 封装是指隐藏类的实现细节,让使用者不用关心这些细节
- 封装的目的是让使用者尽可能少的实例变量(属性)进行操作
私有属性
类中以双下划线 __
开头,不以双下划线结尾的标识符为私有成员,在类的外部无法直接访问
class A:
def __init__(self):
self.__p1 = 100
def test(self):
print(self.__p1)
self.__m1()
def __m1(self):
print('m1')
a = A()
# print(a.__p1) # 访问失败
a.test()
# a.__m1() # 访问失败
多态
- 多态是指在继承/派生关系的类中,调用基类对象的方法,实际能调用子类的覆盖版本方法的现象叫多态
- 多态调用的方法与对象相关,不与类型相关
- Python的全部对象都只有"运行时状态(动态)", 没有"C++/Java"里的"编译时状态(静态)"
class Shape:
def draw(self):
print('Shape.draw被调用')
class Point(Shape):
def draw(self):
print('正在画一个点')
class Circle(Point):
def draw(self):
print('正在画一个圆')
def my_draw(s):
s.draw()
s1 = Circle()
s2 = Point()
my_draw(s1) # 正在画一个圆
my_draw(s2) # 正在画一个点
多继承
class 类名(基类名1, 基类名2, ....):
语句块
- 多继承是指一个子类继承自两个或两个以上的基类
- 一个子类同时继承自多个父类,父类中的方法可以同时被继承下来
class Car:
def run(self, speed):
print("汽车以", speed, '公里/小时的速度行驶')
class Plane:
def fly(self, height):
print("飞机以海拔", height, '的高度飞行')
class PlaneCar(Car, Plane):
'''PlaneCar同时继承自汽车类和飞机类'''
p1 = PlaneCar()
p1.fly(10000) # 飞机以海拔 10000 的高度飞行
p1.run(300) # 汽车以 300 公里/小时的速度行驶
- 如果两个父类中有同名的方法,而在子类中又没有覆盖此方法时,调用结果难以确定,所以要谨慎使用多继承,一般不推荐使用多继承
class A:
def m(self):
print("A.m()被调用")
class B:
def m(self):
print('B.m()被调用')
class AB(A, B):
pass
ab = AB()
ab.m() # 不会报错,但结果难以确定
继承的MRO(Method Resolution Order)问题
- 类内的
__mro__
属性用来记录继承方法的查找顺序 -
super()
查找的规则是按照__mro__
的顺序查找
class A:
def m(self):
print("A.m")
class B(A):
def m(self):
print("B.m")
class C(A):
def m(self):
print("C.m")
class D(B, C):
'''d类继承自B,C'''
def m(self):
print("D.m")
d = D()
d.m() # D.m
for x in D.__mro__:
print(x)
# <class '__main__.D'>
# <class '__main__.B'>
# <class '__main__.C'>
# <class '__main__.A'>
# <class 'object'>
函数重写
重写是在自定义的类内添加相应的方法,让自定义的类生成的对象(实例)像内建对象一样进行内建的函数操作
对象转字符串函数重写
-
repr(obj)
返回一个能代表此对象的表达式字符串,通常:eval(repr(obj)) == obj
-
str(obj)
通过给定的对象返回一个字符串(这个字符串通常是给人看的)
repr() 函数的重写方法:
def __repr__(self):
return 能够表达self内容的字符串
str() 函数的重写方法:
def __str__(self):
return 人能看懂的字符串
-
str(obj)
函数优先调用obj.__str__()
方法返回字符串 - 如果
obj
没有__str__()
方法,则调用obj.__repr__()
方法返回的字符串 - 如果
obj
没有__repr__()
方法,则调用object
类的__repr__()
实例方法显示<xxxx>
格式的字答鼓足
# 此示例示意一个自定义的数字类型重写 repr和 str的方法
class MyNumber:
def __init__(self, value):
self.data = value
def __str__(self):
print("__str__被调用")
return "数字: %d" % self.data
def __repr__(self):
print("__repr__被调用")
return 'MyNumber(%d)' % self.data
n1 = MyNumber(100)
# print(str(n1)) # 调用 n1.__str__(self)
print(repr(n1))
print(n1)
数值转换函数的重写
-
def __complex__(self)
函数调用complex(obj)
-
def __int__(self)
函数调用int(obj)
-
def __float__(self)
函数调用float(obj)
-
def __bool__(self)
函数调用bool(obj)
'''此示例示意自定义的类MyNumber能够转成为数值类型'''
class MyNumber:
def __init__(self, v):
self.data = v
def __repr__(self):
return "MyNumber(%d)" % self.data
def __int__(self):
'''此方法用于int(obj) 函数重载,必须返回整数
此方法通常用于制订自义定对象如何转为整数的规则
'''
return 100
n1 = MyNumber(200)
print(type(n1))
n = int(n1)
print(type(n))
print(n)
内建函数的重写
-
__abs__
调用abs(obj)
-
__len__
调用len(obj)
-
__reversed__
调用reversed(obj)
-
__round__
调用round(obj)
# 自定义一个MyList类,与系统内建的类一样,
# 用来保存有先后顺序关系的数据
class MyList:
'''自定义列表类'''
def __init__(self, iterator=[]):
self.data = [x for x in iterator]
def __repr__(self):
return "MyList(%r)" % self.data
def __abs__(self):
# return MyList([abs(x) for x in self.data])
# 上一句语句可以用如下生成表达式代替已防止过多占内存
return MyList((abs(x) for x in self.data))
def __len__(self):
# return self.data.__len__()
return len(self.data)
myl = MyList([1, -2, 3, -4])
print(myl)
print(abs(myl)) # MyList([1, +2, 3, +4])
print("原来的列表是:", myl)
myl2 = MyList(range(10))
print(myl2)
print('myl2的长度是:', len(myl2))
print('myl的长度是: ', len(myl))
布尔函数的重写
def __bool__(self):
# 代码
- 用于
bool(obj)
函数取值 - 用于
if
语句真值表达式中 - 用于
while
语句真值表达式中 - 优先调用
__bool__
方法取值 - 如果不存在
__bool__
方激动 ,则用__len__()
方法取值后判断是否为零值,如果不为零返回True
,否则返回False
- 如果再没有
__len__
方法,则直接返回True
class MyList:
'''自定义列表类'''
def __init__(self, iterator=[]):
self.data = [x for x in iterator]
def __repr__(self):
return "MyList(%r)" % self.data
def __bool__(self):
print("__bool__方法被调用!")
return False
# def __len__(self):
# print("__len__被调用")
# return len(self.data)
myl = MyList([1, -2, 3, -4])
print(bool(myl)) # True
if myl:
print("myl 是真值")
else:
print("myl 是假值")
迭代器
可以通过 next()
函数取值的对象就是迭代器
迭代器协议
- 迭代器协议是指对象能够使用
next
函数获取下一项数据,在没有下一项数据时触发一个StopIterator
来终止迭代的约定 - 类内需要有
__next__(self)
方法来实现迭代器协议
class MyIterator:
def __next__(self):
迭代器协议的实现
return 数据
可迭代对象
- 指能用
iter(obj)
函数返回迭代器的对象(实例) - 可迭代对象内部一定要定义
__iter__(self)
方法来返回迭代器
class MyIterable:
def __iter__(self):
语句块
return 迭代器
可迭代对象和迭代器的定义及使用方式
# 此示例示意可迭代对象和迭代器的定义及使用方式
class MyList:
def __init__(self, iterator):
'''自定义列表类的初始化方法,此方法创建一个data实例
变量来绑定一个用来存储数据的列表'''
self.data = list(iterator)
def __repr__(self):
'''此方法了为打印此列表的数据'''
return 'MyList(%r)' % self.data
def __iter__(self):
'''有此方法就是可迭代对象,但要求必须返回迭代器'''
print("__iter__方法被调用!")
return MyListIterator(self.data)
class MyListIterator:
'''此类用来创建一个迭代器对象,用此迭代器对象可以迭代访问
MyList类型的数据'''
def __init__(self, iter_data):
self.cur = 0 # 设置迭代器的初始值为0代表列表下标
# it_data 绑定要迭代的列表
self.it_data = iter_data
def __next__(self):
'''有此方法的对象才叫迭代器,
此方法一定要实现迭代器协议'''
print("__next__方法被调用!")
# 如果self.cur已经超出了列表的索引范围就报迭代结束
if self.cur >= len(self.it_data):
raise StopIteration
# 否则尚未迭代完成,需要返回数据
r = self.it_data[self.cur] # 拿到要送回去的数
self.cur += 1 # 将当前值向后移动一个单位
return r
myl = MyList([2, 3, 5, 7])
print(myl)
for x in myl:
print(x) # 此处可以这样做吗?
# it = iter(myl)
# x = next(it)
# print(x)
# x = next(it)
# print(x)
# x = next(it)
# print(x)