第一章 Python 入门
第二章 Python基本概念
第三章 序列
第四章 控制语句
第五章 函数
第六章 面向对象基础
面向对象基础
- Python系列文章目录
- 前言
- 一、面向对象是什么
- 1. 面向对象简介
- 2. 面向对象和面向过程
- 面向对象
- 面向过程
- 面向对象和面向过程总结
- 二、类, 对象, 属性, 方法
- 1. 类
- 类编码规范
- 类对象, 类属性, 类方法, 静态方法
- 类对象
- 类属性
- 类方法
- 静态方法
- 2. 对象
- 对象的演变
- 对象完整内存结构
- 3. 属性和方法
- 属性和方法命名规范总结
- 实例属性
- 实例方法
- 其他操作
- 函数和方法的区别
- 方法没有重载
- 方法的动态性
- 私有属性和私有方法
- 4. 实例对象和类对象创建过程内存分析(重要)
- 实例对象的方法调用本质
- 5. 默认的类方法
- __init__ 构造方法和__new__ 方法
- __del__方法(析构函数)和垃圾回收机制
- `__call__` 方法和可调用对象
- @property 装饰器
- 6. 关于None和判断的总结
- None是什么
- None和其他类型的比较
- 三、实操作业
前言
在本章, 首先将会学习面向过程和面向对象之间的区别和联系
然后, 学习类(类对象, 类属性, 类方法, 静态方法), 对象(演变, 内存结构), 属性和方法(命名规范, 方法相关特性, 实例属性和实例方法, 私有属性和私有方法), 常用的类的方法(init, call, del, @property)以及关键字None 的相关介绍
最后, 进行实操来巩固所学习到的知识
一、面向对象是什么 1. 面向对象简介
Python完全采用了面向对象的思想,是真正面向对象的编程语言,完全支持面向对象的基本功能,例如:继承、多态、封装等。
Python中,一切皆对象. 我们在前面学习的数据类型、函数等,都是对象.
需要注意:
- 面向对象(Object oriented Programming,OOP)编程的思想主要是针对大型软件设计而来的
- 面向对象编程将数据和操作数据相关的方法封装到对象中,组织代码和数据的方式更加接近人的思维,从而大大提高了编程的效率
- Python支持面向过程、面向对象、函数式编程等多种编程范式
为了思考面向是什么, 我们不妨考虑"如何设计事物".
比如造车, 我们首先应该不去思考怎么按步骤造车 , 而是会去思考车怎么设计. 这就是面向过程到面向对象的转变
通过对相关知识的学习, 我们知道汽车主要是由轮胎, 发动机, 车壳, 座椅, 玻璃等几个零件组成
为了能够高效的实现汽车生产, 我们可以找轮胎厂完成制造轮胎的步骤, 发动机厂完成制造发动机的步骤…
这样, 汽车的各项零件就会并行的去制造, 最终去进行组装, 大大提高了汽车生产效率
但是, 具体到轮胎厂的一个流水线操作,仍然是有步骤的,还是离不开执行者、离不开面向过程
注意:
- 面向对象是以功能来划分问题,而不是以步骤解决。
- 面向对象可以帮助我们从宏观上把握、从整体上分析整个系统.
但是,具体到实现部分的微观操作(就是一个个方法),仍然需要面向过程的思路去处理 - 不要把面向过程和面向对象对立起来. 他们是相辅相成的. 面向对象离不开面向过程
面向过程适合简单、不需要协作的事务,重点关注如何执行。
面向过程时,我们首先思考 “ 怎么按步骤实现?” 比如如何开车?我们很容易就列出实现步骤:
注意:
- 面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数
- 当我们思考比较复杂的设计任务时,比如“如何造车?”,就会发现列出1234这样的步骤是不大可能的
那是因为造车太复杂,需要很多协作才能完成. 此时面向对象思想就应运而生了
区别
- 面向过程是一种“执行者思维",解决简单问题可以使用面向过程
- 面向对象是一种“设计者思维”,解决复杂、需要协作的问题可以使用面向对象
联系
- 都是解决问题的思维方式
- 都是代码组织的方式
- 面向对象离不开面向过程:
宏观上:通过面向对象进行整体设计
微观上:执行和处理数据,仍然是面向过程
二、类, 对象, 属性, 方法 1. 类
类编码规范类:我们叫做class . 类一般看做是对象的抽象
我们把对象比作一个“饼干”,类就是制造这个饼干的“模具”. 类和对象的关系如下图所示:
- 类名首字母大写, 多个单词之间采用驼峰原则
- 实例名、模块名采用小写, 多个单词之间采用下划线隔开
- 每个类, 应紧跟“文档字符串”, 说明这个类的作用
- 可以用空行组织代码, 但不能滥用. 在类中, 使用一个空行隔开方法; 模块中, 使用两个空行隔开多个类
前面讲的类定义格式为, class 类名:
实际上, 当解释器执行class 语句时, 就会创建一个类对象
class Student:
pass # 空语句
print(type(Student)) #
print(id(Student)) # 1772468512176
Stu2 = Student
s1 = Stu2()
print(s1) # <__main__.Student object at 0x0000019CAF6E3D90>
注意:
- 我们可以看到实际上生成了一个变量名就是类名 Student 的对象.
- 我们通过赋值给新变量 Stu2 , 也能实现相关的调用. 说明, 确实创建了“类对象”
类属性是从属于“类对象”的属性, 也称为“类变量”.
由于类属性从属于类对象, 因此可以被所有实例对象共享类属性的定义方式:
class 类名: 类变量名= 初始值
注意:
- 在类中或者类的外面, 我们可以通过: 类名.类变量名 来读写
实操代码
class Student:
company = "sxd" # 类属性
s1 = Student()
print(s1.company)
类方法
类方法是从属于“类对象”的方法. 类方法通过装饰器 @classmethod 来定义
格式如下:
@classmethod def 类方法名(cls [, 形参列表]): 方法体
注意:
- @classmethod 必须位于方法上面一行
- 第一个 cls 必须有; cls 指的就是“类对象”本身
- 调用类方法格式: 类名.类方法名(参数列表) . 参数列表中, 不需要也不能给 cls 传值
- 类方法中访问实例属性和实例方法会导致错误
- 子类继承父类方法时, 传入 cls 是子类对象, 而非父类对象
实操代码
class Student:
company = 'sxd'
@classmethod
def printCompany(cls):
print(cls.company)
Student.printCompany()
静态方法
Python中允许定义与“类对象”无关的方法, 称为“静态方法”
“静态方法”和在模块中定义普通函数没有区别, 只不过“静态方法”放到了“类的名字空间里面”, 需要通过“类调用”
静态方法通过装饰器@staticmethod来定义,
格式如下
@staticmethod def 静态方法名([形参列表]): 方法体
注意
- @staticmethod 必须位于方法上面一行
- 调用静态方法格式: 类名.静态方法名(参数列表)
- 静态方法中访问实例属性和实例方法会导致错误
实操代码
class Student:
company = "sxd"
@staticmethod
def add(a, b):
print("{0}+{1}={2}".format(a, b, a+b))
return a + b
Student.add(111, 222)
2. 对象
对象的演变对象:我们叫做object , instance (实例). 以后我们说某个类的对象,某个类的实例. 是一样的意思.
对象一般看做是类的具体体现
随着编程面临的问题越来越复杂,编程语言本身也在进化.
从主要处理简单数据开始,随着数据变多进化“数组”; 数据类型变复杂,进化出了“结构体”; 处理数据的方式和逻辑变复杂,进化出了“对象”.
演变过程如下图所示:
对象完整内存结构类是抽象的,也称之为“对象的模板”. 而对象则是类的具体实现
我们需要通过类这个模板,创建类的实例对象,然后才能使用类定义的功能。
我们前面说过一个Python对象包含三个部分: id (identity识别码)、type (对象类型)、value (对象的值)
现在我们可以更进一步的说,一个Python对象包含如下部分:标识, 类型, 属性和方法. 完整对象内存结构图如下图所示:
3. 属性和方法 属性和方法命名规范总结相较于Java 使用了关键字对类和方法以及属性进行访问权限控制. Python 使用下划线来对类, 对象, 属性和方法进行权限控制
具体区别如下:
- _xxx (方法名称前一个下划线):保护成员, 不能用 from module import * 导入, 只有类对象和子类对象能访问这些成员.
- __xxx__ (方法名称前后两个下划线):系统定义的特殊成员
- __xxx (方法名称前两个下划线): 类中的私有成员, 只有类对象自己能访问, 子类对象也不能访问. (但, 在类外部可以通过 对象名. _类名__xxx 这种特殊方式访问. Python不存在严格意义的私有成员)
实例属性是从属于实例对象的属性, 也称为“实例变量”.
注意:
-
实例属性一般在 __init__() 方法中通过如下代码定义: self.实例属性名 = 初始值
-
在本类的其他实例方法中, 也是通过 self 进行访问: self.实例属性名
-
创建实例对象后, 通过实例对象访问:
obj01 = 类名() #创建和初始化对象, 调用 __init__() 初始化属性 obj01.实例属性名 = 值 #可以给已有属性赋值, 也可以新加属性
实例方法是从属于实例对象的方法.
实例方法的定义格式如下:
def 方法名(self [, 形参列表]): 函数体
方法的调用格式如下:
对象.方法名([实参列表])
注意:
- 定义实例方法时, 第一个参数必须为 self . 和前面一样, self 指当前的实例对象
- 调用实例方法时, 不需要也不能给 self 传参. self 由解释器自动传参
实例对象和实例方法实操代码:
class Student:
def __init__(self, name, score):
self.name = name # 实例属性
self.score = score
self.age = 18 # 增加age属性
def say_score(self): # 实例方法
print(self.name, "的分数是: ", self.score)
s1 = Student("时间静止", 18)
s1.say_score() # 调用示例方法
print(s1.age)
s1.salary = 3000 # s1对象增加salary属性
s2 = Student("不是简史", 81)
s2.say_score()
print(s2.age)
其他操作
- dir(obj) 可以获得对象的所有属性、方法. 返回的是一个字典类型的数据
- obj.__dict__ 对象的属性字典
- pass 空语句. 用于定义空白逻辑的函数/方法
- isinstance(对象,类型) 判断“对象”是不是“指定类型”
方法没有重载都是用来完成一个功能的语句块, 本质一样.
方法调用时, 通过对象来调用. 方法从属于特定实例对象, 普通函数没有这个特点
直观上看, 方法定义时需要传递self, 函数不需要
如果我们在类体中定义了多个重名的方法, 只有最后一个方法有效.
建议: 不要使用重名的方法!Python中方法没有重载. 这一点和java大不相同
注意:
- 在其他一些语言(比如: Java)中, 可以定义多个重名的方法, 只要保证方法签名唯一即可. 方法签名包含3个部分: 方法名、参数数量、参数类型.
- Python中, 方法的的参数没有声明类型(调用时确定参数的类型), 参数的数量也可以由可变参数控制. 因此, Python中是没有方法的重载的
- Python中没有方法的重载. 定义多个同名方法, 只有最后一个有效
实操代码
class Person:
def make_money(self):
print("我要挣钱啦~")
def make_money(self, money):
print("我要挣钱啦~今天挣到{0}元".format(money))
p1 = Person()
# p1.make_money() # 不带参, 报: TypeError: Person.makeMoney() missing 1 required positional argument: 'money'
p1.make_money(666)
方法的动态性
Python是动态语言, 我们可以动态的为类添加新的方法, 或者动态的修改类的已有的方法
实操代码
我们可以看到, Person 动态的新增了 play_game 方法, 以及用 work2 替换了 work 方法
class Person:
def work(self):
print("努力上班!")
def play_game(self): # 静态方法
print("玩游戏")
def work_plus(s):
print("上班好好工作, 下班好好玩")
Person.play = play_game
Person.work = work_plus
p = Person()
p.play()
p.work()
私有属性和私有方法
Python对于类的成员没有严格的访问控制限制, 这与其他面向对象语言有区别.
关于私有属性和私有方法, 有如下要点:
- 通常我们约定, 两个下划线开头的属性是私有的(private). 其他为公共的(public).
- 类内部可以访问私有属性(方法)
- 类外部不能直接访问私有属性(方法)
- 类外部可以通过 _类名__私有属性(方法)名 ”访问私有属性(方法)
注意:
- 方法本质上也是属性!只不过是可以通过()执行而已.
所以, 此处讲的私有属性和公有属性, 也同时讲解了私有方法和公有方法的用法. - 如下测试中, 同时也包含了私有方法和公有方法的例子.
实操代码
class Employee:
__company = "sxd" # 私有.通过dir查到_Employee__company
def __init__(self, name, age):
self.name = name
self.__age = age
def get_company_name(self):
print("我的公司是: ", Employee.__company) # 类内部可以直接访问私有属性
print("{0}的年龄是{1}".format(self.name, self.__age))
def __work(self): # 私有实例方法, 通过dir可查到_Employee__work
print("工作是人类进步的阶梯")
p1 = Employee("timePause", 18)
print(dir(Employee))
print(Employee._Employee__company) # 在将company属性私有化后,直接调用报错, 通过dir可以查到属性_Employee__company,这种方式调用输出sxd, 从而验证规则4
# print(p1.age) # 在age属性未私有化时, 这样可以访问
print(p1._Employee__age) # 在age属性未私有化后, 通过这种方式可以直接访问到私有属性_Employee__age. 也验证了规则4
p1.get_company_name()
# p1.work() # 未私有之前可以调用, 私有后调用报错: AttributeError: 'Employee' object has no attribute 'work'
p1._Employee__work() # 私有之后. 通过这种方式可以直接访问私有熟悉_Employee__work. 也验证了规则4
4. 实例对象和类对象创建过程内存分析(重要)
我们以下面代码为例,分析整个创建过程:
class Student:
company = "sxd" # 类属性
count = 0 # 类属性
def __init__(self, name, score):
self.name = name # 实例属性
self.score = score
Student.count = Student.count + 1
def say_score(self): # 实例方法
print("我的公司是: ", Student.company)
print(self.name, "的分数是: ", self.score)
s1 = Student("时间静止", 18) # s1是实例对象, 自动调用__init__()方法
s2 = Student("不是简史", 81)
s1.say_score()
print('一共创建{0}个Student对象'.format(Student.count))
print("s1的引用:", id(s1))
print("s2的引用:", id(s2))
print("Student的引用:", id(Student))
实例对象和类对象创建过程如下图所示
初始化对象, 我们需要定义构造函数 __init__() 方法
构造方法用于执行“实例对象的初始化工作”, 即对象创建后, 初始化当前对象的相关属性, 无返回值
如果将对象比作一座房子, 则构造方法(__init__)是负责初始化(装修) , 不是建对象(房子)
__init__() 的要点如下(约等于Java的构造方法):
- 名称固定, 必须为: __init__()
- 第一个参数固定, 必须为: self . self 指的就是刚刚创建好的实例对象
- 构造函数通常用来初始化实例对象的实例属性
通过“类名(参数列表)”来调用构造函数. 调用后, 将创建好的对象返回给相应的变量. 比如: s1 = Student("时间静止", 80) - __init__() 方法: 初始化创建好的对象, 初始化指的是: “给实例属性赋值”
- 如果我们不定义 __init__ 方法, 系统会提供一个默认的 __init__ 方法.
如果我们定义了带参的 __init__ 方法, 系统不创建默认的 __init__ 方法 - __new__() 方法: 用于创建对象, 但我们一般无需重定义该方法
注意:
- Python中的 self 相当于C++中的 self指针 , JAVA和C#中的 this 关键字
- Python中, self 必须为构造函数的第一个参数, 名字可以任意修改, 但一般都叫做 self.
他的作用是指向当前对象的本身/当前对象的引用
__del__() 称为“析构方法”, 用于实现对象被销毁时所需的操作.
比如: 释放对象占用的资源, 例如: 打开的文件资源、网络连接等
注意:
- Python实现自动的垃圾回收, 当对象没有被引用时(引用计数为0), 由垃圾回收器调用 __del__()
- 我们也可以通过 del语句 删除对象, 从而保证调用 __del__()
- 系统会自动提供 __del__方法 , 一般不需要自定义析构方法.
实操代码
class Person:
def __del__(self):
print("销毁对象: {0}".format(self))
p1 = Person()
p2 = Person()
del p2
print("程序结束")
__call__ 方法和可调用对象
凡是可以将 () 直接应用到自身并执行, 都称为可调用对象.
注意:
- 可调用对象包括自定义的函数、Python 内置函数、以及本节所讲的实例对象.
- 定义了 __call__() 的对象, 称为“可调用对象”, 即该对象可以像函数一样被调用.
- 该方法使得实例对象可以像调用普通函数那样, 以“对象名()”的形式使用
实操代码
def f1():
print("f1")
f1() # 本质上也是调用了 __call__()方法
class Car:
def __call__(self, age, money):
print("调用__call__()方法")
print("车龄:{0}, 金额:{1}".format(age, money))
f2 = Car()
f2(3, 289000) # 像函数那样调用, 本质上也是调用了 __call__()
@property 装饰器
@property 装饰器相当于在Java中实现了对属性的get 方法
注意:
- @property 可以将一个方法的调用方式变成“属性调用”.
- @property 主要用于帮助我们处理属性的读操作、写操作
- 对于某一个属性, 我们可以直接通过:emp1.salary = 30000 如上的操作读操作、写操作
但是这种做法不安全. 比如, 我需要限制薪水必须为 1-10000 的数字. 这时候我们就需要通过使用装饰器 @property 来处理.
实操代码
class Employee:
def __init__(self, name, salary):
self.name = name
self.__salary = salary
@property # 相当于salary属性的getter方法
def salary(self):
print("月薪为{0}, 年薪为{1}".format(self.__salary, 12 * self.__salary))
return self.__salary
@salary.setter
def salary(self, salary): # 相当于salary属性的setter方法
if 0 < salary < 1000000:
self.__salary = salary
else:
print("薪水录入错误! 只能在0-1000000之间")
emp1 = Employee("时间静止", 8848.48)
print(emp1.salary)
emp1.salary = 12580
print(emp1.salary)
6. 关于None和判断的总结
None是什么
- 与C和JAVA不同, python中是没有 NULL 的, 取而代之的是 None . None 是一个特殊的常量, 表示变量没有指向任何对象.
- 在Python中, None 本身实际上也是对象, 有自己的类型 NoneType .
- 你可以将 None 赋值给任何变量, 但我们不能创建 NoneType 类型的对象
实操代码
obj = None obj2 = None print(type(None)) print(id(None)) print(id(obj)) print(id(obj2)) """输出结果None和其他类型的比较140706372487160 140706372487160 140706372487160 """
- None不是False, None不是0, None不是空字符串. None和任何其他的数据类型比较永远返回False
- if语句判断时,空列表[]、空字典{}、空元组()、0等一系列代表空和无的对象会被转换成False
- == 和 is 判断时, 空列表、空字符串不会自动转成 False
实操代码
# None不是False, None不是0, None不是空字符串. None和任何其他的数据类型比较永远返回False.
a = None
if a is None and a == None:
print("a是None") # 会执行
if a == False or a == 0:
print("None不等于False") # 不会被打印
a = []
b = ()
c = {}
d = ""
e = 0
f = None
# if语句判断时,空列表[]、空字典{}、空元组()、0等一系列代表空和无的对象会被转换成False
if (not a) and (not b) and (not c) and (not d) and (not e) and (not f):
print("if判断时, 空列表[]、空字符串、0、None等代表空和无的对象会被转换成False")
# == 和 is 判断时, 空列表、空字符串不会自动转成 False
if a == False or d == False:
print("==时, 空列表、空字符串不是False!") # 不会执行
if(e == False):
print("==时, 0会转成False")
三、实操作业
设计一个名为MyRectangle的矩形类来表示矩形。这个类包含:
- 左上角顶点的坐标:x,y
- 宽度和高度:width、height
- 构造方法:传入x,y,width,height。如果(x,y)不传则默认是0,如果width 和height不传,则默认是100.
- 定义一个getArea() 计算面积的方法
- 定义一个getPerimeter(),计算周长的方法
- 定义一个draw()方法,使用海龟绘图绘制出这个矩形
考察核心
- 构造方法与属性的默认值设置
- self 的使用
- 海龟绘图包的使用
实操代码
import turtle
class MyRectangle:
"""
设计一个名为MyRectangle的矩形类来表示矩形. 这个类包含:
(1) 左上角顶点的坐标:x, y
(2) 宽度和高度:width、height
(3) 构造方法:传入x, y, width, height. 如果(x, y)不传
则默认是0, 如果width 和height不传, 则默认是100.
(4) 定义一个getArea() 计算面积的方法
(5) 定义一个getPerimeter(), 计算周长的方法
(6) 定义一个draw()方法, 使用海龟绘图绘制出这个矩形
"""
__x = 0
__y = 0
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
def get_area(self):
print("该矩形面积为:", self.width * self.height)
def get_perimeter(self):
print("该矩形周长为:", (self.width + self.height) * 2)
def draw(self):
turtle.showturtle()
turtle.write("画出该矩形, 左上角坐标为:({0},{1}), 宽为:{2}, 高为:{3}".format(self.x, self.y, self.width, self.height))
turtle.speed(1)
turtle.goto(self.x, self.y)
turtle.goto(self.x, self.y - self.height)
turtle.goto(self.x + self.width, self.y - self.height)
turtle.goto(self.x + self.width, self.y)
turtle.goto(self.x, self.y)
m1 = MyRectangle(0, 100, 50, 100)
m1.get_area()
m1.get_perimeter()
m1.draw()



