Python课堂笔记-第十一讲&第十二讲(面相对象)
一、面向对象
1.1 面向对象简介
- 面向过程
面向过程注重的是解决问题的步骤和过程,最直观的实现方式就是函数式编程,通过定义函数描述步骤,通过函数的调用完成过程的执行来解决问题。
优点:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核、git以及Apache HTTP Server等。
- 面向对象
是一种编程思想,是解决问题的一种思路。
面向对象,解决问题时关注的不再是解决问题的步骤和过程,而是更加注重是参与解决问题的对象以及他们的行为。
优点:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果, 面向对象的程序一旦开始就由对象之间的交互解决问题,无法预测最终结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层、互联网应用、企业内部软件、游戏等。
1.2 面向对象术语
-
类
将具有共同特征以及行为的一组对象进行抽象,抽象出来的东西,描述一类事物的概念。 -
对象
实际存在的物体,包含了具体属性和方法的实体,这个事物是通过类创建创建出来的,属于一个类。 -
属性
属性就是一个变量,可以存储数据,用于描述生活中一个事物的特征。
比如:人的姓名、年龄、性别 -
方法
方法就是一个函数,当函数声明在类中,就成为方法,描述一个事物的行为。
比如:人的学习行为、健身行为、玩游戏的行为… -
类和对象的关系
对象是类的实例,类是对象的模板。
类和对象的关系:类是对象的载体,对象是类的实例化。 -
构造方法
new(cls) 创建对象,new方法必须要有返回值,返回实例出来的实例。
init(self) 初始化属性,给对象属性赋值。
二、类
2.1 类的定义
类和对象都是对现实生活中事物的抽象,事物包含两部分
- 数据(属性)
- 行为(方法)
类的声明:
class 类名([⽗类]):
pass
# 定义一个表示人的类
class Person:
# 在类中定义的变量,将会成为所有实例的公共属性
# 所有实例都可以访问这些变量
name = '葫芦娃'
# 在类中定义的函数我们称之为方法
# 这些方法可以通过类的所有实例来访问
# 如果是方法调用的时候默认传递一个参数,所以方法中至少要定义一个形参
def speak(a):
print('大家加油!!')
p1 = Person()
p2 = Person()
p1.speak()
-
实例为什么能访问到类中属性和方法?
类中定义的属性和方法都是公共的,任何该类的实例都可以访问。 -
在类代码块中,定义变量和函数
变量会成为该类实例的公共属性,所有的该实例都可以通过 对象.属性名的形式访问
函数会成为该类实例的公共方法,所有该类实例都可以通过 对象.方法名的形式访问 -
调⽤⽅法 对象.⽅法名()
-
⽅法调⽤和函数调⽤的区别
如果是函数调⽤,调⽤时有⼏个形参,就会传递⼏个实参。如果是⽅法调⽤,默认传递⼀个参数,所以⽅法中⾄少得有⼀个形参。 -
类对象和实例对象都可以保存属性
如果这个属性(方法)是所以的实例共享的,则应该将其保存到类对象中;
如果这个属性(方法)是某个实例独有的,则应该保存到实例对象中;
一般情况下,属性保存到实例对象中,而方法需要保存到类对象中。 -
属性和方法的查找流程
当我们调用一个对象的属性时,解析器会现在当前的对象中寻找是否还有该属性。
如果有,则直接返回当前的对象的属性值。
如果没有,则去当前对象的类对象中去寻找,如果有则返回类对象的属性值。如果没有就报错。
2.2 参数self
- self在定义时需要定义,但是在调⽤时会⾃动传⼊;
- self的名字并不是规定死的,但是最好还是按照约定是⽤self;
- self总是指调⽤时的类的实例
在实例化时自动将对象/实例本身传给__init__()的第一个参数。
class 类名:
def __init__(self,参数1,参数2):
self.对象的属性1 = 参数1
self.对象的属性2 = 参数2
def 方法名(self):pass
def 方法名2(self):pass
如果是方法调用的时候默认传递一个参数,所以方法中至少要定义一个形参;
第一个参数就是调用方法的本身。
# 定义一个表示人的类
class Person:
name = '葫芦娃'
# 如果是p1调用,则第一个参数就是p1
# 如果是p2调用,则第一个参数就是p2
# 一般我们都会将这个参数命名为self
def speak(self):
print('加油!!%s'%self.name)
p1 = Person()
p2 = Person()
p1.name = '钢铁侠'
p2.name = '绿巨人'
p1.speak() # 加油!!钢铁侠
p2.speak() # 加油!!绿巨人
三、特殊方法
3.1 类的基本结构
# 类的基本结构
class 类名([父类]):
# 对象的初始化方法
def __init__(self,.....):
......
# 其他的方法
def method1(self,.....):
...
def method2(self,.....):
...
3.2 类的执行流程
1、先创建一个类(当类中有print时)
2、在内存中创建一个新的对象(解析器会自动在内存中生成新的内存)
3、注意(先执行类中的代码)(注意:定义在类中的代码只执行一次,当定义多个实例对象等于类时,结果也只是执行一次)
4、__init __(self)特殊方法执行
3.3 特殊方法
- 在类中可以定义一些特殊方法也称为魔术方法
- 特殊方法都是形如 xxx()这种形式
- 特殊方法不需要我们调用,特殊方法会在特定时候自动调用
class Person():
# init方法会在对象从创建以后理解执行
def __init__(self,name):
# 通过self向新创建的对象来初始化属性
self.name = name
# print('Person代码块中的代码')
def speak(self):
print('大家好,我是%s'%self.name)
p1 = Person('钢铁侠')
print(p1.name)
四、封装
4.1 封装的原因
出现封装的原因:我们需要一种方式来增强数据的安全性
- 属性不能随意修改
- 属性不能改为任意的值
封装是指隐藏对象中⼀些不希望被外部所访问到的属性或⽅法
4.2 封装是⾯向对象的三⼤特性之⼀
1、封装(Encapsulation)
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。 对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者self间接获取被封装的内容。
2、继承(Inheritance)
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
例如:
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
公共的部分就是 吃、喝、拉、撒
3、多态(Polymorphism)
首先Python不支持多态,也不用支持多态,python是一种多态语言,崇尚鸭子类型。
在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述: “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的走和叫方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
4.3 getter()和setter()⽅法
我们可以提供给⼀个getter()和setter()方法使外部可以访问到属性。
- getter() 获取对象中指定的属性
- setter() 用来设置对象指定的属性
# 如何隐藏一个对象中属性
# 将对象的属性名修改成一个外部不知道的名字
# 如何获取(修改)对象中的属性
class Dog:
def __init__(self,name,age):
self.hidden_name = name
self.hidden_age = age
def speak(self):
print('大家伙,我是%s'%self.hidden_name)
def get_name(self):
# get_name是用来获取对象的name属性
return self.hidden_name
def set_name(self,name):
# set_name()是用来设置对象的name属性
self.hidden_name = name
def get_age(self):
return self.hidden_age
def set_age(self,age):
print('用户修改了属性')
if age > 0:
self.hidden_age = age
d = Dog('德牧',5)
d.set_age(10)
print(d.get_age())
使⽤封装,确实增加了类的定义的复杂程度,但是它也确保了数据的安全。
- 隐藏属性名,使调⽤这⽆法随意的修改对象中的属性;
- 增加了getter()和setter()⽅法,很好控制属性是否是只读的;
- 使用setter()方法,可以增加数据的验证,确保数据的值是正确的;
- 使⽤getter()⽅法获取属性;
- 使⽤setter()⽅法设置属性可以在读取属性和修改属性的同时做⼀些其他的处理。
4.4 可以为对象的属性使用__xxx
- 双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问;
- 其实隐藏属性只不过是Python自动的为属性改了一个名字;
- 实际上它是将名字修改成了 _类名__属性名 例如 __name --> _Person__name
class Person():
def __init__(self,name):
self._name = name
def get_name(self):
return self._name
def set_name(self, name):
self._name = name
p = Person('葫芦娃')
print(p._name)
p.set_name('钢铁侠')
print(p._name)
4.5 property装饰器
我们可以使⽤@property装饰器来创建只读属性
@property装饰器会将⽅法转换为相同名称的只读属性,可以与所定义的属性配合使⽤,这样可以防⽌属性被修改。
class Person():
def __init__(self,name):
self._name = name
@property
def name(self):
print('get方法执行了')
return self._name
# setter方法的装饰器 @属性名.setter
@name.setter
def name(self,name):
print('set方法执行了')
self._name = name
p = Person('葫芦娃')
p.name = '超人'
print(p.name)