对象(object)
内存中专门用来存储数据的一块区域
id、type、value
面向对象(oop)
- python是一门面向对象的编程语言
- 所谓的面向对象语言:语言中的所有操作都是通过对象来进行的
- 面向过程的编程语言:
- 面向过程指的是将我们的程序的逻辑分解成一个一个的步骤
通过对每个步骤的抽象,来完成程序
- 例子:
- 孩子上学
1、妈妈起床
2、妈妈上厕所
3、妈妈洗漱
4、妈妈做早饭
5、妈妈叫孩子起床
6、孩子上厕所
7、孩子吃饭
8、孩子背着书包上学校
- 面向过程的编程思想将一个功能分解成一个一个小的步骤
我们通过完成这些小步骤来完成一个程序(符合人类思维,编写比较简单)
- 但是这种方式编写代码的往往只适用于一个功能
但如果要实现别的功能,即使功能相差极小,也往往要重新编写代码,可复用性比较低
- 面向对象的编程语言:
- 面向对象的编程语言,关注的是对象,而不关注过程
- 对于面向对象的语言来说,一切都是对象
- 例子:
1、孩他妈起床教孩子上学
- 面向对象的编程思想,将所有的功能统一保存到对应的对象中
要使用某种功能,直接找到对应的对象即可
- 这种方式编写的代码,比较容易阅读,并且易于维护,容易复用
- 但是这种方式的编写,不太符合常规思维,编写起来稍微麻烦
- 简单归纳:面向对象的思想
1、找对象
2、搞对象
面向对象的三大特征
- 封装
- 继承
- 多态
类(class)
- 我们目前所学对象都是python内置对象
- 但是内置对象不能满足所有的需求,所以在开发中经常需要自定义一些对象
- 类,简单理解它相当于一个图纸,在程序中我们需要根据<类>来创建对象
- 类就是对象的图纸!(一切皆对象,类也是一个对象)
- 对象是类是实例
- 如果多个对象是通过一个类创建的,则称这些对象是一类对象
- 如int()、float()、bool()...等等,这些都是类
- a = int(10) # 创建一个int类的实例,等价于 a = 10
- 我们自定义的类都需要使用大写字母开头,使用大驼峰命名法(帕斯卡命名法)来对类命名
- class MyClass():
例子:
a = int(10) # 创建一个int类的实例
b = str('hello') # 创建一个str类的实例
类的定义及对象创建
- 使用class关键字来定义类,语法和函数很像!
语法:
class 类名([父类]):
pass
例子:
class MyClass():
pass
print(MyClass) ---> <class '__main__.MyClass'>
- 使用类MyClass来创建一个对象
# 使用类来创建对象,就像调用一个函数一样
mc = MyClass() # mc就是通过MyClass创建的对象,对象mc就是类MyClass的实例
mc_2 = MyClass()
mc_3 = MyClass() ---> mc、mc_2、mc_3都是MyClass的实例,他们都是一类对象
- isinstance(a, b)用来检查一个对象a是否是一个类b的实例
result = isinstance(mc, MyClass) ---> resul = True
print(mc) ---> <__main__.MyClass object at 0x052FB230>
print(type(mc)) ---> <class '__mian__.MyClass'>
print(MyClass) ---> <class '__mian__.MyClass'>
print(type(MyClass)) ---> <class 'type'>
对象的创建流程
- 一切皆对象,类也是一个对象!
- 类就是一个用来创建对象的对象
- 类是type类型的对象,定义类实际上就是定义了一个type类型的对象
- 现在我们通过MyClass这个类创建的对象都是一个空对象,什么都没有,是一个空盒子
- 可以向对象中添加变量,对象中的变量称为属性
语法:
对象.属性名 = 属性值
例:
mc.name = '孙悟空'
mc_2.name = '猪八戒'
调用:
print(mc.name) ---> '孙悟空'
总结:
1、创建一个变量
2、在内存中创建一个新对象
3、将对象的id赋值给变量
类的定义
- 类和对象都是对现实生活中或者程序中的内容的抽象
- 实际上所有的事物都有两部分组成:
1、数据(属性)
2、行为(方法)
例:
# 定义一个表示人的类
class Person : # 括号可写可不写 class Person():,并且类名首字母大写,
# 如果多个单词拼在一起则每个单词开头大写,如SchoolBoy(所谓大驼峰命名法)
# 在类的代码块中,可以定义变量和函数
# 在类中所定义的变量,将会成为所有实例的公共属性
name = '孙悟空' # 公共属性,所有实例都可以访问
# 在类中也可以定义函数,类中定义的函数被称为方法
# 这些方法可以通过该类的所有实例来访问
def say_hello(self):
#方法每次调用时,解析器都会自动传递第一个实参
第一个实参,就是调用方法的对象本身,如:
<__main__.Person object at 454643464>
如果是p1调用,则第一个参数就是p1对象
如果是p2调用,则第一个参数就是p2对象
所以,我们一般将这个形参命名为self(因为该形参代表的是对象本身)
# say_hello这个方法,可以显示如下格式:
你好!我是XXX, 但是在方法中不能直接访问类中的属性(如name = '孙悟空')
可以这么做来实现:
print('你好!我是%s' % self.name) # p1调用的时候,self.name返回的是p1.name;
p2调用的时候,self.name返回的是p2.name...
print('hello')
# 创建person的实例
p1 = Person()
p2 = Person()
# 调用公共属性: 对象.属性名
print(p1.name) ---> 孙悟空
# 调用方法: 对象.方法名()
# 方法调用和函数调用的区别:
方法调用:默认传递一个参数,所以方法中至少要定义一个形参(形参名字无所谓,但是会有规范命名,一般该形参命名为self)
函数调用:传递几个参数,就会有几个实参
p1.say_hello() ---> 虽然括号里没有参数,但是解析器会默认传递一个实参
(因此定义方法时至少要有一个形参)
p2.say_hello()
- 属性和方法查找的流程:
当我们调用一个对象的属性时,解析器会事先在当前对象中寻找是否含有该属性,
如果有,则直接返回当前对象的属性值
如果没有,则会到该对象所属的类中寻找,若有则返回类对象的属性值
如果再没有则报错!
- 属性的删除
del p2.name ---> 删除p2的name属性
- 类对象和实例对象都可以保存属性和方法
- 如果这个属性(方法)是所有实例共享的,则应该保存在类对象中
- 如果这个属性(方法)是某个实例独有的,则应该保存在实例对象中
- 一般情况下,属性保存在实例对象中,方法保存在类对象中
类的特殊方法init
目前来讲,对于Person类来说name属性是必须要定义的,并且每个对象中的name属性基本上都是不同的
而我们现在是在定义完对象之后,将name属性手动添加到对象中,这种方式容易出现错误
我们希望,在创建对象时,必须设置name属性,如果不设置对象则无法创建
并且属性的创建应该是自动完成的,而不是在创建对象以后手动完成
而这个时候我们可以定义一些特殊方法(也称魔术方法)来满足我们的需求
- 特殊方法都是以__开头,以__结尾的方法(均为双下划线)
- 特殊方法不需要我们自己调用,不要尝试去调用特殊方法
- 特殊方法将会在特殊时刻自动调用(解析器调用)
- 学习特殊方法:
1、特殊方法什么时候调用
2、特殊方法有什么作用(init:初始化)
- 创建对象的流程
- p1 = Person()的运行流程
1、创建一个变量
2、在内存中创建一个新对象
3、__init__(self)方法执行
4、将对象的id赋值给变量
- __init__会在对象创建以后立即执行
- __init__可以用来向新创建的对象中初始化属性
例:
class Person():
self.name = '孙悟空' # 此属性在类对象中(是所有根据该类创建的实例共有的)
# 调用类创建对象时,类后边的所有参数都会因此传递到init()中
def __init__(self, name):
# 通过self向新建对象中初始化属性
self.name = name # 此属性在实例对象中,与类对象中属性有本质区别
def say_hello(self):
print('大家好, 我是%s' % self.name)
由于__init__(self, name)方法有两个形参,第一个形参self解析器会自动传递实参
但是第二个形参需要我们手动传入实参(不传入实参就会报错)
当我们根据类对象来创建实例时,需要传入self之后的参数:
p1 = Person('孙悟空') ---> p1.name = '孙悟空' # 不需要传入参数self
# 调用Person里的方法say_hello():
p1.say_hello() ---> 大家好, 我是孙悟空
同理,
p2 = Person('猪八戒') ---> p2.name = '猪八戒'
# 调用Person里的方法say_hello():
p2.say_hello() ---> 大家好, 我是猪八戒
类的基本结构
class 类名([父类]): # 如果没有父类名,则括号可以不写, []表示可选的意思
# docstring,即编写文档,在这里指的是类的文档(docstring可以写在三个地方:模块或包、对象、函数)
# 通过help()函数即可输出一份有格式的文档
''' docstring for ClassName '''
公共属性... # 如 gender = 'male'
# 特殊方法
def __init__(self, ...) ---> 初始化狗的一些基本属性
...
# 其他的方法
def method_1(self, ...)
...
def method_1(self, ...)
...
...
练习:
尝试自定义一个表示狗的类(Dog)
属性:
name
age
gender
height
weight
...
方法:
yell()
run()
bite()
jump()
cry()
...
参考答案:
class Dog():
'''
表示狗的类
'''
def __init__(self, name, age, gender, height):
self.name = name
self.age = age
self.gender = gender
self.height = height
def yell(self):
'''
狗叫的方法
'''
print('汪汪汪~~~')
def bite(self):
'''
狗咬的方法
'''
print('我咬你')
def run(self):
print('%s在快乐地奔跑者~~~' % self.name)
# 创建实例
dog_1 = Dog('旺财', 8, 'male', 30) ---> 当类中有__init__方法时,创建实例时要传入参数
dog_1.run()
dog_1.bite()
dog_1.yell()
注意:目前我们可以通过 对象.属性 的方式来修改属性的值
这个方式导致对象中的属性可以随意修改,很不安全
dog_1.name = '阿黄'
dog_1.age = -10
- 现在我们就需要一种方式来增强数据的安全性
1、属性不能随意修改(让你改你才能改)
2、属性不能修改为任意的值(比如年龄不能是负数)
在类中使用<递归函数>时的注意事项
class MyClass(object):
def IterationFunction(self, name):
# 基线条件
if ...
return ...
# 递归条件(--调用函数本身时要加上self--)
# return IterationFunction(name[1:-1]) ---> 错误
return self.IterationFunction(name[1:-1]) ---> 正确
封装
封装简介
- 封装是面向对象的三大特性之一
- 封装指的是隐藏对象中不希望被外部所访问到的属性或方法
- 如何隐藏一个对象中的属性?
- 将对象的属性名,修改为一个外部不知道的名字(此方法防君子不防小人)
如:
class Dog():
def __init__(self, name):
self.hidden_name = name
def say_hello(self):
print('大家好,我是%s' % self.hidden_name)
dog_2 = Dog('阿黄')
dog_2.name = '小黑' # 此方法不能修改狗的姓名了,因为狗的姓名属性是hidden_name而不是name
# 若一定要改狗的名字也可以:
dog_2.hidden_name = '小黑'
(因此说此方法是防君子不防小人,如果你执意要改,是可以修改的)
- 如何获取(修改)对象中的隐藏属性?
- 需要提供一个getter和setter方法使外部可以访问到属性(通用方法)
- getter 获取对象中的指定属性(get_属性名)
- setter 设置对象的指定属性(set_属性名)
例子:
class Dog():
def __init__(self, name):
self.hidden_name = name # 不一定要命名为hidden_name
def say_hello(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
dog_3 = Dog('旺财')
# 调用 get_name()方法来获取name属性值
dog_3.get_name()
# 调用 set_name()方法来修改name属性值
dog_3.set_name('阿花')
- 使用封装,确实增加了类的定义的复杂度,但也确保了数据的安全性
1、隐藏了属性名,使调用者无法随意修改对象中的属性
2、增加了gettere和setter方法,很好地控制了对象的属性是否是<只读>的
如果希望是只读的,则可以直接去掉setter方法
如果希望属性不能被外部访问,则可以去掉getter方法
3、使用setter方法设置属性,可以增加数据的验证,确保数值是正确的
如:可以在set_name()中增加一些逻辑判断
def set_name(self, age):
if age > 0:
self.hidden_age = age
4、使用getter方法获取属性,使用setter方法设置属性
可以在获取属性和修改属性的同时做一些其他的处理,如:
def set_name(self, age):
if age > 0:
print('用户修改了属性')
self.hidden_age = age
5、使用getter方法也可以做一些计算
隐藏类中的属性
- 可以为对象的属性使用双下划线, __XXX
- 双下划綫的属性,是对象的隐藏属性(只能在类的内部访问,无法通过对象访问)
- 其实隐藏属性只不过是Python自动修改为属性改了个名字
实际上是将名字修改为:_类名__属性名,如 __name --> _Person__name
所以当属性名为_name时,实际上属性名为_Person_name,所以通过_Person__name可以修改属性值
- 因此所谓隐藏是假隐藏(本质上跟我们定义的hidden_name是一样的)
防君子不防小人,在Python中约定俗成不要修改此类__xxx变量
- 由于__开头的属性,实际上依然可以在外部访问,所以这种方式我们一般不用
一般我们会将一些私有属性(不希望被外部访问)以_开头(单下划线)
一般情况下,使用_开头的属性都是私有属性,没有特殊需要不要修改私有属性(全靠人的自觉)
装饰器
- 我们调用getter和setter方法时,需要加括号,如dog_1.get_name(),就显得很麻烦
能不能有办法,使得调用方法跟调用属性一样不需要加括号呢?
- 这就需要装饰器
举例子:
class Person():
def __init__(self, name, age):
self._name = name # 加单下划线_name标明该变量不希望被修改,但是靠人自觉
self._age = age
# property装饰器,用来将一个getter方法,转换为对象的属性
# 添加完property装饰器后,就可以像调用属性一样使用getter方法
# 使用property装饰器的方法的名称,必须和属性名是一样的
@property
def name(self):
print('getter方法调用了~')
return self._name
# setter方法的装饰器: @属性名.setter
@name.setter
def name(self, name):
print('setter方法调用了~')
self._name = name
@property
def age(self):
print('getter方法调用了~')
return self._age
# setter方法的装饰器: @属性名.setter
@age.setter
def age(self, age):
print('setter方法调用了~')
self._age = age
# 注意:如果不提供@property,则不能有@属性名.setter
(两者有递进关系,试想一下:不能读取怎么可能设置)