python基础

简介

解释器

计算机不能直接理解任何除机器语言以外的语言,所以必须要把程序员所写的程序语言翻译成机器语言,计算机才能执行程序。将其他语言翻译成机器语言的工具,被称为编译器。

编译器翻译的方式有两种:一个是编译,另外一个是解释。两种方式之间的区别在于翻译时间点的不同。当编译器以解释方式运行的时候,也称之为解释器。

编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如 C、C++。

解释型语言:解释型语言编写的程序不进行预先编译,以文本方式存储程序代码,会将代码一句一句直接运行。在发布程序时,看起来省了道编译工序,但是在运行程序的时候,必须先解释再运行。

编译型语言和解释型语言对比

  • 速度 —— 编译型语言比解释型语言执行速度快
  • 跨平台性—— 解释型语言比编译型语言跨平台性好

python的设计目标 

  • 一门简单直观的语言并与主要竞争者一样强大
  • 开源,以便任何人都可以为它做贡献
  • 代码像纯英语那样容易理解
  • 适用于短期开发的日常任务

这些想法中的基本都已经成为现实,Python 已经成为一门流行的编程语言

python的设计哲学 

  • 优雅
  • 明确
  • 简单

Python 开发者的哲学是:用一种方法,最好是只有一种方法来做一件事。

如果面临多种选择,Python 开发者一般会拒绝花俏的语法,而选择明确没有或者很少有歧义的语法。

为什么选择python?

同一样问题,用不同的语言解决,代码量差距还是很多的,一般情况下 Python 是 Java 的 1/5。

python特点

  • Python 是完全面向对象的语言
  1. 函数、模块、数字、字符串都是对象,在 Python 中一切皆对象
  2. 完全支持继承、重载、多重继承
  3. 支持重载运算符,也支持泛型设计
  • Python拥有一个强大的标准库,Python 语言的核心只包含数字、字符串、列表、字典、文件等常见类型和函数,而由 Python 标准库提供了系统管理、网络通信、文本处理、数据库接口、图形系统、XML 处理 等额外的功能
  • Python 社区提供了大量的第三方模块,使用方式与标准库类似。它们的功能覆盖科学计算、人工智能、机器学习、Web 开发、数据库接口、图形系统多个领域

面向对象的思维方式

面向对象是一种思维方式,也是一门程序设计技术

要解决一个问题前,首先考虑由谁来做,怎么做事情是谁的职责,最后把事情做好就行!

对象就是谁

要解决复杂的问题,就可以找多个不同的对象,各司其职,共同实现,最终完成需求

python的优缺点

优点

  • 简单、易学
  • 免费、开源
  • 面向对象
  • 丰富的库
  • 可扩展性

如果需要一段关键代码运行得更快或者希望某些算法不公开,可以把这部分程序用 `C` 或 `C++` 编写,然后在 `Python` 程序中使用它们

缺点

  • 运行速度慢
  • 国内市场较小
  • 中文资料匮乏

第一个python程序

Python 源程序的基本概念

Python 源程序就是一个特殊格式的文本文件,可以使用任意文本编辑软件做 `Python` 的开发

Python 程序的文件扩展名通常都是 `.py`

print("hello world")

`print` 是 `python` 中我们学习的第一个函数

`print` 函数的作用,可以把""内部的内容,输出到屏幕上

错误排查

  • 手误

例如使用 `pirnt("Hello world")`

NameError: name 'pirnt' is not defined

名称错误:'pirnt' 名字没有定义

  • 将多条 `print` 写在一行

SyntaxError: invalid syntax

语法错误:语法无效

每行代码负责完成一个动作

  • 缩进错误

IndentationError: unexpected indent

缩进错误:不期望出现的缩进

Python 是一个格式非常严格的程序设计语言

目前而言,大家记住每行代码前面都不要增加空格

执行python的三种方式

1.python的解释器

# 使用 python 2.x 解释器
python xxx.py

# 使用 python 3.x 解释器
python3 xxx.py

Python 的解释器如今有多个语言的实现,包括:

  • `CPython` —— 官方版本的 C 语言实现
  • `Jython` —— 可以运行在 Java 平台
  • `IronPython` —— 可以运行在 .NET 和 Mono 平台
  • `PyPy` —— Python 实现的,支持 JIT 即时编译

2.交互式运行python程序

直接在终端中运行解释器,而不输入要执行的文件名

在 Python 的 `Shell` 中直接输入Python 的代码,会立即看到程序执行结果

  • 交互式运行 Python 的优缺点

优点

适合于学习/验证 Python 语法或者局部代码

缺点

代码不能保存

不适合运行太大的程序

  • 退出官方的解释器

直接输入 `exit`

使用热键退出`ctrl + d`

  • IPython

IPython 中 的 “I” 代表交互 interactive

特点

IPython 是一个 python 的 **交互式 shell**,比默认的 `python shell` 好用得多

  1. 支持自动补全
  2. 自动缩进
  3. 支持 `bash shell` 命令
  4. 内置了许多很有用的功能和函数
  5. IPython 是基于 BSD 开源的
  • IPython 的安装
sudo apt install ipython

3.集成开发环境IDE--Pycharm

集成开发环境(`IDE`,Integrated Development Environment)—— 集成了开发软件需要的所有工具,一般包括以下工具:

  1. 图形用户界面
  2. 代码编辑器(支持 **代码补全**/**自动缩进**)
  3. 编译器/解释器
  4. 调试器(**断点**/**单步执行**)
  5.  ……

PyCharm 介绍

`PyCharm` 是 Python 的一款非常优秀的集成开发环境

`PyCharm` 除了具有一般 IDE 所必备功能外,还可以在 `Windows`、`Linux`、`macOS` 下使用

`PyCharm` 适合开发大型项目

  1. 一个项目通常会包含很多源文件
  2. 每个源文件的代码行数是有限的,通常在几百行之内
  3. 每个源文件各司其职,共同完成复杂的业务功能

PyCharm 界面

文件导航区域能够浏览/定位/打开项目文件

文件编辑区域能够编辑当前打开的文件

控制台区域能够:

  1. 输出程序执行内容
  2. 跟踪调试代码的执行

右上角的工具栏能够执行(SHIFT + F10)/ 调试(SHIFT + F9)代码

通过控制台上方的单步执行按钮(F8),可以单步执行代码

注释

注释的作用

使用用自己熟悉的语言,在程序中对某些代码进行标注说明,增强程序的可读性

单行注释(行注释)

以 `#` 开头,`#` 右边的所有东西都被当做说明文字,而不是真正要执行的程序,只起到辅助说明作用

# 这是第一个单行注释

print("hello python")

为了保证代码的可读性,`#` 后面建议先添加一个空格,然后再编写相应的说明文字

在代码后面增加的单行注释

在程序开发时,同样可以使用 `#` 在代码的后面(旁边)增加说明性的文字

但是,需要注意的是,为了保证代码的可读性,注释和代码之间至少要有两个空格

print("hello python")  # 输出 `hello python`

多行注释(块注释)

如果希望编写的注释信息很多,一行无法显示,就可以使用多行注释

要在 Python 程序中使用多行注释,可以用一对 连续的 三个引号(单引号和双引号都可以)

"""

这是一个多行注释
在多行注释之间,可以写很多很多的内容……

"""

print("hello python")

什么时候需要使用注释?

  1. 注释不是越多越好,对于一目了然的代码,不需要添加注释
  2. 对于复杂的操作,应该在操作开始前写上若干行注释
  3. 对于不是一目了然的代码,应在其行尾添加注释(为了提高可读性,注释应该至少离开代码 2 个空格)
  4. 绝不要描述代码,假设阅读代码的人比你更懂 Python,他只是不知道你的代码要做什么

算术运算符

算数运算符

运算符 描述 实例
+

 10 + 20 = 30

-

10 - 20 = -10

*

10 * 20 = 200

/

10 / 20 = 0.5

// 取整数

返回除法的整数部分(商) 9 // 2 输出结果 4

% 取余数

返回除法的余数 9 % 2 = 1

**

又称次方、乘方,2 ** 3 = 8

在 Python 中 `*` 运算符还可以用于字符串,计算结果就是字符串重复指定次数的结果

In [1]: "-" * 50

Out[1]: '----------------------------------------'

算数运算符的优先级

由最高到最低

运算符 描述
** 幂(最高优先级)
*/%// 乘、除、取余数、取整数
+- 加法减法

变量的基本使用

变量定义

在Python中,每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建

  • 等号(=)用来给变量赋值
  • `=`左边是一个变量名
  • `=`右边是存储在变量中的值
变量名=值
#定义qq_number的变量用来保存qq号码
qq_number="1234567"

#定义qq_password的变量用来保存qq密码
qq_password="123"

使用交互式方式,如果要查看变量内容,直接输入变量名即可,不需要使用`print`函数。

#定义qq号码变量
qq_number="1234567"

#定义qq密码变量
qq_password="123"

#在程序中,如果要输出变量的内容,需要使用print函数
print(qq_number)
print(qq_password)

 使用解释器执行,如果要输出变量的内容,必须要要使用`print`函数

案例

#苹果的价格是8.5元/斤,买了7.5斤苹果,计算付款金额
#定义苹果价格变量
price=8.5
#定义购买重量
weight=7.5
#计算金额
money=price*weight
print(money)

#如果只要买苹果,就返5块钱,请重新计算购买金额
#定义苹果价格变量
price=8.5
#定义购买重量
weight=7.5
#计算金额
money=price*weight
#只要买苹果就返5元
money=money-5
print(money)

上述代码中,一共定义有几个变量?

三个:`price`/`weight`/`money`

`money=money-5`是在定义新的变量还是在使用变量?

直接使用之前已经定义的变量

变量名只有在第一次出现才是定义变量

变量名再次出现,不是定义变量,而是直接使用之前定义过的变量

在程序开发中,可以修改之前定义变量中保存的值吗?

可以

变量中存储的值,就是可以变的

变量的类型

在`Python`中定义变量是不需要指定类型(在其他很多高级语言中都需要)

  • 数字型
  1. 整型(`int`)
  2. 浮点型(`float`)
  3. 布尔型(`bool`):真`True``非0数`——非零即真,假`False``0`
  4. 复数型(`complex`):主要用于科学计算,例如:平面场问题、波动问题、电感电容等问题
  • 非数字型
  1. 字符串
  2. 列表
  3. 元组
  4. 字典

使用`type`函数可以查看一个变量的类型

type(变量名)

不同类型变量之间的计算

1)数字型变量之间可以直接计算

在Python中,两个数字型变量是可以直接进行算数运算的

如果变量是`bool`型,在计算时

`True`对应的数字是`1`

`False`对应的数字是`0`

2)字符串变量之间使用`+`拼接字符串

在Python中,字符串之间可以使用`+`拼接生成新的字符串

first_name="三"
last_name="张"
first_name+last_name
#Out:'三张'

3)字符串变量可以和整数使用`*`重复拼接相同的字符串

"-"50
#Out[:'--------------------------------------------------'

4)数字型变量和字符串之间不能进行其他计算

变量的输入

所谓输入,就是用代码获取用户通过键盘输入的信息

在Python中,如果要获取用户在键盘上的输入信息,需要使用到`input`函数

1)关于函数

一个提前准备好的功能(别人或者自己写的代码),可以直接使用,而不用关心内部的细节

2)input函数实现键盘输入

在Python中可以使用`input`函数从键盘等待用户的输入,用户输入的任何内容Python都认为是一个字符串

字符串变量=input("提示信息:")

3)类型转换函数

|函数|说明|

|---|---|

|int(x)|将x转换为一个整数|

|float(x)|将x转换到一个浮点数|

4)变量输入演练——超市买苹果增强版

"""
收银员输入苹果的价格,单位:元/斤
收银员输入用户购买苹果的重量,单位:斤
计算并且输出付款金额
"""
#1.输入苹果单价
price_str=input("请输入苹果价格:")
#2.要求苹果重量
weight_str=input("请输入苹果重量:")
#3.计算金额
#1>将苹果单价转换成小数
price=float(price_str)
#2>将苹果重量转换成小数
weight=float(weight_str)
#3>计算付款金额
money=priceweight
print(money)

定义一个浮点变量接收用户输入的同时,就使用`float`函数进行转换

price=float(input("请输入价格:"))

变量的格式化输出

苹果单价`9.00`元/斤,购买了`5.00`斤,需要支付`45.00`元

在Python中可以使用`print`函数将信息输出到控制台

如果希望输出文字信息的同时,一起输出数据,就需要使用到格式化操作符

`%`被称为格式化操作符,专门用于处理字符串中的格式

包含`%`的字符串,被称为格式化字符串

`%`和不同的字符连用,不同类型的数据需要使用不同的格式化字符

|格式化字符|含义|

|---|---|

|%s|字符串|

|%d|有符号十进制整数,`%06d`表示输出的整数显示位数,不足的地方使用`0`补全|

|%f|浮点数,`%.2f`表示小数点后只显示两位|

|%%|输出`%`|

print("格式化字符串"%变量1)
print("格式化字符串"%(变量1,变量2...))

案例

"""
1.定义字符串变量`name`,输出我的名字叫小明,请多多关照!
2.定义整数变量`student_no`,输出我的学号是000001
3.定义小数`price`、`weight`、`money`,输出苹果单价9.00元/斤,购买了5.00斤,需要支付45.00元
4.定义一个小数`scale`,输出数据比例是10.00%
"""
name="小明"
print("我的名字叫%s,请多多关照!"%name)
student_no=1
print("我的学号是%06d"%student_no)
price=9.0
weight=5.0
money=45.0
print("苹果单价%.02f元/斤,购买%.02f斤,需要支付%.02f元"%(price,weight,money))
scale=10.0
print("数据比例是%.02f%%"%(scale))


"""
在控制台依次提示用户输入:姓名、公司、职位、电话、邮箱
按照以下格式输出:
公司名称

姓名(职位)

电话:电话
邮箱:邮箱
"""
name=input("请输入姓名:")
company=input("请输入公司:")
title=input("请输入职位:")
phone=input("请输入电话:")
email=input("请输入邮箱:")
print(company)
print()
print("%s(%s)"%(name,title))
print()
print("电话:%s"%phone)
print("邮箱:%s"%email)

变量的命名

标识符和关键字

标识符

标示符就是程序员定义的变量名、函数名

  • 标示符可以由字母、下划线和数字组成
  • 不能以数字开头
  • 不能与关键字重名

