面向对象
Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程
举个最简单点的例子来区分 面向过程和面向对象
有一天你想吃鱼香肉丝了,怎么办呢?你有两个选择
1、自己买材料,肉,鱼香肉丝调料,蒜苔,胡萝卜等等然后切菜切肉,开炒,盛到盘子里。
2、去饭店,张开嘴:老板!来一份鱼香肉丝!
看出来区别了吗?1是面向过程,2是面向对象。
类
类也是一个对象,类就是用来创建实例对象的对象
类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
类的定义
# 使用class去定义一个类,可以在其中定义属性和方法,类中定义的属性和方法都是公共的,任何该类实例都可以访问
class ClassName:
.....
.....
属性引用
属性引用语法:obj.name
class Myclass:
data = [1,2,3,4,5,6]
a=Myclass()
print(a.data)
>>> [1, 2, 3, 4, 5, 6]
self参数
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self,self在定义时需要定义,但是在调用时会自动传入
class Test:
def prt(self):
print(self)
print(self.__class__)
t = Test()
t.prt()
以上实例执行结果为:
<__main__.Test instance at 0x185771878>
__main__.Test
从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类
类的实例化
实例化:创建一个类的实例,类的具体对象。
实例对象可以使用类中的属性和方法
class MyClass:
pass
# 类的实例化
x = MyClass()
特殊方法
在类中可以定义一些特殊方法也称为魔术方法
特殊方法都是形如双下划线开始,结尾的形式
特殊方法不需要我们调用,特殊方法会在特定时候自动调用
类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:
def __init__(self):
self.data = []
类定义了 __init__() 方法,类的实例化操作会自动调用 __init__() 方法。
如下实例化类 MyClass,对应的 __init__() 方法就会被调用:
x = MyClass()
当然, __init__() 方法可以有参数,参数在实例化的时候传递到函数中,例如:
class Complex:
def __init__(self, name, age):
self.r = name
self.i = age
x = Complex('诺诺', 16)
print(x.r, x.i) # 输出结果:诺诺 16
# 如果要把一个类的实例变成 str,就需要实现特殊方法__str__():它用来返回对象的字符串表达式
class Student(object):
def __init__(self,id,name,age):
self.id=id
self.name=name
self.age=age
def __str__(self):
return "学号:{}--姓名:{}--年龄{}".format(self.id,self.name,self.age)
s=Student(111,"Bob",18)
print(s)
>>> 学号:111–姓名:Bob–年龄18
面向对象的三大特性(封装、继承、多态)
封装
封装是面向对象编程的一大特点,将属性和方法放到类的内部,通过对象访问属性或者方法,隐藏功能的实现细节,也可以设置访问权限。
• 出现封装的原因:我们需要一种方式来增强数据的安全性
• 1. 属性不能随意修改
• 2. 属性不能改为任意的值
• 使用封装,确实增加了类的定义的复杂程度,但是它也确保了数据的安全
• 1. 隐藏属性名,使调用者无法随意的修改对象中的属性
• 2. 增加了getter()和setter()方法,很好控制属性是否是只读的
• 3. 使用setter()设置属性,可以再做一个数据的验证
• 4. 使用getter()方法获取属性,使用setter()方法设置属性可以在读取属性和修改属性的同时做一些其他的处理
• 可以为对象的属性使用双下划线开头 __xxx。双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问
• 其实隐藏属性只不过是Python自动为属性改了一个名字 --> _类名__属性名 例如 __name -> _Person__name
• 这种方式实际上依然可以在外部访问,所以这种方式我们一般不用。一般我们会将一些私有属性以_开头
• 一般情况下,使用_开头的属性都是私有属性,没有特殊情况下不要修改私有属性
3. property装饰器
• 我们可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改
继承
继承是一种创建新类的方式,如果子类需要复用父类的属性或者方法时,就可以使用继承。当然,子类也可以提供自己的属性和方法。
比如:
作用:避免重复造轮子,减少代码的冗余
新式类与经典类
在Python2当中类分为新式类和经典类,如果有继承父类object则是新式类,否则为经典类。
但是在Python3当中,全部都是新式类,默认继承object。
方法重写
• 如果在子类中有和父类同名的方法,则通过子类实例去调用方法时,会调用子类的方法而不是父类的方法,这个特点我们称之为方法的重写(覆盖)
• 当我们调用一个对象的方法时:
• 会优先去当前对象中寻找是否具有该方法,如果有则直接调用
• 如果没有,则去当前对象的父类中寻找,如果父类中有则直接调用父类中的方法
• 如果没有,则去父类中的父类寻找,以此类推,直到找到object,如果依然没有找到就报错了
super()
语法:super(type[, object-or-type])
• type-->类
• object-or-type -- 类,一般是 self
• super()可以获取当前类的父类
• 并且通过super()返回对象调用父类方法时,不需要传递self
注意
• __init__方法也会继承,同实例方法一致
• 私有属性以及私有方法没有被继承
多继承
所谓多继承,即子类有多个父类,并且具有它们的特征。
遵循左边优先原则
左边一条路走到黑
1.左边优先 2.根最后执行
多态
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚"鸭子类型"
动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
所谓多态:一个对象可以以不同形态去呈现
属性和方法
• 属性
• 类属性(静态字段),直接在类中定义的属性是类属性
• 类属性可以通过类或类的实例访问到。但是类属性只能通过类对象来修改,无法通过实例对象修改
• 实例属性 通过实例对象添加的属性属于实例属性
• 实例属性只能通过实例对象来访问和修改,类对象无法访问修改
• 方法
• 在类中定义,以self为第一个参数的方法都是实例方法(普通方法)
• 实例方法在调用时,Python会将调用对象以self传入
• 实例方法可以通过类实例和类去调用
• 当通过实例调用时,会自动将当前调用对象作为self传入
• 当通过类调用时,不会自动传递self,我们必须手动传递self
• 类方法 在类的内容以@classmethod 来修饰的方法属性类方法
• 类方法第一个参数是cls 也会自动被传递。cls就是当前的类对象
• 类方法和实例方法的区别,实例方法的第一个参数是self,类方法的第一个参数是cls
• 类方法可以通过类去调用,也可以通过实例调用
• 静态方法
• 在类中用@staticmethod来修饰的方法属于静态方法
• 静态方法不需要指定任何的默认参数,静态方法可以通过类和实例调用
• 静态方法,基本上是一个和当前类无关的方法,它只是一个保存到当前类中的函数
• 静态方法一般都是些工具方法,和当前类无关
魔法方法
魔法方法介绍
在Python中,有一些内置好的特定方法,这些方法在进行特定的操作时会自动被调用,称为魔法方法。
魔法方法的命名总是被双下划线包围,比如__名称__
__doc__
__doc__用来查看类的说明文档
__module__
__module__用来查看当前操作的类所在模块
当执行模块是类所在模块时,执行结果为__main__。否则,执行结果是类所在模块的名称
__class__
__class__用来查看当前对象的类
练习
• 创建Demo类
• 创建Demo对象为d
• 查看Demo类的__class__
• 查看Demo对象d的__class__
总结
类也是对象,Demo是type类的对象,d是Demo类的对象
__dict__
__dict__用于获取类或者实例的属性字典
注意
• 普通字段存储在对象中,所以通过对象.__dict__获取的是普通字段
• 除普通字段以外的成员都存储在类中,所以通过类.__dict__来获取
__del__()方法
__del__()方法也叫做析构方法。当由该类创建的实例对象,被删除或者说在内存中被释放,将会自动触发执行。
总结
• 当代码全部执行完毕才自动触发__del__()
• 如果需要提前触发,则需要通过del关键字,删除所有对象后触发__del__()
注意
此方法一般不需要定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配与释放,一般都是交给Python解释器来执行。所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行。
__call__()方法
__call__()方法用于将对象变成一个可调用的对象。也就是说,当一个类中有__call__()方法时,其实例化得到的对象便是可调用的(callable)
__new__()方法
__new__()方法用于创建与返回一个对象。在类准备将自身实例化时调用。
注意
• __new__()方法用于创建对象
• __init__()方法在对象创建的时候,自动调用
• 如果重写了父类的__new__()方法,覆盖了父类__new__()创建对象的功能,所以对象并没有创建成功。所以仅执行__new__()方法内部代码
对象创建执行顺序
• 1.通过__new__()方法创建对象
• 2.并将对象返回,传给__init__()
注意
• 在创建对象时,一定要将对象返回,才会自动触发__init__()方法
• __init__()方法当中的self,实际上就是__new__返回的实例,也就是该对象
__init__()与__new__()区别
• __init__实例方法,__new__类方法
• __init__在对象创建后自动调用,__new__创建对象的方法
单例模式
单例模式是一种常用的软件设计模式。也就是说该类只包含一个实例。
通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
单例模式实现
思路
• 当对象不存在时,创建对象
• 当对象存在时,永远返回当前已经创建对象
class Single(object):
__obj = None
def __new__(cls, *args, **kwargs):
if cls.__obj is None:
cls.__obj = super().__new__(cls)
return cls.__obj
else:
return cls.__obj
a = Single()
b = Single()
print(id(a))
print(id(b))