语言的分类
- 面向机器
抽象成机器指令,机器容易理解
代表:汇编语言
- 面向过程
做一件事情,排出了步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理
问题规模小,可以步骤化,按部就班处理
代表:c语言
- 面向对象OPP
随着计算机需要解决的问题的规模扩大,情况越来越复杂。需要很多人、很多部门协作,面向过程编程不太适合了。Python是支持面向对象编程范式的高级语言。
代表:C++、Java、Python等
面向对象
什么事面向对象呢?
一种认识世界、分析世界的方法论。将万事万物抽象为各种对象。
类class
类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
用计算机语言来描述类,是属性和方法的集合
对象instance、object
对象是类的具象,是一个实体。
对于我们每个人这个个体,都是抽象概念人类的不同的实体
举例:
你吃鱼
你,就是对象;鱼,也是对象;吃是动作
你是具体的人,是具体的对象。你属于人类,人类是个抽象的概念,是无数具体的人的个体的抽象。鱼,也是具体的对象,就是你吃的这一条具体的鱼。这条鱼属于鱼类,鱼类是无数的鱼抽象出来的概的
吃,是动作, 也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法。如果反过来,鱼吃人。吃就是鱼类的动作了。
吃,这个动作,很多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但是吃法不同而已。
你驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的 ,是人类具有的方法。
属性:对对象状态的抽象,用数据结构来描述。
操作:对对象行为的抽象,用操作名和实现该操作的方法的描述
每个人都是人类的一个单独的实例,都有自己的名字、身高、体重等信息,这些信息是个人的属性,但是,这些信息不能保存在人类中,因为人类是抽象的概念,不能保留每个具体的个体的值。
而人类的实例,是具体的人,他可以存储这些具体的属性,而且可以不同人有不同的属性。
哲学
- 一切皆对象
- 对象是数据和操作的封装
- 对象是独立的,但是对象之间可以相互作用
- 目前OPP是最接近人类认知的编程范式
面试题: 什么是面向对象?
面向对象3要素
- 封装
- 组装:将数据和操作组装在一起
- 隐性数据:对外只暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的结构细节,只需要知道使用什么不见怎么驾驶就行,踩了油门就能跑,可以不了解其中的机动原理。
- 目的: 简化编程, 提高安全性和复用性
- 继承
- 多复用,继承来的就不用自己写了
- 多继承少修改,OCP(Open-closed Principle),使用继承来改变,来体现个性
- 多态
- 面向对象编程最灵活的地方,动态绑定
人类就是封装;
人类继承自动物类,孩子继承父母特征。分为单一继承、多继承;
多态,继承自动物类的人类、猫类的操作“吃”不同
1. 封装
封装就是定义类,将属性和操作组织在类中
1.1 python类定义
- 必须使用class关键字
- 类名强烈建议使用大驼峰命名方式(CamelCase)。其本质上就是一个标识符。
- 类定义完成之后,就产生了一个类对象,绑定到了标识符ClassName上
举例:
1.2 类及类属性
- 类对象:类也是对象,类的定义执行后会生成一个类对象
- 类属性:类定义中变量和类中定义的方法都是类的属性。上例中类Person的x和show
- 类变量:属性也是标识符,也是变量。上例中类Person的x和show
Person中,x、show都是类的属性,__name__、__doc__是类的特殊属性
show方法是类的属性,如同吃是人类的方法,但是诶一个具体的人才能吃东西,也就是说吃是人的实例能调用的方法
show是方法method,本质上就是普通的函数对象function,它一般要求至少有一个参数。第一个形式参数可以是self(self只是个惯用标识符,可以换名字),这个参数位置就留给了self。
self指代当前实例本身
1.3 实例化
使用上面的语法,在类对象名称后面加一个括号,就调用类的实例化方法,完成实例化。
实例化就是真正创建一个该类的对象(实例instance)。例如
上面的tom和jerry都是Person类的实例,通过实例化生成2个不同的实例
通常,每次实例化后获得的实例,是不同的实例,即使是使用同样的参数实例化,也得到不一样的对象
Python类实例化后,会自动调用__init__方法。这个方法第一个形式参数必须留给self,其他形式参数随意
1.3.1 实例化构造2个阶段
确切地讲,tom = Person()过程分为2个阶段:实例化(__new___)和初始化(__init__)
如同,流水线上生产一辆汽车,首先的先造一个车的实例,即造一辆实实在在的一个真实的车,但是这个车不能直接交给消费者。
而__init__方法称为初始化方法,要对生成的每一辆车做出厂配置。这样才能得到一个能使用的汽车。
但是需要注意的是,很多人习惯上把这两个阶段不加区分含糊地叫做实例化、初始化,说的就是这俩个阶段的总称。
__
init
__方法
有些人把Python的__init__方法称为构造方法或构造器。
Person()实例化后,要初始化,要调用的是
__init__(self)
方法,可以不定义,如果没有定义会在实例化隐式调用其父类的。__init__方法不能有返回值(返回值必须是None)
作用:对实例化进行初始化
初始化函数可以多个参数,请注意第一个位置必须是self,例如
__init__(self, name, age)
注意:__init__()方法不能有返回值,也就是只能是return None
1.3.2 实例对象instance
上例中,类Person实例化后获得一个该类的实例,就是实例对象。
上例中的Jerry就是Person类的实例。
__init__方法的第一个参数self就是指代某一个实例自身
执行Person("Tom", 20) 时,调用__init__()方法。self.name就是tom对象的name,name是保存在了tom对象上,而不是Person类上。所以,称为实例变量。
类实例化后,得到一个实例对象,调用方法时采用tom.showage()的方法,但是showage方法的形参需要一个形参self,我们并没有提供,并没有报错,为什么?
1.3.3 方法绑定
采用tom.showage()的方法调用,实例对象会绑定到方法上,这个self就是tom,指向当前调用该方法
用tom.showage(showage定义在类中,showage是类属性),Python会得到一个绑定方法(bound method)
将tom实例绑定到了类属性showage方法上了,当你调用该方法时,解释器把第一参数self使用实参tom注入。
1.3.4 self
上例说明,self就是调用者,就是tom对应的实例对象
self这个形参标识符的名字只是一个惯例,它可以修改,但是请不要修改,否则影响代码可读性。
看打印的结果,思考一下执行的顺序,为什么?
1.4 实例属性和类属性
- 实例属性是每一个实例自己的属性,是自己独有的
- 类属性是的类的属性,是类的所有实例共享的属性或方法
- 给实例添加的属性是这单个实例独有的,类和其他实例没有该属性
1.5 特殊属性
特殊属性 | 含义 |
__name__ | 对象名 |
__class__ | 对象的类型 |
__dict__ | 对象的属性的字典,存储所有类型成员信息 |
__qualname__ | 类的限定名 |
__doc__ | 类的说明文档 |
__module__ | 类型所在模块 |
__bases__ | 类型所继承的基类 |
注意:
Python中每一种对象都拥有不同的属性。函数是对象,类是对象,类的实例也是对象。
1.5 属性本质
上例子中,可以看到类属性保存在类的
__dict__
中,实例属性保存在实例的__dict__
中,如果从实例访问类的属性,也可以借助__class__
找到实例所属的类,在通过类来访问类属性,例如tom.__class__.age
。有了上面知识,再看下面的代码
总结:
是类的属性,也是这个类所有实例的,其 实例都可以访问到
是实例的属性,就是这个实例自己的,通过类访问不了
类变量是属于类的变量,这个类的所有实例可以共享这个变量
对象(实例或类)可以动态的给自己增加一个属性(赋值即定义一个新属性)。这也是动态语言的特性。
实例.__dict__[变量名]
和 实例.变量名
都可以访问到实例自己的属性(注意着两种访问是有本质区别的,前者显示通过属性访问实例字典,再通过字典key访问值,后者是成员访问)实例属性的查找属性
指的是实例使用.点号来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找自己的类,再去类的__dict__中找
注意:如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的key查找,不是属性查找
一般来说,类变量可使用全大写来命名。
1.6 类方法和静态方法
前面的例子中定义的__init__等方法,这些方法本身都是类的属性,第一个参数必须是self,必须指向一个对象,也就是类实例化之后,由实例来调用这个方法。
1.6.1 普通函数(禁用)
Person.normal_function()
可以放在类中定义,因为这个方法只是被Person这个类管理的普通方法,normal_function是Person的一个属性而已。
由于normal_function在定义的时候没有指定形参self,所以不能用Person().normal_function调用。原因是,Person()是实例,实例调用的时候,由于做了实例绑定,那么就需要normal_function的第一个形参来接收绑定的实例。
注意:虽然语法是对的,但是,没有人这么用,也就是说禁止这么写
1.6.2 类方法
我们在使用某模块的时候,有时候会用到类方法,了解其构造过程是有必要的。
类方法:
1、在类定义中,使用@classmethod装饰器的方法
2、必须至少有一个参数,且第一个参数必须留给cls,cls指代调用者即类对象自身(cls绑定类)
3、cls这个标识符可以是任意合法名称,但是为了易读,请不要修改
4、通过cls可以直接操作类的属性,既可以用实例访问,也可用类访问
通过类、实例都可以非常方便地调用类方法。classmethod装饰器内存将类或提取实例的类注入到类方法的第一个参数中。
注意:无法通过cls操作类的实例。为什么?类存在不一定有实例,也不一定只有一个实例。
1.6.3 静态方法(常用)
静态方法
1、在类定义中使用@staticmethod装饰器修饰的方法
2、调用时,不会隐式的传入参数(无绑定)
通过类、实例都可以调用静态方法, 推荐用类调用静态方法,不会像普通方法、类方法那样注入参数。
静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理。
1.6.4 实例方法(普通方法)
方法的调用
类可以定义这么多种方法,究竟如何调用它们?
类几乎可以调用所用内部定义的方法,但是调用
普通的方法
时会报错,原因是第一参数必须是类的实例实例也几乎可以调用所用的方法,
普通的函数
的调用一般不可能出现,因为原则上不允许这么定义。总结:
- 类除了普通方法都可以调用
- 普通方法需要对象的实例作为第一参数
- 实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法内部都要使用实例的类。
看下面的代码
tom.method()调用的时候,会绑定实例,调用method方法时,实例tom会注入到method中,这样第一参数就满足了。
Person.method(),使用类调用,不会有实例绑定,调用method方法时,就缺少了第一参数,需要手动的填入。
1.7 访问控制
上例,本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性。
Python提供了私有属性可以解决这个问题
1.7.1 私有(Private)属性
使用双下划线开头的属性,就是私有属性
通过实验可以看出,外部已经访问不到
__age
了,age根本就没有定义,更是访问不到只能使用方法来进行访问。
1.7.2 私有成员
在Python中,在类变量或实例变量前使用两个双下划线的变量,称为私有成员,包括私有属性、私有方法。
1.7.3 私有变量的本质
私密都在__dict__中,原来私有成员都被改了名
私有变量的本质:
类定义的时候,如果声明一个实例的时候,使用双下划线,Python解释器会将其改名,转化名称为
_类名__变量名
的名称,所以原来的名字访问不到了。但是通过__dict__知道了被修改后的私有变量的新名称,就可以直接在外部访问到,并进行修改,就绕过了Python做的限制。
1.7.4 保护(protected)成员
在类变量或实例变量前使用一个下划线的变量,称为保护成员。
可以看出,保护成员没有改名,解释器不做任何特殊处理。这只是开发者共同的约定,看见这种变量,就如同私有变量不要直接使用
1.7.5 总结
在Python中使用_但下划线或者__双下滑线来标识一个成员或者被私有化隐藏起来
但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员。Python中没有绝对的安全的保护成员或者私有成员。
因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改它们。
在Pycharm中,已经对访问私有、保护成员访问的时候不会直接提示,就是一种善意的提醒。
1.8 补丁
可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是类提供的功能可能已经改变了。
猴子补丁(Monkey Patch)
在运行时,对属性、方法、函数等进行动态替换。
其目的往往是为了通过替换、修改来增强、扩展原有代码的能力。
黑魔法,慎用。
上例中,假设Person类get_score方法是从数据库拿数据,但是测试的时候,不方便。为了测试时方便,使用猴子补丁,替换了get_score方法,返回模拟的数据。
1.9 属性装饰器
一般好的设计是:把实例的某些属性保护起来,不让外部访问,外部使用getter读取属性和setter方法设置属性。
看下面代码
上面例子中,对于私有成员,我们要对他进行访问修改删除,是通过getag,setage和delage方法操作属性。有没有更方便的方式呢?Python提供了
属性property装饰器。
property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定的隐藏效果
特别注意:使用property装饰器的时候这三个方法同名
property装饰器
- 后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性。
- setter装饰器
- 与属性名同名,且就收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写
- deleter装饰器
- 可以控制是否删除属性。很少用
- property装饰器必须在前,setter、deleter装饰器在后
对象销毁
类中可以定义__del__方法,称为析构函数(方法)。
作用:销毁类的实例的时候调用,来释放占用的资源。其中就放些清理资源的代码,比如释放连接。
注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它。
使用del语句删除实例,引用计数减1。当引用计数为0时,会调用__del__方法。
由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收。
由于垃圾回收对象销毁时,才会真正清理对象,还会在回收对象之前自动调用__del__方法,除非你明确知道自己的目的,建议不要手动调用这个方法。
方法重载(overload)
其他面向对象的高级语言中,会有重载的概念
所谓重载,就是同一个方法名,但是形式参数个数、类型不一样,就是同一个方法的重载。
Python没有重载,也不需要重载
Python中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是一个说明而非约束),形参个数也不固定(可变参数)。一个函数的定义可以实现很多种不同形式参数的调用。所以Python不需要方法的重载。
总结(封装)
面向对象的三要素之一,封装Encapsulation
封装
- 将数据和操作组织到类中,即属性和方法
- 将数据隐藏在起来给使用者提供操作(方法)。使用者通过操作就可以获取或者修改数据。getter和setter
- 通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来,例如保护成员或私有成员