关键字

  • 关键字就是在`Python`内部已经使用的标识符
  • 关键字具有特殊的功能和含义
  • 开发者不允许定义和关键字相同的名字的标示符
#通过以下命令可以查看`Python`中的关键字
import keyword
print(keyword.kwlist)

变量的命名规则

命名规则可以被视为一种惯例,并无绝对与强制

目的是为了增加代码的识别和可读性

  • 在定义变量时,为了保证代码格式,`=`的左右应该各保留一个空格
  • 在`Python`中,如果变量名需要由二个或多个单词组成时,可以按照以下方式命名
  1. 每个单词都使用小写字母
  2. 单词与单词之间使用`_`下划线连接
  3. 例如:`first_name`、`last_name`、`qq_number`、`qq_password`

当变量名是由二个或多个单词组成时,还可以利用驼峰命名法来命名

小驼峰式命名法

第一个单词以小写字母开始,后续单词的首字母大写

例如:`firstName`、`lastName`

大驼峰式命名法

每一个单词的首字母都采用大写字母

例如:`FirstName`、`LastName`、`CamelCase`

判断语句

判断的定义

如果条件满足,才能做某件事情,

如果条件不满足,就做另外一件事情,或者什么也不做

判断语句又被称为“分支语句”,正是因为有了判断,才让程序有了很多的分支

if判断语句基本语法

在`Python`中,if语句就是用来进行判断的,格式如下:

if要判断的条件:    
    条件成立时,要做的事情

注意:代码的缩进为一个`tab`键,或者4个空格——建议使用空格,在Python开发中,Tab和空格不要混用!

案例

"""
1.定义一个整数变量记录年龄
2.判断是否满18岁(>=)
3.如果满18岁,允许进网吧嗨皮
"""
#1.定义年龄变量
age=18
#2.判断是否满18岁
#if语句以及缩进部分的代码是一个完整的代码块
if age>=18:
    print("可以进网吧嗨皮……")

在使用`if`判断时,只能做到满足条件时要做的事情。那如果需要在不满足条件的时候,做某些事情,该如何做呢?

if要判断的条件:
    条件成立时,要做的事情
else:
    条件不成立时,要做的事情

案例

"""
1.输入用户年龄
2.判断是否满18岁(>=)
3.如果满18岁,允许进网吧嗨皮
4.如果未满18岁,提示回家写作业
"""
#1.输入用户年龄
age=int(input("今年多大了?"))
#2.判断是否满18岁
#if语句以及缩进部分的代码是一个完整的语法块
if age>=18:
    print("可以进网吧嗨皮……")
else:
    print("你还没长大,应该回家写作业!")

逻辑运算

在程序开发中,通常在判断条件时,会需要同时判断多个条件

只有多个条件都满足,才能够执行后续代码,这个时候需要使用到逻辑运算符

逻辑运算符可以把多个条件按照逻辑进行连接,变成更复杂的条件

Python中的逻辑运算符包括:与and/或or/非not三种

`and`

条件1 and 条件2

与/并且

两个条件同时满足,返回`True`

只要有一个不满足,就返回`False`

|条件1|条件2|结果|

|:---:|:---:|:---:|

|成立|成立|成立|

|成立|不成立|不成立|

|不成立|成立|不成立|

|不成立|不成立|不成立|

`or`

条件1or条件2

或/或者

两个条件只要有一个满足,返回`True`

两个条件都不满足,返回`False`

|条件1|条件2|结果|

|:---:|:---:|:---:|

|成立|成立|成立|

|成立|不成立|成立|

|不成立|成立|成立|

|不成立|不成立|不成立|

`not`

not条件

非/不是

|条件|结果|

|:---:|:---:|

|成立|不成立|

|不成立|成立|

案例

"""
1.练习1:定义一个整数变量`age`,编写代码判断年龄是否正确
要求人的年龄在0-120之间
2.练习2:定义两个整数变量`python_score`、`c_score`,编写代码判断成绩
要求只要有一门成绩>60分就算合格
3.练习3:定义一个布尔型变量`is_employee`,编写代码判断是否是本公司员工
如果不是提示不允许入内

"""
#练习1:定义一个整数变量age,编写代码判断年龄是否正确
age=100
#要求人的年龄在0-120之间
if age>=0 and age<=120:
    print("年龄正确")
else:
    print("年龄不正确")

#练习2:定义两个整数变量python_score、c_score,编写代码判断成绩
python_score=50
c_score=50
#要求只要有一门成绩>60分就算合格
if python_score>60 or c_score>60:
    print("考试通过")
else:
    print("再接再厉!")

#练习3:定义一个布尔型变量`is_employee`,编写代码判断是否是本公司员工
is_employee=True
#如果不是提示不允许入内
if not is_employee:
    print("非公勿内")

if语句进阶

`elif`

  • 在开发中,使用`if`可以判断条件
  • 使用`else`可以处理条件不成立的情况
  • 但是,如果希望再增加一些条件,条件不同,需要执行的代码也不同时,就可以使用`elif`
if条件1:
    条件1满足执行的代码
elif条件2:
    条件2满足时,执行的代码
elif条件3:
    条件3满足时,执行的代码
else:
    以上条件都不满足时,执行的代码

`if`的嵌套

  • elif的应用场景是:同时判断多个条件,所有的条件是平级的
  • 在开发中,使用`if`进行条件判断,如果希望在条件成立的执行语句中再增加条件判断,就可以使用if的嵌套
  • if的嵌套的应用场景就是:在之前条件满足的前提下,再增加额外的判断
  • if的嵌套的语法格式,除了缩进之外和之前的没有区别
if条件1:
    条件1满足执行的代码
    if条件1基础上的条件2:
        条件2满足时,执行的代码
    #条件2不满足的处理
    else:
        条件2不满足时,执行的代码
#条件1不满足的处理
else:
    条件1不满足时,执行的代码

综合案例

猜拳游戏

"""
1.从控制台输入要出的拳——石头(1)/剪刀(2)/布(3)
2.电脑随机出拳——先假定电脑只会出石头,完成整体代码功能
3.比较胜负
|序号|规则|
|:---:|:---:|
|1|石头胜剪刀|
|2|剪刀胜布|
|3|布胜石头|
"""
player=int(input("请出拳石头(1)/剪刀(2)/布(3):"))
#电脑随机出拳-假定电脑永远出石头
computer=1
#比较胜负
#如果条件判断的内容太长,可以在最外侧的条件增加一对大括号
#再在每一个条件之间,使用回车,PyCharm可以自动增加8个空格
if ((player==1 and computer==2) or
(player==2 and computer==3) or
(player==3 and computer==1)):
    print("噢耶!!!电脑弱爆了!!!")
elif player==computer:
    print("心有灵犀,再来一盘!")
else:
    print("不行,我要和你决战到天亮!")

随机数的处理

在`Python`中,要使用随机数,首先需要导入随机数的模块——“工具包”

import random

`random.randint(a,b)`,返回`[a,b]`之间的整数,包含`a`和`b`

random.randint(12,20)#生成的随机数n:12<=n<=20
random.randint(20,20)#结果永远是20
random.randint(20,10)#该语句是错误的,下限必须小于上限

#猜拳游戏改进,电脑随机出拳
import random
player=int(input("请出拳石头(1)/剪刀(2)/布(3):"))
#电脑随机出拳-假定电脑永远出石头
computer=random.randint(1,3)
#比较胜负
#如果条件判断的内容太长,可以在最外侧的条件增加一对大括号
#再在每一个条件之间,使用回车,PyCharm可以自动增加8个空格
if ((player==1 and computer==2) or
(player==2 and computer==3) or
(player==3 and computer==1)):
    print("噢耶!!!电脑弱爆了!!!")
elif player==computer:
    print("心有灵犀,再来一盘!")
else:
    print("不行,我要和你决战到天亮!")

运算符

算数运算符

是完成基本的算术运算使用的符号,用来处理四则运算

|运算符|描述|实例|

|:---:|:---:|---|

|+|加|10+20=30|

|-|减|10-20=-10|

||乘|10*20=200|

|/|除|10/20=0.5|

|//|取整除|返回除法的整数部分(商)9//2输出结果4|

|%|取余数|返回除法的余数9%2=1|

||幂|又称次方、乘方,2**3=8|

比较(关系)运算符

|运算符|描述|

|---|---|

|==|检查两个操作数的值是否相等,如果是,则条件成立,返回True|

|!=|检查两个操作数的值是否不相等,如果是,则条件成立,返回True|

|>|检查左操作数的值是否大于右操作数的值,如果是,则条件成立,返回True|

|<|检查左操作数的值是否小于右操作数的值,如果是,则条件成立,返回True|

|>=|检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成立,返回True|

|<=|检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成立,返回True|

逻辑运算符

|运算符|逻辑表达式|描述|

|---|---|---|

|and|x and y|只有x和y的值都为True,才会返回True
否则只要x或者y有一个值为False,就返回False|

|or|x or y|只要x或者y有一个值为True,就返回True
只有x和y的值都为False,才会返回False|

|not|not x|如果x为True,返回False
如果x为False,返回True|

赋值运算符

在Python中,使用`=`可以给变量赋值

在算术运算时,为了简化代码的编写,`Python`还提供了一系列的与算术运算符对应的赋值运算符

注意:赋值运算符中间不能使用空格

|运算符|描述|实例|

|---|---|---|

|=|简单的赋值运算符|c=a+b将a+b的运算结果赋值为c|

|+=|加法赋值运算符|c+=a等效于c=c+a|

|-=|减法赋值运算符|c-=a等效于c=c-a|

|*=|乘法赋值运算符|c*=a等效于c=ca|

|/=|除法赋值运算符|c/=a等效于c=c/a|

|//=|取整除赋值运算符|c//=a等效于c=c//a|

|%=|取模(余数)赋值运算符|c%=a等效于c=c%a|

|**=|幂赋值运算符|c**=a等效于c=c**a|

运算符的优先级

以下表格的算数优先级由高到最低顺序排列

|运算符|描述|

|---|---|

|**|幂(最高优先级)|

|*/%//|乘、除、取余数、取整除|

|+-|加法、减法|

|<= < > >=|比较运算符|

|== !=|等于运算符|

|*= %= /= //= -= += ==|赋值运算符|

|not or and|逻辑运算符|

循环

程序的三大流程

在程序开发中,一共有三种流程方式:

  • 顺序——从上向下,顺序执行代码
  • 分支——根据条件判断,决定执行代码的分支
  • 循环——让特定代码重复执行

while循环基本使用

循环的作用就是让指定的代码重复的执行

初始条件设置——通常是重复执行的计数器
while条件(判断计数器是否达到目标次数):
    条件满足时,做的事情1
    条件满足时,做的事情2
    条件满足时,做的事情3
    ...(省略)...
    处理条件(计数器+1)

#打印5遍HelloPython
#1.定义重复次数计数器
i=1
#2.使用while判断条件
while i<=5:
    #要重复执行的代码
    print("HelloPython")
#处理计数器i
    i=i+1
print("循环结束后的i=%d"%i)

注意:循环结束后,之前定义的计数器条件的数值是依旧存在的

死循环:由于程序员的原因,忘记在循环内部修改循环的判断条件,导致循环持续执行,程序无法终止!

循环计算

在程序开发中,通常会遇到利用循环重复计算的需求

#计算0~100之间所有数字的累计求和结果
#0.定义最终结果的变量
result=0
#1.定义一个整数的变量记录循环的次数
i=0
#2.开始循环
while i<=100:
    print(i)
    #每一次循环,都让result这个变量和i这个计数器相加
    result+=i
    #处理计数器
    i+=1
print("0~100之间的数字求和结果=%d"%result)

#计算0~100之间所有偶数的累计求和结果
#1.编写循环确认要计算的数字
#2.添加结果变量,在循环内部处理计算结果
#0.最终结果
result=0
#1.计数器
i=0
#2.开始循环
while i<=100:
    #判断偶数
    if i%2==0:
        print(i)
        result+=i
    #处理计数器
    i+=1
print("0~100之间偶数求和结果=%d"%result)

break和continue

  • `break`和`continue`是专门在循环中使用的关键字
  • `break`某一条件满足时,退出循环,不再执行后续重复的代码
  • `continue`某一条件满足时,不执行后续重复的代码
  • `break`和`continue`只针对当前所在循环有效

break

在循环过程中,如果某一个条件满足后,不再希望循环继续执行,可以使用`break`退出循环

i=0
while i<10:
#break某一条件满足时,退出循环,不再执行后续重复的代码
    #i==3
    if i==3:
        break
    print(i)
    i+=1
print("over")

在循环过程中,如果某一个条件满足后,不希望执行循环代码,但是又不希望退出循环,可以使用`continue`

也就是:在整个循环中,只有某些条件,不需要执行循环代码,而其他条件都需要执行

i=0
while i<10:
#当i==7时,不希望执行需要重复执行的代码
    if i==7:
    #在使用continue之前,同样应该修改计数器,否则会出现死循环
        i+=1
        continue
    #重复执行的代码
    print(i)
    i+=1

 注意:使用`continue`时,条件处理部分的代码,需要特别注意,不小心会出现死循环

while循环嵌套

`while`嵌套就是:`while`里面还有`while`

while条件1:
    条件满足时,做的事情1
    条件满足时,做的事情2
    条件满足时,做的事情3
    ...(省略)...
    while条件2:
        条件满足时,做的事情1
        条件满足时,做的事情2
        条件满足时,做的事情3
        ...(省略)...
        处理条件2
    处理条件1

案例

#在控制台连续输出五行`*`,每一行星号的数量依次递增
#使用字符串打印
#1.定义一个计数器变量,从数字1开始,循环会比较方便
row=1
while row<=5:
    print("*"*row)
    row+=1

在默认情况下,`print`函数输出内容之后,会自动在内容末尾增加换行

如果不希望末尾增加换行,可以在`print`函数输出内容的后面增加`,end=""`

其中`""`中间可以指定`print`函数输出内容之后,继续希望显示的内容

"""
在控制台连续输出五行`*`,每一行星号的数量依次递增
1>完成5行内容的简单输出
2>分析每行内部的`*`应该如何处理?
每行显示的星星和当前所在的行数是一致的
嵌套一个小的循环,专门处理每一行中`列`的星星显示
"""
row=1
while row<=5:
    #假设python没有提供字符串操作
    #在循环内部,再增加一个循环,实现每一行的星星打印
    col=1
    while col<=row:
        print("*",end="")
        col+=1
    #每一行星号输出完成后,再增加一个换行
    print("")
    row+=1

 九九乘法表

