Python 面向对象
1. 面向对象
- 类和对象是面向对象编程的两个核心概念;
1.1 类
- 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用:
- 特征被称为属性;
- 行为被称为方法;
- 类相当于制造飞机时的图纸,是一个模板,是负责创建对象的;
- 类的名称需要满足大驼峰命名法;
1.2 对象
- 对象是由类创建出来的一个具体存在, 可以直接使用;
dir()
内置函数,可以查看对象内的所有属性及方法;__方法名__
格式的方法是Python
提供的内置方法/属性;
# 示例: 定义一个类
class 类名:
def 方法1(self, 参数列表):
# 哪一个对象调用的方法, self 就是哪一个对象的引用
pass
def 方法2(self, 参数列表):
pass
# 示例二: 创建对象
对象变量 = 类名()
print(对象变量): 输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
1.3 初始化方法(构造方法)
- 当使用
类名()
创建对象时, 会自动执行以下操作:- 为对象在内存中分配空间 -- 创建对象;
- 为对象的属性设置初始值 -- 初始化方法(
init
)
__init__
是对象的内置方法,专门用来定义一个类具有哪些属性的方法!- 在
__init__
方法内部使用self.属性名 = 属性的初始值
就可以定义属性; - 定义属性之后,再使用
Cat
类创建的对象,都会拥有该属性;
class Cat:
def __init__(self):
print("这是一个初始化方法")
self.name = "Tom"
def eat(self):
print("%s 爱吃鱼" % self.name)
tom = Cat()
tom.eat()
1.4 改造初始化方法 -- 初始化的同时设置初始值
- 在创建对象的同时,设置对象的属性:
- 把希望设置的属性值,定义成
__init__
方法的参数; - 在方法内部使用
self.属性 = 形参
接收外部传递的参数; - 在创建对象时,使用
类名(属性1, 属性2,...)
调用;
- 把希望设置的属性值,定义成
- 可以使用关键字
None
, 定义没有初始值的属性;
# 示例一:
class Cat:
def __init__(self, name):
print("初始化方法 %s" % name)
self.name = name
tom = Cat("大懒猫")
1.5 内置方法和属性
__del__
方法:在一个对象被从内存中销毁前,会自动调用__del__
方法;__str__
方法: 返回对象的描述信息,print
函数输出使用;
# 示例:
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 来也" % self.name)
def __del__(self):
print("%s 匆匆去也" % self.name)
# __str__ 方法必须返回一个字符串
def __str__(self):
return "我是大花猫: %s" % self.name
tom = Cat("Tom")
print(tom) # 输出: 我是大花猫: Tom
1.6 身份运算符
- 身份运算符用于比较两个对象的内存地址是否一致, 即是否是对同一个对象的引用;
is
: 判断两个标识符是不是引用同一个对象is not
- 针对
None
比较时,建议使用is
判断;
is
与==
区别:is
用于判断两个变量引用对象是否为同一个;==
用于判断引用变量的值是否相等;
# 示例:
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True
a is b # False id(a) 可以查看 a 的内存地址
1.7 私有属性和私有方法
- 对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到:
- 定义私有属性或方法,在属性名或方法名前增加两个下划线
- 伪私有属性(或方法):
- Python 只是在给属性,方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到;
- 处理方式: 在名称前面加上
_类名
,即私有属性名_类名__名称
; - 在日常开发中,不要使用这种方式,访问对象的私有属性或私有方法;
# 示例:
class Woman:
def __init__(self, name):
self.name = name
self.__age = 18
def setAge(self, newAge):
self.__age = newAge
def getAge(self):
return self.__age
def secret(self):
# 在对象的方法内部, 是可以访问对象的私有属性
print("%s 的年龄是 %d" % (self.name, self.__age))
xiaofang = Woman("小芳")
# 私有属性, 在外界不能够被直接访问
# print(xiaofang.__age)
xiaofang.secret()
2. 继承
- 方法重写(override): 在子类中定义一个和父类同名的方法并且实现;
- 对父类方法进行扩展:父类原本封装的方法实现是子类方法的一部分
- 在子类中重写父类的方法;
- 在需要的位置,使用
super().父类方法
或super(子类名, self).父类方法
来调用父类方法的执行; - 代码其他的位置,针对子类的需求,编写子类特有的代码实现;
- 关于
super
- 在Python中,
super
是一个特殊的类; super()
就是使用super
类创建出来的对象;- 最常使用的场景就是在重写父类方法时,调用父类中封装的方法实现;
- 在Python中,
- 在
Python 2.x
中, 如果需要调用父类的方法,可以使用以下方式:父类名.方法(self)
# 示例: 继承语法
class 类名(父类名):
pass
2.1 父类的私有属性和私有方法
- 子类对象不能在自己的方法内部直接访问父类的私有属性或私有方法;
- 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法;
2.2 多继承
- 子类可以拥有多个父类,并且具有所有父类的属性和方法;
- 注意:如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承;
- Python 中的 MRO(方法搜索顺序)
- MRO(method resolution order): 主要用于在多继承时,判断方法,属性的调用路径;
- Python 中针对类提供了一个内置属性
__mro__
可以查看方法搜索顺序;
# 示例: 语法
class 子类名(父类名1, 父类名2...):
pass
# 示例一: 多继承的执行顺序
class F0:
def a(self):
print('F0.a')
class F1(F0):
def b(self):
print('F1.a')
class F2:
def a(self):
print('F2.a')
class S(F1, F2):
pass
obj = S()
obj.a() # 输出: F0.a
# 示例二:
class BaseRequest:
def __init__(self):
print('BaseRequest.init') # 第三步
class RequestHandler(BaseRequest):
def __init__(self): # 第二步
print('RequestHandler.init')
BaseRequest.__init__(self)
def serve_forever(self): # 第五步
print('RequestHandler.serve_forever')
self.process_request() # 注意: 此处self表示obj, 输出: minx.process_request
def process_request(self):
print('RequestHandler.process_request')
class Minx: # 第六步
def process_request(self):
print('minx.process_request')
class Son(Minx, RequestHandler):
pass
obj = Son() # 第一步
obj.serve_forever() # 第四步
2.3 新式类和旧式(经典)类
- 新式类: 以
object
为基类的类,推荐使用;Python 3.x
中定义的类都是新式类; - 经典类: 不以
object
为基类的类,不推荐使用; 在Python 2.x
中定义类时,如果没有指定父类,则不会以object
作为基类; - 可以使用
dir
函数,查看object
类提供的内置属性和方法;
# 示例: 如果没有父类,建议统一继承自 object
class 类名(object):
pass
3. 类的结构
- 每一个对象都有自己独立的内存空间,保存各自不同的属性;
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部;
- Python 中,一切皆对象。其中,类也是一个特殊的对象:
class AAA:
定义的类属于类对象;demo = AAA()
属于实例对象;- 类是一个特殊的对象(类对象)。类对象在内存中只有一份,使用一个类可以创建出很多个对象实例;
- 类对象拥有自己的属性和方法, 即类属性和类方法;
- 通过
类名.
的方式,可以访问类的属性或者调用类的方法;
- 类属性
- 类属性就是给类对象中定义的属性;
- 通常用来记录与这个类相关的特征;
- 类属性不会用于记录具体对象的特征;
- 类方法
- 在类方法内部,可以直接访问类属性或者调用其他的类方法;
- 类方法需要用修饰器(@classmethod)来标识,告诉解释器这是一个类方法;
- 类方法的第一个参数应该是
cls
- 由哪一个类调用的方法,方法内的
cls
就是哪一个类的引用; - 这个参数和实例方法的第一个参数是
self
类似; - 使用其他名称也可以,不过习惯使用
cls
;
- 由哪一个类调用的方法,方法内的
- 通过
类名.
调用类方法,调用方法时,不需要传递cls
参数; - 在方法内部,可以通过
cls.
访问类的属性或者调用其他的类方法;
- 静态方法
- 如果需要在类中封装一个方法,这个方法:
- 既不需要访问实例属性或者调用实例方法;
- 也不需要访问类属性或者调用类方法;
- 可以把这个方法封装成一个静态方法
- 静态方法需要用修饰器
@staticmethod
来标识, 告诉解释器这是一个静态方法; - 通过
类名.
调用静态方法;
- 如果需要在类中封装一个方法,这个方法:
- 总结:
- 实例方法:方法内部需要访问实例属性
- 实例方法内部可以使用
类名.
访问类属性;
- 实例方法内部可以使用
- 类方法:方法内部只需要访问类属性;
- 静态方法:方法内部不需要访问实例属性和类属性;
- 实例方法:方法内部需要访问实例属性
# 示例一: 定义类属性
class Tool(object):
# 使用赋值语句, 定义类属性,记录创建工具对象的总数
count = 0
def __init__(self, name):
self.name = name
# 针对类属性做一个计数+1
Tool.count += 1
@classmethod
def show_tool_count(cls):
"""显示工具对象的总数"""
print("工具对象的总数 %d" % cls.count)
# 创建工具对象
tool1 = Tool("铁锹")
tool2 = Tool("扳手")
tool2 = Tool("斧头")
# 输出工具对象的总数
print(Tool.count)
# 示例二: 定义类方法
@classmethod
def 类方法名(cls): # cls 是 class 的简写
pass
# 示例三: 定义静态方法
@staticmethod
def 静态方法名():
pass
# 示例四: 静态属性
class Province:
# 静态属性,属于类(可以通过对象或类来访问)
country = '中国'
def __init__(self, name):
# 普通字段, 属于对象(只能通过对象访问)
self.name = name
3.1 Python 中动态添加方法
- 动态语言: 可以在运行过程中,修改代码;
- 静态语言: 编译时,已经确定好代码,运行过程中不能修改;
# 示例:
class Person(object):
def __init__(self, newName, newAge):
self.name = newName
self.age = newAge
def eat(self):
print("=== %s 正在吃饭 ===" % self.name)
def sports(self):
print("=== %s 正在锻炼 ===" % self.name)
# 静态方法
@staticmethod
def test():
print("=== static method ===")
# 类方法
@classmethod
def talk(cls):
print("=== class method ===")
f = Person("zhangsan", 14)
f.eat()
# Python 是动态语言,可以动态的给对象添加方法
# 需要导入 types 模块
import types
# 动态添加实例方法
f.sports = types.MethodType(sports, f)
f.sports()
# 动态添加静态方法(针对类)
Person.test = test
Person.test()
# 动态添加类方法(针对类)
Person.talk = talk
Person.talk()
3.2 __slots__
- Python 允许在定义类的时候,定义一个特殊的变量
__slots__
,来限制该类实例能添加的属性; __slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的;
class Person(object):
__slots__ = ("name", "age")
P = Person()
P.name = "张三"
P.age = 24
P.score = 87 # 程序会报错
3.3 属性 property
# 示例一: 私有属性添加 getter 和 setter 方法
class Money(object):
def __init__(self):
self.__money = 0
def getMoney(self):
return self.__money
def setMoney(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
# 示例二: 使用 property,升级 getter 和 setter 方法
class Money(object):
def __init__(self):
self.__money = 0
def getMoney(self):
return self.__money
def setMoney(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
money = property(getMoney, setMoney)
m = Money()
m.money = 2000 # 相当于调用了 m.setMoney(2000)
print(m.money) # 相当于调用了 m.getMoney()
# 备注: property 的作用,相当于把方法进行了封装,方便对属性设置数据;
# 示例三: property 的第二种使用方式
class Money(object):
def __init__(self):
self.__money = 0
@property
def money(self):
return self.__money
@money.setter
def money(self, value):
if isinstance(value, int):
self.__money = value
else:
print("error:不是整型数字")
4. 元类
- 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段;
- 在Python中的类远不止如此,类还是一种对象;
- 使用
type
动态的创建类,- 格式:
type(类名, 有父类名称组成的元组(针对继承的情况,可以为空), 包括属性的字典(名称和值))
- 格式:
type
就是Python在背后用来创建所有类的元类;
# 示例:
# 传统方式定义类
class Test:
pass
t1 = Test()
# type 定义类
Test2 = type("Test2", (), {})
t2 = Test2()
type(t1) # 输出: __main__.Test
type(t2) # 输出: __main__.Test2
# 带有属性的类
class Person:
age = 15
Person2 = type("Person2", (), {"age":15})
# 带有方法的类
# 定义方法
def printAge(self):
print("=== 年龄是: %d ===" % self.num)
Person3 = type("Person3", (), {"printAge": printAge})
p = Person3()
p.age = 22
p.printAge()
# 存在继承关系的类
class Animal:
def eat(self):
print("=== eat ===")
Cat = type("Cat", (Animal,), {})
tom = Cat()
tom.eat()
4.1 __metaclass__
属性
# 示例:
class Foo(Bar):
pass
# Python 创建类的过程中,做了如下操作
# 1, Foo 中有 __metaclass__ 这个属性吗?如果有,Python 会通过 __metaclass__ 创建一个名字为Foo的类对象;
# 2, 如果Python没有找到__metaclass__, 它会继续在Bar(父类)中寻找 __metaclass__ 属性,并尝试做和前面同样的操作;
# 3, 如果Python在任何父类中到找不到__metaclass__, 它就会在模块层次中去寻找 __metaclass__, 并尝试做同样的操作;
# 4. 如果还是找不到 __metaclass__, Python 就会用内置的type来创建这个类对象;
# 示例二:
class MyType(type):
def __init__(self, *args, **kwargs):
print('123')
# 在此处,使用 MyType 创建Foo类
# 创建类的过程中,会调用 MyType 的 __init__ 方法, 因此,会输出: 123
class Foo(object, metaclass=MyType):
def func(self):
print('hello world')
# 示例三:
class MyType(type):
def __init__(self, *args, **kwargs):
# 此处, self = Foo
print('123')
def __call__(self, *args, **kwargs):
# 此处, self = Foo
r = self.__new__(self, *args, **kwargs)
# 执行Foo类的 __init__ 方法
self.__init__(obj)
print(455)
class Foo(object, metaclass=MyType):
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
return '对象'
def func(self):
print('hello world')
# 第一阶段: 解释器从上到下执行代码创建 Foo 类
# 定义类 Foo时, 会调用 MyType 的 __init__ 方法
obj = Foo()
# 第二阶段: 通过Foo类创建 obj 对象:
# Foo() 会调用 MyType 的 __call__ 方法;
# __call__ 方法会调用 Foo类的 __new__ 方法;
# __new__ 方法会返回创建的对象 obj
# 然后执行Foo 类的 __init__ 方法;
5. 反射
# 示例一:通过字符串的形式操作对象中的成员
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
return "%s-%s" % (self.name, self.age)
# 创建对象
obj = Foo('Tom', 19)
# 获取obj对象中name对应的值
# 方式一:
b = "name"
obj.__dict__["name"]
# 方式二: getattr()
s = getattr(obj, "name")
print(s)
# 获取obj对象的show方法
func = getattr(obj, "show")
print(func)
r = func()
print(r) # 输出: Tom-19
# 判断对象中是否存在name属性
print(hasattr(obj, "name"))
# 设置值
setattr(obj, "k", "v")
print(obj.k)
# 删除
delattr(obj, "name")
obj.name
6. 单例设计模式
- 目的:让类创建的对象,在系统中只有唯一的一个实例;
- 每一次执行
类名()
返回的对象,内存地址是相同的;
6.1 __new__
方法
- 使用
类名()
创建对象时, Python 解释器首先会调用__new__
方法为对象分配空间; __new__
是一个由object
基类提供的内置静态方法,注意作用有两个:- 在内存中为对象分配空间;
- 返回对象的引用
Python
的解释器获得对象的引用后,将引用作为第一个参数,传递给给__init__
方法- 重写
__new__
方法:- 一定要
return super().__new__(cls)
- 否则, Python 的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法;
__new__
是一个静态方法,在调用时,需要主动传递cls
参数;
- 一定要
# 示例: 单例设计模式
class MusicPlayer(object):
# 记录第一个被创建对象的引用
instance = None
# 记录是否执行过初始化动作
init_flag = False
def __new__(cls, *args, **kwargs):
# 1. 判断类属性是否是空对象
if cls.instance is None:
# 2. 调用父类的方法,为第一个对象分配空间
cls.instance = super().__new__(cls)
# 3. 返回类属性保存的对象引用
return cls.instance
def __init__(self):
# 1. 判断是否执行过初始化动作
if MusicPlayer.init_flag:
return
# 2. 如果没有执行过, 则执行初始化动作
print("初始化播放器")
# 3. 修改类属性的标记
MusicPlayer.init_flag = True
# 创建多个对象
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
7. 异常
7.1 捕获异常
# 示例一: 异常捕获完整语法
try:
# 尝试执行的代码
pass
except 错误类型1:
pass
except (错误类型2, 错误类型3):
pass
except Exception as result:
print("未知错误 %s" % result)
else:
# 没有异常才会执行的代码
pass
finally:
# 无论是否有异常,都会执行的代码
print("无论是否有异常,都会执行的代码")
# 备注: 当Python解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型
7.2 异常的传递
- 当函数(或方法)执行出现异常,会将异常传递给函数(或方法)的调用一方
- 如果传递到主程序,仍然没有异常处理,程序才会被终止
7.3 主动抛出 raise
异常
# 示例:
def input_password():
# 1. 提示用户输入密码
pwd = input("请输入密码: ")
# 2. 判断密码长度 >= 8, 返回用户输入的密码
if len(pwd) >= 8:
return pwd
# 3. 如果 < 8 主动抛出异常
print("主动抛出异常")
# 创建异常对象
ex = Exception("密码长度不够")
# 主动抛出异常
raise ex
# 提示用户输入密码
try:
print(input_password())
except Exception as result:
print(result)
7.4 自定义异常
# 示例:
class MyError(Exception):
def __init__(self, msg):
self.message = msg
def __str__(self):
return self.message
# obj = MyError('程序输入异常')
# print(obj)
# 使用自定义异常
try:
raise MyError('输入错误')
except MyError as e:
print(e) # 此处,会执行 e 对象的 __str__ 方法,获取返回值
7.5 断言
# 示例:
a = input('请输入一个数字:')
assert int(a) > 8 # assert 条件, 当条件成立,输出下面的内容;不成立,就抛出异常:AssertionError;
print('123')