文章目录
一、函数
1.python中函数的定义
所谓函数,就是把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用
函数的调用包括两个步骤:
1.定义函数一一封装 独立的功能
2.调用函数一一享受 封装 的成果
函数的作用,在开发程序时,使用函数可以提高编写的效率以及代码的 重用
2.如何定义函数
1.def 是英文 define(定义) 的缩写
2.函数名称 应该能够表达 函数封装代码 的功能,方便后续的调用
3.函数名称 的命名应该 符合 标识符的命名规则
可以由字母、下划线和数字组成
不能以数字开头
不能与关键字重名
3.函数的参数
开发一个 sum_2_num 的函数
函数能够实现 两个数字的求和 功能
代码如下:
def sum_2_num():
num1 = 10
num2 = 20
result = num1 + num2
print("%d + %d = %d" % (num1, num2, num1 + num2))
sum_2_num()
在函数名的后面小括号内部填写 参数
多个参数之间使用 , 分离
def sum_2_num(num1,num2):
result = num1 + num2
print("%d + %d = %d" % (num1, num2, num1 + num2))
sum_2_num(50,20)
4.参数的作用
函数,把具有独立功能的代码块组织为一个小模块,在需要的时候调用函数的参数、增加函数的通用性,针对 相同的数据处理逻辑、能够适应 更多的数据
1.在函数内部,把参数当做变量使用,进行需要的数据处理
2.函数调用时,按照函数定义的参数顺序、把希望在函数内部处理的数据 通过 参数 传递
形参:定义函数时,小括号中的参数,是用来接收参数用的,在函数内部作为变量使用
实参:调用函数时,小括号中的参数,是用来把数据传递到函数内部用的
5.函数的嵌套调用
一个函数里面又 调用了 另外一个函致,这就是 函数嵌套调用
如果函数 test2 中,调用了另外一个函数 test1
那么执行到调用 test1 函数时,会先把函数test1中的任务都执行完
才会回到 test2 中调用函数 test1 的位置,继续执行后续的代码
def test1():
print("*" * 50)
print("test 1")
print("*" * 50)
def test2():
print("_" * 50)
print("test 2")
test1()
print("_" * 50)
test2()
6.函数的递归
函数调用自身的编程技巧称为递归
递归函数的特点:
- 一个函数 内部 调用自己
- 函数内部可以调用其他函数,当然在函数内部也可以调用自己
代码特点:
- 函数内部的 代码 是相同的,只是针对 参数 不同,处理的结果不同
- 当参数满足一个条件时,函数不再执行
这个非常重要,通常被称为递归的出口,否则会出现死循环
7.内部函数
在函数内部定义另一个函数,也就是函数的嵌套
在外部函数的作用域内,外部函数可以随意调用内部函数
由于内部函数的整个定义过程都在外部函数中,所以出了外部函数就无法再被调用了
def outside():
print('外部函数被调用')
def inside():
print('内部函数被调用')
inside()
执行结果
>>>outside()
外部函数被调用
内部函数被调用
>>>inside() NameError: name 'inside' is not defined
内部函数的特点:
1.可以访问外部函数的变量
2.内部函数可以修改外部函数的可变类型的变量 比如:list1
3.函数内部修改全局变量,要加global 变量名,内部函数要使用外部函数的不可变类型的变量的时候要加nonlocal
4.locals() 查看本地变量有哪些,以字典的形式输出
globals() 查看全局变量有哪些,以字典的形式输出()
内部函数案例:
- 使用locals() 内置函数进行查看,可以看到在当前函数中声明的内容有哪些
- locals() 返回一个字典。 以 key:values的形式存在
a = 100 # 全局变量
def func():
b = 99
# 内部函数
def inner_func():
nonlocal b
global a
c = 88
# 尝试修改
b += 1
a += 10
c += 12
# 尝试打印
print(a, b, c)
inner_func()
print(locals())
print(globals())
func()
8.闭包
如果在一个内部函数里引用了外部函数的变量,此时这个内部函数就被称为闭包
#格式:
def 外部函数():
...
def 内部函数()
...
return 内部函数
二、集合
1.集合的操作
集合的定义:
某些指定的对象集在一起就成为一个集合,其中每一个对象叫元素
格式:集合名 = {元素1,元素2,…}
例:my_set = {1, 3, 5}
集合是无序的-> 不支持下标索引
集合是可变的数据类型
集合中的元素是唯一的
集合一般用于元组或者列表中的元素去
定义一个空的集合 my_set = set()
注意: my_set = {} 这样的写法为一个空字典
集合的常见操作:
.add() #增加元素
s1 = set()
s1.add('hello')
s1.add('小猪佩奇')
s1.add('lucy')
print(s1)
.update() #可以添加多个元素
t1 = (‘林志玲’, ‘言承旭’)
s1.update(t1)
print(s1)
s1.add(t1) # t1作为一个元素被添加进集合
print(s1)
.remove() #增加删除元素,如果元素存在则删除,不存在则报错:KeyError:
s1.remove("言承旭")
print(s1)
s1.remove('道明寺')
print(s1)
.pop() #随机删除
s1.pop()
print(s1)
.discard() # 类似remove() 移除不存在的元素的时候会报错
s1.discard("道明寺")
print(s1)
.clear() #清空集合
s1.clear()
print(s1)
2.交集和并集
集合之差集(-)
.difference()
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
new_set = set1 - set2
new_set1 = set2 - set1
print(new_set)
print(new_set1)
new_set = set1.difference(set2)
new_set1 = set2.difference(set1)
print(new_set)
print(new_set1)
集合之交集( & )
.intersection()
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
new_set = set1 & set2
print(new_set)
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
# new_set = set1.intersection(set2)
new_set = set2.intersection(set1)
print(new_set)
集合之并集( | )
.union()
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
new_set = set1 | set2
print(new_set)
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
new_set = set1.union(set2)
print(new_set)
集合之对称差集(^)
在前一个集合或后一个集合中但不会同时出现在二者中
.symmetric_difference()
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
#new_set = (set1 | set2) - (set1 & set2)
new_set = set1 ^ set2
print(new_set)
# new_set = set1.symmetric_difference(set2)
new_set = set2.symmetric_difference(set1)
print(new_set)
3.可变和不可变类型转换
不可变类型,内存中的数据不允许被修改:
数字类型 int bool float,complex,long(2.x)
字符串 str
元组 tuple
可变类型,内存中的数据可以被修改
列表 list
字典 dict
集合/frozenset()除外,frozenset() 返回一个冻结的集合,冻结后集合不能再添加或删除任何元素
案例演示:
列表
a = [1, 2, 3]
print(id(a))
a.append(999)
print(a)
print(id(a))
a.remove(2)
print(a)
print(id(a))
a.clear()
print(a)
print(id(a))
字典
d = {"name": "xiaoming"}
print(id(d))
d["age"] = 20
print(d)
print(id(d))
d.pop("age")
print(d)
print(id(d))
d.clear()
print(d)
print(id(d))
注意:
可变类型的数据变化,是通过方法来实现的
如果给一个可变类型的变量,赋值了一个新的数据,引用会被修改
变量不再对之前的数据引用
变量改为对新赋值的数据引用
字典的 key 只能使用不可变类型的数据
d = {"name": "小明"}
d[1] = "整数"
d[(2,)] = "元组"
print(d)
运行结果:
{'name':'小明',1:'整数',(2,):'元组'}
数据类型转换
str() - int() list() set() tuple()
str() - int() list() set() tuple()
list() - set() tuple() dict() # 转换成字典,元素要有固定的格式
dict() - list() # 只能保存字典的键
三、面向对象基础
1.面对对象
面对对象(OOP)基本概念
把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。
面向过程和面向对象,是两种不同的编码方式
过程和函数
- 过程是早期的一个编程概念
- 过程类似于函数,只能执行,但是没有返回值
- 函数不仅能执行,还可以返回结果
面向过程–怎么做
- 把完成某一个需求的 所有步骤 从头到尾 逐步实现
- 根据开发需求,将某些 功能独立 的代码 封装 成一个有一个 函数
- 最后完成的代码,就顺序地调用不同的函数
特点
- 注重步骤与过程,不注重职责分工
- 如果需求复杂,代码会变得很复杂
- 开发复杂项目,没有固定的套路,开发难度很大
面向对象–谁来做
- 在完成某一个需求前,首先 确定职责一一要做的事情(方法)
- 根据 职责 确定不同的 对象,在 对象 内部封装不同的方法(多个)
- 最后完成的代码,就顺序地让 不同的对象 调用 不同的方法
特点
- 注重对象和职责,不同的对象承担不同的职责
- 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
- 需要在面向过程基础上,再学习一些面向对象的语法
类
类和对象 是 面向对象编程的两个核心概念
类是对一群具有相同特征或者行为 的事物的一个统称,是抽象的,不能直接使用
特征 被称为属性
行为 被称为方法
对象
对象 是 由类创建出来的一个具体存在,可以直接使用
由哪一个类创建出来的 对象,就拥有在哪一个类 中定义的:属性、方法
对象就是相当于用图纸制造的飞机
在程序开发中,应该先有类,再有对象
类和对象的关系
- 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象
- 类只有一个,而对象可以有很多个
不同的对象之间属性 可能会各不相同 - 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少
类的设计
在程序开发中,要设计一个类,通常需要满足三个要素:
1.类名 这类事物的名字,满足大驼峰命名法
2.属性 这类事物具有什么样的特征
3.方法 这类事物具有什么样的行为
大驼峰命名法
CapWords
1.每一个单词的首字母大写
2.单词与单词之间没有 下划线
2.定义简单化
定义简单的类(只包含方法)
面向对象是更大的封装, 在一个类中封装多个方法, 这样通过这个类创建出来的对象,就可以直接调用这些方法了!
定义只包含方法的类:
案例演示
需求:小猫爱吃鱼
class Cat:
"""这是一个猫类"""
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫在喝水")
# 创建猫对象
tom = Cat()
tom.eat()
tom.drink()
print(tom)
结果输出
小猫爱吃鱼
小猫在喝水
<__mian__.Cat.object at 0x000001ccc70AE1D0>
3.初始化
初始化方法
当使用类名( ) 创建对象时,会自动执行以下操作
1.为对象在内存中分配空间一一创建对象--- __new__
2.为对象的属性设置初始值一一初始化方法(__init__)
这个 初始化方法 就是__init__方法, __init__是对象的内置方法
__init__方法是 专门 用来定义ー个类 具有 哪些属性的方法!
在初始化方法内部定义属性
在__init__方法内部使用 self.属性名=属性的初始值 就可以 定义属性
定义属性之后,再使用 Cat 类创建的对象,都会拥有该属性
class Cat:
"""这是一个猫类"""
def __init__(self):
print("初始化方法")
# 定义用 Cat 类创建的猫对象都有一个 name 的属性
self.name = "Tom"
def eat(self):
print("%s爱吃鱼" % self.name)
# 使用类名() 创建对象的时候,会自动调用初始化方法 __init__
tom = Cat()
tom.eat()
四、私有属性单继承和重写
1.私有属性
私有属性和方法
在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到
私有属性就是对象不希望公开的属性
私有方法就是对象不希望公开的方法
定义方式
在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
私有属性在外界不能被直接访问
class Girl:
def __init__(self, name):
self.name = name
self.__age = 18 # 将 age 设置成私有属性
def secret(self):
print("{}的年龄是{}岁".format(self.name, self.__age))
Anna = Girl("小美")
Anna.secret()
print(Anna)
# 私有属性,不能被外界访问!
print(Anna.__age)
程序运行结果:
AttributeError: 'Girl' object has no attribute '__age'
私有方法在外界也不能直接调用
2.单继承
面对对象三大特性
1.封装根据职责将属性和方法封装到一个抽象的 类中
2.继承实现代码的重用,相同的代码不需要重复的编写
3.多态不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度
继承的概念:子类拥有父类的所有方法和属性
继承的语法:
class 子类名(父类名):
pass
子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
子类中应该根据职责,封装子类特有的属性和方法
继承的传递性
C类从B类继承,B类又从A 类继承
那么C类就具有B类和A 类的所有属性和方法
子类拥有父类以及父类的父类中封装的所有属性 和方法
3.方法的重写
子类拥有父类的所有方法和属性
子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
当父类的方法实现不能满足子类需求时,可以对方法进行重写(override)
重写 父类方法有两种情况
- 覆盖父类的方法
- 对父类方法进行扩展
覆盖父类的方法:
- 如果在开发中,父类的方法实现和子类的方法实现 完全不同
- 就可以使用覆盖的方式,在子类中重新编写父类的方法实现
具体的实现方式,就相当于在子类中定义了一个 和父类相同名的方法并且实现
重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
对父类方法进行扩展
如果在开发中,子类的方法实现中包含父类的方法实现
父类原本封装的方法实现是子类方法的一部分
就可以使用扩展的方式
在子类中重写父类的方法
在需要的位置使用 super( ).父类方法 来调用父类方法的执行
代码其他的位置针对子类的需求,编写子类特有的代码实现
关于 super
在python 中 super 是一个 特殊的类
super( )就是使用 super 类创建出来的对象
最常使用的场景就是在重写父类的方法时,调用 在父类中封装的方法实现
五、异常获取
1.异常
异常的概念
程序在运行时,如果Python解释器遇到一个错误,会停止程序的执行,并且提示一些错误信息。这就是异常
程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出(raise)异常
异常捕获 – 简单的捕获异常语法
程序在运行时,如果对某些代码的执行不能确定是否正确,可以增加try来捕获异常
捕获异常最简单的语法格式:
错误类型捕获
在程序执行时,可能会遇到不同类型的异常,并且需要针对不同类型的异常,做出不同的响应,这个时候,就需要捕获错误类型了
语法如下:
当 Python 解释器 抛出异常 时,最后一行错误信息的第一个单词,就是错误类型
捕获错误未知
在开发时,要预判到所有可能出现的错误,还是有一定难度的
如果希望程序无论出现任何错误,都不会因为 Python 解释器抛出异常而被终止,可以增加一个except
语法如下:
except Exception as result:
print("未知错误%s" % result)
异常捕获完整语法
异常的传递
- 异常的传递 — 当函数/方法执行出现异常,会将异常传递给函数/方法的调用一方
- 如果传递到主程序,仍然没有异常处理,程序才会被终止
提示: - 在开发中,可以在主函数中增加 异常捕获
- 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中
- 这样就不需要在代码中,增加大量的异常捕获,能够保证代码的整洁
六、多继承和多态
1.多继承
多继承:子类可以拥有多个父类,并且具有所有父类的属性和方法
class Base1:
def demo1(self):
print("Base1 的方法")
class Bass2:
def demo2(self):
print("Base2 的方法")
class Derived(Base1, Bass2):
def test(self):
self.demo1()
self.demo2()
test = Derived()
test.demo1()
test.demo2()
开发时,应该尽量避免这种容易产生混淆的情况!——如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承
2.多态
1.封装根据职责将属性和方法封装到一个抽象的 类 中
定义类的准则
2.继承 实现代码的重用,相同的代码不需要重复的编写
设计类的技巧
子类针对自己特有的需求,编写特定的代码
3.多态不同的子类对象,调用相同的父类方法,产生 不同的执行结果
多态可以增加代码的灵活度
以继承和重写父类方法为前提
是调用方法的技巧,不会影响到类的内部设计
七、类属性类方法静态方法单例模式
1.类属性
类的结构
术语–实例
- 使用面向对象开发,第1步是设计类
- 使用类名( ) 创建对象, 创建对象的动作有两步:
在内存中为对象分配空间
调用初始化方法 init 为对象初始化 - 创建对象后,内存中就有了一个对象的实实在在的存在 — 实例
通常也会把:
1.创建出来的对象叫做类的实例
2.创建对象的动作叫做实例化
3.对象的属性叫做实例属性
4.对象调用的方法叫做实例方法
在程序执行时
对象各自拥有自己的实例属性
调用对象方法,可以通过 self
访问自己的属性
调用自己的方法
结论
每一个对象,都有自己独立的内存空间,保存各自不同的属性
对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用,传递到方法内部
类属性和实例属性
类属性就是给类对象中定义的属性
通常用来记录与这个类相关的特征
类属性不会用于记录具体对象的特征
如果使用 对象.类属性 = 值 赋值语句,只会给对象添加一个属性,而不会影响到类属性的值
2.类方法和静态方法
- 类属性 就是针对 类对象 定义的属性
使用赋值语句在 class 关键字下方可以定义类属性
类属性用于记录与这个类相关的特征 - 类方法 就是记录与这个类相关的方法
在类方法内部可以直接访问类属性或者调用其他的类方法
语法如下
类方法需要用修饰器 @classmethod 来标识,告诉解释器这是一个类方法
类方法的第一个参数应该是 cls
由哪一个类调用的方法,方法内的cls就是哪一个类的引用
这个参数和实例方法的第一个参数和self 类似
提示:使用其他名称也可以,不过习惯使用 cls
通过类名,调用类方法,调用方法时,不需要传递 cls 参数
在方法内部
可以通过cls访问类的属性
也可以通过cls 调用其他的类方法
静态方法
- 在开发时,如果需要在类中封装一个方法,这个方法:
既不需要访问实例属性或者调用实例方法
也不需要访问类属性或者调用类方法 - 这个时候,可以把这个方法封装成一个静态方法
语法如下:
3.单例模式
单例 设计模式
-
设计模式
设计模式是前人工作的总结和提炼,通常,被人们广泛流传的设计都是针对某一特定问题的成熟的方案
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性 -
单例设计模式
目的 — 让类创建的对象,在系统中只有唯一的一个实例
每一次执行类名( ) 返回的对象,内存地址是相同的 -
单例设计模式的应用场景
音乐播放 对象
回收站 对象
打印机 对象
……
new 方法
- 使用类名( )创建对象时,Python的解释器首先 会 调用 new 方法为对象分配空间
- new 是一个由object 基类提供的内置的静态方法,是要作用有两个:
在内存中为对象分配空间
返回对象的引用 - Python 的解释器获得对象的引用后,将引用作为第一个参数,传递给 __init__方法
重写 new 方法的代码非常固定!
重写 new 方法 一定要return super( ).new(cls)
否则Python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法
注意: new 是一个静态方法,在调用时需要主动传递 cls 参数
Python 中的单例
- 单例 — 让类创建的对象,在系统中只有唯一的一个实例
定义一个类属性,初始值是 None ,用于记录单例对象的引用
重写 new 方法
如果类属性 is None,调用父类方法分配空间,并在类属性中记录结果
返回类属性中记录的对象引用
案例
class MusicPlayer(object)
pass
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)
程序运行结果
<__main__.MusicPlayer object at 0x00A0E6B8>
<__main__.MusicPlayer object at 0x00A0EC10>
只执行一次初始化工作
- 在每次使用类名( ) 创建对象时,Python的解释器都会自动调用两个方法:
new 分配空间
init 对象初始化 - 在对 new 方法改造后,每次都会得到第一次被创建对象的引用
- 但是:初始化方法还会被再次调用