"""
1.打印9行小星星
2.将每一个`*`替换成对应的行与列相乘
"""
#定义起始行
row=1
#最大打印9行
while row<=9:
    #定义起始列
    col=1
    #最大打印row列
    while col<=row:
        #end="",表示输出结束后,不换行
        #"\t"可以在控制台输出一个制表符,协助在输出文本时对齐
        print("%d*%d=%d"%(col,row,row*col),end="\t")
        #列数+1
        col+=1
    #一行打印完成的换行
    print("")
    #行数+1
    row+=1

字符串中的转义字符

`\t`在控制台输出一个制表符,协助在输出文本时垂直方向保持对齐

`\n`在控制台输出一个换行符

制表符的功能是在不使用表格的情况下在垂直方向按列对齐文本

|转义字符|描述|

|---|---|

|\\|反斜杠符号|

|\'|单引号|

|\"|双引号|

|\n|换行|

|\t|横向制表符|

|\r|回车|

函数

函数的基本使用

所谓函数,就是把具有独立功能的代码块组织为一个小模块,在需要的时候调用

 函数的使用包含两个步骤:

  1. 定义函数 —— 封装独立的功能
  2. 调用函数 —— 享受封装的成果

 函数的作用,在开发程序时,使用函数可以提高编写的效率以及代码的重用

#定义函数的格式如下:
def 函数名():
    函数封装的代码
def multiple():
    """
    1.打印9行小星星
    2.将每一个`*`替换成对应的行与列相乘
    """
    # 定义起始行
    row = 1
    # 最大打印9行
    while row <= 9:
        # 定义起始列
        col = 1
        # 最大打印row列
        while col <= row:
            # end="",表示输出结束后,不换行
            # "\t"可以在控制台输出一个制表符,协助在输出文本时对齐
            print("%d*%d=%d" % (col, row, row * col), end="\t")
            # 列数+1
            col += 1
        # 一行打印完成的换行
        print("")
        # 行数+1
        row += 1
multiple()
  • `def` 是英文 `define` 的缩写
  • 函数名称应该能够表达函数封装代码 的功能方便后续的调用
  • 函数名称的命名应该符合标识符的命名规则
  1. 可以由 字母、下划线和数字组成
  2. 不能以数字开头
  3. 不能与关键字重名
  • 调用函数很简单的,通过`函数名()`即可完成对函数的调用

在开发中,如果希望给函数添加注释,应该在定义函数的下方,使用连续的三对引号

在函数调用位置,使用快捷键`CTRL+Q`可以查看函数的说明信息

注意:因为函数体相对比较独立,函数定义的上方,应该和其他代码保留两个空行

函数的参数

#在函数名的后面的小括号内部填写参数,多个参数之间使用`,`分隔
def sum_num(num1,num2):
    result=num1+num2
    print("%d+%d=%d"%(num1,num2,result))
sum_num(50,20)

参数的作用

函数的参数,增加函数的通用性,针对相同的数据处理逻辑,能够适应更多的数据

在函数内部,把参数当做变量使用,进行需要的数据处理

函数调用时,按照函数定义的参数顺序,把希望在函数内部处理的数据,通过参数传递

形参和实参

形参:定义函数时,小括号中的参数,是用来接收参数用的,在函数内部作为变量使用

实参:调用函数时,小括号中的参数,是用来把数据传递到函数内部用的

函数的返回值

在程序开发中,有时候,会希望一个函数执行结束后,告诉调用者一个结果,以便调用者针对具体的结果做后续的处理

返回值是函数完成工作后,最后给调用者的一个结果

在函数中使用`return`关键字可以返回结果

调用函数一方,可以使用变量来接收函数的返回结果

def sum_2_num(num1,num2):
    """对两个数字的求和"""
    return num1+num2
#调用函数,并使用result变量接收计算结果
result=sum_2_num(10,20)
print("计算结果是%d"%result)

函数的嵌套调用

一个函数里面又调用了另外一个函数,这就是函数嵌套调用

如果函数`test2`中,调用了另外一个函数`test1`

那么执行到调用`test1`函数时,会先把函数`test1`中的任务都执行完

才会回到`test2`中调用函数`test1`的位置,继续执行后续的代码

def test1():
    print("*"*50)
    print("test1")
    print("*"*50)


def test2():
    print("-"*50)
    print("test2")
    test1()
    print("-"*50)
test2()
#定义一个函数能够打印5行的分隔线,要求符合打印任意重复次数的任意类型分隔线
def print_line(char,times):
    print(char*times)
def print_lines(char,times):
    """
    打印分割线
    :param char: 分割字符
    :param times: 字符个数
    :return: 
    """
    row=0
    while row<5:
        print_line(char,times)
        row+=1
print_lines("*",5)

在模块中定义函数

  • 模块是Python程序架构的一个核心概念
  • 模块就好比是工具包,要想使用这个工具包中的工具,就需要导入import这个模块
  • 每一个以扩展名`py`结尾的`Python`源代码文件都是一个模块
  • 在模块中定义的全局变量、函数都是模块能够提供给外界直接使用的工具
  1. 可以在一个Python文件中定义变量或者函数
  2. 然后在另外一个文件中使用`import`导入这个模块
  3. 导入之后,就可以使用`模块名.变量`/`模块名.函数`的方式,使用这个模块中定义的变量或者函数

模块名也是一个标识符

  1. 标示符可以由字母、下划线和数字组成
  2. 不能以数字开头
  3. 不能与关键字重名

注意:如果在给Python文件起名时,以数字开头是无法在`PyCharm`中通过导入这个模块的

 高级变量类型

在`Python`中,所有非数字型变量都支持以下特点:

都是一个序列`sequence`,也可以理解为容器

取值`[]`

遍历`for in`

计算长度、最大/最小值、比较、删除

链接`+`和重复`*`

切片

列表

列表的定义

`List`(列表)是`Python`中使用最频繁的数据类型,在其他语言中通常叫做数组

专门用于存储一串信息

列表用`[]`定义,数据之间使用`,`分隔

列表的索引从`0`开始

  • 索引就是数据在列表中的位置编号,索引又可以被称为下标
name_list=["zhangsan","lisi","wangwu"]

列表常用操作

|序号|分类|关键字/函数/方法|说明|

|---|---|---|---|

|1|增加|列表.insert(索引,数据)|在指定位置插入数据|

|||列表.append(数据)|在末尾追加数据

|||列表.extend(列表2)|将列表2的数据追加到列表|

|2|修改|列表[索引]=数据|修改指定索引的数据|

|3|删除|del列表[索引]|删除指定索引的数据|

|||列表.remove[数据]|删除第一个出现的指定数据|

|||列表.pop|删除末尾数据|

|||列表.pop(索引)|删除指定索引数据|

|||列表.clear|清空列表|

|4|统计|len(列表)|列表长度|

|||列表.count(数据)|数据在列表中出现的次数|

|5|排序|列表.sort()|升序排序|

|||列表.sort(reverse=True)|降序排序|

|||列表.reverse()|逆序、反转|

del关键字

`del`关键字本质上是用来将一个变量从内存中删除的

如果使用`del`关键字将变量从内存中删除,后续的代码就不能再使用这个变量了

del name_list

循环遍历

遍历就是从头到尾依次从列表中获取数据

在循环体内部针对每一个元素,执行相同的操作

在`Python`中为了提高列表的遍历效率,专门提供的迭代iteration遍历

使用`for`就能够实现迭代遍历

#for循环内部使用的变量in列表
for name in name_list:
#循环内部针对列表元素进行操作
    print(name)

应用场景

尽管`Python`的列表中可以存储不同类型的数据

但是在开发中,更多的应用场景是

  • 列表存储相同类型的数据
  • 通过迭代遍历,在循环体内部,针对列表中的每一项元素,执行相同的操作

元组

元组的定义

  • `Tuple`(元组)与列表类似,不同之处在于元组的元素不能修改
  1. 元组表示多个元素组成的序列
  2. 元组在`Python`开发中,有特定的应用场景
  • 用于存储一串信息,数据之间使用`,`分隔
  • 元组用`()`定义
  • 元组的索引从`0`开始
  1. 索引就是数据在元组中的位置编号
info_tuple=("zhangsan",18,1.75)
#创建空元组
info_tuple=()

#元组中只包含一个元素时,需要在元素后面添加逗号
info_tuple=(50,)

元组常用操作

info_tuple.count("zhangsan")#计算出现次数
info_tuple.index("zhangsan")#取索引

在`Python`中,可以使用`for`循环遍历所有非数字型类型的变量:列表、元组、字典以及字符串

尽管可以使用`for in`遍历元组

但是在开发中,更多的应用场景是:

  • 函数的参数和返回值,一个函数可以接收任意多个参数,或者一次返回多个数据
  • 格式字符串,格式化字符串后面的`()`本质上就是一个元组
  • 让列表不可以被修改,以保护数据安全
info=("zhangsan",18)
print("%s的年龄是%d"%info)

元组和列表之间的转换

使用`list`函数可以把元组转换成列表

list(元组)

使用`tuple`函数可以把列表转换成元组

tuple(列表)

字典

字典的定义

  • `dictionary`(字典)是除列表以外`Python`之中最灵活的数据类型
  • 字典同样可以用来存储多个数据
  1. 通常用于存储描述一个`物体`的相关信息
  • 和列表的区别
  1. 列表是有序的对象集合
  2. 字典是无序的对象集合
  • 字典用`{}`定义
  • 字典使用键值对存储数据,键值对之间使用`,`分隔
  1. 键`key`是索引
  2. 值`value`是数据
  3. 键和值之间使用`:`分隔
  4. 键必须是唯一的
  5. 值可以取任何数据类型,但键只能使用字符串、数字或元组
xiaoming={"name":"小明",
"age":18,
"gender":True,
"height":1.75}

字典常用操作

#定义一个字典
xiaoming={"name":"小明",
"gender":True,
"height":1.75}

#取值
xiaoming["name"]
#增加
xiaoming["age"]=18
#修改
xiaoming["name"]="大名"
#字典能够使用的函数
xiaoming.clear()#清空字典
xiaoming.items
xiaoming.setdefault
xiaoming.copy
xiaoming.keys
xiaoming.update(字典2)#合并字典,覆盖更新
xiaoming.fromkey
sxiaoming.pop
xiaoming.values
xiaoming.get
xiaoming.popitem

循环遍历

#for循环内部使用的`key的变量`in字典
for k in xiaoming:
    print("%s:%s"%(k,xiaoming[k]))

应用场景

尽管可以使用`for in`遍历字典

但是在开发中,更多的应用场景是:

  • 使用多个键值对,存储描述一个`物体`的相关信息——描述更复杂的数据信息
  • 将多个字典放在一个列表中,再进行遍历,在循环体内部针对每一个字典进行相同的处理
card_list=[{"name":"张三",
            "qq":"12345",
            "phone":"110"},
            {"name":"李四",
            "qq":"54321",
            "phone":"10086"}]
for k in card_list:
    print(k)

字符串

字符串的定义

  • 字符串就是一串字符,是编程语言中表示文本的数据类型
  • 在Python中可以使用一对双引号`"`或者一对单引号`'`定义一个字符串
  • 虽然可以使用`\"`或者`\'`做字符串的转义,但是在实际开发中:
  1. 如果字符串内部需要使用`"`,可以使用`'`定义字符串
  2. 如果字符串内部需要使用`'`,可以使用`"`定义字符串
  • 可以使用索引获取一个字符串中指定位置的字符,索引计数从0开始
  • 也可以使用`for`循环遍历字符串中每一个字符

大多数编程语言都是用`"`来定义字符串

string="Hello Python"
for c in string:
    print(c)

字符串的常用操作

1)判断类型-9

|方法|说明|

|---|---|

|string.isspace()|如果string中只包含空格,则返回True|

|string.isalnum()|如果string至少有一个字符并且所有字符都是字母或数字则返回True|

|string.isalpha()|如果string至少有一个字符并且所有字符都是字母则返回True|

|string.isdecimal()|如果string只包含数字则返回True,`全角数字`|

|string.isdigit()|如果string只包含数字则返回True,`全角数字`、`\u00b2`|

|string.isnumeric()|如果string只包含数字则返回True,`全角数字`,`\u00b2`,`汉字数字`|

|string.istitle()|如果string是标题化的(每个单词的首字母大写)则返回True|

|string.islower()|如果string中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回True|

|string.isupper()|如果string中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是大写,则返回True|

2)查找和替换-7

|方法|说明|

|---|---|

|string.startswith(str)|检查字符串是否是以str开头,是则返回True|

|string.endswith(str)|检查字符串是否是以str结束,是则返回True|

|string.find(str,start=0,end=len(string))|检测str是否包含在string中,如果start和end指定范围,则检查是否包含在指定范围内,如果是返回开始的索引值,否则返回`-1`|

|string.rfind(str,start=0,end=len(string))|类似于find(),不过是从右边开始查找|

|string.index(str,start=0,end=len(string))|跟find()方法类似,不过如果str不在string会报错|

|string.rindex(str,start=0,end=len(string))|类似于index(),不过是从右边开始|

|string.replace(old_str,new_str,num=string.count(old))|把string中的old_str替换成new_str,如果num指定,则替换不超过num次|

3)大小写转换-5

|方法|说明|

|---|---|

|string.capitalize()|把字符串的第一个字符大写|

|string.title()|把字符串的每个单词首字母大写|

|string.lower()|转换string中所有大写字符为小写|

|string.upper()|转换string中的小写字母为大写|

|string.swapcase()|翻转string中的大小写|

4)文本对齐-3

|方法|说明|

|---|---|

|string.ljust(width)|返回一个原字符串左对齐,并使用空格填充至长度width的新字符串|

|string.rjust(width)|返回一个原字符串右对齐,并使用空格填充至长度width的新字符串|

|string.center(width)|返回一个原字符串居中,并使用空格填充至长度width的新字符串|

5)去除空白字符-3

|方法|说明|

|---|---|

|string.lstrip()|截掉string左边(开始)的空白字符|

|string.rstrip()|截掉string右边(末尾)的空白字符|

|string.strip()|截掉string左右两边的空白字符|

6)拆分和连接-5

|方法|说明|

|---|---|

|string.partition(str)|把字符串string分成一个3元素的元组(str前面,str,str后面)|

