风变编程笔记(一)-Python基础语法
关键字:print() type() input() append() pop() range() numpy math.ceil() index() @classmethod @staticmethod self __init__() __str__() split() join() random dir() csv time os smtplib email myqr
第0关 print()函数与变量
1. print()函数print()函数:告诉计算机,把括号的内容显示在屏幕上
# 不带引号 print(1+1) # 让计算机读懂括号里的内容,打印最终的结果 # 》》2 # 带引号 print(\'hello world!\') # 让计算机无需理解,原样复述引号中的内容 # 》》hello world! # 带双引号 print("Let\'s play") # 让计算机无需理解,原样复述引号中的内容,可以输出内容中的单引号 # 》》Let\'s play # 三引号 print(\'\'\' へ /| /\7 ∠_/ / │ / / │ Z _,< / /`ヽ │ ヽ / 〉 Y ` / / イ● 、 ● ⊂⊃〈 / () へ | \〈 >ー 、_ ィ │ // / へ / ノ<| \\ ヽ_ノ (_/ │//\'\'\') # 原样打印,实现换行 # 》》 へ /| # /\7 ∠_/ # / │ / / # │ Z _,< / /`ヽ # │ ヽ / 〉 # Y ` / / # イ● 、 ● ⊂⊃〈 / # () へ | \〈 # >ー 、_ ィ │ // # / へ / ノ<| \\ # ヽ_ノ (_/ │//
2. 转义字符
3. 变量
变量赋值用=表示,不要跟==搞混
第1关 数据类型与转换
1. 数据类型
Python里,最常用的数据类型有三种——字符串(str)、整数(int)和浮点数(float)
· 字符串英文string,简写str,有层名为【引号】的皮,只要是被【单/双/三引号】这层皮括起来的内容,不论那个内容是中文、英文、数字甚至火星文。只要是被括起来的,就表示是字符串类型
· 整数英文为integer,简写做int,Python世界的整数其实和现实世界数学中定义的一样:是正整数、负整数和零的统称,是没有小数点的数字
· 浮点数的英文名是float,是带小数点的数字。与【整数运算结果永远精确】的特点不同,计算机里浮点数的运算是不精确的,因为计算浮点数时会先讲数字转换成二进制数,通过二进制法则运算后,再通过复杂的计算公式,将二进制结果转成十进制小数
2. 算术运算符
print(2+1) # 加法 # 》》3 print(1-2) # 减法 # 》》-1 print(1*2) # 乘法 # 》》 2 print(1/2) # 除法 # 》》0.5 print(5 % 2) # 取模(做除法返回余数) # 》》1 print(2**3) # 幂(这里是2的3次方) # 》》8 print(11//2) # 取整除(做除法返回商的整数部分) # 》》5
一些列表的相关操作↓
s = [\'柯佳嬿\', \'许光汉\', \'施柏宇\', \'颜毓麟\', \'林鹤轩\', \'张翰\'] print(len(s)) # 打印列表s的长度 # 》》6 print(type(s)) # 打印列表s的类型 # 》》<class \'list\'> print(s[3]) # 打印列表s里面的第3个元素 # 》》颜毓麟 print(s[-3]) # 打印列表s里面的倒数第3个元素 # 》》颜毓麟 list1 = [91, 95, 97, 99] list2 = [92, 93, 96, 98] # 列表的合并 list3 = list1+list2 # 第一种方法:+直接拼接 print(list3) # 》》[91, 95, 97, 99, 92, 93, 96, 98] list1.extend(list2) # 第二种方法:extend()方法 print(list1) # 》》[91, 95, 97, 99, 92, 93, 96, 98] # 列表的排序 a = [91, 95, 97, 99, 92, 93, 96, 98] a.sort() # 列表从小到大排序 print(a) # 》》[91, 92, 93, 95, 96, 97, 98, 99] b = [91, 95, 97, 99, 92, 93, 96, 98] b.sort(reverse=True) # 列表从大到小排序 print(b) # 》》[99, 98, 97, 96, 95, 93, 92, 91] c = [91, 95, 97, 99, 92, 93, 96, 98] c.reverse() # 列表反向排序 print(c) # 》》[98, 96, 93, 92, 99, 97, 95, 91]
用冒号来截取列表元素的操作叫作切片(slice),切片可以单拿出来说很长的篇幅,网上搜到一篇讲的很清楚的:彻底搞懂Python切片操作
给列表增加元素:列表名.append()
给列表删除元素:del 列表名[元素的索引]pop()函数,用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值↓
students = [\'小明\', \'小红\', \'小刚\'] for i in range(3): student1 = students.pop(0) # 运用pop()函数,同时完成提取和删除。 students.append(student1) # 将移除的student1安排到最后一个座位。 print(students) # 》》》 [\'小红\', \'小刚\', \'小明\'] # 》》》 [\'小刚\', \'小明\', \'小红\'] # 》》》 [\'小明\', \'小红\', \'小刚\']
2. 字典(dict)
字典中的键具备唯一性,而值可重复
从字典中提取元素:字典名[字典的键]
字典新增键值对:字典名[键] = 值↓
scores = {\'小明\': 95, \'小红\': 90, \'小刚\': 90}
print(scores[\'小明\']) # 从字典中提取元素
# 》》95
scores[\'小红\'] = 88 # 改变字典中的值
print(scores[\'小红\'])
# 》》88
scores[\'小北\'] = 98 # 给字典新增键和值
print(scores)
# 》》{\'小明\': 95, \'小红\': 88, \'小刚\': 90, \'小北\': 98}
字典删除键值对:del 字典名[键]
3. 列表和字典的不同点
列表中的元素是有自己明确的“位置”的,所以即使看似相同的元素,只要在列表所处的位置不同,它们就是两个不同的列表。而字典相比起来就显得随和很多,调动顺序也不影响。因为列表中的数据是有序排列的,而字典中的数据是随机排列的。
4. 列表和字典的嵌套
students = { \'第一组\':[\'小明\',\'小红\',\'小刚\',\'小美\'], \'第二组\':[\'小强\',\'小兰\',\'小伟\',\'小芳\'] } print(students[\'第一组\'][3]) #取出\'第一组\'对应列表偏移量为3的元素,即\'小美\' scores = [ {\'小明\':95,\'小红\':90,\'小刚\':100,\'小美\':85}, {\'小强\':99,\'小兰\':89,\'小伟\':93,\'小芳\':88} ] print(scores[1][\'小强\']) #先定位到列表偏移量为1的元素,即第二个字典,再取出字典里键为\'小强\'对应的值,即99。
5. 元祖(tuple)
元组的写法是将数据放在小括号()中,它的用法和列表用法类似,主要区别在于列表中的元素可以随时修改,但元组中的元素不可更改
list2 = [(\'A\', \'B\'), (\'C\', \'D\'), (\'E\', \'F\')] print(list2[1][1]) # 》》 D # 从代码里可看出:1.元组内数据的提取也是用偏移量;2.元组也支持互相嵌套。
以上几种关于收纳的数据类型,最常用的还是列表,而对偏移量和切片的使用是写算法非常重要的技能
第5关 for循环和while循环
1. for...in...循环语句
2. range()函数
3. while循环
4. 两种循环对比
第6关 布尔值和四种语句
布尔运算的三种方式:
第一种计算:两个数值做比较
一共有6种比较方式:==、!=、>、<、>=、<=
print(3 == 3.0) # 》》True print(\'a\' != \'a\') # 》》False print(3 > 5) # 》》False print(3 < 5) # 》》True print(100 >= 101) # 》》False print(100 <= 101) # 》》True
第二种计算:直接用数值做运算
第三种计算:布尔值之间的运算
一共有5种计算方式:and、or、not、in、not in
# and计算和or计算 a = 1 b = -1 print(a == 1 and b == 1) # and要求条件都满足才为True # 》》False print(a == 1 or b == 1) # or只要求其中一个条件满足就为True # 》》True # not计算 a = True print(not a) # not计算会反转真假 # 》》False # in计算和not in计算 a = [1, 2, 3, 4, 5] print(0 in a) # in用来判断一个元素是否在一堆数据之中 # 》》False print(1 in a) # 》》True print(0 not in a) # not in用来判断一个元素是否不在一堆数据之中 # 》》True print(1 not in a) # 》》False
第7关 项目实操:PK小游戏(1)
import time #调用time模块 time.sleep(secs) #使用time模块下面的sleep()函数,括号里填的是间隔的秒数(seconds,简称secs) #time.sleep(1.5)就表示停留1.5秒再运行后续代码
import random # 调用random模块 a = random.randint(1, 100) # 随机生成1-100范围内(含1和100)的一个整数,并赋值给变量a print(a) print(random.choice([5, 7, 9, 11, 13, 15])) # 在给定列表中随机选择
取英文变量名推荐网站:CODELF
三种格式化字符串方法↓
a = 5 b = 7.85 print(str(a)+\'+\'+str(b)+\'=\'+str(a+b)) # 第一种:+拼接字符串和变量 # 》》5+7.85=12.85 print(\'%d+%s=%.2f\' % (a, b, a+b)) # 第二种:格式符+类型码 %.2f的意思是格式化字符串为浮点型并保留两位小数 # 》》5+7.85=12.85 print(\'{}+{}={}\'.format(a, b, a+b)) # 第三种:format()函数 # 》》5+7.85=12.85
第8关 编程思维:如何解决问题
拓展程序库numpy的简单用法↓
import numpy as np # 导入 numpy库,下面出现的 np 即 numpy库 a = [91, 95, 97, 99, 92, 93, 96, 98] average = np.mean(a) # 求平均数,average是numpy.float64类型 print(average) # 》》95.125 b = np.array(a) # 将列表a转换为numpy.ndarray类型 print(\'小于平均数的有:{}\' .format(b[b < average])) # 》》[91 95 92 93] c = b.tolist() # 将数组b转换回列表 print(type(c)) # 》》<class \'list\'>
第9关 函数
函数是组织好的、可以重复使用的、用来实现单一功能的代码
1. 定义和调用函数
# 函数名:1. 名字最好能体现函数的功能,一般用小写字母和单下划线、数字等组合 # 2. 不可与内置函数重名(内置函数不需要定义即可直接使用) def math(x): # 参数:根据函数功能,括号里可以有多个参数,也可以不带参数,命名规则与函数名相同 # 规范:括号是英文括号,后面的冒号不能丢 y = x ** 2 + x # 函数体:函数的执行过程,体现函数功能的语句,要缩进,一般是四个空格 return y # return语句:后面可以接多种数据类型,如果函数不需要返回值的话,可以省略 print(math(5)) # 调用math()函数
2. 参数类型
以最熟悉的print()函数为例↓
print(*objects, sep = \' \', end = \'\n\', file = sys.stdout, flush = False)
可以看到第一个参数objects带了*号,为不定长参数——这也是为什么print()函数可以传递任意数量的参数。其余四个为默认参数,我们可以通过修改默认值来改变参数。
print(\'www\', \'pypypy\', \'cn\') # objects -- 复数,表示可以一次输出多个对象。输出多个对象时,需要用 , 分隔。 # 》》www pypypy cn print(\'www\', \'pypypy\', \'cn\', sep=".") # sep -- 用来间隔多个对象,默认值是一个空格。 # 》》www.pypypy.cn print(\'hello\', end=\'~~~~~~\') # end -- 用来设定以什么结尾。默认值是换行符 \n,可以换成其他字符串。 print(\'world\') # 》》hello~~~~~~world
print()函数的参数file和flush说起来篇幅比较长,单写了篇关于print()、sys.stdout、sys.stderr的一些理解
3. 返回多个值
def a(): return 1, 2, 3 b = a() # 变量b接收函数a的返回值 print(b) # 打印返回值 # 》》(1, 2, 3) print(type(b)) # 打印返回值的类型 # 》》<class \'tuple\'>
也可以同时定义多个变量,来接收元组中的多个元素
def a(): return 1, 2, 3 x, y, z = a() print(x) # 》》1 print(y) # 》》2 print(z) # 》》3
4. 变量作用域
· 一个在函数内部赋值的变量仅能在该函数内部使用(局部作用域),它们被称作【局部变量】
· 在所有函数之外赋值的变量,可以在程序的任何位置使用(全局作用域),它们被称作【全局变量】
如果想将局部变量声明为全局变量,就要用到global语句
tfc = 1000 def tvc(): global tvc # global语句一般写在函数体的第一行,它会告诉Python,“我希望tvc是个全局变量,所以请不要用这个名字创建一个局部变量” vc = 200 x = 10 tvc = vc * x def tc(): print(tfc+tvc) # tc()函数内部现在可以直接使用声明后的全局变量tvc tvc() tc() # 》》3000
5. 函数的嵌套
tfc = 1000 def tvc(): vc = 200 x = 10 return vc * x def tc(): print(tfc+tvc()) # 在tc()函数中调用了tvc()函数 tc() # 》》3000
6. 递归
def digui(num): if (num == 1): return 1 return num * digui(num-1) x = digui(5) print(x) # 》》120
第10关 项目实操:PK小游戏(2)
import math # 人力计算 def estimated_number(size, time): number = size * 80 / time print(number) # 》》1.3333333333333333 print(\'项目大小为%.1f个标准项目,如果需要在%.1f个工时完成,则需要人力数量为:%d人\' % (size, time, math.ceil(number))) # 通过ceil()函数向上取整 # 》》项目大小为1.0个标准项目,如果需要在60.0个工时完成,则需要人力数量为:2人 estimated_number(1, 60)
如果不用ceil()函数,这个人力计算向上取整也可以通过判断余数不为零就加一的方法实现
2. index()函数index()函数用于找出列表中某个元素第一次出现的索引位置
import random punches = [\'石头\', \'剪刀\', \'布\'] # 猜拳游戏 def punches_game(user_choice): computer_choice = random.choice(punches) if user_choice == computer_choice: print(\'平局\') # 电脑的选择有3种,索引位置分别是:0石头、1剪刀、2布。 # 假设在电脑索引位置上减1,对应:-1布,0石头,1剪刀,皆胜。 elif user_choice == punches[punches.index(computer_choice)-1]: print(\'你赢了\') else: print(\'你输了\') punches_game(user_choice=\'布\')
第11关 编程思维:如何debug
多行注释有两种快捷操作:1) 在需要注释的多行代码块前后加一组三引号\'\'\' 2) 选中代码后使用快捷键操作:Mac为cmd+/
异常处理:
# 方式1: try: x = input(\'请你输入被除数:\') y = input(\'请你输入除数:\') z = float(x)/float(y) print(x, \'/\', y, \'= %.2f\' % z, sep=\'\') except ZeroDivisionError: # 当除数为0时,跳出提示,重新输入。 print(\'0是不能做除数的!\') except ValueError: # 当除数或被除数中有一个无法转换成浮点数时,跳出提示,重新输入。 print(\'除数和被除数都应该是整值或浮点数!\') # 方式2:将两个(或多个)异常放在一起,只要触发其中一个,就执行所包含的代码。 # except(ZeroDivisionError,ValueError): # print(\'你的输入有误,请重新输入!\') # 方式3:常规错误的基类,假设不想提供很精细的提示,可以用这个语句响应常规错误。 # except Exception: #换成BaseException也可以 # print(\'你的输入有误,请重新输入!\')
关于Python的所有报错类型,一般可以在这里查阅:https://www.runoob.com/python/python-exceptions.html
异常层次结构需到官网查看https://docs.python.org/zh-cn/3/library/exceptions.html#exception-hierarchy
第12关 类与对象1
Python中的对象等于类(class)和实例(instance)的集合:即类可以看作是对象,实例也可以看作是对象
1. 类的创建和调用
Python里的每个类都有自己独特的属性(attribute)和方法(method),是这个类的所有实例都共享的。换言之,每个实例都可以调用类中所有的属性和方法。
类名的首字母要大写,以便让我们轻松地辨认出“哦!这个是类!”
实例方法的创建语句,和函数的定义语句很类似,唯一不同的是:实例方法中有个必须放在首位的参数self
class Computer: # 创建一个类 screen = True def start(self): print(\'电脑正在开机中……\') my_computer = Computer() # 类的实例化 print(type(my_computer)) # <class \'__main__.Computer\'>验证了my_computer属于Computer这个类 # 》》<class \'__main__.Computer\'> print(my_computer) # 打印出Computer类的一个实例对象(object),后面的一串字符表示这个对象的内存地址(每次实例化都不一样) # 》》<__main__.Computer object at 0x10e6bad00> # 当实例my_computer一被创建出来,就可以调用类中的属性和方法。一句话概括就是:类有的实例都会有 print(my_computer.screen) # 实例调用类属性 # 》》True my_computer.start() # 调用类中的方法(传参不用管self) # 》》电脑正在开机中……
类中创建的属性和方法可以被其所有的实例调用,而且,实例的数目在理论上是无限的。我们可以同时“新建”多个实例
因此,类也被称为“实例工厂”,因其为所有实例提供了一套蓝图(即预先设定好有什么属性和方法)
除了实例方法,还有两个比较常见的方法类型,类方法和静态方法
类方法需使用装饰器@classmethod,第一个参数必须是当前类对象,该参数名一般约定为cls,通过它来传递类的属性和方法
class Computer: screen = \'已开机\' @classmethod def start(cls): print(\'电脑的状态为:\', cls.screen, sep=\'\') Computer.start() # 可是是类对象调用 # 》》电脑的状态为:已开机 my_computer = Computer() my_computer.start() # 也可以是实例对象调用 # 》》电脑的状态为:已开机
类方法是实例对象和类对象都可以调用,但实例方法只能由实例对象调用,如果非要用类调用,比如继承重写时,那就把实例放在括号里传参
静态方法使用装饰器@staticmethod,参数随意,实例对象和类对象都可以调用
如果只是存放逻辑性代码,不涉及类中其他属性或方法,就可以选择使用静态方法
import random class Computer: @staticmethod def start(): return random.randint(1, 99) print(Computer.start())
2. 特殊参数:self
self会接收实例化过程中传入的数据,当实例对象创建后,实例便会代替 self,在代码中运行。
class Chinese: name = \'周冬雨\' # 类属性name def greeting(self): print(\'你好啊冬叔\') def say(self): print(self.name + \'是中国人\') # 调用类属性 self.greeting() # 调用实例方法 person = Chinese() # 创建Chinese的实例person person.say() # 调用实例方法 # 》》周冬雨是中国人 # 》》你好啊冬叔
当最后一行代码运行时,实例person会像参数一样传给self,替换掉self
作用相当于:
class Chinese: name = \'周冬雨\' # 类属性name def greeting(person): print(\'你好啊冬叔\') def say(person): print(person.name + \'是中国人\') # 调用类属性 person.greeting() # 调用实例方法 person = Chinese() # 创建Chinese的实例person person.say() # 调用实例方法 # 》》周冬雨是中国人 # 》》你好啊冬叔
self的作用相当于先给实例占了个位置,关键在于占位,即使叫别的名字也是可以的,只是一般不这样做而已
综上,所以我们说self代表的是类的实例本身,方便数据的流转。对此,需要记住两点:
第一点:只要在类中用def创建实例方法时,就必须把第一个参数位置留给self,并在调用方法时忽略它(不用给self传参)
第二点:当在类的方法内部想调用类属性或其他方法时,就要采用self.属性名或self.方法名的格式
3. 特殊方法:初始化方法(也叫构造函数)
定义初始化方法的格式是def __init__(self),是由init加左右两边的【双】下划线组成( initialize “初始化”的缩写)
初始化方法的作用在于:当每个实例对象创建时,该方法内的代码无须调用就会自动运行
在编写习惯上,我们会在初始化方法内部完成类属性的创建,为类属性设置初始值,这样类中的其他方法就能直接、随时调用
class Chinese: def __init__(self, name, birth, region): self.name = name # self.name = \'周冬雨\' self.birth = birth # self.birth = \'河北\' self.region = region # self.region = \'石家庄\' def born(self): print(self.name + \'出生在\' + self.birth) def live(self): print(self.name + \'居住在\' + self.region) person = Chinese(\'周冬雨\', \'河北\', \'石家庄\') # 传入初始化方法的参数 person.born() # 》》周冬雨出生在河北 person.live() # 》》冬雨居住在石家庄
第13关 类与对象2
在Python中,我们的习惯表述是:A类是B类的子类,而B类是A类的父类(或超类)
类的定制,不仅可以让子类拥有新的功能,还能让它有权修改继承到的代码
所以,当我们谈定制时,已经包含了继承。毕竟,类的定制的前提是继承,而定制的加入让类的继承不仅仅只是单纯的复制而已
1. 继承的基础语法
class Chinese: eye = \'black\' def eat(self): print(\'吃饭,选择用筷子\') class Cantonese(Chinese): # 通过继承,Chinese类有的,Cantonese类也有 pass # 验证子类可以继承父类的属性和方法,进而传递给子类创建的实例 yewen = Cantonese() # 子类创建的实例,从子类那间接得到了父类的所有属性和方法 print(yewen.eye) # 子类创建的实例,可调用父类的属性 # 》》black yewen.eat() # 子类创建的实例,可调用父类的方法 # 》》吃饭,选择用筷子
函数isinstance(),可以用来判断某个实例是否属于某个类
class Chinese: pass class Cantonese(Chinese): pass gonger = Chinese() yewen = Cantonese() print(isinstance(yewen, Cantonese)) # 判断yewen是否为Cantonese的实例 # 》》True print(isinstance(yewen, Chinese)) # 判断yewen是否为Chinese的实例 # 》》True print(isinstance(gonger, Cantonese)) # 判断gonger是否为Cantonese的实例 # 》》False print(isinstance(gonger, (Cantonese, Chinese))) # 判断gonger实例是否属于元组里几个类中的一个 # 》》True print(isinstance(gonger, object)) # 判断gonger是否为object的实例 # 》》True print(isinstance(yewen, object)) # 判断yewen是否为object的实例 # 》》True
理论上,父类可以被无限个子类所继承
2. 类的继承之多层继承
子类创建的实例可调用所有层级父类的属性和方法
class Earthman: eye_number = 2 class Chinese(Earthman): eye_color = \'black\' class Cantonese(Chinese): # Cantonese继承了Chinese,同时也继承了Earthman pass yewen = Cantonese() print(yewen.eye_number) # 》》2 print(yewen.eye_color) # 》》black
3. 类的继承之多重继承
一个类,可以同时继承多个类,语法为class A(B,C,D):括号里的顺序是有讲究的,和子类更相关的父类会放在更左侧,所以A的实例在调用属性和方法时,会先在左侧的父类中找,找不到才会去右侧的父类找(可理解为“就近原则”)
class Su: born_city = \'Jiangsu\' wearing = \'thick\' def diet(self): print(\'我们爱吃甜\') class Yue: settle_city = \'Guangdong\' wearing = \'thin\' def diet(self): print(\'我们吃得清淡\') class Yuesu(Yue, Su): pass xiaoming = Yuesu() print(xiaoming.wearing) # 先在 Yue类找,找到了,打印出来 # 》》thin print(xiaoming.born_city) # Yue类没有born_city,才去Su类找 # 》》Jiangsu xiaoming.diet() # 方法调用,和属性调用一样,也符合就近原则 # 》》我们吃得清淡
class C0: name = \'C0\' class C2(C0): num = 2 class C1: num = 1 class C3: name = \'C3\' class C4(C1, C2, C3): pass ins = C4() print(ins.name) # 》》C0 print(ins.num) # 》》1
可以发现就近原则中的一个细节:多重继承中,若某父类还有父类的话,会先继续往上找到顶
4. 类的定制,新增代码
class Chinese: eye = \'black\' def eat(self): print(\'吃饭,选择用筷子\') class Cantonese(Chinese): # 类的继承 native_place = \'guangdong\' # 类的定制 def dialect(self): # 类的定制 print(\'我们会讲广东话\') yewen = Cantonese() print(yewen.eye) # 父类的属性能用 # 》》black print(yewen.native_place) # 子类的定制属性也能用 # 》》guangdong yewen.eat() # 父类的方法能用 # 》》吃饭,选择用筷子 yewen.dialect() # 子类的定制方法也能用 # 》》我们会讲广东话
可见:我们可以在子类下新建属性或方法,让子类可以用上父类所没有的属性或方法。这种操作,属于定制中的一种:新增代码
5. 类的定制,重写代码
重写代码,是在子类中,对父类代码的修改
class Chinese: def land_area(self, area): print(\'我们居住的地方,陆地面积是%d万平方公里左右\' % area) class Cantonese(Chinese): def land_area(self, area, rate=0.0188): # 间接对方法进行重写 Chinese.land_area(self, area * rate) # 直接继承父类方法,再调整参数 gonger = Chinese() yewen = Cantonese() gonger.land_area(960) # 》》我们居住的地方,陆地面积是960万平方公里左右 yewen.land_area(960) # 》》我们居住的地方,陆地面积是18万平方公里左右
子类继承父类方法的操作是在def语句后接父类.方法(参数)
第14关 项目实操:PK小游戏(3)
在Python中,如果方法名形式是左右带双下划线的,那么就属于特殊方法(如初始化方法),有着特殊的功能
如果在类中定义了__str__(self)方法,那么当使用print打印实例对象的时候,就会直接打印出在这个方法中return的数据
class Book: def __init__(self, name, author, comment, state=0): self.name = name self.author = author self.comment = comment self.state = state def __str__(self): if self.state == 0: status = \'未借出\' else: status = \'已借出\' return \'名称:《%s》 作者:%s 推荐语:%s\n状态:%s \' % (self.name, self.author, self.comment, status) book1 = Book(\'像自由一样美丽\', \'林达\', \'你要用光明来定义黑暗,用黑暗来定义光明\') # 传入参数,创建实例对象 print(book1) # 直接打印对象即可,不能写成print(book1.__str__()) # 》》名称:《像自由一样美丽》 作者:林达 推荐语:你要用光明来定义黑暗,用黑暗来定义光明 # 》》状态:未借出
第15关 编码和文件读写
1. 二进制
二进制转十进制,例如:(11010)2 = 1*24+1*23+0*22+1*21+0*20 = (26)10
两个二进制位可以表示22,十进制的0,1,2,3,四种状态
八个二进制位可以表示28,也就是256种状态
用来存放一位0或1,就是计算机里最小的存储单位,叫做【位】,也叫【比特】(bit)。8个比特构成一个【字节】(byte),这是计算机里最常用的单位
“流量”就是这么计算的↓
百兆宽带,下载速度最多只能达到十多兆,是因为运营商的带宽是以比特每秒为单位的,比如100M就是100Mbit/s
而我们常看到的下载速度KB却是以字节每秒为单位显示的,1byte = 8bit,所以运营商说的带宽得先除以8,百兆宽带下载速度,也就是十几兆了
2. 编码表
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,而当字符在ASCII码(读音:/ˈæski/)的范围时,就用一个字节表示,所以UTF-8还可以兼容ASCII编码
Unicode与UTF-8这种暧昧的关系一言以蔽之:Unicode是内存编码的规范,而UTF-8是如何保存和传输Unicode的手段
因为二进制是由一堆0和1构成的,过长的数字对于人的阅读有很大障碍,为了解决这一问题,也减少书写的复杂性,又引入了八进制和十六进制
为什么偏偏是16或8进制?2、8、16,分别是2的1次方、3次方、4次方。这一点使得三种进制之间可以非常直接地互相转换
8进制是用0,1,2,3,4,5,6,7;16进制是用0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f来表示。比如说,字母K在ASCII编码表用不同进制表示的话是这样的↓
3. 编码方案在当前的使用情况
第0,计算机是有自己的工作区的,这个工作区被称为“内存”。数据在内存当中处理时,使用的格式是Unicode,统一标准
在Python3当中,程序处理输入的字符串是默认使用Unicode编码的,所以什么语言都可以输入
第1,数据在硬盘上存储,或者是在网络上传输时,用的是UTF-8,因为节省空间。不必操心如何转换UTF-8和Unicode,当点击保存的时候,程序已经“默默地”做好了编码工作
第2,一些中文的文件和中文网站,还在使用GBK,和GB2312
4. encode()和decode()
编码,即将人类语言转换为计算机语言,就是【编码】encode();反之,就是【解码】decode()。它们的用法如下图所表示↓
print(\'柯佳嬿\'.encode(\'utf-8\')) # 》》b\'\xe6\x9f\xaf\xe4\xbd\xb3\xe5\xac\xbf\' print(\'柯佳嬿\'.encode(\'gbk\')) # 》》b\'\xbf\xc2\xbc\xd1\x8b\xf7\' print(b\'\xe6\x9f\xaf\xe4\xbd\xb3\xe5\xac\xbf\'.decode(\'utf-8\')) # 》》柯佳嬿 print(b\'\xbf\xc2\xbc\xd1\x8b\xf7\'.decode(\'gbk\')) # 》》柯佳嬿 print(type(b\'\xbf\xc2\xbc\xd1\x8b\xf7\')) # 》》<class \'bytes\'>
将人类语言编码后得到的结果,最前面都有字母b,代表是bytes(字节)类型的数据;\x是分隔符,用来分隔一个字节和另一个字节
所谓的编码,其实本质就是把str(字符串)类型的数据,利用不同的编码表,转换成bytes(字节)类型的数据
将\x替换为%,就是网址中的搜索条件了,百度搜索“柯佳嬿”↓
https://www.baidu.com/s?wd=%bf%c2%bc%d1%8b%f7(gbk)
https://www.baidu.com/s?wd=%e6%9f%af%e4%bd%b3%e5%ac%bf(utf-8)
用decode()解码的时候要注意,UTF-8编码的字节就一定要用UTF-8的规则解码,其他编码同理,否则就会出现乱码或者报错的情况
print(\'K\'.encode(\'ASCII\')) # 》》b\'K\'
大写字母K被编码后还是K,但这两个K对计算机来说意义是不同的,前者是字符串,采用系统默认的Unicode编码,占两个字节,后者则是bytes类型的数据,只占一个字节,这也验证了前面所说的编码就是将str类型转换成bytes类型
5. 读写文件open()函数的第一个参数是文件路径,分为两种:相对路径和绝对路径,绝对路径就是最完整的路径,相对路径指的就是【相对于当前文件夹】的路径,也就是当前编写的这个py文件所放的文件夹路径
可以把文件直接拖到终端窗口获取绝对路径,还可以用VS Code打开文件夹,在文件点击右键↓
需要注意的是,Windows系统里,常用\来表示绝对路径,由于\在Python中是转义字符,所以时常会有冲突。为了避坑,Windows的绝对路径通常要稍作处理,写成以下两种格式↓
open(\'C:\\Users\\Ted\\Desktop\\test\\abc.txt\') # 将\'\\'替换成\'\\\' open(r\'C:\Users\Ted\Desktop\test\abc.txt\') # 在路径前加上字母r
第二个参数表示打开文件时的模式,这里是字符串 \'r\',表示 read,表示我们以读的模式打开了这个文件
第三个参数encoding=\'utf-8\',表示的是返回的数据采用何种编码,一般采用utf-8或者gbk,注意这里是写encoding而不是encode
打开文件file1之后,就可以用read()函数进行读取的操作
关闭文件,使用的是close()函数
为啥要关闭文件呢?原因有两个:1.计算机能够打开的文件数量是有限制的,open()过多而不close()的话,就不能再打开文件了。2.能保证写入的内容已经在文件里被保存好了。
open()的第二个参数如果是\'w\',表示write,会先清空掉文件内容,然后再写入;不想完全覆盖掉原文件的话,就要使用\'a\'模式,表示append,追加的意思write()函数写入文本文件的是字符串类型
在\'w\'和\'a\'模式下,如果打开的文件不存在,那么open()函数会自动创建一个
图片和音频是以二进制的形式保存的,就可以用wb模式,它的意思是以二进制的方式打开一个文件用于写入
为了避免打开文件后忘记关闭,占用资源或当不能确定关闭文件的恰当时机的时候,我们可以用到关键字with
# 普通写法 file1 = open(\'abc.txt\', \'a\') file1.write(\'张无忌\') file1.close() # 使用with关键字的写法 with open(\'abc.txt\', \'a\') as file1: # with open(\'文件地址\',\'读写模式\') as 变量名: # 格式:冒号不能丢 file1.write(\'张无忌\') # 格式:对文件的操作要缩进 # 格式:无需用close()关闭
6. readlines()和writelines()
file1 = open(\'scores.txt\', \'r\', encoding=\'utf-8\') file_lines = file1.readlines() file1.close() print(file_lines) # 》》[\'罗恩 23 35 44\n\', \'哈利 60 77 68 88 90\n\', \'赫敏 97 99 89 91 95 99\n\', \'马尔福 100 85 90\']
readlines()是“按行读取”,readlines() 会从txt文件取得一个列表,列表中的每个字符串就是scores.txt中的每一行,而且每个字符串后面还有换行的\n符号file1 = open(\'scores.txt\', \'w\', encoding=\'utf-8\') file_lines = file1.writelines( [\'罗恩 23 35 44\n\', \'哈利 60 77 68 88 90\n\', \'赫敏 97 99 89 91 95 99\n\', \'马尔福 100 85 90\']) file1.close()
writelines()的参数可以是序列也可以是字符串 7. split()
split() 通过指定分隔符对字符串进行切片a = \'jjj kkk lll\' print(a.split(\' \')) # 》》[\'jjj\', \'kkk\', \'lll\'] b = input(\'请输入内容\').split(\',\') print(type(b)) # 》》<class \'list\'>
join()函数,是把字符串合并
a = [\'c\', \'a\', \'t\'] b = \'\' print(b.join(a)) # 》》cat c = \'-\' print(c.join(a)) # 》》c-a-t
join()的用法是str.join(sequence),str代表在这些字符串之中,要用什么字符串连接,上面两个例子,一个是空字符串,一个是横杠,sequence代表数据序列,在这里是列表a
第16关 模块
在模块中,我们不但可以直接存放变量,还能存放函数,还能存放类
定义变量需要用赋值语句,封装函数需要用def语句,封装类需要用class语句,但封装模块不需要任何语句
之所以不用任何语句,是因为每一份单独的Python代码文件(后缀名是.py的文件)就是一个单独的模块
封装模块的目的也是为了把程序代码和数据存放起来以便再次利用。如果封装成类和函数,主要还是便于自己调用,但封装了模块,不仅能自己使用,文件的方式也很容易共享给其他人使用
所以,使用模块主要有两种方式,一种是自己建立模块并使用,另外一种是使用他人共享的模块
1. import语句
使用import语句导入一个模块,最主要的目的并不是运行模块中的执行语句,而是为了利用模块中已经封装好的变量、函数、类
当导入模块后,要使用模块中的变量、函数、类,需要在使用时加上模块.的格式
# 这是模块test.py print(\'正在学习模块...\') a = \'我是模块中的变量a\' def hi(): a = \'我是函数里的变量a\' print(\'函数“hi”已经运行!\') class Go2: a = \'我是类Go2中的变量a\' def do2(self): print(\'类Go2中的函数“do2”已经运行!\')
# 这是主程序main.py import test # 导入test模块 print(test.a) # 使用“模块.变量”调用模块中的变量 test.hi() # 使用“模块.函数()”调用模块中的函数 A = test.Go2() # 使用“变量 = 模块.类()”实例化模块中的类 print(A.a) # 实例化后,不再需要“模块.” A.do2() # 实例化后,不再需要“模块.”
下面是主程序main.py的执行结果↓
在学习模块...
我是模块中的变量a
函数“hi”已经运行!
我是类Go2中的变量a
类Go2中的函数“do2”已经运行!
当然test.py这种为了组织代码而封装的模块是最好不要出现print(\'正在学习模块...\')这种执行语句,这里写一下主要是为了体现什么时候需要模块.
import语句还有一种用法是import…as…,比如我们觉得import test太长,就可以用import test as t语句,意思是为“test”取个别名为“t”,上面的main.py文件可以写成这样↓
# 这是主程序main.py import test as t print(t.a) t.hi() A = t.Go2() print(A.a) A.do2()
当需要同时导入多个模块时,可以用逗号隔开。比如import a,b,c可以同时导入“a.py,b.py,c.py”三个文件
2. from … import … 语句from … import …语句可以从模块中导入一个指定的部分到当前模块,格式如下↓
当需要从模块中同时导入多个指定内容,也可以用逗号隔开,写成from xx模块 import a,b,c的形式,上面的main.py文件也可以写成这样↓
# 这是主程序main.py from test import a from test import hi, Go2 print(a) # 打印变量“a” hi() # 调用函数“hi” A = Go2() # 实例化“Go2”类 print(A.a) # 打印实例属性“a” A.do2() # 调用实例方法“do2”
对于from … import …语句要注意的是,没有被写在import后面的内容,将不会被导入
当需要从模块中指定所有内容直接使用时,可以写成【from xx模块 import *】的形式,*代表“模块中所有的变量、函数、类”
不过一般情况下,不要为了图方便直接使用【from xx模块 import *】的形式
这是因为,模块.xx的调用形式能通过阅读代码一眼看出是在调用模块中的变量/函数/方法,而去掉模块.后代码就不是那么直观了
3. if __name__ == \'__main__\'
对于Python和其他许多编程语言来说,程序都要有一个运行入口
在Python中,当在运行某一个py文件,就能启动程序 ——— 这个py文件就是程序的运行入口,例如刚才的main.py就是运行入口
更复杂的情况,也可以运行一个主模块,然后层层导入其他模块
当有了一大堆py文件组成一个程序的时候,为了【指明】某个py文件是程序的运行入口,可以在该py文件中写出这样的代码
# 【文件:xx.py】 代码块 ①…… if __name__ == \'__main__\': 代码块 ②……
这句话的意思是这样的↓
这里的if __name__ == \'__main__\'就相当于是 Python 模拟的程序入口。Python 本身并没有规定这么写,这是一种程序员达成共识的编码习惯
4. 借用模块
之前见过的time模块和random模块是Python的系统内置模块,也就是说Python安装后就准备好了这些模块提供使用
如果是第三方编写的模块,需要先从Python的资源管理库下载安装相关的模块文件
下载安装的方式是打开终端,Windows用户输入pip install + 模块名;苹果电脑输入:pip3 install + 模块名,点击enter即可。(需要预装python解释器和pip)
比如爬虫时会需要用到requests这个库(库是具有相关功能模块的集合),就需要在终端输入pip3 install requests(Mac用户)的指令
Python标准库官方文档:https://docs.python.org/zh-cn/3.8/library/index.html
借用模块重点关注的是模块中的函数和类方法有什么作用,然后把使用案例做成笔记慢慢积累
例如random模块的关键知识(也就是比较有用的函数和类方法),可以做成这样的笔记↓
import random # 调用random模块 a = random.random() # 随机从0-1之间(包括0不包括1)抽取一个小数 print(a) a = random.randint(0, 100) # 随机从0-100(包括0和100)之间抽取一个数字 print(a) a = random.choice(\'abcdefg\') # 随机从字符串,列表等对象中抽取一个元素(可能会重复) print(a) a = random.sample(\'abcdefg\', 3) # 随机从字符串,列表等对象中抽取多个不重复的元素 print(a) items = [1, 2, 3, 4, 5, 6] # “随机洗牌”,比如打乱列表 random.shuffle(items) print(items)
可以使用dir()函数查看一个模块,看看它里面有什么变量、函数、类、类方法
import random # 调用random模块 print(dir(random)) # 》》[\'BPF\', \'LOG4\', \'NV_MAGICCONST\', \'RECIP_BPF\', \'Random\', \'SG_MAGICCONST\', \'SystemRandom\', \'TWOPI\', \'_Sequence\', \'_Set\', \'__all__\', \'__builtins__\', \'__cached__\', \'__doc__\', \'__file__\', \'__loader__\', \'__name__\', \'__package__\', \'__spec__\', \'_accumulate\', \'_acos\', \'_bisect\', \'_ceil\', \'_cos\', \'_e\', \'_exp\', \'_inst\', \'_log\', \'_os\', \'_pi\', \'_random\', \'_repeat\', \'_sha512\', \'_sin\', \'_sqrt\', \'_test\', \'_test_generator\', \'_urandom\', \'_warn\', \'betavariate\', \'choice\', \'choices\', \'expovariate\', \'gammavariate\', \'gauss\', \'getrandbits\', \'getstate\', \'lognormvariate\', \'normalvariate\', \'paretovariate\', \'randint\', \'random\', \'randrange\', \'sample\', \'seed\', \'setstate\', \'shuffle\', \'triangular\', \'uniform\', \'vonmisesvariate\', \'weibullvariate\']
这样可以把模块中的函数(函数和类方法)一览无余地暴露出来。对于查到的结果"__xx__"结构的(如__doc__),它们是系统相关的函数,我们不用理会,直接看全英文的函数名即可
dir(x),可以查询到x相关的函数,x可以是模块,也可以是任意一种对象
a = \'\' # 设置一个字符串 print(dir(a)) # 把字符串相关的函数展示出来 # 》》[\'__add__\', \'__class__\', \'__contains__\', \'__delattr__\', \'__dir__\', \'__doc__\', \'__eq__\', \'__format__\', \'__ge__\', \'__getattribute__\', \'__getitem__\', \'__getnewargs__\', \'__gt__\', \'__hash__\', \'__init__\', \'__init_subclass__\', \'__iter__\', \'__le__\', \'__len__\', \'__lt__\', \'__mod__\', \'__mul__\', \'__ne__\', \'__new__\', \'__reduce__\', \'__reduce_ex__\', \'__repr__\', \'__rmod__\', \'__rmul__\', \'__setattr__\', \'__sizeof__\', \'__str__\', \'__subclasshook__\', \'capitalize\', \'casefold\', \'center\', \'count\', \'encode\', \'endswith\', \'expandtabs\', \'find\', \'format\', \'format_map\', \'index\', \'isalnum\', \'isalpha\', \'isascii\', \'isdecimal\', \'isdigit\', \'isidentifier\', \'islower\', \'isnumeric\', \'isprintable\', \'isspace\', \'istitle\', \'isupper\', \'join\', \'ljust\', \'lower\', \'lstrip\', \'maketrans\', \'partition\', \'replace\', \'rfind\', \'rindex\', \'rjust\', \'rpartition\', \'rsplit\', \'rstrip\', \'split\', \'splitlines\', \'startswith\', \'strip\', \'swapcase\', \'title\', \'translate\', \'upper\', \'zfill\'] a = [] # 设置一个列表 print(dir(a)) # 把列表相关的函数展示出来 # 》》[\'__add__\', \'__class__\', \'__contains__\', \'__delattr__\', \'__delitem__\', \'__dir__\', \'__doc__\', \'__eq__\', \'__format__\', \'__ge__\', \'__getattribute__\', \'__getitem__\', \'__gt__\', \'__hash__\', \'__iadd__\', \'__imul__\', \'__init__\', \'__init_subclass__\', \'__iter__\', \'__le__\', \'__len__\', \'__lt__\', \'__mul__\', \'__ne__\', \'__new__\', \'__reduce__\', \'__reduce_ex__\', \'__repr__\', \'__reversed__\', \'__rmul__\', \'__setattr__\', \'__setitem__\', \'__sizeof__\', \'__str__\', \'__subclasshook__\', \'append\', \'clear\', \'copy\', \'count\', \'extend\', \'index\', \'insert\', \'pop\', \'remove\', \'reverse\', \'sort\'] a = {} # 设置一个字典 print(dir(a)) # 把字典相关的函数展示出来 # 》》[\'__class__\', \'__contains__\', \'__delattr__\', \'__delitem__\', \'__dir__\', \'__doc__\', \'__eq__\', \'__format__\', \'__ge__\', \'__getattribute__\', \'__getitem__\', \'__gt__\', \'__hash__\', \'__init__\', \'__init_subclass__\', \'__iter__\', \'__le__\', \'__len__\', \'__lt__\', \'__ne__\', \'__new__\', \'__reduce__\', \'__reduce_ex__\', \'__repr__\', \'__reversed__\', \'__setattr__\', \'__setitem__\', \'__sizeof__\', \'__str__\', \'__subclasshook__\', \'clear\', \'copy\', \'fromkeys\', \'get\', \'items\', \'keys\', \'pop\', \'popitem\', \'setdefault\', \'update\', \'values\']
数据分析需要用到pandas和NumPy模块,网页开发要用到Django模块
5. csv模块csv是一种文件格式,你可以把它理解成“简易版excel”。学会了csv模块,就可以用程序处理简单的电子表格了
csv模块的中英文文档:https://yiyibooks.cn/xx/python_352/library/csv.html#module-csv
新建了一个名为test.csv的文件,里面的内容是这样↓
csv读取数据↓
import csv # 参数encoding=\'utf-8\'防止出现乱码 with open(\'test.csv\', newline=\'\', encoding=\'utf-8\') as f: # 使用csv的reader()方法,创建一个reader对象 reader = csv.reader(f) # 遍历reader对象的每一行 for row in reader: print(row) # 》》[\'商品编号\', \'商品名称\', \'单价\', \'库存\', \'销量\'] # 》》[\'1\', \'猫零食\', \'12\', \'3133\', \'5164\'] # 》》[\'2\', \'普通猫粮\', \'33\', \'5055\', \'2231\'] # 》》[\'3\', \'猫粮四合一\', \'187\', \'212\', \'334\']
终端输出的每一行信息都是一个列表
csv写入数据
import csv with open(\'test.csv\', \'a\', newline=\'\', encoding=\'utf-8\') as f: writer = csv.writer(f) writer.writerow([\'4\', \'猫砂\', \'25\', \'1022\', \'886\']) writer.writerow([\'5\', \'猫罐头\', \'18\', \'2234\', \'3121\'])
import time # 开始计时 start = time.time() # 格式化当前日期和时间 print(time.strftime(\'%Y-%m-%d %H:%M:%S\', time.localtime())) # 倒计时,第一种写法 for t in range(5, 0, -1): print(\'\r倒计时 \' + str(t) + \' 秒\', end=\'\', flush=True) time.sleep(1) print(\'\') # 倒计时,第二种写法 for t in range(5, 0, -1): info = \'倒计时 \' + str(t) + \' 秒\' print(info, end=\'\') print(\'\b\'*(len(info)*2), end=\'\', flush=True) time.sleep(1) print(\'\') # 结束计时 end = time.time() print(\'开始时间:\', end=\'\') # 格式化开始时间 print(time.strftime(\'%H:%M:%S\', time.localtime(start))) print(\'结束时间:\', end=\'\') # 格式化结束时间 print(time.strftime(\'%H:%M:%S\', time.localtime(end))) # 计算持续时间 print(\'持续了%d秒\' % (end-start))
import os # 读取本地文件test.txt的内容并赋值给变量a with open(\'test.txt\', \'r\') as f: a = f.read() # 在a的基础上添加内容并写入到test_new.txt with open(\'test_new.txt\', \'w\') as new: new.write(a+\'Bewear\') # 将test_new.txt重命名为test.txt,将其替代 os.replace(\'test_new.txt\', \'test.txt\') # os.getcwd() # 返回当前工作目录 # os.listdir(path) # 返回path指定的文件夹包含的文件或文件夹的名字的列表 # os.mkdir(path) # 创建文件夹 # os.path.abspath(path) # 返回绝对路径 # os.path.basename(path) # 返回文件名 # os.path.isfile(path) # 判断路径是否为文件 # os.path.isdir(path) # 判断路径是否为目录
第17关 项目实操:收发邮件
负责发送邮件的smtplib模块,和负责构造邮件内容的email模块,这两个都是Python内置模块
1. smtplib模块
要发送邮件,需要用到smtplib模块的以下方法↓
import smtplib server = smtplib.SMTP() server.connect(host, port) server.login(username, password) server.sendmail(sender, to_addr, msg.as_string()) server.quit()
SMTP (Simple Mail Transfer Protocol)代表“简单邮件传输协议”,SMTP 协议是由源服务器到目的地服务器传送邮件的一组规则
可以简单理解为:需要通过SMTP指定一个服务器,这样才能把邮件送到另一个服务器smtplib.SMTP.connect(host,port)就是在连接指定的服务器
host是指定连接的邮箱服务器,可以指定服务器的域名;port 是“端口”,一般情况下SMTP默认端口号为25。这两个参数都可以到邮箱设置中找到
以QQ邮箱为例,SMTP服务器地址是:smtp.qq.com,端口是465或587
这里有两种写法,一是使用默认端口:25
import smtplib server = smtplib.SMTP() server.connect(\'smtp.qq.com\', 25)
二是尝试设置中的端口,比如465。这时会有些不同,QQ 邮箱采用的加密方式是SSL,需要写成这样↓
import smtplib server = smtplib.SMTP_SSL() # 如果端口是用SSL加密,需要这样写 server.connect(\'smtp.qq.com\', 465) # 如果出现编码错误UnicodeDecodeError,可以这样写:server.connect(\'smtp.qq.com\', 465,\'utf-8\')
然后就要登录邮箱了,login()方法需要输入两个参数:登录邮箱和授权码
username = \'xxx@qq.com\' password = \'你的授权码数字\' server.login(username, password) # username:登录邮箱的用户名 # password:登录密码/授权码
这个授权码需要到这里获取:打开https://mail.qq.com/,登录邮箱,然后点击位于顶部的【设置】按钮,选择【账户设置】,然后下拉到这个位置↓
把首个SMTP服务开启。这时,QQ邮箱会提供一个授权码,注意保护好这个授权码↓
接着是发送邮件,sendmail()方法需要三个参数:发件人,收件人和邮件内容
这里的发件人from_addr与上面的username是一样的,都是登录邮箱,所以只用设置一次
server.sendmail(from_addr, to_addr, msg.as_string()) # from_addr:邮件发送地址,就是上面的username # to_addr:邮件收件人地址 # msg.as_string():为一个字符串类型
msg.as_string()是一个字符串类型:as_string()是将发送的信息msg变为字符串类型
msg就需要用到email模块了,之后说
最后是quit()退出服务器
server.quit() # 退出服务器,结束SMTP会话
2. email模块email模块是用来写邮件内容的模块。这个内容可以是纯文本、HTML内容、图片、附件等多种形式
每种形式对应的导入方式是这样的↓
from email.mime.text import MIMEText from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipartfi
按住command同时点击mime,会看到一个名为__init__.py的空文件,这说明email其实是一个“包”
模块(module)一般是一个文件,而包(package)是一个目录,一个包中可以包含很多个模块,可以说包是“模块打包”组成的
Python中的包都必须默认包含一个__init__.py的文件
__init__.py控制着包的导入行为,假如这个文件为空,那么仅仅导入包的话,就什么都做不了,所以直接import email是行不通的,需要使用from … import …语句,从email包目录下的【某个文件】引入【需要的对象】
关于模块和包的概念可以看下这篇文章:https://www.cnblogs.com/ymjyqsx/p/6560210.html
还是以QQ邮箱为例发送一封纯文本邮件,除了引入了MIMEText,还要引入为了构建邮件头的Header
建议代码结构:①导入模块 ②定义变量 ③使用方法
# smtplib 用于邮件的发信动作 import smtplib # email 用于构建邮件内容 from email.mime.text import MIMEText # 用于构建邮件头 from email.header import Header # 发信方的信息:发信邮箱,QQ邮箱授权码 from_addr = \'xxx@qq.com\' password = \'授权码数字\' # 收信方邮箱 to_addr = \'xxx@qq.com\' # 发信服务器 smtp_server = \'smtp.qq.com\' # 邮件正文内容 text = \'\'\'寒蝉凄切,对长亭晚,骤雨初歇 都门帐饮无绪,留恋处,兰舟催发 执手相看泪眼,竟无语凝噎 念去去,千里烟波,暮霭沉沉楚天阔 多情自古伤离别,更那堪,冷落清秋节 今宵酒醒何处?杨柳岸,晓风残月 此去经年,应是良辰好景虚设 便纵有千种风情,更与何人说? 柳永 《雨霖铃·寒蝉凄切》 \'\'\' # 实例化一个MIMEText邮件对象,第一个参数为正文内容,第二个参数为文本格式(plain为纯文本),第三个参数为编码 msg = MIMEText(text, \'plain\', \'utf-8\') # 邮件头信息 msg[\'From\'] = Header(from_addr) msg[\'To\'] = Header(to_addr) msg[\'Subject\'] = Header(\'python测试\', \'utf-8\') # 开启发信服务,这里使用的是加密传输 server = smtplib.SMTP_SSL() server.connect(smtp_server, 465) # 登录发信邮箱 server.login(from_addr, password) # 发送邮件 try: server.sendmail(from_addr, to_addr, msg.as_string()) print(\'恭喜,发送成功\') except: print(\'发送失败,请重试\') # 关闭服务器 server.quit()
邮件头信息的写法比较特殊,以msg[\'Subject\'] = Header(\'python测试\', \'utf-8\')这句来说
等号右边是实例化了一个Header邮件头对象,该对象需要写入两个参数,分别是邮件主题和编码
等号左边的msg[\'Subject\']的变量是一个a[\'b\']的代码形式,它长得特别像字典根据键取值的表达,但是这里的msg是一个MIMEText类的对象,并不是一个字典
其实,字典和类在结构上,有相似之处↓
字典里面的元素是【键】和【值】一一对应,而类里面的【属性名】和【属性】也是一一对应的。可以根据字典里的【键】取到对应的【值】,同样的,也可以根据类里面的【属性名】取到【属性】
所以msg[\'Subject\']就代表着根据MIMEText类里面的Subject的属性名取到该属性
需要注意的是,不是每一个类都可以这样访问其属性的,之所以能这样访问是因为MIMEText类实现了这个功能
所以,msg[\'Subject\'] = Header(\'python测试\', \'utf-8\') 就是在为msg[\'Subject\']这个属性赋值
3. 报错解决
报错信息:ValueError: server_hostname cannot be an empty string or start with a leading dot.
Python 3.7版本之后修改了ssl.py,导致smtplib.SMTP_SSL也连带产生了问题
解决方法↓
# 改动这句代码 server = smtplib.SMTP_SSL() # 在括号内加入host参数 server = smtplib.SMTP_SSL(smtp_server)
如果用的是QQ以外的其他邮箱,比如企业邮箱的话,可能会出现报错信息:smtplib.SMTPException: No suitable authentication method found
解决方案是在登录(login)之前调用starttls()方法就可以了
server.starttls()
还有些报错实在搞不清楚是什么问题的话,可以尝试将端口参数改一下,使用默认端口25
# 把这两行代码改掉 server = smtplib.SMTP_SSL() server.connect(smtp_server, 465) # 改掉port参数,并去掉_SSL server = smtplib.SMTP() server.connect(smtp_server, 25)
4. 群发邮件
将收件人信箱的变量设置成一个可以装多个内容的列表↓
to_addrs = [\'xxx@qq.com\', \'xxxxx@qq.com\']
需要注意的是,Header接受的第一个参数的数据类型必须要是字符串或者字节,所以在为msg[\'To\']赋值的时候,需要将to_addrs转换为字符串↓
msg[\'to\'] = Header(",".join(to_addrs))
其他地方用到变量to_addr的地方替换成to_addrs就可以了
5. 制作二维码-MyQR
第三方库首先需要安装,mac电脑在终端输入pip3 install myqr
· 普通二维码
from MyQR import myqr # 扫描二维码后,显示的内容或是跳转的链接 myqr.run(words=\'https://www.cnblogs.com/oyster25\')
在当前目录会生成一个叫qrcode.png的图片,就是我们要的二维码了
· 艺术二维码
在当前目录有一张名为lm.jpg的图片
只要在刚才的基础上增加一个picture参数,会默认生成黑白的二维码,如果想要有颜色,还要增加colorized=True这个参数
from MyQR import myqr myqr.run( words=\'https://www.cnblogs.com/oyster25\', picture=\'lm.jpg\', # 将QR二维码图像与图片相结合 colorized=True # 黑白(False)还是彩色(True),默认是False )
· 动态二维码
当前目录的lm.gif图片
from MyQR import myqr myqr.run( words=\'https://www.cnblogs.com/oyster25\', picture=\'lm.gif\', colorized=True )
会生成一个叫lm_qrcode.gif的动态二维码myqr.run()还有一些其他的参数↓
from MyQR import myqr import os myqr.run( words=\'https://www.cnblogs.com/oyster25\', # 扫描二维码后,显示的内容或是跳转的链接 version=10, # 设置容错率,范围是1-40 level=\'H\', # 控制纠错水平,范围是L、M、Q、H,从左到右依次升高,默认为H picture=\'lm.gif\', # 将QR二维码图像与图片相结合 colorized=True, # 黑白(False)还是彩色(True),默认是False contrast=1.0, # 用以调节图片的对比度,1.0 表示原始图片。默认为1.0 brightness=1.0, # 用来调节图片的亮度,用法同上。 save_name=\'Python.gif\', # 控制输出文件名,格式可以是 .jpg, .png ,.bmp ,.gif,默认输出文件名qrcode.png或xx_qrcode.png(或.gif) save_dir=os.getcwd() # 控制存储位置,默认是当前目录 )
由于将容错率version调高了,所以生成的会慢一些;然后改了输出的文件名,会生成一个叫Python.gif的动态二维码
6. 绝对值abs()和math.fabs()
import math # 内置模块 math 求绝对值 print(math.fabs(-1.32)) # 》》1.32 # 内置函数 abs() 求绝对值 print(abs(-9)) # 》》9
第18关 编程思维:产品思维
1. 流程图
所有的流程图,总结起来有三种类型↓
用到的图形样式↓
有个在线画流程图的网站还挺好用的,就是只能免费做9张图,网站链接:https://www.processon.com/diagrams
2. 产品设计
3. 滚动的广告牌
方法一↓
import os, time def main(): content = \'欢迎光临穿着熊快餐车,喵喵喵~\' for i in range(16): os.system(\'clear\') print(content) content = content[1:]+content[0] time.sleep(0.25) if __name__ == \'__main__\': # 也可以直接用在函数上 main()
方法二↓
import time content = \'欢迎光临穿着熊快餐车,喵喵喵~\' for i in range(16): print(\'\r\'+content, end=\'\', flush=True) content = content[1:]+content[0] time.sleep(0.25) print(\'\')
第19关 项目实操:毕业项目
一些遍历字典的方法↓
tv_dict = {\'芒果台\': \'湖南卫视\', \'荔枝台\': \'江苏卫视\', \'番茄台\': \'东方卫视\'}
# 直接遍历是取字典的键
for tv in tv_dict:
print(tv, end=\' \')
print(\'\')
# 》》芒果台 荔枝台 番茄台
# 遍历字典取值
for value in tv_dict.values():
print(value, end=\' \')
print(\'\')
# 》》南卫视 江苏卫视 东方卫视
# 遍历字典取键值对
for logo, name in tv_dict.items():
print(logo + \'是\' + name)
# 》》芒果台是湖南卫视
# 》》荔枝台是江苏卫视
# 》》番茄台是东方卫视