一,面向过程和面向对象
面向过程:
把完成某个需求的所有步骤,从头到尾逐步实现。
根据开发需求,将某些功能独立的代码封装成一个又一个函数最后完成的代码,就是顺序地调用不同的函数。
面向对象:
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法。
二,类和对象
1.类和对象的区别
类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用。
- 特征被称为属性
- 行为被称为方法
对象是由类创建出来的一个具体存在。
类只有一个,而对象可以有很多个,不同的对象之间属性可能会各不相同。
2.类的设计
三大要素:
- 类名 这类事物的名字,满足大驼峰命名法 (名称)
- 属性 这类事物具有什么样的特征
- 方法 这类事物具有什么样的行为(动词)
3.dir内置函数
在python中对象几乎无所不在的,我们之前学习的变量,数据,函数都是对象。
使用内置函数 dir 传入标识符/数据,可以查看对象内的所有属性及方法。
提示:__ 方法名 __ 格式的方法是python提供的内置方法/属性。
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __ new __ | 方法 | 创建对象时,会被自动调用 |
02 | __ init __ | 方法 | 对象被初始化时,会被自动调用 |
03 | __ del __ | 方法 | 对象被从内存中销毁前,会被自动调用 |
04 | __ str __ | 方法 | 返回对象的描述信息,print 函数输出使用 |
4.定义简单的类
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
- 方法的定义格式和之前学习过的函数几乎一样
- 区别在于第一个参数必须是self
注意:类名的命名规则则要符号大驼峰命名法。
创建对象:
对象变量 = 类名()
引用概念
print(tom) # 对象在内存中的十六进制地址
addr = id(tom)
print("%d" % addr) # 对象在内部中十进制地址
print("%x" % addr) # 对象在内存中十六进制地址
5.方法中的self参数
给对象增加属性
注意:这种方式虽然简单,但是不推荐使用,因为在运行时,没有找到属性,程序会报错。
tom.name = "Tom"
class Cat:
def eat(self):
# 哪一个对象调用的方法,self就是哪一个对象的引用
print(" %s 爱吃鱼" % self.name)
6.初始化
01 初始化方法
当使用类名()创建对象时,会自动执行以下操作:
为对象在内存中分配空间——创建对象
为对象的属性设置初始值——初始化方法(init)
- 这个初始化方法就是 __ init __ 方法,__ init __ 是对象的内置方法。
- __ init __ 方法是专门用来定义一个类具有哪些属性的方法。
02 在初始化方法内部定义属性
- 在 __ init __ 方法内部使用 self.属性名 = 属性的初始值就可以定义属性。
- 定义属性之后,再使用Cat类创建的对象,都会拥有该属性。
class Cat:
def __init__(self):
print("这是一个初始化方法")
self.name = "Tom"
def eat(self):
print("%s 爱吃鱼" % self.name)
tom = Cat()
tom.eat()
03 初始化方法
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | __ del __ | 方法 | 对象被从内存中销毁前,会被自动调动 |
02 | __ str __ | 方法 | 返回对象的描述信息,print函数输出使用,注意:必须返回字符串 |
另:del 关键字可以删除对象。
class Cat:
def __init__(self,new_name):
self.name = new_name
print("%s 来了" % self.name)
def __str__(self): # 必须返回一个字符串
return "我是小猫 [%s]" % self.name
def __del__(self):
print("%s 我去了" % self.name)
tom = Cat("Tom")
print(tom)
04 案例分析
- 被使用的类,通常应该先开发。
- 一个对象的属性可以是另外一个类创建的对象。
- 在定义属性时,如果不知道设置什么初始值,可以设置为 None 。
class Gun:
def __init__(self, model):
# 1.枪的型号
self.model = model
# 2.子弹的数量
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
def shoot(self):
# 1.判断子弹数量
if self.bullet_count <= 0:
print("[%s] 没有子弹了..." % self.model)
return
# 2.发射子弹 -1
self.bullet_count -= 1
# 3.提示发射信息
print("[%s] 突突突...[%d]" % (self.model, self.bullet_count))
class Soldier:
def __init__(self, name):
# 1.姓名
self.name = name
self.gun = None
def fire(self):
# 1.判断士兵是否有枪
if self.gun is None:
print("[%s] 还没有枪..." % self.name)
return
# 2.高喊口号
print("冲啊... [%s]" % self.name)
# 3.让枪装填子弹
self.gun.add_bullet(50)
# 4.让枪发射子弹
self.gun.shoot()
# 1.创建枪对象
ak47 = Gun("AK47")
# 2.创建许三多
xusanduo = Soldier("许三多")
xusanduo.gun = ak47
xusanduo.fire()
print(xusanduo.gun)
在 Python 中针对 None 比较时,建议使用 is 判断。
身份运算符:
运算符 | 描述 | 实例 |
---|---|---|
is | is 是判断两个标识符是不是引用同一个对象 | x is y ,类似于 id(x) == id(y) |
is not | is not 是判断两个标识符是不是引用不同对象 | x is not y ,类似于id(a) != id(b) |
05 私有属性和私有方法
对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到。
用法:在属性名或方法名前加两个下划线:
# 私有属性,在外界不难被直接访问
print(xiaofang.__age)
# 私有方法,同样不允许在外界直接访问
xiaofang.__secret
提示:在日常开发中,不要使用这种方式访问对象的私有属性或私有方法。
python中并没有真正意义的私有
可用 下划线 类名 方法名/属性名 来访问。
class Woman:
def __init__(self,name):
self.name = name
self.__age = 18
def __secret(self):
pass
xiaofang = Woman("小芳")
print(xiaofang._Woman__age)
xiaofang._Woman__secret()
三,继承
面向对象三大特性:
- 封装根据职责将属性和方法封装到一个抽象的类中。
- 继承实现代码的重用,相同的代码不需要重复的编写。
- 多态不同的子类对象调用相同父类的方法,产生不同的执行结果,增加代码的灵活度。(以继承和重写父类方法为前提)
1.继承的基本概念
(1)继承的概念
子类拥有父类的所有属性和方法。
(2) 继承的语法
class 类名(父类名):
pass
(3)专业术语
- Dog 类是 Animal 类的子类,Animal 类是 Dog 类的父类,Dog 类从 Animal 类继承。
- Dog 类是 Animal类的派生类,Animal类是 Dog 类的基类 ,Dog 类从 Animal 类派生。
(4)继承的传递性
子类拥有父类以及父类的父类中继承的所有属性和方法。
2.方法的重写
(1)覆盖父类的方法
具体的实现方式,在子类中定义了和父类同名的方法并且实现。
(2)对父类方法进行扩展
- 在子类重写父类的方法。
- 在需要的位置使用 super().父类方法 来调用父类方法的执行。
- 代码其他的位置针对子类的需求,编写子类特有的代码实现。
(3)关于 super
- 在 Python 中 super 是一个特殊的类
- super() 就是使用 super类创建出来的对象
- 最常见使用的场景就是在重写父类方法时,调用父类中封装的方法
在Python 2.x 时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
- 目前在 python3.x 还支持这种方式。
- 这种方法不推荐使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改。
3.父类的私有属性和私有方法
- 子类不能在自己的方法内部,直接访问父类的私有属性或私有方法。
- 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法。
4.多继承
子类可以拥有多个父类,并且具有所有父类的属性和方法。
语法:
class 子类名(父类名1,父类名...):
pass
注意:开发是时,应该尽量避免容易产生混淆的情况!— 如果父类之间,存在同名的属性或者方法,应该尽量避免使用多继承。
5.同名时继承顺序:
Python 中的MRO—方法搜索顺序
语法:
print(C.__mro__)
6.新式类与旧式类
object 是 Python 为所有对象提供的基类,提供有一些内置的属性和方法,可以使用 dir 函数查看。
- 新式类:以object为基类的类,推荐使用。
- 经典类:不以object为基类的类,不推荐使用。
- 在 python 3.x 中定义类时,如果没有指定父类,会默认使用object作为该类的基类—Python 3.x 中定义的类都是新式类。
- 在 python 2.x 中定义类时,如果没有指定父类,则不会以object作为基类。
- 新式类和经典类在多继承时,会影响到方法的搜索顺序。
为了保证编写的代码能够同时在Python2.x和Python3.x运行:
今后在定义类时,如果没有父类,建议统一继承自object:
class 类名(object):
pass
四,多态
1.术语——实例
创建出来的对象叫做类的实例。
创建对象的动作叫做实例化。
对象的属性叫做实例属性。
对象调用的方法叫做实例方法。
- 每一个对象都有自己独立内存空间,保存各自不同的属性。
- 各个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部。
2.类对象
- 在程序运行时,类同样会加载到内存中。
- 在Python中,类是一个特殊的对象—类对象。
- 在程序运行时,类对象在内存中只有一份,使用一个类可以创建多个实例。
- 除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法。
类属性;
类方法;
通过 类名. 的方式可以访问类的属性或者调用类的方法。
例:
定义类的属性:
class Tool(object):
# 使用赋值语句,定义类属性,记录创建工具对象的总数
count=0
def __init__(self,name):
self.name = name
# 针对类属性做一个 +1
Tool.count += 1
tool1 = Tool("斧头")
print(Tool.count)
在Python中属性的获取存在一个向上查找的机制:
因此,要访问类属性有两种方式:
- 类名.类属性
- 对象.类属性(不推荐)
注意:
如果使用 对象.类属性 = 值 赋值语句,只会给对象添加一个属性,而不会影响到类属性的值。
3.类方法
- 类方法就是针对对象定义的方法。
- 在类方法内部可以直接访问类属性或者调用其他的类方法。
语法:
@ classmethod
def 类方法名(cls):
pass
例:
@classmethod
def show_tool_count(cls):
print("工具对象的数量 %d " % cls.count)
4.静态方法
- 既不需要访问实例属性或者调用实例方法。
- 也不需要访问类属性或者调用类方法
语法:
@staticmethod
def 静态方法名():
pass
例:
class Dog(object):
@staticmethod
def run():
print("小狗要跑...")
# 通过类名,调用静态方法---不需要创建对象
# 不访问实例属性/类属性
Dog.run()
5.小结
- 实例方法——方法内部需要访问实例属性
- 类方法——方法内部只需要访问类属性
- 静态方法——方法内部,不需要访问实例属性和类属性
五,单例
1.单例设计模式
设计模式是前人工作的总结和提炼,通常被人们广泛流传的设计模式,都是针对某一特定问题的成熟方案。
单例设计模式目的:
目的——让类创建的对象在系统中只有唯一的一个实例,每一次执行1类名()返回的对象,内存地址是相同的。
2.__ new __方法
__ new __ 是一个由object 基类提供的内置的静态方法。
重写new方法:
class MusicPlayer(object):
def __new__(cls,*args,**kwargs):
# 1.创建对象时,new方法会被子弹调用
print("创建对象,分配空间")
# 2.为对象分配空间
instance = super().__new__(cls)
# 3.返回对象的引用
return instance
def __init__(self):
print("播放器初始化")
# 创建播放器对象
player = MusicPlayer()
print(player)
单例模式代码实现,同时让初始化动作只被执行一次:
class MusicPlayer(object):
# 记录第一个被创建的引用
instance = None
# 记录是否执行过初始化动作
init_flag = False
def __new__(cls, *args, **kwargs):
# 1.判断类属性是否是空对象
if cls.instance is None:
# 2.调用父类的方法,为第一个对象分配空间
cls.instance =super().__new__(cls)
# 3.返回类属性保存的对象引用
return cls.instance
def __init__(self):
# 1.判断是否执行过初始化动作
if MusicPlayer.init_flag:
return
# 2.如果没有执行过,在执行初始化动作
print("初始化播放器")
# 3.修改类属性的标记
MusicPlayer.init_flag = True
# 创建多个对象
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
六,异常
- 程序在运行时,如果Python解释器遇到一个错误,会停止程序的执行,并且提示这些错误信息,这就是异常。
- 程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出(raise)异常。
1.捕获异常
在程序开发中,如果对某些代码的执行不能确定是否正确,可以增加 try(尝试) 来捕获异常。
捕获异常最简单的语法格式:
try:
尝试执行的代码
except:
出现错误的处理
try:
num = int(input("请输入一个整数:"))
except:
print("请输入正确的整数")
print("-" * 50)
无论是否出现错误,都会继续执行下面的代码。
2.捕获未知错误
如果希望程序无论出现任何错误,都不会因为Python解释器抛出异常而被终止,可以再增加一个except。
try:
num = int(input("输入一个整数:"))
result = 8 / num
print(result)
except ValueError:
print("请输入正确的整数")
except Exception as result:
print("未知错误 %s" % result)
3.异常捕获完整语法
4.异常的传递
当函数/方法执行出现异常,会将异常传递给函数/方法的调用一方,如果没有异常处理,程序才会被终止。
利用异常的传递性,在主程序捕获异常。
def demo1():
return int(input("输入整数:"))
def demo2():
return demo1()
try:
print(demo2())
except Exception as result:
print("未知错误 %s" % result)
4.抛出raise异常
根据应用程序特有的业务需求主动抛出异常:
- 创建一个 Exception 的对象
- 使用 raise 关键字抛出异常对象
def input_password():
pwd = input("请输入密码:")
# 判断密码长度 >=8,返回用户输入的密码
if len(pwd) >= 8:
return pwd
# 如果 < 8 主动抛出异常
print("主动抛出异常")
# 创建异常对象 - 可以使用错误信息字符串作为参数
ex = Exception("密码长度不够")
# 主动抛出异常
raise ex
try:
print(input_password())
except Exception as result:
print(result)
七,模块
- 每一个以扩展名py结尾的Python源代码文件都是一个模块。
- 模块名同样也是一个标识符,需要符合标识符命名规则。
- 在模块中定义的全局变量,函数,类都是提供给外界直接使用的工具。
- 模块就好比是工具包中的工具,就需要导入这个模块
1.模块的导入方式
01 import 导入
import 模块名1,模块名2
提示:在导入模块时,每个导入应该独占一行。
import 模块名1
import 模块名2
导入之后:
通过模块名.使用模块提供的工具——全局变量,函数,类。
如果模块的名字 as 指定模块的别名
如果模块的名字太长,可以使用 as 指定模块的名字,以方便在代码中的使用。
import 模块名1 as 模块别名
注意:模块别名应该符合大驼峰命名法。
02 from…import 导入
如果希望从某一个模块中,导入部分工具,就可以使用 from…import 的方式。
从模块导入某一个工具:
from 模块名1 import 工具名
导入之后,不需要通过模块名,可以直接使用模块提供的工具——全局变量,函数,类。
注意:
如果两个模块,存在两个同名的函数,那么导入模块的函数,会覆盖掉先前导入的函数。
开发时 import 代码应该统一写在代码的顶部,更容易及时发现冲突。
一旦发生冲突,可以使用 as 关键字给其中一个工具起一个别名。
03 from…import *
从模块导入所有工具
from 模块名1 import *
注意:
这种方式不推荐使用,因为函数重名并不会有提示,出现问题不好排查。
2.模块的搜索顺序
Python 的解释器在导入模块时会:
- 搜索当前目录指定模块名的文件,如果有就直接导入。
- 如果没有再搜索系统目录
注意:在开发时,给文件起名,不要和系统的模块文件重名。
python 中每一个模块都有一个内置属性 __ file __ 可以查看模块的完整路径。
import random
print(random.__file__)
3. __ name __ 属性
原则——每一个文件都应该是可以导入的。
- 一个独立的 Python 文件就是一个模块。
- 在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍。
__ name __ 属性可以做到,测试模块的代码只在测试情况下被运行,而在被导入时不会被执行!
在很多 Python 文件中都会看到以下格式的代码。
八,包
1.包的基本使用
概念:
- 包是一个包含多个模块的特殊目录
- 目录下有一个特殊的文件 __ init __ . py
- 包名的命名方式和变量名一致,小写字母 + “_”
好处:
使用import 包名 可以一次性导入包中所有的模块。
__ init __ .py
要在外界使用包中的模块,需要在 __ init __ .py 中指定对外界提供的模块列表。
# 从当前目录导入模块列表
from .import send_message
from .import receive_message
2.制作分享模块压缩包
(1)创建setup.py
(2)构建模块
(3)生成发布压缩包
安装模块
卸载模块
3.pip安装第三方模块
第三方模块通常是指由知名的第三方团队开发并且被程序员广泛使用的python包/模块
- 例如 pygame 就是一套非常成熟的游戏开发模块
安装与卸载命令:
在Mac下安装ipython
九,文件
1.基本文件类型
01 文本文件
可以使用文本编辑软件查看
本质上还是二进制文件
例如:python 的源程序
02 二进制文件
保存的内容不是给人之间阅读的,而是提供给其他软件使用的。
例如:图片文件,音频文件,视频文件等等
二进制文件不能使用文本编辑软件查看
本质上都是二进制文件
2.文件的基本操作
操作文件的套路:
(1)打开文件
(2)读,写文件
- 读:将文件内容存入内存
- 写:将内存内容写如文件
(3)关闭文件
3.操作文件的函数/方法
在python中要操作方法需要记住1个函数和3个方法。
序号 | 函数/方法 | 说明 |
---|---|---|
01 | open | 打开文件,并且返回文件操作对象 |
02 | read | 将文件内容读取到内存 |
03 | write | 将指定内容写入文件 |
04 | close | 关闭文件 |
- open函数负责打开文件,并且返回文件对象
- read/write/close 三个方法都需要通过文件对象来调用
read方法——读取文件
- open函数第一个参数是要打开的文件名(文件名区分大小写)
如果文件存在,返回文件操作对象
如果文件不存在,会抛出异常 - read 方法可以一次性读入并返回文件的所有内容
- close 方法负责关闭文件
如果忘记关闭文件,会造成系统资源消耗,而且会影响后续对文件的访问。
文件指针
文件指针标记从哪个位置开始读取数据
第一次打开文件时,通常文件指针会指向文件的开始位置
当执行了read方法后,文件指针会移动到读取内容的末尾
4.打开文件的方式,并且返回文件对象
语法:
f = open ("文件名","打开方式")
访问方式 | 说明 |
---|---|
r | 以只读方式打开文件,文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛出异常 |
w | 以只写方式打开文件,如果文件存在,则会覆盖。如果文件不存在,创建新文件 |
a | 以追加方式打开文件。如果文件存在,文件的指针将会放在文件的结尾。如果文件不存在,创建新文件写入 |
r+ | 以读写方式打开文件,文件的指针将会放在文件的开头。如果文件不存在,抛出异常 |
w+ | 以读写方式打开文件,如果文件存在,则会覆盖。如果文件不存在,创建新文件 |
a+ | 以读写方式打开文件。如果文件存在,文件的指针将会放在文件的结尾。 如果文件不存在,创建新文件写入 |
提示:
频繁的移动文件指针,会影响文件的读写效率,开发时更多时候会以只读,只写的方式来操作文件。
5.按行读取文件内容
read 方法默认会把文件的所有内容一次性读取到内存
如果文件太大,对内存的占用会非常严重
readline方法
readline 方法可以一次读取一行内容
方法执行后,会吧文件指针移动到下一行,准备再次读取
案例——复制文件
小文件复制:
file_read = open("README")
file_write = open("REAMDE[复件]","w")
text = file_read.read()
file_write.write(text)
file_read.close()
file_write.close()
大文件复制:
file_read = open("README")
file_write = open("REAMDE[复件]","w")
while True:
text = file_read.readline()
if not text:
break
file_write.write(text)
file_read.close()
file_write.close()
6.文件/目录的常用管理操作
导入os模块
文件操作:
目录操作:
7.文本文件的编码格式
python2.x 默认使用ASCII编码
python3.x 默认使用UTF-8编码
ASCII 编码:
计算机只有256个ASCII字符
一个 ASCII 在内存中占用1个字符的空间
8个 0/1 的排列组合方式一共有256种,也就是 2**8
UTF-8 编码方式:
计算机中使用1~6个字节来表示一个UTF-8 字符,涵盖了地球上几乎所有地区的名字。
大多数汉字会使用3个字节表示
UTF-8 是 UNICODE 编码的一种编码格式
Python 2.x 中如何使用中文
在 Python 2.x 的第一行使用以下代码(官方)
# *-* coding:utf-8 *-*
unicode 字符串
在Python 2.x 中,即使指定了文件使用 UTF-8 的编码方式,在遍历字符串时,仍然会以字节为单位遍历字符串。
解决方法:在字符串的引号前,增加一个小写字母u,告诉解释器这是一个unicode 字符串。
# *-* coding:utf-8 *-*
hello_str = u"hello世界"
print(hello_str)
for c in hello_str:
print(c)
十,eval 函数
将字符串当成有效的表达式来求值并返回计算错误:
在开发时,千万不要使用 eval 直接转换input 的结果:
__ import __('os').system('ls')
等价代码:
import os
os.system("终端命令")