|string.rpartition(str)|类似于partition()方法,不过是从右边开始查找|

|string.split(str="",num)|以str为分隔符拆分string,如果num有指定值,则仅分隔num+1个子字符串,str默认包含'\r','\t','\n'和空格|

|string.splitlines()|按照行('\r','\n','\r\n')分隔,返回一个包含各行作为元素的列表|

|string.join(seq)|以string作为分隔符,将seq中所有的元素(的字符串表示)合并为一个新的字符串|

字符串的切片

  • 切片方法适用于字符串、列表、元组
  1. 切片使用索引值来限定范围,从一个大的字符串中切出小的字符串
  2. 列表和元组都是有序的集合,都能够通过索引值获取到对应的数据
  3. 字典是一个无序的集合,是使用键值对保存数据
字符串[开始索引:结束索引:步长]

注意:

  • 指定的区间属于左闭右开型`[开始索引,结束索引)`=>`开始索引>=范围<结束索引`
  1. 从`起始`位开始,到`结束`位的前一位结束(不包含结束位本身)
  • 从头开始,开始索引数字可以省略,冒号不能省略
  • 到末尾结束,结束索引数字可以省略,冒号不能省略
  • 步长默认为`1`,如果连续切片,数字和冒号都可以省略

索引的顺序和倒序

在Python中不仅支持顺序索引,同时还支持倒序索引

所谓倒序索引就是从右向左计算索引

最右边的索引值是-1,依次递减

num_str="0123456789"
#1.截取从2~5位置的字符串
print(num_str[2:6])

#2.截取从2~`末尾`的字符串
print(num_str[2:])

#3.截取从`开始`~5位置的字符串
print(num_str[:6])

#4.截取完整的字符串
print(num_str[:])

#5.从开始位置,每隔一个字符截取字符串
print(num_str[::2])

#6.从索引1开始,每隔一个取一个
print(num_str[1::2])

#倒序切片
#-1表示倒数第一个字符
print(num_str[-1])

#7.截取从2~`末尾-1`的字符串
print(num_str[2:-1])

#8.截取字符串末尾两个字符
print(num_str[-2:])

#9.字符串的逆序(面试题)
print(num_str[::-1])

公共方法

Python内置函数

Python包含了以下内置函数:

|函数|描述|备注|

|---|---|---|

|len(item)|计算容器中元素个数||

|del(item)|删除变量|del有两种方式|

|max(item)|返回容器中元素最大值|如果是字典,只针对key比较|

|min(item)|返回容器中元素最小值|如果是字典,只针对key比较|

注意:字符串比较符合以下规则:"0"<"A"<"a",字典不能比较大小

切片

|描述|Python表达式|结果|支持的数据类型|

|:---:|---|---|---|---|

|切片|"0123456789"[::-2]|"97531"|字符串、列表、元组|

  • 切片使用索引值来限定范围,从一个大的字符串中切出小的字符串
  • 列表和元组都是有序的集合,都能够通过索引值获取到对应的数据
  • 字典是一个无序的集合,是使用键值对保存数据

运算符

|运算符|Python表达式|结果|描述|支持的数据类型|

|:---:|---|---|---|---|

|+|[1,2]+[3,4]|[1,2,3,4]|合并|字符串、列表、元组|

|*|["Hi!"]*4|['Hi!','Hi!','Hi!','Hi!']|重复|字符串、列表、元组|

|in|3 in (1,2,3)|True|元素是否存在|字符串、列表、元组、字典|

|not in|4 not in(1,2,3)|True|元素是否不存在|字符串、列表、元组、字典|

|> >= == < <=|(1,2,3)<(2,2,3)|True|元素比较|字符串、列表、元组|

注意

  • `in`在对字典操作时,判断的是字典的键
  • `in`和`not in`被称为成员运算符

完整的for循环语法

for变量in集合:
    循环体代码
else:
    没有通过break退出循环,循环结束后,会执行的代码

应用场景

在迭代遍历嵌套的数据类型时,例如一个列表包含了多个字典

需求:要判断某一个字典中是否存在指定的值

如果存在,提示并且退出循环

如果不存在,在循环整体结束后,希望得到一个统一的提示

students=[
{"name":"阿土",
"age":20,
"gender":True,
"height":1.7,
"weight":75.0},
{"name":"小美",
"age":19,
"gender":False,
"height":1.6,
"weight":45.0},
]
find_name="阿土"
for stu_dict in students:
    print(stu_dict)
    #判断当前遍历的字典中姓名是否为find_name
    if  stu_dict["name"]==find_name:
        print("找到了")
        #如果已经找到,直接退出循环,就不需要再对后续的数据进行比较
        break
else:
    print("没有找到")
print("循环结束")

名片管理系统

系统需求

1.程序启动,显示名片管理系统欢迎界面,并显示功能菜单

欢迎使用【名片管理系统】V1.0

  1. 新建名片
  2. 显示全部
  3. 查询名片
  4. 退出系统

2.用户用数字选择不同的功能

3.根据功能选择,执行不同的功能

4.用户名片需要记录用户的姓名、电话、QQ、邮件

5.如果查询到指定的名片,用户可以选择修改或者删除名片

框架搭建

搭建名片管理系统框架结构

  • 准备文件,确定文件名,保证能够在需要的位置编写代码
  • 编写主运行循环,实现基本的用户输入和判断

1.文件准备

新建`cards_main.py`保存主程序功能代码

  • 程序的入口
  • 每一次启动名片管理系统都通过`main`这个文件启动

新建`cards_tools.py`保存所有名片功能函数

  • 将对名片的新增、查询、修改、删除等功能封装在不同的函数中

2.编写主运行循环

在`cards_main`中添加一个无限循环

while True:
    #在`#`后跟上`TODO`,用于标记需要去做的工作
    # TODO(小明)显示系统菜单
    action=input("请选择操作功能:")
    print("您选择的操作是:%s"%action)
    #根据用户输入决定后续的操作
    if action in["1","2","3"]:
        #新增名片
        if action == "1":
            pass
        #显示全部
        elif action == "2":
            pass
        #查询名片
        elif action == "3":
            pass
        pass#`pass`就是一个空语句,不做任何事情,一般用做占位语句,为了保持程序结构的完整性
    elif action=="0":
        print("欢迎再次使用【名片管理系统】")
        break
    else:
        print("输入错误,请重新输入")

3.在`cards_tools`中增加四个新函数

def show_menu():
    """
    显示菜单
    """
    print("*"*50)
    print("欢迎使用【菜单管理系统】")
    print("")
    print("1.新建名片")
    print("2.显示全部")
    print("3.查询名片")
    print("")
    print("0.退出系统")
    print("*"*50)
pass
def new_card():
    """
    新建名片
    """
    print("-"*50)
    print("功能:新建名片")
def show_all():
    """
    显示全部
    """
    print("-"*50)
    print("功能:显示全部")
def search_card():
    """
    搜索名片
    """
    print("-"*50)
    print("功能:搜索名片")

4.导入模块

在`cards_main.py`中使用`import`导入`cards_tools`模块

import cards_tools
while True:
    # TODO(小明)显示系统菜单
    cards_tools.show_menu()
    action=input("请选择操作功能:")
    print("您选择的操作是:%s"%action)
    #根据用户输入决定后续的操作
    if action in["1","2","3"]:
        #新增名片
        if action == "1":
            cards_tools.new_card()
        #显示全部
        elif action == "2":
            cards_tools.show_all()
        #查询名片
        elif action == "3":
            cards_tools.search_card()
        pass#`pass`就是一个空语句,不做任何事情,一般用做占位语句,为了保持程序结构的完整性
    elif action=="0":
        print("欢迎再次使用【名片管理系统】")
        break
    else:
        print("输入错误,请重新输入")

保存名片数据的结构

程序就是用来处理数据的,而变量就是用来存储数据的

  • 使用字典记录每一张名片的详细信息
  • 使用列表统一记录所有的名片字典

定义名片列表变量

  • 在`cards_tools`文件的顶部增加一个列表变量
#所有名片记录的列表
card_list=[]

注意

  • 所有名片相关操作,都需要使用这个列表,所以应该定义在程序的顶部
  • 程序刚运行时,没有数据,所以是空列表

新增名片

功能分析

  • 提示用户依次输入名片信息
  • 将名片信息保存到一个字典
  • 将字典添加到名片列表
  • 提示名片添加完成

实现new_card方法

def new_card():
    """
    新建名片
    """
    print("-"*50)
    print("功能:新建名片")
    # 1.提示用户输入名片信息
    name = input("请输入姓名:")
    phone = input("请输入电话:")
    qq = input("请输入QQ号码:")
    email = input("请输入邮箱:")
    # 2.将用户信息保存到一个字典
    card_dict = {"name": name,
                 "phone": phone,
                 "qq": qq,
                 "email": email}
    # 3.将用户字典添加到名片列表
    card_list.append(card_dict)
    print(card_list)
    # 4.提示添加成功信息
    print("成功添加%s的名片" % card_dict["name"])

技巧:在`PyCharm`中,可以使用`SHIFT+F6`统一修改变量名

显示所有名片

功能分析

  • 循环遍历名片列表,顺序显示每一个字典的信息
  • 增加标题和使用`\t`显示
  • 增加没有名片记录判断

实现show_card方法

def show_all():
    """
    显示全部
    """
    print("-"*50)
    print("功能:显示全部")
    if len(card_list) == 0:
        print("提示:没有任何名片记录")
        return
    # 打印表头
    for name in["姓名", "电话", "QQ", "邮箱"]:
        print(name, end="\t\t")
    print("")
    # 打印分隔线
    print("="*50)
    for card_dict in card_list:
        print("%s\t\t%s\t\t%s\t\t%s" % (card_dict["name"],
                                        card_dict["phone"],
                                        card_dict["qq"],
                                        card_dict["email"]))

注意

  • 在函数中使用`return`表示返回
  • 如果在`return`后没有跟任何内容,只是表示该函数执行到此就不再执行后续的代码

查询名片

功能分析

  • 提示用户要搜索的姓名
  • 根据用户输入的姓名遍历列表
  • 搜索到指定的名片后,再执行后续的操作

实现search_card方法

def search_card():
    """
    搜索名片
    """
    print("-"*50)
    print("功能:搜索名片")
    # 1.提示要搜索的姓名
    find_name = input("请输入要搜索的姓名:")
    # 2.遍历字典
    for card_dict in card_list:
        if card_dict["name"] == find_name:
            print("姓名\t\t\t电话\t\t\tQQ\t\t\t邮箱")
            print("-"*40)
            print("%s\t\t\t%s\t\t\t%s\t\t\t%s" % (
                card_dict["name"],
                card_dict["phone"],
                card_dict["qq"],
                card_dict["email"]))
            print("-"*40)
            deal_card(card_dict)
            break
        else:
            print("没有找到%s" % find_name)
def deal_card(find_dict):
    """
    处理查找到的名片
    :param find_dict:查找到的名片
    :return:
    """
    print(find_dict)
    action_str = input("请选择要执行的操作"
                       "[1]修改[2]删除")
    if action_str == "1":
        find_dict["name"] = input_card_info(find_dict["name"],"请输入姓名:")
        find_dict["phone"] = input_card_info(find_dict["phone"],"请输入电话:")
        find_dict["qq"] = input_card_info(find_dict["qq"],"请输入QQ:")
        find_dict["email"] = input_card_info(find_dict["email"],"请输入邮件:")
        print("%s的名片修改成功" % find_dict["name"])
        print("修改")
    elif action_str == "2":
        card_list.remove(find_dict)
        print("删除")
def input_card_info(dict_value,tip_message):
    """
    输入名片信息
    :param dict_value:字典中原有的值
    :param tip_message: 输入的提示文字
    :return:如果用户输入了内容就返回内容,否则返会字典中原有的值
    """
    #1.提示用户输入内容
    result_str=input(tip_message)
    #2.针对用户的输入进行判断,如果用户输入了内容,直接返回结果
    if len(result_str)>0:
        return result_str
    #3.如果用户没有输入内容,返回`字典中原有的值`
    else:
        return dict_value

Linux让Python程序能够直接运行

LINUX上的`Shebang`符号(`#!`)

  • `#!`这个符号叫做`Shebang`或者`Sha-bang`
  • `Shebang`通常在`Unix`系统脚本的中第一行开头使用
  • 指明执行这个脚本文件的解释程序

使用Shebang的步骤

1.使用`which`查询`python3`解释器所在路径

which python3

2.修改要运行的主python文件,在第一行增加以下内容

#!/usr/bin/python3

3.修改主python文件的文件权限,增加执行权限

chmod +x cards_main.py

4.在需要时执行程序即可

./cards_main.py

完整代码

cards_main.py

import cards_tools
while True:
    cards_tools.show_menu()
    action=input("请选择操作功能:")
    print("您选择的操作是:%s"%action)
    #根据用户输入决定后续的操作
    if action in["1","2","3"]:
        #新增名片
        if action == "1":
            cards_tools.new_card()
        #显示全部
        elif action == "2":
            cards_tools.show_all()
        #查询名片
        elif action == "3":
            cards_tools.search_card()
        pass#`pass`就是一个空语句,不做任何事情,一般用做占位语句,为了保持程序结构的完整性
    elif action=="0":
        print("欢迎再次使用【名片管理系统】")
        break
    else:
        print("输入错误,请重新输入")

cards_tools.py

card_list=[]
def show_menu():
    """
    显示菜单
    """
    print("*"*50)
    print("欢迎使用【菜单管理系统】")
    print("")
    print("1.新建名片")
    print("2.显示全部")
    print("3.查询名片")
    print("")
    print("0.退出系统")
    print("*"*50)
pass
def new_card():
    """
    新建名片
    """
    print("-"*50)
    print("功能:新建名片")
    # 1.提示用户输入名片信息
    name_str = input("请输入姓名:")
    phone_str = input("请输入电话:")
    qq_str = input("请输入QQ号码:")
    email_str = input("请输入邮箱:")
    # 2.将用户信息保存到一个字典
    card_dict = {"name": name_str,
                 "phone": phone_str,
                 "qq": qq_str,
                 "email": email_str}
    # 3.将用户字典添加到名片列表
    card_list.append(card_dict)
    print(card_list)
    # 4.提示添加成功信息
    print("成功添加%s的名片" % card_dict["name"])
def show_all():
    """
    显示全部
    """
    print("-"*50)
    print("功能:显示全部")
    if len(card_list) == 0:
        print("提示:没有任何名片记录")
        return
    # 打印表头
    for name in["姓名", "电话", "QQ", "邮箱"]:
        print(name, end="\t\t")
    print("")
    # 打印分隔线
    print("="*50)
    for card_dict in card_list:
        print("%s\t\t%s\t\t%s\t\t%s" % (card_dict["name"],
                                        card_dict["phone"],
                                        card_dict["qq"],
                                        card_dict["email"]))
def search_card():
    """
    搜索名片
    """
    print("-"*50)
    print("功能:搜索名片")
    # 1.提示要搜索的姓名
    find_name = input("请输入要搜索的姓名:")
    # 2.遍历字典
    for card_dict in card_list:
        if card_dict["name"] == find_name:
            print("姓名\t\t\t电话\t\t\tQQ\t\t\t邮箱")
            print("-"*40)
            print("%s\t\t\t%s\t\t\t%s\t\t\t%s" % (
                card_dict["name"],
                card_dict["phone"],
                card_dict["qq"],
                card_dict["email"]))
            print("-"*40)
            deal_card(card_dict)
            break
        else:
            print("没有找到%s" % find_name)
def deal_card(find_dict):
    """
    处理查找到的名片
    :param find_dict:查找到的名片
    :return:
    """
    print(find_dict)
    action_str = input("请选择要执行的操作"
                       "[1]修改[2]删除")
    if action_str == "1":
        find_dict["name"] = input_card_info(find_dict["name"],"请输入姓名:")
        find_dict["phone"] = input_card_info(find_dict["phone"],"请输入电话:")
        find_dict["qq"] = input_card_info(find_dict["qq"],"请输入QQ:")
        find_dict["email"] = input_card_info(find_dict["email"],"请输入邮件:")
        print("%s的名片修改成功" % find_dict["name"])
        print("修改")
    elif action_str == "2":
        card_list.remove(find_dict)
        print("删除")
def input_card_info(dict_value,tip_message):
    """
    输入名片信息
    :param dict_value:字典中原有的值
    :param tip_message: 输入的提示文字
    :return:如果用户输入了内容就返回内容,否则返会字典中原有的值
    """
    #1.提示用户输入内容
    result_str=input(tip_message)
    #2.针对用户的输入进行判断,如果用户输入了内容,直接返回结果
    if len(result_str)>0:
        return result_str
    #3.如果用户没有输入内容,返回`字典中原有的值`
    else:
        return dict_value

变量进阶

变量的引用

变量的引用

  • 变量和数据都是保存在内存中的
  • 在`Python`中函数的参数传递以及返回值都是靠引用传递的

引用的概念

  • 变量和数据是分开存储的
  • 数据保存在内存中的一个位置
  • 变量中保存着数据在内存中的地址
  • 变量中记录数据的地址,就叫做引用
  • 使用`id()`函数可以查看变量中保存数据所在的内存地址

注意:如果变量已经被定义,当给一个变量赋值的时候,本质上是修改了数据的引用

  • 变量不再对之前的数据引用
  • 变量改为对新赋值的数据引用

函数的参数和返回值的传递

在`Python`中,函数的实参/返回值都是是靠引用来传递来的

可变和不可变类型

不可变类型,内存中的数据不允许被修改:

  • 数字类型`int`,`bool`,`float`,`complex`,`long(2.x)`
  • 字符串`str`
  • 元组`tuple`

可变类型,内存中的数据可以被修改:

  • 列表`list`
  • 字典`dict`
  1. 字典的`key`只能使用不可变类型的数据

注意

  • 可变类型的数据变化,是通过方法来实现的
  • 如果给一个可变类型的变量,赋值了一个新的数据,引用会修改
  1. 变量不再对之前的数据引用
  2. 变量改为对新赋值的数据引用

哈希`(hash)`

`Python`中内置有一个名字叫做`hash(o)`的函数

  • 接收一个不可变类型的数据作为参数
  • 返回结果是一个整数

`哈希`是一种算法,其作用就是提取数据的特征码(指纹)

  • 相同的内容得到相同的结果
  • 不同的内容得到不同的结果

在`Python`中,设置字典的键值对时,会首先对`key`进行`hash`已决定如何在内存中保存字典的数据,以方便后续对字典的操作:增、删、改、查

  • 键值对的`key`必须是不可变类型数据
  • 键值对的`value`可以是任意类型的数据

局部变量和全局变量

局部变量

  • 局部变量是在函数内部定义的变量,只能在函数内部使用
  • 函数执行结束后,函数内部的局部变量,会被系统回收
  • 不同的函数,可以定义相同的名字的局部变量,但是彼此之间不会产生影响

局部变量的生命周期

  • 所谓生命周期就是变量从被创建到被系统回收的过程
  • 局部变量在函数执行时才会被创建
  • 函数执行结束后局部变量被系统回收
  • 局部变量在生命周期内,可以用来存储函数内部临时使用到的数据

全局变量

  • 全局变量是在函数外部定义的变量,所有函数内部都可以使用这个变量
  • 但是,不允许直接修改全局变量的引用——使用赋值语句修改全局变量的值
  • 如果在函数中需要修改全局变量,需要使用`global`进行声明

注意:函数执行时,需要处理变量时会:

  • 首先查找函数内部是否存在指定名称的局部变量,如果有,直接使用
  • 如果没有,查找函数外部是否存在指定名称的全局变量,如果有,直接使用
  • 如果还没有,程序报错!

全局变量命名的建议

  • 为了避免局部变量和全局变量出现混淆,在定义全局变量时,全局变量名前应该增加`g_`或者`gl_`的前缀

函数进阶

函数参数和返回值的作用

定义函数时,是否接收参数,或者是否返回结果,是根据实际的功能需求来决定的!

  • 如果函数内部处理的数据不确定,就可以将外界的数据以参数传递到函数内部
  • 如果希望一个函数执行完成后,向外界汇报执行结果,就可以增加函数的返回值

1.无参数,无返回值

此类函数,不接收参数,也没有返回值,应用场景如下:

  • 只是单纯地做一件事情,例如显示菜单
  • 在函数内部针对全局变量进行操作,例如:新建名片,最终结果记录在全局变量中

如果全局变量的数据类型是一个可变类型,在函数内部可以使用方法修改全局变量的内容——变量的引用不会改变

在函数内部,使用赋值语句才会修改变量的引用

2.无参数,有返回值

此类函数,不接收参数,但是有返回值,应用场景如下:

  • 采集数据,例如温度计,返回结果就是当前的温度,而不需要传递任何的参数

3.有参数,无返回值

此类函数,接收参数,没有返回值,应用场景如下:

  • 函数内部的代码保持不变,针对不同的参数处理不同的数据
  • 例如名片管理系统针对找到的名片做修改、删除操作

4.有参数,有返回值

此类函数,接收参数,同时有返回值,应用场景如下:

  • 函数内部的代码保持不变,针对不同的参数处理不同的数据,并且返回期望的处理结果
  • 例如名片管理系统使用字典默认值和提示信息提示用户输入内容
  1. 如果输入,返回输入内容
  2. 如果没有输入,返回字典默认值

函数的返回值进阶

  • 在程序开发中,有时候,会希望一个函数执行结束后,告诉调用者一个结果,以便调用者针对具体的结果做后续的处理
  • 返回值是函数完成工作后,最后给调用者的一个结果
  • 在函数中使用`return`关键字可以返回结果
  • 调用函数一方,可以使用变量来接收函数的返回结果

一个函数执行后能否返回多个结果?

在利用元组在返回温度的同时,也能够返回湿度

def measure():
    """返回当前的温度"""
    print("开始测量...")
    temp=39
    wetness=10
    print("测量结束...")
    return(temp,wetness)
temp,wetness=measure()
print(temp,wetness)

面试题——交换两个数字
题目要求

  1. 有两个整数变量`a=6`,`b=100`
  2. 不使用其他变量,交换两个变量的值
#解法1-使用临时变量
a=6
b=100
c=b
b=a
a=c
print(a,b)
#解法2-不使用临时变量
a=a+b
b=a-b
a=a-b
#解法3——Python专有,利用元组
a,b=b,a

函数的参数进阶

1.不可变和可变的参数

问题1:在函数内部,针对参数使用赋值语句,会不会影响调用函数时传递的实参变量?——不会!

  • 无论传递的参数是可变还是不可变
  • 只要针对参数使用赋值语句,会在函数内部修改局部变量的引用,不会影响到外部变量的引用

问题2:如果传递的参数是可变类型,在函数内部,使用方法修改了数据的内容,同样会影响到外部的数据

面试题——`+=`

  • 在`python`中,列表变量调用`+=`本质上是在执行列表变量的`extend`方法,不会修改变量的引用

2.缺省参数

  • 定义函数时,可以给某个参数指定一个默认值,具有默认值的参数就叫做缺省参数
  • 调用函数时,如果没有传入缺省参数的值,则在函数内部使用定义函数时指定的参数默认值
  • 函数的缺省参数,将常见的值设置为参数的缺省值,从而简化函数的调用
  • 例如:对列表排序的方法
gl_num_list=[6,3,9]
#默认就是升序排序,因为这种应用需求更多
gl_num_list.sort()
print(gl_num_list)
#只有当需要降序排序时,才需要传递`reverse`参数
gl_num_list.sort(reverse=True)
print(gl_num_list)

指定函数的缺省参数

  • 在参数后使用赋值语句,可以指定参数的缺省值
def print_info(name,gender=True):
    gender_text="男生"
    if not gender:
        gender_text="女生"
    print("%s是%s"%(name,gender_text))
print_info("wps")
print_info("wps",False)

提示

  • 缺省参数,需要使用最常见的值作为默认值!
  • 如果一个参数的值不能确定,则不应该设置默认值,具体的数值在调用函数时,由外界传递!

缺省参数的注意事项

1)缺省参数的定义位置

  • 必须保证带有默认值的缺省参数在参数列表末尾
  • 所以,以下定义是错误的!
def print_info(name,gender=True,title):

2)调用带有多个缺省参数的函数

  • 在调用函数时,如果有多个缺省参数,需要指定参数名,这样解释器才能够知道参数的对应关系!
def print_info(name,title="",gender=True):

3. 多值参数

定义支持多值参数的函数

  • 有时可能需要一个函数能够处理的参数个数是不确定的,这个时候,就可以使用多值参数
  • `python`中有两种多值参数:
  1. 参数名前增加一个`*`可以接收元组
  2. 参数名前增加两个`**`可以接收字典
  • 一般在给多值参数命名时,习惯使用以下两个名字
  1. `args`——存放元组参数,前面有一个`*`
  2. `kwargs`——存放字典参数,前面有两个`**`
  • `args`是`arguments`的缩写,有变量的含义
  • `kw`是`keyword`的缩写,`kwargs`可以记忆键值对参数
def demo(num,*args,**kwargs):
    print(num)
    print(args)
    print(kwargs)
demo(1,2,3,4,5,name="小明",age=18,gender=True)

多值参数案例——计算任意多个数字的和

需求

  • 定义一个函数`sum_numbers`,可以接收的任意多个整数
  • 功能要求:将传递的所有数字累加并且返回累加结果
def sum_numbers(*args):
    num=0
    #遍历args元组顺序求和
    for n in args:
        num+=n
    return num
print(sum_numbers(1,2,3,4,5,6,7,8,9))

元组和字典的拆包(知道)

  • 在调用带有多值参数的函数时,如果希望:
  1. 将一个元组变量,直接传递给`args`
  2. 将一个字典变量,直接传递给`kwargs`
  • 就可以使用拆包,简化参数的传递,拆包的方式是:
  1. 在元组变量前,增加一个`*`
  2. 在字典变量前,增加两个`**`
def demo(*args,**kwargs):
    print(args)
    print(kwargs)
#需要将一个元组变量/字典变量传递给函数对应的参数
gl_nums=(1,2,3)
gl_xiaoming={"name":"小明","age":18}
#会把num_tuple和xiaoming作为元组传递给args
#demo(gl_nums,gl_xiaoming)
demo(*gl_nums,**gl_xiaoming)

递归函数

函数调用自身的编程技巧称为递归

1.递归函数的特点

特点

  • 一个函数内部调用自己
  1. 函数内部可以调用其他函数,当然在函数内部也可以调用自己

代码特点

  • 函数内部的代码是相同的,只是针对参数不同,处理的结果不同
  • 当参数满足一个条件时,函数不再执行
  1. 这个非常重要,通常被称为递归的出口,否则会出现死循环!
def sum_numbers(num):
    print(num)
    #递归的出口很重要,否则会出现死循环
    if num==1:
        return
    sum_numbers(num-1)
sum_numbers(3)

2.递归案例——计算数字累加

需求

  1. 定义一个函数`sum_numbers`
  2. 能够接收一个`num`的整数参数
  3. 计算1+2+...num的结果
def sum_numbers(num):
    if num==1:
        return 1
    #假设sum_numbers能够完成num-1的累加
    temp=sum_numbers(num-1)
    #函数内部的核心算法就是两个数字的相加
    return num+temp
print(sum_numbers(2))

面向对象(OOP)

面向对象基本概念

  • 我们之前学习的编程方式就是面向过程的
  • 面相过程和面相对象,是两种不同的编程方式
  • 对比面向过程的特点,可以更好地了解什么是面向对象

1.过程和函数

  • 过程是早期的一个编程概念
  • 过程类似于函数,只能执行,但是没有返回值
  • 函数不仅能执行,还可以返回结果

2.面相过程

  • 把完成某一个需求的`所有步骤``从头到尾`逐步实现
  • 根据开发需求,将某些功能独立的代码封装成一个又一个函数
  • 最后完成的代码,就是顺序地调用不同的函数
  • 特点
  1. 注重步骤与过程,不注重职责分工
  2. 如果需求复杂,代码会变得很复杂
  3. 开发复杂项目,没有固定的套路,开发难度很大!

3.面向对象

相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法

  • 在完成某一个需求前,首先确定职责——要做的事情(方法)
  • 根据职责确定不同的对象,在对象内部封装不同的方法(多个)
  • 最后完成的代码,就是顺序地让不同的对象调用不同的方法
  • 特点
  1. 注重对象和职责,不同的对象承担不同的职责
  2. 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路

类和对象

1.类和对象的概念

  • 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
  1. 特征被称为属性
  2. 行为被称为方法
  • 类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的

对象

  • 对象是由类创建出来的一个具体存在,可以直接使用
  • 由哪一个类创建出来的对象,就拥有在哪一个类中定义的:
  1. 属性
  2. 方法
  • 对象就相当于用图纸制造的飞机

在程序开发中,应该先有类,再有对象

2.类和对象的关系

  • 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象
  • 类只有一个,而对象可以有很多个
  • 不同的对象之间属性可能会各不相同
  • 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少

3.类的设计

在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类!

在程序开发中,要设计一个类,通常需要满足一下三个要素:

  1. 类名这类事物的名字,满足大驼峰命名法
  2. 属性这类事物具有什么样的特征
  3. 方法这类事物具有什么样的行为

大驼峰命名法

`CapWords`

  1. 每一个单词的首字母大写
  2. 单词与单词之间没有下划线

类名的确定

  • 名词提炼法分析整个业务流程,出现的名词,通常就是找到的类

属性和方法的确定

  • 对对象的特征描述,通常可以定义成属性
  • 对象具有的行为(动词),通常可以定义成方法

面相对象基础语法

1.`dir`内置函数

在`Python`中对象几乎是无所不在的,我们之前学习的变量、数据、函数都是对象

在`Python`中可以使用以下方法验证:

  • 使用内置函数`dir`传入标识符/数据,可以查看对象内的所有属性及方法

提示`__方法名__`格式的方法是`Python`提供的内置方法/属性

|序号|方法名|类型|作用|

|:---:|:---:|:---:|---|

|01|`__new__`|方法|创建对象时,会被自动调用|

|02|`__init__`|方法|对象被初始化时,会被自动调用|

|03|`__del__`|方法|对象被从内存中销毁前,会被自动调用|

|04|`__str__`|方法|返回对象的描述信息,`print`函数输出使用|

2.定义简单的类(只包含方法)

面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了!

定义只包含方法的类

class 类名:
    def 方法1(self,参数列表):
        pass
    def 方法2(self,参数列表):
        pass

注意:类名的命名规则要符合大驼峰命名法

创建对象

当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:

对象变量=类名()

第一个面向对象程序

需求

  • 小猫爱吃鱼,小猫要喝水

分析

  • 定义一个猫类`Cat`
  • 定义两个方法`eat`和`drink`
class Cat:
    """这是一个猫类"""
    def eat(self):
        print("小猫爱吃鱼")
    def drink(self):
        print("小猫在喝水")
tom=Cat()
tom.drink()
tom.eat()

引用概念的强调

在面向对象开发中,引用的概念是同样适用的!

  • 在`Python`中使用类创建对象之后,`tom`变量中仍然记录的是对象在内存中的地址
  • 也就是`tom`变量引用了新建的猫对象
  • 使用`print`输出对象变量,默认情况下,是能够输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)

案例进阶——使用Cat类再创建一个对象

class Cat:
    """这是一个猫类"""
    def eat(self):
        print("小猫爱吃鱼")
    def drink(self):
        print("小猫在喝水")
tom=Cat()
tom.drink()
tom.eat()
lazy_cat=Cat()
lazy_cat.eat()
lazy_cat.drink()
lazy_cat2=lazy_cat

`tom`和`lazy_cat`不是同一个对象,`lazy_cat2`和`lazy_cat`是同一个对象

3.方法中的`self`参数

给对象增加属性

tom.name="Tom"

提示

  • 在日常开发中,不推荐在类的外部给对象增加属性
  • 如果在运行时,没有找到属性,程序会报错
  • 对象应该包含有哪些属性,应该封装在类的内部

使用`self`在方法内部输出每一只猫的名字

由哪一个对象调用的方法,方法内的`self`就是哪一个对象的引用

  • 在类封装的方法内部,`self`就表示当前调用方法的对象自己
  • 调用方法时,程序员不需要传递`self`参数
  • 在方法内部
  1. 可以通过`self.`访问对象的属性
  2. 也可以通过`self.`调用其他的对象方法
class Cat:
    """这是一个猫类"""
    def eat(self):
        print("%s爱吃鱼"%self.name)
tom=Cat()
tom.name="Tom"
tom.eat()
lazy_cat=Cat()
lazy_cat.name="大懒猫"
lazy_cat.eat()

4.初始化方法

  • 当使用`类名()`创建对象时,会自动执行以下操作:
  1. 为对象在内存中分配空间——创建对象
  2. 为对象的属性设置初始值——初始化方法(`init`)
  • 这个初始化方法就是`__init__`方法,`__init__`是对象的内置方法

`__init__`方法是专门用来定义一个类具有哪些属性的方法!

在`Cat`中增加`__init__`方法,验证该方法在创建对象时会被自动调用

class Cat:
    """这是一个猫类"""
    def __init__(self):
        print("初始化方法")

在初始化方法内部定义属性

在`__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()

改造初始化方法——初始化的同时设置初始值

在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对`__init__`方法进行改造

  1. 把希望设置的属性值,定义成`__init__`方法的参数
  2. 在方法内部使用`self.属性=形参`接收外部传递的参数
  3. 在创建对象时,使用`类名(属性1,属性2...)`调用
class Cat:
    def __init__(self,name):
        print("初始化方法%s"%name)
        self.name=name
    def eat(self):
        print("%s爱吃鱼" % self.name)
tom=Cat("Tom")
tom.eat()
lazy_cat=Cat("大懒猫")
lazy_cat.eat()

5.内置方法和属性

|序号|方法名|类型|作用|

|:---:|:---:|:---:|---|

|01|`__del__`|方法|对象被从内存中销毁前,会被自动调用|

|02|`__str__`|方法|返回对象的描述信息,`print`函数输出使用|

`__del__`方法

  • 在`Python`中
  1. 当使用`类名()`创建对象时,为对象分配完空间后,自动调用`__init__`方法
  2. 当一个对象被从内存中销毁前,会自动调用`__del__`方法
  • 应用场景
  1. `__init__`改造初始化方法,可以让创建对象更加灵活
  2. `__del__`如果希望在对象被销毁前,再做一些事情,可以考虑一下`__del__`方法
  • 生命周期
  1. 一个对象从调用`类名()`创建,生命周期开始
  2. 一个对象的`__del__`方法一旦被调用,生命周期结束
  3. 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
class Cat:
    def __init__(self,new_name):
        self.name=new_name
        print("%s来了"%self.name)
    def __del__(self):
        print("%s去了"%self.name)
#tom是一个全局变量,代码执行完才会删除
tom=Cat("Tom")
print(tom.name)
#del关键字可以删除一个对象,代码没有执行完就已经删除了tom对象
del tom
print("-"*50)

`__str__`方法

  • 在`Python`中,使用`print`输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
  • 如果在开发中,希望使用`print`输出对象变量时,能够打印自定义的内容,就可以利用`__str__`这个内置方法了

注意:`__str__`方法必须返回一个字符串

class Cat:
    def __init__(self,new_name):
        self.name=new_name
        print("%s来了"%self.name)
    def __del__(self):
        print("%s去了"%self.name)
    def __str__(self):
        return"我是小猫:%s"%self.name
tom=Cat("Tom")
print(tom)

面向对象封装案例

1.封装

  • 封装是面向对象编程的一大特点
  • 面向对象编程的第一步——将属性和方法封装到一个抽象的类中
  • 外界使用类创建对象,然后让对象调用方法
  • 对象方法的细节都被封装在类的内部

2.小明爱跑步

需求

  • 小明体重`75.0`公斤
  • 小明每次跑步会减肥`0.5`公斤
  • 小明每次吃东西体重增加`1`公斤
class Person:
    """人类"""
    def __init__(self,name,weight):
        self.name=name
        self.weight=weight
    def __str__(self):
        return"我的名字叫%s体重%.2f公斤"%(self.name,self.weight)
    def run(self):
        """跑步"""
        print("%s爱跑步,跑步锻炼身体"%self.name)
        self.weight-=0.5

    def eat(self):
        """吃东西"""
        print("%s是吃货,吃完这顿再减肥"%self.name)
        self.weight+=1
xiaoming=Person("小明",75)
xiaoming.run()
xiaoming.eat()
xiaoming.eat()
print(xiaoming)

小明爱跑步扩展——小美也爱跑步

需求

  • 小明和小美都爱跑步
  • 小明体重`75.0`公斤
  • 小美体重`45.0`公斤
  • 每次跑步都会减少`0.5`公斤
  • 每次吃东西都会增加`1`公斤

提示

  • 在对象的方法内部,是可以直接访问对象的属性的
  • 同一个类创建的多个对象之间,属性互不干扰!
class Person:
    """人类"""
    def __init__(self,name,weight):
        self.name=name
        self.weight=weight
    def __str__(self):
        return"我的名字叫%s体重%.2f公斤"%(self.name,self.weight)
    def run(self):
        """跑步"""
        print("%s爱跑步,跑步锻炼身体"%self.name)
        self.weight-=0.5
    def eat(self):
        """吃东西"""
        print("%s是吃货,吃完这顿再减肥"%self.name)
        self.weight+=1
xiaoming=Person("小明",75)
xiaoming.run()
xiaoming.eat()
xiaoming.eat()
print(xiaoming)
xiaomei=Person("小美",60)
xiaomei.run()
xiaomei.eat()
xiaomei.eat()
print(xiaomei)

3.摆放家具

需求

  • 房子(House)有户型、总面积和家具名称列表
  1. 新房子没有任何的家具
  • 家具(HouseItem)有名字和占地面积,其中
  1. 席梦思(bed)占地`4`平米
  2. 衣柜(chest)占地`2`平米
  3. 餐桌(table)占地`1.5`平米
  • 将以上三件家具添加到房子中
  • 打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表

剩余面积

  • 在创建房子对象时,定义一个剩余面积的属性,初始值和总面积相等
  • 当调用`add_item`方法,向房间添加家具时,让剩余面积-=家具面积
class HouseItem:
    def __init__(self,name,area):
        """
        :paramname:家具名称
        :paramarea:占地面积
        """
        self.name=name
        self.area=area
    def __str__(self):
        return"[%s]占地面积%.2f"%(self.name,self.area)


class House:
    def __init__(self,house_type,area):
        """
        :paramhouse_type:户型
        :paramarea:总面积
        """
        self.house_type=house_type
        self.area=area
        #剩余面积默认和总面积一致
        self.free_area=area
        #默认没有任何的家具
        self.item_list=[]
    def __str__(self):
        #Python能够自动的将一对括号内部的代码连接在一起
        return("户型:%s\n总面积:%.2f[剩余:%.2f]\n家具:%s"
        %(self.house_type,self.area,
        self.free_area,self.item_list))
    def add_item(self,item):
        print("要添加%s" % item)
        # 1.判断家具面积是否大于剩余面积
        if item.area > self.free_area:
            print("%s的面积太大,不能添加到房子中" % item.name)
            return
        # 2.将家具的名称追加到名称列表中
        self.item_list.append(item.name)
        # 3.计算剩余面积
        self.free_area -= item.area
#1.创建家具
bed=HouseItem("席梦思",4)
chest=HouseItem("衣柜",2)
table=HouseItem("餐桌",1.5)
print(bed)
print(chest)
print(table)
#2.创建房子对象
my_home=House("两室一厅",60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)

注意

  • 主程序只负责创建房子对象和家具对象
  • 让房子对象调用`add_item`方法将家具添加到房子中
  • 面积计算、剩余面积、家具列表等处理都被封装到房子类的内部

4.士兵突击

需求

  • 士兵许三多有一把AK47
  • 士兵可以开火
  • 枪能够发射子弹
  • 枪装填装填子弹——增加子弹数量
class Gun:
    def __init__(self,model):
        #枪的型号
        self.model=model
        #子弹数量
        self.bullet_count=0
    def add_bullet(self,count):
        self.bullet_count+=count
    def shoot(self):
        #判断是否还有子弹
        if self.bullet_count<=0:
            print("没有子弹了...")
            return
        #发射一颗子弹
        self.bullet_count-=1
        print("%s发射子弹[%d]"%(self.model,self.bullet_count))


class Soldier:
    def __init__(self,name):
        #姓名
        self.name=name
        #枪,士兵初始没有枪None关键字表示什么都没有
        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()
#创建枪对象
ak47=Gun("ak47")
person=Soldier("许三多")
person.fire()
person.gun=ak47
person.fire()

 注意:一个对象的属性可以是另外一个类创建的对象

私有属性和私有方法

应用场景

  • 在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到
  • 私有属性就是对象不希望公开的属性
  • 私有方法就是对象不希望公开的方法

定义方式

  • 在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法
class Women:
    def __init__(self,name):
        self.name=name
        #不要问女生的年龄
        self.__age=18
    def __secret(self):
        print("我的年龄是%d"%self.__age)
xiaofang=Women("小芳")
#私有属性,外部不能直接访问
#print(xiaofang.__age)
#私有方法,外部不能直接调用
#xiaofang.__secret()

伪私有属性和私有方法

`Python`中,并没有真正意义的私有

  • 在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到
  • 处理方式:在名称前面加上`_类名`=>`_类名__名称`
class Women:
    def __init__(self,name):
        self.name=name
        #不要问女生的年龄
        self.__age=18
    def __secret(self):
        print("我的年龄是%d"%self.__age)
xiaofang=Women("小芳")
print(xiaofang._Women__age)
xiaofang._Women__secret()

继承

面向对象三大特性

  • 封装根据职责将属性和方法封装到一个抽象的类中
  • 继承实现代码的重用,相同的代码不需要重复的编写
  • 多态不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度

单继承

1.继承的概念:子类拥有父类的所有方法和属性

2.语法:

class 类名(父类名):
    pass
  • 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
  • 子类中应该根据职责,封装子类特有的属性和方法

3.继承的传递性

  • `C`类从`B`类继承,`B`类又从`A`类继承
  • 那么`C`类就具有`B`类和`A`类的所有属性和方法

4.方法的重写

  • 当父类的方法实现不能满足子类需求时,可以对方法进行重写(override)
  • 覆盖父类的方法
  • 对父类方法进行扩展

1)覆盖父类的方法

  • 如果在开发中,父类的方法实现和子类的方法实现,完全不同
  • 就可以使用覆盖的方式,在子类中重新编写父类的方法实现
  • 具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现
  • 重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法

2)对父类方法进行扩展

  • 如果在开发中,子类的方法实现中包含父类的方法实现
  1. 父类原本封装的方法实现是子类方法的一部分
  • 就可以使用扩展的方式
  1. 在子类中重写父类的方法
  2. 在需要的位置使用`super().父类方法`来调用父类方法的执行
  3. 代码其他的位置针对子类的需求,编写子类特有的代码实现

调用父类方法的另外一种方式

父类名.方法(self)

注意:如果使用当前子类名调用方法,会形成递归调用,出现死循环

5.父类的私有属性和私有方法

  • 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
  • 子类对象可以通过父类的公有方法间接访问到私有属性或私有方法
  1. 私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问
  2. 私有属性、方法通常用于做一些内部的事情

多继承

1.概念

  • 子类可以拥有多个父类,并且具有所有父类的属性和方法
  • 例如:孩子会继承自己父亲和母亲的特性

2.语法

class 子类名(父类名1,父类名2...)
    pass

3.问题

如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?

Python中的MRO——方法搜索顺序

  • `Python`中针对类提供了一个内置属性`__mro__`可以查看方法搜索顺序
  • MRO是`method resolution order`,主要用于在多继承时判断方法、属性的调用路径
  • 在搜索方法时,是按照`__mro__`的输出结果从左至右的顺序查找的
  • 如果在当前类中找到方法,就直接执行,不再搜索
  • 如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索
  • 如果找到最后一个类,还没有找到方法,程序报错

4.新式类与旧式(经典)类

  • `object`是`Python`为所有对象提供的基类,提供有一些内置的属性和方法,可以使用`dir`函数查看
  • 新式类:以`object`为基类的类,在`Python3.x`中定义类时,如果没有指定父类,会默认使用`object`作为该类的基类,`Python3.x`中定义的类都是新式类
  • 经典类:不以`object`为基类的类,在`Python2.x`中定义类时,如果没有指定父类,则不会以`object`作为基类
  • 新式类和经典类在多继承时——会影响到方法的搜索顺序
class 类名(object):
    pass

多态

  • 多态不同的子类对象调用相同的父类方法,产生不同的执行结果
  • 多态可以增加代码的灵活度
  • 以继承和重写父类方法为前提
  • 是调用方法的技巧,不会影响到类的内部设计

多态案例演练

需求

  • 在`Dog`类中封装方法`game`
  1. 普通狗只是简单的玩耍
  • 定义`XiaoTianDog`继承自`Dog`,并且重写`game`方法
  1. 哮天犬需要在天上玩耍
  • 定义`Person`类,并且封装一个和狗玩的方法
  1. 在方法内部,直接让狗对象调用`game`方法
class Dog(object):
    def __init__(self,name):
        self.name=name
    def game(self):
        print("%s蹦蹦跳跳的玩耍..."%self.name)


class XiaoTianDog(Dog):
    def game(self):
        print("%s飞到天上去玩耍..."%self.name)


class Person(object):
    def __init__(self,name):
        self.name=name
    def game_with_dog(self,dog):
        print("%s和%s快乐的玩耍..."%(self.name,dog.name))
        #让狗玩耍
        dog.game()
#1.创建一个狗对象
#wangcai=Dog("旺财")
#wangcai.game()
wangcai=XiaoTianDog("飞天旺财")
wangcai.game()
#2.创建一个小明对象
xiaoming=Person("小明")
#3.让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)

类属性和类方法

类的结构

1.实例

  • 创建出来的对象叫做类的实例
  • 创建对象的动作叫做实例化
  • 对象的属性叫做实例属性
  • 对象调用的方法叫做实例方法

每一个对象都有自己独立的内存空间,保存各自不同的属性

多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部

在程序运行时,类同样会被加载到内存

2.在`Python`中,类是一个特殊的对象——类对象

  • 在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例
  • 除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法
  • 通过类名.的方式可以访问类的属性或者调用类的方法

类属性

概念和使用

  • 类属性就是给类对象中定义的属性
  • 通常用来记录与这个类相关的特征
  • 类属性不会用于记录具体对象的特征

示例需求

  • 定义一个工具类
  • 每件工具都有自己的`name`
  • 知道使用这个类,创建了多少个工具对象?
class Tool(object):
    #使用赋值语句,定义类属性,记录创建工具对象的总数
    count=0
    def __init__(self,name):
        self.name=name
        #针对类属性做一个计数+1
        Tool.count+=1
#创建工具对象
tool1=Tool("斧头")
tool2=Tool("榔头")
tool3=Tool("铁锹")
#知道使用Tool类到底创建了多少个对象?
print("现在创建了%d个工具"%Tool.count)

属性的获取机制

在`Python`中属性的获取存在一个向上查找机制

要访问类属性有两种方式:

  • 类名.类属性
  • 对象.类属性

注意:如果使用`对象.类属性=值`赋值语句,只会给对象添加一个属性,而不会影响到类属性的值

类方法和静态方法

1.类方法

  • 类属性就是针对类对象定义的属性
  1. 使用赋值语句在`class`关键字下方可以定义类属性
  2. 类属性用于记录与这个类相关的特征
  • 类方法就是针对类对象定义的方法
  1. 在类方法内部可以直接访问类属性或者调用其他的类方法

语法如下

@classmethod
def 类方法名(cls):
    pass
  • 类方法需要用修饰器`@classmethod`来标识,告诉解释器这是一个类方法
  • 类方法的第一个参数应该是`cls`
  1. 由哪一个类调用的方法,方法内的`cls`就是哪一个类的引用
  2. 这个参数和实例方法的第一个参数是`self`类似
  3. 提示使用其他名称也可以,不过习惯使用`cls`
  • 通过类名.调用类方法,调用方法时,不需要传递`cls`参数
  • 在方法内部
  1. 可以通过`cls.`访问类的属性
  2. 也可以通过`cls.`调用其他的类方法

示例需求

  • 定义一个工具类
  • 每件工具都有自己的`name`
  • 需求——在类封装一个`show_tool_count`的类方法,输出使用当前这个类,创建的对象个数
class Tool(object):
    #使用赋值语句,定义类属性,记录创建工具对象的总数
    count=0
    @classmethod
    def show_tool_count(cls):
        """显示工具对象的总数"""
        print("工具对象的总数%d" % cls.count)
    def __init__(self,name):
        self.name=name
        #针对类属性做一个计数+1
        Tool.count+=1
#创建工具对象
tool1=Tool("斧头")
tool2=Tool("榔头")
tool3=Tool("铁锹")
Tool.show_tool_count()

2.静态方法

  • 在开发时,如果需要在类中封装一个方法,这个方法:
  1. 既不需要访问实例属性或者调用实例方法
  2. 也不需要访问类属性或者调用类方法
  • 这个时候,可以把这个方法封装成一个静态方法

语法如下

@staticmethod
    def 静态方法名():
        pass

静态方法需要用修饰器`@staticmethod`来标识,告诉解释器这是一个静态方法

通过类名.调用静态方法

class Dog(object):
    #狗对象计数
    dog_count=0
    @staticmethod
    def run():
        #不需要访问实例属性也不需要访问类属性的方法
        print("狗在跑...")
    def __init__(self,name):
        self.name=name
Dog.run()

3.方法综合案例

需求

  • 设计一个`Game`类
  • 属性:
  1. 定义一个类属性`top_score`记录游戏的历史最高分
  2. 定义一个实例属性`player_name`记录当前游戏的玩家姓名
  • 方法:
  1. 静态方法`show_help`显示游戏帮助信息
  2. 类方法`show_top_score`显示历史最高分
  3. 实例方法`start_game`开始当前玩家的游戏
  • 主程序步骤
  1. 查看帮助信息
  2. 查看历史最高分
  3. 创建游戏对象,开始游戏
class Game(object):
    #游戏最高分,类属性
    top_score=0
    @staticmethod
    def show_help():
        print("帮助信息:让僵尸走进房间")
    @classmethod
    def show_top_score(cls):
        print("游戏最高分是%d"%cls.top_score)
    def __init__(self,player_name):
        self.player_name=player_name
    def start_game(self):
        print("[%s]开始游戏..."%self.player_name)
#使用类名.修改历史最高分
Game.top_score=999
#1.查看游戏帮助
Game.show_help()
#2.查看游戏最高分
Game.show_top_score()
#3.创建游戏对象,开始游戏
game=Game("小明")
game.start_game()
#4.游戏结束,查看游戏最高分
Game.show_top_score()

单例

单例设计模式

设计模式

  • 设计模式是前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案
  • 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性

单例设计模式

  • 目的——让类创建的对象,在系统中只有唯一的一个实例
  • 每一次执行`类名()`返回的对象,内存地址是相同的

单例设计模式的应用场景

  • 音乐播放对象
  • 回收站对象
  • 打印机对象

`__new__`方法

  • 使用类名()创建对象时,`Python`的解释器首先会调用`__new__`方法为对象分配空间
  • `__new__`是一个由`object`基类提供的内置的静态方法,主要作用有两个:
  1. 在内存中为对象分配空间
  2. 返回对象的引用
  • `Python`的解释器获得对象的引用后,将引用作为第一个参数,传递给`__init__`方法

重写`__new__`方法的代码非常固定!

  • 重写`__new__`方法一定要`returnsuper().__new__(cls)`
  • 否则Python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法
  • 注意:`__new__`是一个静态方法,不能直接调用,而只能在实例方法中调用。这是因为`__new__`在实例化对象时需要访问类级别的属性,所以必须有一个类实例存在。因此,在调用`__new__`时,我们需要主动传递类(cls)参数。
class MusicPlayer(object):
    def __new__(cls,*args,**kwargs):
        print("创建对象,分配空间")
        #如果不返回任何结果,
        return super().__new__(cls)
    def __init__(self):
        print("初始化音乐播放对象")
player=MusicPlayer()
print(player)

Python中的单例

单例——让类创建的对象,在系统中只有唯一的一个实例

  • 定义一个类属性,初始值是`None`,用于记录单例对象的引用
  • 重写`__new__`方法
  • 如果类属性`is None`,调用父类方法分配空间,并在类属性中记录结果
  • 返回类属性中记录的对象引用
class MusicPlayer(object):
    #定义类属性记录单例对象引用
    instance=None
    def __new__(cls,*args,**kwargs):
        #1.判断类属性是否已经被赋值
        if cls.instance is None:
            cls.instance=super().__new__(cls)
        #2.返回类属性的单例引用
        return cls.instance
player1=MusicPlayer()
player2=MusicPlayer()
print(player1)
print(player2)

只执行一次初始化工作

  • 定义一个类属性`init_flag`标记是否执行过初始化动作,初始值为`False`
  • 在`__init__`方法中,判断`init_flag`,如果为`False`就执行初始化动作
  • 然后将`init_flag`设置为`True`
  • 这样,再次自动调用`__init__`方法时,初始化动作就不会被再次执行了
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):
        if not MusicPlayer.init_flag:
            print("初始化音乐播放器")
            MusicPlayer.init_flag=True
#创建多个对象
player1=MusicPlayer()
print(player1)
player2=MusicPlayer()
print(player2)

异常

异常的概念

程序在运行时,如果`Python解释器`遇到到一个错误,会停止程序的执行,并且提示一些错误信息,这就是异常

程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出(raise)异常

程序开发时,很难将所有的特殊情况都处理的面面俱到,通过异常捕获可以针对突发事件做集中的处理,从而保证程序的稳定性和健壮性

捕获异常

1.简单的捕获异常语法

在程序开发中,如果对某些代码的执行不能确定是否正确,可以增加`try(尝试)`来捕获异常

捕获异常最简单的语法格式:

try:
    尝试执行的代码
except:
    出现错误的处理

简单异常捕获演练——要求用户输入整数

try:
    #提示用户输入一个数字
    num=int(input("请输入数字:"))
    print(num)
except:
    print("请输入正确的数字")

2.错误类型捕获

在程序执行时,可能会遇到不同类型的异常,并且需要针对不同类型的异常,做出不同的响应,这个时候,就需要捕获错误类型了

语法如下:

try:
    #尝试执行的代码
    pass
except 错误类型1:
    #针对错误类型1,对应的代码处理
    pass
except(错误类型2,错误类型3):
    #针对错误类型2和3,对应的代码处理
    pass
except Exception as result:
    print("未知错误%s"%result)

当`Python`解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型

异常类型捕获演练——要求用户输入整数

需求

  • 提示用户输入一个整数
  • 使用`8`除以用户输入的整数并且输出
try:
    num=int(input("请输入整数:"))
    result=8/num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除0错误")

捕获未知错误

在开发时,要预判到所有可能出现的错误,还是有一定难度的

如果希望程序无论出现任何错误,都不会因为`Python`解释器抛出异常而被终止,可以再增加一个`except`

语法如下:

except Exception as result:
    print("未知错误%s"%result)

3.异常捕获完整语法

在实际开发中,为了能够处理复杂的异常情况,完整的异常语法如下:

try:
    #尝试执行的代码
    pass
except 错误类型1:
    #针对错误类型1,对应的代码处理
    pass
except 错误类型2:
    #针对错误类型2,对应的代码处理
    pass
except(错误类型3,错误类型4):
    #针对错误类型3和4,对应的代码处理
    pass
except Exception as result:
    #打印错误信息
    print(result)
else:
    #没有异常才会执行的代码
    pass
finally:
    #无论是否有异常,都会执行的代码
    print("无论是否有异常,都会执行的代码")

实例

try:
    num=int(input("请输入整数:"))
    result=8/num
    print(result)
except ValueError:
    print("请输入正确的整数")
except ZeroDivisionError:
    print("除0错误")
except Exception as result:
    print("未知错误%s"%result)
else:
    print("正常执行")
finally:
    print("执行完成,但是不保证正确")

异常的传递

异常的传递——当函数/方法执行出现异常,会将异常传递给函数/方法的调用一方

如果传递到主程序,仍然没有异常处理,程序才会被终止

提示

  • 在开发中,可以在主函数中增加异常捕获
  • 而在主函数中调用的其他函数,只要出现异常,都会传递到主函数的异常捕获中
  • 这样就不需要在代码中,增加大量的异常捕获,能够保证代码的整洁

需求

  • 定义函数`demo1()`提示用户输入一个整数并且返回
  • 定义函数`demo2()`调用`demo1()`
  • 在主程序中调用`demo2()`
def demo1():
    return int(input("请输入一个整数:"))
def demo2():
    return demo1()
try:
    print(demo2())
except ValueError:
    print("请输入正确的整数")
except Exception as result:
    print("未知错误%s"%result)

抛出异常

1.应用场景

  • 在开发中,除了代码执行出错`Python`解释器会抛出异常之外
  • 还可以根据应用程序特有的业务需求主动抛出异常

示例

提示用户输入密码,如果长度少于8,抛出异常

注意

  • 当前函数只负责提示用户输入密码,如果密码长度不正确,需要其他的函数进行额外处理
  • 因此可以抛出异常,由其他需要处理的函数捕获异常

2.抛出异常

  • `Python`中提供了一个`Exception`异常类
  • 在开发时,如果满足特定业务需求时,希望抛出异常,可以:
  1. 创建一个`Exception`的对象
  2. 使用`raise`关键字抛出异常对象

需求

  • 定义`input_password`函数,提示用户输入密码
  • 如果用户输入长度<8,抛出异常
  • 如果用户输入长度>=8,返回输入的密码
def input_password():
    #1.提示用户输入密码
    pwd=input("请输入密码:")
    #2.判断密码长度,如果长度>=8,返回用户输入的密码
    if len(pwd)>=8:
        return pwd
    #3.密码长度不够,需要抛出异常
    #1>创建异常对象-使用异常的错误信息字符串作为参数
    ex=Exception("密码长度不够")
    #2>抛出异常对象
    raise ex
try:
    user_pwd=input_password()
    print(user_pwd)
except Exception as result:
    print("发现错误:%s"%result)

模块和包

模块

1.模块的概念

模块是Python程序架构的一个核心概念

  • 每一个以扩展名`py`结尾的`Python`源代码文件都是一个模块
  • 模块名同样也是一个标识符,需要符合标识符的命名规则
  • 在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具
  • 模块就好比是工具包,要想使用这个工具包中的工具,就需要先导入这个模块

2.模块的两种导入方式

1)import导入

import 模块名1,模块名2

提示:在导入模块时,每个导入应该独占一行

import 模块名1
import 模块名2

导入之后

  • 通过`模块名.`使用模块提供的工具——全局变量、函数、类

使用`as`指定模块的别名

  • 如果模块的名字太长,可以使用`as`指定模块的名称,以方便在代码中的使用
import 模块名1 as 模块别名

注意:模块别名应该符合大驼峰命名法

2)from...import导入

  • 如果希望从某一个模块中,导入部分工具,就可以使用`from...import`的方式
  • `import模块名`是一次性把模块中所有工具全部导入,并且通过模块名/别名访问
#从模块导入某一个工具
from 模块名1 import 工具名
#从模块导入所有工具,这种方式不推荐使用,因为函数重名并没有任何的提示,出现问题不好排查
from 模块名1 import *

导入之后

  • 不需要通过`模块名.`,可以直接使用模块提供的工具——全局变量、函数、类

注意

  • 如果两个模块,存在同名的函数,那么后导入模块的函数,会覆盖掉先导入的函数
  • 开发时`import`代码应该统一写在代码的顶部,更容易及时发现冲突
  • 一旦发现冲突,可以使用`as`关键字给其中一个工具起一个别名

3.模块的搜索顺序

`Python`的解释器在导入模块时,会:

  1. 搜索当前目录指定模块名的文件,如果有就直接导入
  2. 如果没有,再搜索系统目录

在开发时,给文件起名,不要和系统的模块文件重名

  • `Python`中每一个模块都有一个内置属性`__file__`可以查看模块的完整路径

示例

import random
#生成一个0~10的数字
rand=random.randint(0,10)
print(rand)
print(random.__file__)

注意:如果当前目录下,存在一个`random.py`的文件,程序就无法正常执行了!

这个时候,`Python`的解释器会加载当前目录下的`random.py`而不会加载系统的`random`模块

4.原则——每一个文件都应该是可以被导入的

  • 一个独立的`Python`文件就是一个模块
  • 在导入文件时,文件中所有没有任何缩进的代码都会被执行一遍!

实际开发场景

  • 在实际开发中,每一个模块都是独立开发的,大多都有专人负责
  • 开发人员通常会在模块下方增加一些测试代码
  • 仅在模块内使用,而被导入到其他文件中不需要执行

`__name__`属性可以做到,测试模块的代码只在测试情况下被运行,而在被导入时不会被执行!

  • `__name__`是`Python`的一个内置属性,记录着一个字符串
  • 如果是被其他文件导入的,`__name__`就是模块名
  • 如果是当前执行的程序,`__name__`是`__main__`

在很多`Python`文件中都会看到以下格式的代码:

#导入模块
#定义全局变量
#定义类
#定义函数
#在代码的最下方
def main():
    #...
    pass
#根据__name__判断是否执行下方代码
if __name__=="__main__":
    main()

概念

  • 包是一个包含多个模块的特殊目录
  • 目录下有一个特殊的文件`__init__.py`
  • 包名的命名方式和变量名一致,小写字母+`_`

好处

  • 使用`import包名`可以一次性导入包中所有的模块

案例演练

  • 新建一个`hm_message`的包
  • 在目录下,新建两个文件`send_message`和`receive_message`
  • 在`send_message`文件中定义一个`send`函数
  • 在`receive_message`文件中定义一个`receive`函数
  • 在外部直接导入`hm_message`的包

`__init__.py`

  • 要在外界使用包中的模块,需要在`__init__.py`中指定对外界提供的模块列表
#从当前目录导入模块列表
from . import send_message
from . import receive_message

发布模块

如果希望自己开发的模块,分享给其他人,可以按照以下步骤操作

1.制作发布压缩包步骤

1)创建setup.py

from distutils.core import setup
setup(name="hm_message",#包名
version="1.0",#版本
description="itheima's发送和接收消息模块",#描述信息
long_description="完整的发送和接收消息模块",#完整描述信息
author="itheima",#作者
author_email="[email protected]",#作者邮箱
url="www.itheima.com",#主页
py_modules=["hm_message.send_message",
"hm_message.receive_message"])

2)构建模块

python3 setup.py build

3)生成发布压缩包

python3 setup.py sdist

注意:要制作哪个版本的模块,就使用哪个版本的解释器执行!

2.安装模块

tar-zxvf hm_message-1.0.tar.gz
sudo python3 setup.py install

卸载模块

  • 直接从安装目录下,把安装模块的目录删除就可以
cd /usr/local/lib/python3/dist-packages/
sudo rm-r hm_message

3.`pip`安装第三方模块

  • 第三方模块通常是指由知名的第三方团队开发的并且被程序员广泛使用的`Python`包/模块
  1. 例如`pygame`就是一套非常成熟的游戏开发模块
  • `pip`是一个现代的,通用的`Python`包管理工具
  • 提供了对`Python`包的查找、下载、安装、卸载等功能
#将模块安装/卸载到Python3.x环境
sudo pip3 install pygame
sudo pip3 uninstall pygame

#在`Linux`下安装`iPython3`
sudo apt install ipython3

文件

文件的概念

1.文件的概念和作用

  • 计算机的文件,就是存储在某种长期储存设备上的一段数据
  • 长期存储设备包括:硬盘、U盘、移动硬盘、光盘...

2.文件的作用

  • 将数据长期保存下来,在需要的时候使用

3.文件的存储方式

在计算机中,文件是以二进制的方式保存在磁盘上的

文本文件

  • 可以使用文本编辑软件查看
  • 本质上还是二进制文件
  • 例如:python的源程序

二进制文件

  • 保存的内容不是给人直接阅读的,而是提供给其他软件使用的
  • 例如:图片文件、音频文件、视频文件等等
  • 二进制文件不能使用文本编辑软件查看

文件的基本操作

1.在计算机中要操作文件的套路非常固定,一共包含三个步骤:

  • 打开文件
  • 读、写文件
  1. 读将文件内容读入内存
  2. 写将内存内容写入文件
  • 关闭文件

2.操作文件的函数/方法

|序号|函数/方法|说明|

|---|---|---|

|01|open|打开文件,并且返回文件操作对象|

|02|read|将文件内容读取到内存|

|03|write|将指定内容写入文件|

|04|close|关闭文件|

`open`函数负责打开文件,并且返回文件对象

`read`/`write`/`close`三个方法都需要通过文件对象来调用

3.read方法——读取文件

  • `open`函数的第一个参数是要打开的文件名(文件名区分大小写)
  1. 如果文件存在,返回文件操作对象
  2. 如果文件不存在,会抛出异常
  • `read`方法可以一次性读入并返回文件的所有内容
  • `close`方法负责关闭文件
  • 如果忘记关闭文件,会造成系统资源消耗,而且会影响到后续对文件的访问

注意:`read`方法执行后,会把文件指针移动到文件的末尾

#1.打开-文件名需要注意大小写
file=open("123.txt")
#2.读取
text=file.read()
print(text)
#3.关闭
file.close()

文件指针

  • 文件指针标记从哪个位置开始读取数据
  • 第一次打开文件时,通常文件指针会指向文件的开始位置
  • 当执行了`read`方法后,文件指针会移动到读取内容的末尾
  1. 默认情况下会移动到文件末尾
  • 第一次读取之后,文件指针移动到了文件末尾,再次调用不会读取到任何的内容

4.打开文件的方式

`open`函数默认以只读方式打开文件,并且返回文件对象

f=open("文件名","访问方式")

|访问方式|说明|

|:---:|---|

|r|以只读方式打开文件。文件的指针将会放在文件的开头,这是默认模式。如果文件不存在,抛出异常|

|w|以只写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件|

|a|以追加方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入|

|r+|以读写方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常|

|w+|以读写方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件|

|a+|以读写方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入|

提示:频繁的移动文件指针,会影响文件的读写效率,开发中更多的时候会以只读、只写的方式来操作文件

5.按行读取文件内容

`read`方法默认会把文件的所有内容一次性读取到内存

如果文件太大,对内存的占用会非常严重

`readline`方法可以一次读取一行内容

方法执行后,会把文件指针移动到下一行,准备再次读取

#打开文件
file=open("123.txt")
while True:
    #读取一行内容
    text=file.readline()
    #判断是否读到内容
    if not text:
        break
    #每读取一行的末尾已经有了一个`\n`
    print(text,end="\n")
#关闭文件
file.close()

6.文件读写案例——复制文件

目标

  • 用代码的方式,来实现文件复制过程

小文件复制

  • 打开一个已有文件,读取完整内容,并写入到另外一个文件
#1.打开文件
file_read=open("123.txt")
file_write=open("123.txt[复件]","w")
#2.读取并写入文件
text=file_read.read()
file_write.write(text)
#3.关闭文件
file_read.close()
file_write.close()

大文件复制

  • 打开一个已有文件,逐行读取内容,并顺序写入到另外一个文件
#1.打开文件
file_read=open("123.txt")
file_write=open("123.txt[复件]","w")
#2.读取并写入文件
while True:
    #每次读取一行
    text=file_read.readline()
    #判断是否读取到内容
    if not text:
        break
    file_write.write(text)
#3.关闭文件
file_read.close()
file_write.close()

文件/文件夹的常用操作

  • 在终端/文件浏览器、中可以执行常规的文件/目录管理操作,例如:
  1. 创建、重命名、删除、改变路径、查看目录内容、……
  • 在`Python`中,如果希望通过程序实现上述功能,需要导入`os`模块

文件操作

|序号|方法名|说明|示例|

|---|---|---|---|

|01|rename|重命名文件|`os.rename(源文件名,目标文件名)`|

|02|remove|删除文件|`os.remove(文件名)`|

目录操作

|序号|方法名|说明|示例|

|---|---|---|---|

|01|listdir|目录列表|`os.listdir(目录名)`|

|02|mkdir|创建目录|`os.mkdir(目录名)`|

|03|rmdir|删除目录|`os.rmdir(目录名)`|

|04|getcwd|获取当前目录|`os.getcwd()`|

|05|chdir|修改工作目录|`os.chdir(目标目录)`|

|06|path.isdir|判断是否是文件|`os.path.isdir(文件路径)`|

提示:文件或者目录操作都支持相对路径和绝对路径

文本文件的编码方式

1.文本文件存储的内容是基于字符编码的文件,常见的编码有`ASCII`编码,`UNICODE`编码等

  • Python2.x默认使用`ASCII`编码格式
  • Python3.x默认使用`UTF-8`编码格式

`ASCII`编码

  • 计算机中只有`256`个`ASCII`字符
  • 一个`ASCII`在内存中占用1个字节的空间
  1. `8`个`0/1`的排列组合方式一共有`256`种,也就是`2**8`

`UTF-8`编码格式

  • 计算机中使用1~6个字节来表示一个`UTF-8`字符,涵盖了地球上几乎所有地区的文字
  • 大多数汉字会使用3个字节表示
  • `UTF-8`是`UNICODE`编码的一种编码格式

2.Ptyhon2.x中如何使用中文

  • Python2.x默认使用`ASCII`编码格式
  • Python3.x默认使用`UTF-8`编码格式
  • 在Python2.x文件的第一行增加以下代码,解释器会以`utf-8`编码来处理python文件
# *-* coding:utf8 *-*

也可以使用

#coding=utf8

unicode字符串

  • 在`Python2.x`中,即使指定了文件使用`UTF-8`的编码格式,但是在遍历字符串时,仍然会以字节为单位遍历字符串
  • 要能够正确的遍历字符串,在定义字符串时,需要在字符串的引号前,增加一个小写字母`u`,告诉解释器这是一个`unicode`字符串(使用`UTF-8`编码格式的字符串)
# *-* coding:utf8 *-*
#在字符串前,增加一个`u`表示这个字符串是一个utf8字符串
hello_str=u"你好世界"
print(hello_str)
for c in hello_str:
    print(c)

 `eval` 函数

`eval()` 函数十分强大 —— 将字符串当成有效的表达式来求值并返回计算结果

# 基本的数学计算
In [1]: eval("1 + 1")
Out[1]: 2

# 字符串重复
In [2]: eval("'*' * 10")
Out[2]: '**********'

# 将字符串转换成列表
In [3]: type(eval("[1, 2, 3, 4, 5]"))
Out[3]: list

# 将字符串转换成字典
In [4]: type(eval("{'name': 'xiaoming', 'age': 18}"))
Out[4]: dict

案例 - 计算器

需求

  • 提示用户输入一个加减乘除混合运算
  • 返回计算结果
input_str = input("请输入一个算术题:")
print(eval(input_str))

在开发时千万不要使用 `eval` 直接转换 `input` 的结果,以下命令是非常危险的

__import__('os').system('rmdir(目录名)')

你可能感兴趣的:(python,开发语言,1024程序员节)