Python基础篇--学习记录2.0-程序员宅基地

技术标签: 学习  python  开发语言  

 1. logging模块

日志分为5个等级,调试,消息,警告,错误和严重错误5个等级,日志的输出等级是可以配置的,在调试的时候可以将日志等级设置为debug,将日志等级设置为info级别之后,info级别之下的日志就都不会输出,默认输出warning级别及以上的日志

logging.debug("debug")
logging.info("info")
logging.warning("warning")
logging.error("error")
logging.critical("critical")

结果图:

(日志的级别:日志的名字:日志的详细信息)

 c57746b65c8a4d7d8a6210469f1a328d.png

1.1日志基本配置

1.1.1 日志基本配置内容

1.日志级别,2.日志输出格式,3、asctime的时间格式,4、日志输出位置:终端|文件,不指定此项配置,默认输出到终端

import logging

logging.basicConfig(
    # 1.日志级别
    # DEBUG:10
    # INFO:20
    # WARNING:30
    # ERROR:40
    # CRITICAL:50
    level=30,

    # 2.日志输出格式
    # %(asctime)s -> 获取当前时间
    # %(name)s -> 当前日志的名字
    # %(pathname)s -> 产生日志文件的名字
    # %(lineno)d -> 产生日日志的行
    # %(levelname)s -> 产生日志的等级
    # %(message)s -> 日志的详细内容
    format='%(asctime)s %(name)s [%(pathname)s line:%(lineno)d] %(levelname)s %(message)s',

    # 3、asctime的时间格式
    datefmt="%Y-%m-%d %H:%M:%S",

    # 4、日志输出位置:终端|文件,不指定此项配置,默认输出到终端
    filename="user.log"
)

# 日志的输出级别是可以设置的
logging.debug("debug")
logging.info("info")
logging.warning("warning")
logging.error("error")
logging.critical("critical")

日志输出格式: 6b715f1efe0b4fb7b40415e7a03f4755.png

1.1.2 日志基本配置存在的问题

基本配置只能配置日志一些基本的东西,在这里不能设置日志的编码方式。没有指定编码方式的话,windows系统默认使用gbk编码方式,写入的中文log内容, pycharm默认是使用utf-8来打开时,会产生乱码。此问题只会在windows电脑上面出现,linux和mac默认的编码方式都是utf-8。

73137508513a4c2eb0b1cee0909efdc6.png

 查询Windows电脑的默认编码方式,输入DOS命令chcp,输出为936,就是gbk(GB2312)

341f7b9eda974bdda9570be93722d620.png

基本配置也无法实现将日志往终端显示的同时,还往文件中间写入。

1.2日志配置字典

1.2.1 日志字典配置

日志处理器:handlers

日志记录器:loggers

将loggers产生的日志->handlers进行处理

可以将日志所有的配置内容都写在这个配置字典里面,通过加载这个字典,让logging模块来使用这里面的配置项。要将这个配置字典往项目里面放的话,就需要放置到settings.py里面

LOGGING_DIC = {
    "version": 1.0,
    "disable_existing_log": False,
    # 日志格式,这里可以指定多种日志格式,这里的standard...就是对应日志名字
    "formatters": {
        "standard": {
                    "format": '%(asctime)s %(name)s [%(pathname)s line:%(lineno)d] %(levelname)s %(message)s',
                    "datefmt": "%Y-%m-%d %H:%M:%S"},
        "simple": {
                    "format": '%(asctime)s %(name)s %(levelname)s %(message)s',
                     "datefmt": "%Y-%m-%d %H:%M:%S"},
        "test": {"format": '%(asctime)s %(message)s'}
    },
    "filters": {},
    # 日志处理器:将记录的日志进行处理(输出到文件/显示到控制台)
    # 可以设置多个handler不同的handler做不同的处理,做不同的配置
    "handlers": {
        "console_debug_handler": {
            "level": 20,  # 日志处理的级别限制
            "class": "logging.StreamHandler",  # 输出到终端
            "formatter": "simple",  # 日志格式
        },
        # "file_info_handler": {},
        "file_debug_handler": {
            "level": 10,
            "class": "logging.FileHandler",  # 保存到文件
            "filename": "test.log",  # 日志存放的路径
            "encoding": "utf-8",  # 日志文件的编码
            "formatter": "test"  # 日志格式
        }
    },
    # 日志记录器
    "loggers": {
        "logger1": {  # 导入时logging.getLogger时使用的app_name
            "handlers": ["console_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "DEBUG",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "logger2": {
            "handlers": ["console_debug_handler","file_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        }
    }
}

1.2.2 包的导入

logging模块本身是一个包,config作为logging模块下的一个子包,不能直接使用logging.config,会报错。是因为没有将config这个名字导入到logging下的__init__.py里面。因为导入logging导入的就是logging下面的__init__.py

18d4d98fcbf44f7d80761fde254f5d95.png

但是可以使用from logging import config,此种方式不但可以找logging的__init__里面的名字,还可以找到logging这个文件夹里面的名字。

因为现在导入的是config所以logging下面的功能是用不到的,logging根本就没有导入。logging下面的__init__文件是有运行过的,from logging的这种方式它会先检索logging的init里面有没有config,要检索init就一定会先执行一遍init,如果init里面没有config它才会在logging这个文件夹里面寻找。

我现在导入的是mm包下面的m名字,m被定义在__init__和module.py里面,输出的结果是20

7351aed5aeee4600ae9fcb462df37d6c.png

 601699429edd4c119a37a3ee71211e3e.png43ac5fdd69874e71babf8b73cc047433.png

虽然执行了logging下面的init,但是还是用不了logging下面的功能的,因为只是执行了init并没有导入logging

所以可以使用import logging.config,这种方式和from导入一样,同样会先执行logging下面的init,看存不存在config这个名字,没有再找logging这个文件夹下面的模块名。但是和from不同的是,这种方式会导入logging,所以可以使用logging下面的内容。

import logging.config
import setting
# from logging import config #不但可以找到logging __init__里面的名字,还可以找到文件夹里面的名字
logging.config.dictConfig(setting.LOGGING_DIC)
logger1 = logging.getLogger("logger1")
logger1.info("账户余额5毛")

1.3 日志分类 

通过不同的handler控制将不同的日志写入不同的文件中,在loggers里面设置操作的handler即可

LOGGING_DIC = {
    "version": 1.0,
    "disable_existing_log": False,
    # 日志格式,这里可以指定多种日志格式,这里的standard...就是对应日志名字
    "formatters": {
        "standard": {
                    "format": '%(asctime)s %(name)s [%(pathname)s line:%(lineno)d] %(levelname)s %(message)s',
                    "datefmt": "%Y-%m-%d %H:%M:%S"},
        "simple": {
                    "format": '%(asctime)s %(name)s %(levelname)s %(message)s',
                     "datefmt": "%Y-%m-%d %H:%M:%S"},
        "test": {"format": '%(asctime)s %(message)s'}
    },
    "filters": {},
    # 日志处理器:将记录的日志进行处理(输出到文件/显示到控制台)
    # 可以设置多个handler不同的handler做不同的处理,做不同的配置
    "handlers": {
        "console_debug_handler": {
            "level": 20,  # 日志处理的级别限制
            "class": "logging.StreamHandler",  # 输出到终端
            "formatter": "simple",  # 日志格式
        },
        # "file_info_handler": {},
        "file_debug_handler": {
            "level": 10,
            "class": "logging.FileHandler",  # 保存到文件
            "filename": "test.log",  # 日志存放的路径
            "encoding": "utf-8",  # 日志文件的编码
            "formatter": "test"  # 日志格式
        },
        "file_deal_handler": {
                    "level": 20,
                    "class": "logging.FileHandler",  # 保存到文件
                    "filename": "deal.log",  # 日志存放的路径
                    "encoding": "utf-8",  # 日志文件的编码
                    "formatter": "standard"  # 日志格式
                },
        "file_operate_handler": {
                            "level": 20,
                            "class": "logging.FileHandler",  # 保存到文件
                            "filename": "operate.log",  # 日志存放的路径
                            "encoding": "utf-8",  # 日志文件的编码
                            "formatter": "standard"  # 日志格式
                        }
    },
    # 日志记录器
    "loggers": {
        "logger1": {  # 导入时logging.getLogger时使用的app_name
            "handlers": ["console_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "DEBUG",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "logger2": {
            "handlers": ["console_debug_handler","file_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "logger3":{
            "handlers": ["file_deal_handler"],  # 日志将要分配给哪个handler进行日志处理
                        "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
                        "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "logger4":{
                    "handlers": ["file_operate_handler"],  # 日志将要分配给哪个handler进行日志处理
                                "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
                                "propagate": False  # 默认为True,向更高级别的日志进行传递
                }
    }
}
logging.getLogger("logger3")进行操作
import logging.config
import settings
# from logging import config #不但可以找到logging __init__里面的名字,还可以找到文件夹里面的名字
logging.config.dictConfig(settings.LOGGING_DIC)
logger1 = logging.getLogger("logger1")
logger1.info("账户余额5毛")

logger3 = logging.getLogger("logger3")
logger3.info("XXX账户交易10万")

logger4 = logging.getLogger("logger4")
logger4.info("XXX账户在操作")

1.4 日志命名 

1.4.1 将log设置名称

将loggers里面的名字进行设置,生成的log就有了名字

"loggers": {
        "logger1": {  # 导入时logging.getLogger时使用的app_name
            "handlers": ["console_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "DEBUG",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "logger2": {
            "handlers": ["console_debug_handler","file_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "用户交易":{
            "handlers": ["file_deal_handler"],  # 日志将要分配给哪个handler进行日志处理
                        "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
                        "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "用户操作":{
                    "handlers": ["file_operate_handler"],  # 日志将要分配给哪个handler进行日志处理
                                "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
                                "propagate": False  # 默认为True,向更高级别的日志进行传递
                }
    }

操作:

logger3 = logging.getLogger("用户交易")
logger3.info("XXX账户交易10万")

logger4 = logging.getLogger("用户操作")
logger4.info("XXX账户在操作")

结果图:

5591e645089f47db964df951af3c6b3d.png

1.4.2 将不同类型的log写入同一个文件

如果存在多个类型的log要写入同一个文件里面,将loggers的app_name设置为空,当logging.getLogger获取不到传递的app_name时,就会默认使用loggers里面没有设置app_name的loggers

# 日志记录器
    "loggers": {
        "logger1": {  # 导入时logging.getLogger时使用的app_name
            "handlers": ["console_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "DEBUG",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "logger2": {
            "handlers": ["console_debug_handler","file_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "":{
            "handlers": ["file_deal_handler"],  # 日志将要分配给哪个handler进行日志处理
                        "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
                        "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "用户操作":{
                    "handlers": ["file_operate_handler"],  # 日志将要分配给哪个handler进行日志处理
                                "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
                                "propagate": False  # 默认为True,向更高级别的日志进行传递
                }
    }

操作:

logger3 = logging.getLogger("用户交易")
logger3.info("XXX账户交易10万")

logger5 = logging.getLogger("用户充值")
logger5.info("XXX账户充值10万")

logger6 = logging.getLogger("用户转账")
logger6.info("XXX账户转账10万")

结果图:

1205a1b99d914589b2618ab920c194be.png

1.5 日志轮转

日志记录着我们关键信息的,即使日志存放很久也不可以删除,但是可能会造成的问题就是,日志的文件内容过大。我们需要做的就是,当日志内容超过大时,将原先的log文件重命名,这样产生的新的log文件就是重0开始了。

在handler里面设置file_info_handler(随便命名)

"file_info_handler": {
            "level": "INFO",
            'class': "logging.handlers.RotatingFileHandler",
            "filename": "deal.log",
            # 日志大小,10M,日志文件达到10M的时候进行轮转
            # 默认单位为字节,1KB 1024 Byte,1MB为1024KB
            "maxBytes": 800,
            "backupCount": 10,  # 日志文件保存数量的限制
            "encoding":"utf-8",
            "formatter":"standard"
        },

代码all:

# _*_ coding utf-8 _*_
# george
# time: 2024/1/9上午10:46
# name: settings.py
# comment:
import logging

LOGGING_DIC = {
    "version": 1.0,
    "disable_existing_log": False,
    # 日志格式,这里可以指定多种日志格式,这里的standard...就是对应日志名字
    "formatters": {
        "standard": {
            "format": '%(asctime)s %(name)s [%(pathname)s line:%(lineno)d] %(levelname)s %(message)s',
            "datefmt": "%Y-%m-%d %H:%M:%S"},
        "simple": {
            "format": '%(asctime)s %(name)s %(levelname)s %(message)s',
            "datefmt": "%Y-%m-%d %H:%M:%S"},
        "test": {"format": '%(asctime)s %(message)s'}
    },
    "filters": {},
    # 日志处理器:将记录的日志进行处理(输出到文件/显示到控制台)
    # 可以设置多个handler不同的handler做不同的处理,做不同的配置
    "handlers": {
        "console_debug_handler": {
            "level": 20,  # 日志处理的级别限制
            "class": "logging.StreamHandler",  # 输出到终端
            "formatter": "simple",  # 日志格式
        },
        "file_info_handler": {
            "level": "INFO",
            'class': "logging.handlers.RotatingFileHandler",
            "filename": "deal.log",
            # 日志大小,10M,日志文件达到10M的时候进行轮转
            # 默认单位为字节,1KB 1024 Byte,1MB为1024KB
            "maxBytes": 800,
            "backupCount": 10,  # 日志文件保存数量的限制
            "encoding":"utf-8",
            "formatter":"standard"
        },
        "file_debug_handler": {
            "level": 10,
            "class": "logging.FileHandler",  # 保存到文件
            "filename": "test.log",  # 日志存放的路径
            "encoding": "utf-8",  # 日志文件的编码
            "formatter": "test"  # 日志格式
        },
        "file_deal_handler": {
            "level": 20,
            "class": "logging.FileHandler",  # 保存到文件
            "filename": "deal.log",  # 日志存放的路径
            "encoding": "utf-8",  # 日志文件的编码
            "formatter": "standard"  # 日志格式
        },
        "file_operate_handler": {
            "level": 20,
            "class": "logging.FileHandler",  # 保存到文件
            "filename": "operate.log",  # 日志存放的路径
            "encoding": "utf-8",  # 日志文件的编码
            "formatter": "standard"  # 日志格式
        }
    },
    # 日志记录器
    "loggers": {
        "logger1": {  # 导入时logging.getLogger时使用的app_name
            "handlers": ["console_debug_handler","file_info_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "DEBUG",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "logger2": {
            "handlers": ["console_debug_handler", "file_debug_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "": {
            "handlers": ["file_deal_handler","file_info_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        },
        "用户操作": {
            "handlers": ["file_operate_handler"],  # 日志将要分配给哪个handler进行日志处理
            "level": "INFO",  # 日志记录的级别限制,和handlers里面的日志一起组成两层日志过滤
            "propagate": False  # 默认为True,向更高级别的日志进行传递
        }
    }
}

调用:

import logging.config
import settings
# from logging import config #不但可以找到logging __init__里面的名字,还可以找到文件夹里面的名字
logging.config.dictConfig(settings.LOGGING_DIC)
logger1 = logging.getLogger("logger1")
logger1.info("账户余额5毛")

logger4 = logging.getLogger("用户操作")
logger4.info("XXX账户在操作")

logger3 = logging.getLogger("用户交易")
logger3.info("XXX账户交易10万")

logger5 = logging.getLogger("用户充值")
logger5.info("XXX账户充值10万")

logger6 = logging.getLogger("用户转账")
logger6.info("XXX账户转账10万")

2. JSON模块

json的内置模块直接import json即可

2.1 序列化 dumps: 字典-> json字符串

在python里面写的单引号,经过序列化之后就变为了双引号了.因为双引号是所有编程语言通用的写法.

汉字都转变为了unicode格式的二进制了.如果我们序列化的数据类型里面包含汉字,就加上参数ensure_ascii,让汉字正常显示.

在python里面True和False首字母都是大写的,但是在json文件里面都变为了小写.

def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True,
        allow_nan=True, cls=None, indent=None, separators=None,
        default=None, sort_keys=False, **kw):
    """Serialize ``obj`` to a JSON formatted ``str``.
    #默认为True,就是保证序列化的结果里面,所有的字符都能够用ASCII显示.但是汉字在ASCII里面
    #有对应关系,所以序列化的时候,会直接将unicode格式存进去
    If ``ensure_ascii`` is false, then the return value can contain non-ASCII
    characters if they appear in strings contained in ``obj``. Otherwise, all
    such characters are escaped in JSON strings.
# _*_ coding utf-8 _*_
# george
# time: 2024/1/31下午1:53
# name: json_test.py
# comment:序列化->json字符串

import json

dict = {
    'name': '刘谦',
    'age': 88,
    "salary": 100,
    'married': True,
    "hobbies": ['抽烟', '喝酒', '烫头']
}
# 字典 -> 序列化 -> json字符串
json_res = json.dumps(dict,ensure_ascii=False)
print(json_res,type(json_res))

# 后缀名仅仅是为了提示我们这个文件里面存储的是什么类型的数据,它根本影响不了文件里面的内容.
with open("./123.json",mode='wt',encoding='utf-8') as f:
    f.write(json_res)

5e9cae897a0a446cb9a95cbb47df3d46.png

2.2 反序列化 loads: json字符串 -> 字典

def loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None,
        parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
    """Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance
    containing a JSON document) to a Python object.
# _*_ coding utf-8 _*_
# george
# time: 2024/1/31下午2:23
# name: josn_test2.py
# comment:反序列化->json字符串
import json

with open("./123.json", mode='rt', encoding='utf-8') as f:
    json_str = f.read()
    dict = json.loads(json_str)
    print(dict, type(dict))

05ef0577f2ae4dcea9af47874074ff4a.png 2.3 序列化 dump: 字典 -> json字符串 -> 写入文件

json.dump()会自动调用json.dumps()将字典序列化为json字符串,再自动调用f.write()将json字符串写入文件

def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True,
        allow_nan=True, cls=None, indent=None, separators=None,
        default=None, sort_keys=False, **kw):
    """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
    ``.write()``-supporting file-like object).
# _*_ coding utf-8 _*_
# george
# time: 2024/1/31下午2:35
# name: josn-test3.py
# comment: 序列化2->josn字符串

import json

dict = {
    'name': '刘谦',
    'age': 88,
    "salary": 100,
    'married': True,
    "hobbies": ['抽烟', '喝酒', '烫头']
}
# 字典 -> 序列化 -> json字符串 -> 写入文件
with open("./456.json",mode='wt',encoding='utf-8') as f:
    json.dump(dict,f)

e6381683e26c48cb9d55414e56e78d34.png

2.4 反序列化 load: 读取文件-> json字符串->字典

def load(fp, *, cls=None, object_hook=None, parse_float=None,
        parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
    """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
    a JSON document) to a Python object.
# _*_ coding utf-8 _*_
# george
# time: 2024/1/31下午2:40
# name: json_test4.py
# comment:反序列化2->dict对象
import json

with open("./456.json", mode='rt', encoding='utf-8') as f:
    dict = json.load(f)
    print(dict, type(dict))

2b8224161ec44f57ad48f17746b89fc5.png

2.5 基于json模块实现json文件的读写

json文件不存在就创建,文件存在就读取.

# _*_ coding utf-8 _*_
# george
# time: 2024/1/31下午3:12
# name: josn_read_write.py
# comment: json文件的读写

import json
from pathlib import Path

path_json = Path.home() / 'Desktop' / 'liuqian.json'

dict = {
    'name': '刘谦',
    'age': 88,
    "salary": 100,
    'married': True,
    "hobbies": ['抽烟', '喝酒', '烫头']
}
if not path_json.exists():
    with open(path_json, mode='wt', encoding="utf-8") as f:
        json.dump(dict, f)
with open(path_json, mode='rt', encoding='utf-8') as f:
    dict = json.load(f)
    print(dict)

3. pickle模块

跨平台交互的时候应该使用json,但是文件存档的时候就应该使用pickle.pickle模块也是python内置的模块.和json文件一样也是存在dumps,dump,loads,load四种方法.

3.1 序列化 : 字典 -> Bytes

序列化的结果是Bytes类型,如果要将序列化的结果写入文件的话那么就是需要使用b模式

14f133c1a9ca44a2bc1d0a820c42957f.png

# _*_ coding:utf-8 _*_
# @Time  :  23:09
# @Author: george
# @File  : pickle_test.py
# @Comment: pickle模块-->序列化
import pickle
dict = {
    'name': '刘谦',
    'age': 88,
    "salary": 100,
    'married': True,
    "hobbies": ['抽烟', '喝酒', '烫头']
}
pickle_res = pickle.dumps(dict,protocol=0)
with open("./test.pickle",mode="wb") as f:
    f.write(pickle_res)
# _*_ coding:utf-8 _*_
# @Time  :  23:09
# @Author: george
# @File  : pickle_test.py
# @Comment: pickle模块-->序列化
import pickle
dict = {
    'name': '刘谦',
    'age': 88,
    "salary": 100,
    'married': True,
    "hobbies": ['抽烟', '喝酒', '烫头']
}
# pickle_res = pickle.dumps(dict,protocol=0)
with open("./test.pickle",mode="wb") as f:
    pickle.dump(dict,f,protocol=0)

0ab8630d4bd34f1aaefe6e04d440567a.png

3.2 反序列化: Bytes->字典

# _*_ coding:utf-8 _*_
# @Time  :  23:30
# @Author: george
# @File  : pickle_test2.py
# @Comment: pickle->反序列化
import pickle
with open("./test.pickle",mode="rb") as f:
    pickle_bytes = f.read()
    dict = pickle.loads(pickle_bytes)
    print(dict,type(dict))

aa578f9170a64e63a095cc3cc632bd4b.png

# _*_ coding:utf-8 _*_
# @Time  :  23:30
# @Author: george
# @File  : pickle_test2.py
# @Comment: pickle->反序列化
import pickle
with open("./test.pickle",mode="rb") as f:
    dict = pickle.load(f)
    print(dict,type(dict))

4.xml模块

...

5. configparser模块

这个模块是用来加载一种特定格式的配置文件的.配置文件的后缀名为ini或是cfg是什么名字其实并不重要,只要让别人一看就知道你这是配置文件就可以了

# 这是注释
; 这也是注释
[default]
delay = 45
compression = true
compression_level = 9
language_code = en-us
time_zone = UTC

[db]
db_type = mysql
database_name = catalogue_data
user = root
password = root
host = 127.0.0.1
port = 3306
charset = utf8

5.1 配置项特点

[default]是这一部分配置的标题,名字可以随便起.现在这个配置文件有两部分,这两个部分被称之为section.每个section里面的这些配置被叫做option.这些option的表现形式可以是=,也可以是:这两种做法都是被支持的.

注释可以使用#,也可以使用;

5.2 代码获取配置项

configparser同样是python内置模块,先找section-> option

configparser模块也可以进行配置文件的修改,但是配置文件一般都是用户自己配置的.我们一般不会通过程序去修改配置文件.除非是写一个带界面的程序,用户可以通过你写的界面,用鼠标点点点的方式将配置文件给修改了,这个时候我们才会用到修改配置文件的功能.

# _*_ coding:utf-8 _*_
# @Time  :  23:55
# @Author: george
# @File  : configparser_test.py
# @Comment: configparser模块

from configparser import ConfigParser

config = ConfigParser()  # 使用它来读取配置文件
config.read("./test.ini", encoding="utf-8")

# 获取两个sections
print(config.sections())  # => ['default', 'db']

# 获取一个sections下面的所有配置项
print(config.options('default'))  # => ['delay', 'compression', 'compression_level', 'language_code', 'time_zone']

# 获取一个sections下面的所有配置项的所有key和values
print(config.items('default'))  # => 输出结果如下
# [('delay', '45'), ('compression', 'true'), ('compression_level', '9'), ('language_code', 'en-us'), ('time_zone', 'UTC')]

# 获取一个sections下面的某一个配置项的内容
delay = config.get('default', 'delay')
print(delay,type(delay)) # => 45 <class 'str'>

6. hash(哈希)

6.1 hash介绍

hash是一类算法,算法就是功能.我们可以看为一个函数.给它传入一段内容,它经过运算之后,就会返回一串hash值或者说是散列值,而它就是一串字符串

常见的hash算法有md5,sha1,sha256,sha512等等,它们的功能都是一样的,只是算法的负责程度是不一样的

6.2 hash值特点

1)输入敏感: 只要我们输入的内容发生任何一丁点的变化,新的hash值都会出现巨大的变化.如果我们的输入的内容一样,使用的hash算法也是一样,那得到的hash值一定是一样的.

2)不可逆:我们不能根据hash值,反推出来传入的内容是什么

3)计算极快且长度固定.使用相同的hash算法,计算出来的hash值的长度都是固定的

# _*_ coding utf-8 _*_
# george
# time: 2024/2/1下午2:53
# name: hash_test.py
# comment:
import hashlib
# 这个h1就相当于一个hash工厂
h1 = hashlib.md5()
# 将要计算的源数据传递给它,但是传递的数据必须是Bytes类型
# 传递的源数据可以传递多次,将所有内容汇在一起进行计算
h1.update("abc".encode("utf-8"))
h1.update("123".encode("utf-8"))
# 返回计算之后的hash值
a = h1.hexdigest()
print(a)

h2 = hashlib.md5()
h2.update("abc123".encode("utf-8"))
b = h2.hexdigest()
print(b)
print(a==b)

9df43a97f2de4d519ac085925b15c881.png

6.3 hash算法用途

1) 密码加密,我们只要把我们的密码通过hash算法转成密文之后,再进行网络传输,这样即便是我们的数据包被别人截到,也不知道我们的密码是什么

2) 文件完整性校验

6.4 hash算法的使用

需要导入一个python的内置模块import hashlib 

# _*_ coding utf-8 _*_
# george
# time: 2024/2/1下午2:53
# name: hash_test.py
# comment:
import hashlib
# 这个h1就相当于一个hash工厂
h1 = hashlib.md5()
# 将要计算的源数据传递给它,但是传递的数据必须是Bytes类型
# 传递的源数据可以传递多次,将所有内容汇在一起进行计算
h1.update("abc".encode("utf-8"))
h1.update("123".encode("utf-8"))
# 返回计算之后的hash值
h1.hexdigest()

hash算法实现文件完整性校验

# _*_ coding utf-8 _*_
# george
# time: 2024/2/1下午5:16
# name: hash_value_generate.py
# comment:基于sha1的文件hash值的产生

import hashlib
from pathlib import Path

file_path = Path.home()/"Desktop"/"文件名"

with open(file_path, "rb") as f:
    h1 = hashlib.sha1(f.read())
    print(h1.hexdigest())

但是此种方式存在问题,如果是对于30G的超高清电视剧做完整性校验,直接通过f.read()直接就会将内存撑爆了.如果是用for循环读进来,读一次update一次,这样做确实可以缓解内存的压力,但是如果文件真的很大的话,用for循环来读会很慢.

切换思路:

在这个文件中间选取几段来做hash运算.比如使用f.seek()的方式将文件指针移动到文件1/10的地方,读取100个Bytes,update一次.接着将文件指针移动到2/10的地方再读取100个Bytes,再update一次.这样我们只是取了10个点,速度就比较快了.

在服务端按照这个方法计算出来一个hash值,然后这个文件通过网络传输到客户端之后.客户端也同样按照这个方法来做hash校验,就可以了

6.5 大型文件校验

获取文件的大小有两种方式:

1) size = os.path.getsize(file_path)

2) f.seek(0,2)

size = f.tell()

# _*_ coding utf-8 _*_
# george
# time: 2024/2/1下午5:16
# name: hash_value_generate.py
# comment:大型文件校验

import hashlib
from pathlib import Path
import os

file_path = Path.home() / "Desktop" / "20240122_070727_j537 rf-ota-1_1HWEgE198.zip"
h1 = hashlib.md5()
with open(file_path, "rb") as f:
    size = os.path.getsize(file_path)  # 获取文件大小
    one_tenth = size // 10
    for i in range(10):
        f.seek(i * one_tenth, 0)
        res = f.read(100)
        h1.update(res)
    print(h1.hexdigest())

6.6 密码加盐

虽然hash值是不可逆的,但是可以通过hash碰撞的方式算出你的密码是什么.于是就有了一种操作叫做密码加盐.加盐就是在原始的密码中添加一点别的东西

只要用户注册的时候按照这个加盐的方式,将加盐之后的密码存到服务端.然后用户登陆的时候正常输入密码.用户将密码输入完成之后,客户端也按照这个加盐方式将密码加密就可以了.

# _*_ coding utf-8 _*_
# george
# time: 2024/2/1下午6:13
# name: 密码加盐.py
# comment:
import hashlib

pwd = "xyxy520"
h1 = hashlib.md5()
h1.update("天青色的烟雨".encode("utf-8"))
h1.update(pwd.encode("utf-8"))
h1.update("而我在等你".encode('utf-8'))
print(h1.hexdigest()) 
# 原始:e63f8ff0a7f7ab59c9b3253902220d84
# 加盐:c6c98654af3029ff035fc8051b65a77c

7.模块

模块其实就是一系列功能的集合体.

7.1 模块分类

1)Pyhton内置模块 : 随着我们安装好python解释器,这些内置模块也就安置好了.

2)第三方模块 : 别人写好的模块,我们将这个文件下载下来就是可以直接使用了.第三方模块直接使用pip指令安装即可.

3)自定义模块:这个模块得我们自己写,写好之后就得考虑这个模块往哪里放了.

比如我们使用python写了一堆功能,然后将一堆功能放到一个地方.比如说放到一个python文件里面.那么这个python文件就可以称之为一个模块.内置模块基本上都是使用c和c++来写的

模块: 我们写的python文件就是一系列功能的集合体,就可以称之为一个模块.文件名叫做module.py,模块名叫做module.

包: 我们把多个Python文件或者多个模块组织到一个文件夹里面,这个文件夹也可以看做一系列功能的集合体.而这个一系列模块组成的文件夹也就可以称之为一个模块.如果我们要将这个文件夹作为模块来使用的话,这个文件夹里面必须有一个__init__.py文件.这个文件夹我们才可以称之为模块,专业叫做包.

7.2 模块导入

同样的功能需要在不同的文件里面使用,我们就是使用专门的文件来存放这两个功能.现在其他文件需要使用这两个功能,直接导入模块就可以了

import 模块名

首次导入模块的时候,就会立即执行module.py这个文件(后面无论导入多少次,只会引用首次产生的名字)

6dc2aa443d624f159abb9b46822a9142.png

7.3 模块名称空间

既然导入这个文件,就会执行它内部的代码,也就意味着会产生module.py的名称空间,存module.py这个文件里面的名字.并且在当前文件"模块测试.py"中产生一个名字叫做module,将这个名字指向刚刚运行module.py时产生的名称空间.

f8bd61913d1f4f9b9acdea6c5ed86c52.png

当 "模块测试.py"文件运行之后,就会产生一个名称空间,将这个文件运行过程中产生的名字放进去.先是x,再是y,接着是module.导入这个module就会运行module.py这个文件.于是又产生了一个module.py的名称空间.将module.py文件里面的名字存储进去.当前名称空间的module.就会指向module.py的名称空间

在当前名称空间里面,我们可以 直接访问它里面的名字x,y

如果我们要访问module.py的名称空间里面的名字,通过module.的方式进行访问.因为我们调用module.py的名称空间里面的名字,是通过module.调用的.所以即便是当前名称空间里面也有一个name.双方也不会存在冲突.

名称空间的查找顺序是在定义阶段就确定的

d0767671e8454aa78243b7331f6cbfff.png

7.4 导入模块的规范

内置模块->第三方模块->自定义模块

这仅仅是一种规范,不按照这个顺序导入也没有任何问题,按照这个导入会更加符合规范

自定义模块名的时候应该使用纯小写加下划线的方式命名,这是python3的命名规范

7.5 导入模块起别名

import module as m 就相当于m = module,将module的内存地址给了m.使用的时候可以直接是m.name,m.func1()

25e865ed551849a58e6365b8951081bc.png

7.6 在函数内部导入模块

我们导入模块也是可以在函数内部导的,只是在函数内部导入模块,就意味着这个名字是属于函数局部名称空间的,其他地方是无法直接访问的.除非是将模块名传递到了全局.

0019fb9e4ba046679b1e952ddd6a08bc.png

7.7 模块名称空间回收

python文件有两种运行方式,一种是作为程序直接运行.第二种方式是作为模块被其他程序导入

图:

所以这两个名称空间的回收顺序是怎么样的?将代码作为程序直接运行,那么代码运行结束,这个名称空间就会被回收了.因为我们运行这个文件里面的名字,没有被别人引用,只是被自己引用.但是module.py是作为模块导入的,即使是文件运行完毕了,"模块测试.py"里面的module也引用着module.py的名称空间,所以只有等"模块测试.py"运行完毕之后,module.py的名称空间才会被回收.

7.8 __name__属性

测试文件里面功能的时候,需要执行文件里面的几行代码.但是当文件作为模块被导入的时候,不希望这几行代码被执行.这只是针对放在__name__的代码,外面的代码该怎么执行还是怎么执行

每一个python文件都内置了这个__name__属性,直接运行module.py文件的时候它的__name__的值就是__main__.但是当我们将module.py作为模块导入的时候,__name__的值就是module这个模块名

7.9 from导入模块

使用import "模块名"导入模块之后, 每次调用模块里面的名字都需要通过模块名来调用,就会显的很麻烦.所以如何不通过模块名直接使用里面的功能?

7.9.1 导入方式

from 模块名 import 功能名

使用from这种方式导入模块,和直接import导入模块是一样的

产生module模块的名称空间->运行module.py ->将运行过程中产生的名字丢到模块的名称空间里面

7.9.2 和import导入的区别

不同的是:

import直接导入会在当前的名称空间"模块测试.py"里面产生一个名字module,这个module指向module.py的名称空间

from这种方式产生的名字,就不再是指向模块的名称空间了.而是直接指向模块名称空间里面对应功能的内存地址.from导入之后无法在当前名称空间里面使用module这个名字.因为from这种方式不会在当前名称空间里面产生module这个名字,只会产生import后面的名字

当前名称空前里面的name这个名字指向的是moudle这个名称空间里面name的值(张大仙)的内存地址 ,并不是指向的module里面的name,所以即便是我们将module里面的name值修改了.在当前名称空间里面获取的值仍然是"张大仙".但是通过get函数获取的name的值就是"周杰伦",因为get()找name的值就需要在module名称空间里面寻找

7.9.3 from导入特点

1)同样支持同一行导入多个模块

2) from module import *

导入module模块里面所有名字,但是正常情况下不要使用,*号导入的名字我们没有办法掌控,很容易和我们当前名称空间里面的名字起冲突.每一个被导入的模块里面都有一个内置的变量叫__all__=[],里面放置的是模块里面的名字,默认情况下是模块里面所有的名字.我们使用from module import *能够拿到模块里面所有的名字,这个*找的就是__all__里面的名字.我们自己定义__all__时,内置的__all__就会被覆盖了,我们就可以改这个列表里面存储的名字.所以我们可以使用__all__来控制from module import *导入的内容有哪些了.

3) 支持使用别名

 

7.10 模块循环导入问题

模块a导入了模块b,模块b又导入了模块a 

我们在写模块的时候一定不能出现循环导入

图....

7.11 模块的查找顺序

import module,module模块本质其实就是一个python文件,既然它是一个python文件他就一定会存放在某一个文件夹里面.所以无论如何导入module,都涉及到查找module文件.

当我们导入模块的时候,它会在两个位置依次找

1)在内存里面,可能这个模块前面已经导入过了,现在内存里面查找一遍

2)硬盘里面查找,在硬盘里面就是按照sys.path这个环境变量中存放的文件夹路径依次查找,当导入的模块不在同一层级时,可以将包含模块的文件夹路径添加到sys.path列表里面.但是这个添加后的sys.path只是临时有效,执行完毕之后就失效了.

sys.path这是一个环境变量,但是和安装python配置的环境变量不是一回事.里面存储的也是一堆路径.这里面的路径就是模块的查找路径.使用pycharm输出sys.path时就存在项目路径,但是使用终端时就不存在

路径1:当前执行文件所在的文件夹路径

路径2:pycharm里面打开的这个项目的文件夹

导入模块不在同一层级:

硬盘里面查找,在硬盘里面就是按照sys.path这个环境变量中存放的文件夹路径依次查找,当导入的模块不在同一层级时,可以将包含模块的文件夹路径添加到sys.path列表里面.但是这个添加后的sys.path只是临时有效,执行完毕之后就失效了. (不是好的手段)->涉及到包的导入

7.12 模块编写规范

#!/usr/bin/python #通常在unix环境下面才有效,作用是指定解释器路径,直接使用脚本名执行,不需要调用解释器

"""
模块的文档描述
"""

import time  # 导入模块

name = "张大仙"  # 定义全局变量;如果不是必须,最好使用局部变量


class Test:  # 定义类
    """
    类注释
    """
    pass


def func():  # 定义函数
    """
    函数注释
    :return: 
    """

if __name__ == "__main__":
    func()

8.包

8.1 导包流程

包就是一个含有__init__.py文件的文件夹,它也是模块的一种.不管是导入模块还是导入包,流程都是一样的

1) 创建名称空间

2) 执行python文件

3)在执行文件的名称空间中创建一个名字,指向前面产生的名称空间

既然导包的流程和导入模块的流程是一样的话,那么导入pack包,就会先创建一个pack名称空间->执行python文件,这个python文件就是包里面的__init__文件.也就是说在当前名称空间中->pack这个名字指向的就是__init__的名称空间

我们现在就成功的把一个文件夹做成一个模板,就是在这个文件夹里面放一个__init__,只要我们导入文件夹里面的名字,就是导入init里面的名字

8.2 导包时的问题 

但是这种效果和单独使用一个python文件作为模块的区别不大.我们需要的是把多个pyhton文件都组织在这一个文件夹里面.或者说是将多个模块都组织在一个包里面.然后通过导入包,就可以使用它下面各种各样的 功能模块

但是问题是:

如果我们在pack包里面放置了很多的模块,然后每个模块下面都有自己一堆的名字,那么我们就需要将每个模块里面的名字都放到__init__文件里面,因为只要我们导入文件夹里面的名字,就是导入init里面的名字.这样导入就有两种方式

8.3 __init__导入模块内容

1)绝对导入以game这个文件夹为起始位置来进行导入

但是为什么要以game为起始位置开始找呢?

我们现在要找game这个模块,只要涉及到找模块,那查找顺序就一定是内存->sys.path路径需找.它的第一个路径是执行文件所在的文件夹路径

所有的被导入的模块他们的sys.path参照的都是执行文件的sys.path.

因为sys.path里面第一个路径是'/Users/f7692281/PycharmProjects/database/module_test',module里面根本没有chat这个模块.只有game,所以必须从game开始找.

当然前面这些都是包的设计者的事情,我作为使用者.game就是一个模块.我要导入这个模块,只需要保证它的存放路径是在sys.path里面的就可以了.就需要将game所在的文件夹的路径添加到sys.path里面去.

2) 相对导入

相对导入不能跨出包,所以相对导入仅限于包内模块导入.绝对导入是没有任何限制的.绝对导入参照的是执行文件的sys.path.相对导入参照的是当前文件所在的文件夹.

.:当前文件夹,就是__init__所在的文件夹,就是game

..:上层文件夹 

 8.4 logging包的导入

logging模块本身是一个包,config作为logging模块下的一个子包,不能直接使用logging.config,会报错。是因为没有将config这个名字导入到logging下的__init__.py里面。因为导入logging导入的就是logging下面的__init__.py

18d4d98fcbf44f7d80761fde254f5d95.png

但是可以使用from logging import config,此种方式不但可以找logging的__init__里面的名字,还可以找到logging这个文件夹里面的名字。

因为现在导入的是config所以logging下面的功能是用不到的,logging根本就没有导入。logging下面的__init__文件是有运行过的,from logging的这种方式它会先检索logging的init里面有没有config,要检索init就一定会先执行一遍init,如果init里面没有config它才会在logging这个文件夹里面寻找。

我现在导入的是mm包下面的m名字,m被定义在__init__和module.py里面,输出的结果是20

7351aed5aeee4600ae9fcb462df37d6c.png

 601699429edd4c119a37a3ee71211e3e.png43ac5fdd69874e71babf8b73cc047433.png

虽然执行了logging下面的init,但是还是用不了logging下面的功能的,因为只是执行了init并没有导入logging

所以可以使用import logging.config,这种方式和from导入一样,同样会先执行logging下面的init,看存不存在config这个名字,没有再找logging这个文件夹下面的模块名。但是和from不同的是,这种方式会导入logging,所以可以使用logging下面的内容。

import logging.config
import setting
# from logging import config #不但可以找到logging __init__里面的名字,还可以找到文件夹里面的名字
logging.config.dictConfig(setting.LOGGING_DIC)
logger1 = logging.getLogger("logger1")
logger1.info("账户余额5毛")

8.5 对于8.4的说明

import game

from game import shop 

import game.shop 

9.time模块

9.1 time的表现形式

1)时间戳,自1970开始到现在所经历的秒数.可以用作时间间隔计算

 2)格式化的字符串形式,按照特定的格式,以字符串的形式显示时间 2030-11-11 11 11:11:11.主要用于展示时间.

print(time.strftime("%Y-%m-%d %H-%M-%S"))  # 2024-02-02 17-16-10
print(time.strftime("%Y-%m-%d %H-%M-%S %A"))  # 2024-02-02 17-17-01 Friday
print(time.strftime("%Y-%m-%d %X %A"))  # 2024-02-02 17:17:24 Friday
print(time.strftime("%x %X %A"))  # 02/02/24 17:17:51 Friday

3)结构化时间:结构化时间可以显示年月日时分秒,并且获取的时间为整型

9.2 datetime模块

9.2.1 直接显示格式化后的时间

import datetime

res = datetime.datetime.now()
# 直接显示格式化之后的时间
print(res)  # 2024-02-02 18:28:43.829028
# 这个replace是datetime类自定义的方法
print(res.replace(microsecond=0))  # 2024-02-02 18:29:38

9.2.2 直接进行时间的加减

datetime.timedelta(参数),使用的参数如下

class timedelta(SupportsAbs[timedelta]):
    min: ClassVar[timedelta]
    max: ClassVar[timedelta]
    resolution: ClassVar[timedelta]
    def __init__(
        self,
        days: float = ...,
        seconds: float = ...,
        microseconds: float = ...,
        milliseconds: float = ...,
        minutes: float = ...,
        hours: float = ...,
        weeks: float = ...,
        *,
        fold: int = ...,
    ) -> None: ...
import datetime

now = datetime.datetime.now()
print("当前时间:", now)
res = datetime.datetime.now() + datetime.timedelta(days=7)
print("七天后的时间", res)

# 当前时间: 2024-02-02 18:39:37.620342
# 七天后的时间 2024-02-09 18:39:37.620397

9.3 时间格式转换

时间戳 <----> 结构化时间 <---->格式化的字符串时间.时间戳和格式化字符串时间的转化必须要经过结构化时间的过渡.

import time

# 时间戳 --localtime/gmtime--> 结构化时间 --strftime--> 格式化的字符串时间
res = time.time()
# time.struct_time(tm_year=2024, tm_mon=2, tm_mday=3, tm_hour=8, tm_min=26, tm_sec=38, tm_wday=5, tm_yday=34, tm_isdst=0)
res = time.localtime(res)
print(time.strftime("%Y-%m-%d %X", res))  # 2024-02-03 08:32:33

# 时间戳 <--mktime-- 结构化时间 <--strptime-- 格式化的字符串时间
t = '1983-12-07 01:02:02'
print(time.strptime(t, "%Y-%m-%d %H:%M:%S"))
# time.struct_time(tm_year=1983, tm_mon=12, tm_mday=7, tm_hour=1, tm_min=2, tm_sec=2, tm_wday=2, tm_yday=341, tm_isdst=-1)
res = time.strptime(t, "%Y-%m-%d %H:%M:%S")
print(time.mktime(res))  # 439578122.0

10.pathlib模块

pathlib是使用python不同的类来操作路径的,pathlib里面集成的是路径类.通过创建了路径类的相关实例之后可以很方便得到路径的相关属性信息以及实现路径相关的操作

提升跨平台兼容性:使用字符串操作路径需要考虑不同操作系统下面的命名规则.pathlib的类会自动将这些考虑进去.

PurePath类:主要处理系统的"纯路径",即只负责对路径字符串执行操作

Path类:PurePath的子类,除了支持PurePath的各种操作,属性和方法之外,还会真正的访问底层的文件系统

10.1 Path路径类的方法

用法 作用 官方描述

Path.chmode(path)

修改文件权限和时间戳

Change the permissions of the path, like os.chmod().
 

Path.mkdir(path)

创建目录

Create a new directory at this given path.

Path.rename(self,target)

重命名文件或是文件夹

Rename this path to the given path.

Path.replace(self,target)

重命名文件或是文件夹

Rename this path to the given path, clobbering the existing
destination if it exists.(删除现有路径)

Path.resolve()

获取路径对象的绝对路径

(规范路径)

Return an absolute version of this path.  This function works
even if the path doesn't point to anything.

get the canonical path to a file

Path.absolute(self)

获取路径对象的绝对路径

(非规范化的,.和..都会保持)

Return an absolute version of this path.  This function works
even if the path doesn't point to anything.

No normalization is done, i.e. all '.' and '..' will be kept along.
Use resolve() to get the canonical path to a file.

Path.rmdir()

删除目录或文件夹

(必须为空)

Remove this directory.  The directory must be empty.

Path.unlink()

删除文件

Remove this file or link.
If the path is a directory, use rmdir() instead.

Path.cwd()

获取当前执行文件的工作目录

Return a new path pointing to the current working directory
(as returned by os.getcwd()).

Path.exists()

判断是否存在文件或目录

Whether this path exists.

Path.home()

用户家目录

Return a new path pointing to the user's home directory (as
returned by os.path.expanduser('~')

Path.is_dir()

判断是否是一个文件夹

Whether this path is a directory.

Path.is_file()

判断是否是一个文件

Whether this path is a regular file (also True for symlinks pointing
to regular files).

Path.is_symlink

判断是否是一个符号链接

Whether this path is a symbolic link.

Path.stat()

获取文件属性

Return the result of the stat() system call on this path, like
os.stat() does.

Path.samefile()

判断两个路径是否相同

Return whether other_path is the same or not as this file
(as returned by os.path.samefile()).

Path.expanduser()

返回完整路径对象

Return a new path with expanded ~ and ~user constructs
(as returned by os.path.expanduser)

Path.glob(pattern)

查找路径对象下所有与parttern匹配的文件,返回的是一个生成器类型

Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given relative pattern.

Path.rglob(pattern)

查找路径对象下所有子文件夹,文件夹中与pattern匹配的文件,返回的是一个生成器类型

Recursively yield all existing files (of any kind, including
directories) matching the given relative pattern, anywhere in
this subtree.

Path.with_name(name)

更改路径名称,即更改目录及目录名

Return a new path with the file name changed

Path.with_suffix()

更改路径后缀

Return a new path with the file suffix changed.  If the path
has no suffix, add given suffix.  If the given suffix is an empty
string, remove the suffix from the path.

Path.as_uri()

更改当前路径为url

Return the path as a 'file' URI

Path.as_posix()

将当前路径更换为UNIX系统路径

Return the string representation of the path with forward (/)
slashes

Path.iterdir()

查找文件夹下面的所有文件,返回一个生成器类型

Iterate over the files in this directory.  Does not yield any
result for the special paths '.' and '..'

10.2 Path路径类的属性

10.3 目录遍历

用法 作用 官方描述

Path.glob(pattern)

查找路径对象下所有与parttern匹配的文件,返回的是一个生成器类型

Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given relative pattern.

Path.rglob(pattern)

查找路径对象下所有子文件夹,文件夹中与pattern匹配的文件,返回的是一个生成器类型

Recursively yield all existing files (of any kind, including
directories) matching the given relative pattern, anywhere in
this subtree

Path.iterdir()

查找文件夹下面的所有文件,返回一个生成器类型

Iterate over the files in this directory.  Does not yield any
result for the special paths '.' and '..'

10.4 文件和文件夹的创建和删除 

# _*_ coding utf-8 _*_
# george
# time: 2024/2/3下午1:53
# name: 文件和文件夹删除.py
# comment:
from pathlib import Path

path_obj = Path(Path.home() / "Desktop" / "test")
path_obj2 = Path(path_obj.absolute() / "demon2")

if path_obj2.exists() == False:
    path_obj2.mkdir()
    with open(path_obj2.absolute() / "abc.txt", mode="wt", encoding="utf-8") as f:
        f.write("天青色的烟雨\n")
        f.write("而我在等你")

if Path(path_obj2.absolute() / "abc.txt").exists() == False:
    with open(path_obj2.absolute() / "abc.txt", mode="wt", encoding="utf-8") as f:
        f.write("秦时明月汉时关\n")
        f.write("万里长征人未还")

11.subprocess模块 

"""
This module allows you to spawn processes, connect to their
input/output/error pipes, and obtain their return codes.
Main API
========
run(...): Runs a command, waits for it to complete, then returns a
          CompletedProcess instance.
Popen(...): A class for flexibly executing a command in a new process

Constants
---------
DEVNULL: Special value that indicates that os.devnull should be used
PIPE:    Special value that indicates a pipe should be created
STDOUT:  Special value that indicates that stderr should go to stdout
"""

功能和os.system很类似,也是用来执行终端命令的.

12.闭包函数

闭函数:闭,就是封闭,函数是被封闭起来的.

包函数:函数内部包含对外层函数作用域名字的引用

作用:打破了作用域的限制,把局部的内存地址拿到了全局来使用

使用时间:当原本的函数f2,不能够再给它增加新的形参的时候,而函数内部恰好又需要有外部传参进来.这时候就需要应用到闭包函数.闭包函数的主要应用场景就是装饰器

def f1(x):
    def f2():
        print(x)

    return f2


# 这个res现在就是f1内部的这个f2的内存地址
res = f1(10)
print(res)
res()

13.装饰器

13.1 定义

定义一个函数,这个函数就是用来装饰其他函数的.也就是说这个函数是用来给其他函数添加额外功能的

13.2 开放封闭原则

它是所有面向对象原则的核心

开放:指对扩展功能(增加功能)开放,扩展功能的意思是在源代码不做任何改变的情况下,为其增加功能

封闭:对修改源代码是封闭的

也就是说在一个功能已经写OK的情况下,只是要新增功能的情况,就不要再修改代码了.

所以装饰器,在不改变被装饰对象的源代码,也不修改调用方式的前提下,给被装饰对象添加新的功能

# _*_ coding utf-8 _*_
# george
# time: 2024/3/4下午3:05
# name: decorator1.py
# comment:

import time


## 有新的需求就是统计该功能的运行时间

# 方案三
# 问题:解决了方案二的代码冗余问题,也没有修改被装饰对象的源代码,同时还为其增加了新功能
# 但是被装饰对象的调用方式被修改了
# def inside(group, s):
#     print("欢迎来到王者荣耀")
#     print(f"你出生在{group}阵营")
#     print(f"敌方还有{s}秒到达战场")
#     time.sleep(s)
#     print("全军出击")
#
#
# def wrapper():
#     start = time.time()
#     inside("蓝", 3)
#     end = time.time()


# 方案四
# 需求就是inside想怎么改就怎么改,装饰功能写好之后,就不再修改装饰功能了
# 抛开调用方式不谈,这个装饰功能被写死了,只能用作装饰inside
# def inside(group, s,z):
#     print("欢迎来到王者荣耀")
#     print(f"你出生在{group}阵营")
#     print(f"敌方还有{s}秒到达战场")
#     time.sleep(s)
#     print(f"全军{z}出击")
#
#
# def wrapper(*args,**kwargs):
#     start = time.time()
#     inside(*args,**kwargs)
#     end = time.time()
#
# wrapper("蓝",3,"炮车")


# 方案五
# 需求: 将装饰功能写活
# 问题: 成功调用了inside函数,同时还统计了它的运行时间,被装饰对象的源代码没有改变,函数调用方式也没有变化
def inside(group, s, z):
    print("欢迎来到王者荣耀")
    print(f"你出生在{group}阵营")
    print(f"敌方还有{s}秒到达战场")
    time.sleep(s)
    print(f"全军{z}出击")


print(f"原来的inside地址:{inside}")


# 我们给wrapper传递的参数,原封不动的传递给了被装饰对象,所以我们调用wrapper的时候不能随意修改实参
# 所以现在只能使用闭包函数进行传参
# 之前的wrapper是在全局的,我们可以直接调用,但是现在为了包一个参数给它,将其缩进到了outer里面,全局就访问不到wrapper
# 所以为了能够让全局访问到wrapper,得将wrapper返回出去
def outer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print(end - start)

    return wrapper  # 全局拿到wrapper的内存地址


inside = outer(inside)  # 现在res就是wrapper
print(f"新的inside地址:{inside}")
inside("蓝", 3, "炮车")  # 给wrapper传参就相当于给其装饰对象传参,现在的装饰对象是inside,所以参照inside传参

# _*_ coding utf-8 _*_
# george
# time: 2024/3/4下午5:14
# name: decorator2.py
# comment:
import time


# 方案六,给充电功能添加计算时间
def recharge(num):
    for i in range(num, 101):
        time.sleep(0.05)
        print(f"\r当前电量:{'|' * i} {i}%", end="")
    print("\n充电已完成")


def outer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print(end - start)

    return wrapper


recharge = outer(recharge)
recharge(20)

13.3 装饰器最终效果 

装饰器要达到的效果要让函数的调用者完全感觉不出来有什么变化,但是如果被装饰对象是有返回值的呢?基于方案六返回值是None,因为recharge是wrapper的内存地址,wrapper并没有返回值,自然现在的返回值就是None了.所以也是不行的.那么就要将原本的recharge函数的返回值返回出去即可

# _*_ coding:utf-8 _*_
# @Time  :  21:55
# @Author: george
# @File  : decoractor.py
# @Comment: ***
import time

def recharge(num):
    for i in range(num,101):
        time.sleep(0.05)
        print(f"\r当前电量为:{'|'*i} {i}%",end="")
    print("电量已充满")
    return 100


def outer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        reponse = func(*args, **kwargs)
        end = time.time()
        print(end-start)
        return reponse
    return wrapper
recharge = outer(recharge)
res = recharge(10)
print(res)

 13.4 语法糖

现在每次装饰一个对象的时候,都是需要执行一下outter,将被装饰对象传递进去,得到一个新的函数地址,再覆盖给原来的名字.这样很是麻烦,所以需要一个简洁的方式来做这个偷梁换柱的操作,现在就可以用到语法糖

# _*_ coding:utf-8 _*_
# @Time  :  22:16
# @Author: george
# @File  : 语法糖.py
# @Comment: ***

import time


def count_time(func):  # 装饰器的定义要在使用之前,因为函数的定义要在使用之前
    def wrapper(*args, **kwargs):
        start = time.time()
        reponse = func(*args, **kwargs)
        end = time.time()
        print(end - start)
        return reponse

    return wrapper


@count_time  # 在这里加上装饰器的名称,相当于 recharge = outer(recharge)自动帮你做了
def recharge(num):
    for i in range(num, 101):
        time.sleep(0.05)
        print(f"\r当前电量为:{'|' * i} {i}%", end="")
    print("电量已充满")
    return 100


@count_time  # inside = outer(inside)
def inside(group, s, z):
    print("欢迎来到王者荣耀")
    print(f"你出生在{group}阵营")
    print(f"敌方还有{s}秒到达战场")
    time.sleep(s)
    print(f"全军{z}出击")


# recharge = outer(recharge)
# inside = outer(inside)
# 现在可以直接调用
inside("蓝", 3, "炮车")
recharge(20)

13.5 装饰器模板

对于我们使用的wrapper函数而言其实只是需要做两件事情,第一件事情就是调用原来的函数,
第二件事情就是在调用原来函数的基础上面还要增加新的功能

# _*_ coding:utf-8 _*_
# @Time  :  22:35
# @Author: george
# @File  : 装饰器模板.py
# @Comment: ***

import time


# 这就是装饰器模板,只是调用了原来的函数但是没有增加新的功能
def outer(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res

    return wrapper


# 需求统计这个函数的运行时间
def counter_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
        return res
    return wrapper


@counter_time
def home():
    time.sleep(0.2)
    print("this is my home")

home()

13.6 实时模板

为了快速写出一个装饰器,pycharm提供了一个模板功能

13.7 完美伪装

为了将wrapper伪装的和被装饰对象一模一样:

1) 为了给wrapper传的参数和被装饰对象一模一样,我们将给wrapper传递的参数,原封不动的传递给了被装饰对象

2) 为了把wrapper的返回值伪装的和被装饰对象一模一样,我们将被装饰对象的结果赋值给一个变量,然后将这个变量作为wrapper的返回值 

但是现在还是有些小问题,比如

print(home) 显示的是<function auth.<locals>.wrapper at 0x106a2c510> ,这是auth局部的wrapper的内存地址,因为我们使用语法糖将home指向的内存地址换成了wrapper的内存地址.home 原来的内存地址是<function home at 0x10a24a488>,所以我们的伪装还是不够完善print(home.__name__) # 用来查看函数名的 

print(home.__doc__) # 用于查看函数文档注释的

这两个显示的也是和原来的home函数的并不一样

我们可以使用下面两句进行替换

wrapper.__name__ = home.__name__

 wrapper.__doc__ = home.__doc__

但是这两行代码不能放到函数内部,warpper加了装饰器之后的是wrapper内部是它的函数子代码,函数子代码要运行,必须要是调用这个函数才可以 ,所以这两行代码必须是在wrapper定义之后,调用之前使用的.

import time


def auth(func):
    def wrapper(*args, **kwargs):
        name = input("输入姓名>>>").strip()
        passwd = input("输入密码>>>").strip()
        if name == "zs" and passwd == "123":
            res = func(*args, **kwargs)
            return res
        else:
            print("账号密码错误")

    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper


@auth
def home():
    """这是主页"""
    time.sleep(3)
    print("this is my home")

但是做到这一步之后还是不够,因为home下面的__属性有很多,理论上我们要将home所有的__开头__结尾的属性全部都赋值给wrapper,但是这样做的话实在是太麻烦了.

python提供了一个功能 from functools import wraps,wraps也是一个装饰器,它的功能就是把函数的所有属性全都复制给wrapper,这样就是完美伪装

# _*_ coding utf-8 _*_
# george
# time: 2024/3/5上午8:31
# name: 完美伪装1.py
# comment:
import time
from functools import wraps


def auth(func):
    @wraps(func) # 这样wraps内部就会将func的一系列属性全部都赋值给wrapper
    def wrapper(*args, **kwargs):
        name = input("输入姓名>>>").strip()
        passwd = input("输入密码>>>").strip()
        if name == "zs" and passwd == "123":
            res = func(*args, **kwargs)
            return res
        else:
            print("账号密码错误")

    # wrapper.__name__ = func.__name__
    # wrapper.__doc__ = func.__doc__
    return wrapper


@auth
def home():
    """这是主页"""
    time.sleep(3)
    print("this is my home")


print(home)  # <function auth.<locals>.wrapper at 0x106a2c510>
# 这是auth局部的wrapper的内存地址,因为我们使用语法糖将home指向的内存地址换成了wrapper的内存地址
# home 原来的内存地址是<function home at 0x10a24a488>,所以我们的伪装还是不够完善

print(home.__name__)  # 用来查看函数名的
# 原来的是home,
print(home.__doc__)  # 用于查看函数文档注释的


以后我们不加@wraps其实也是没有问题的,因为最关键的伪装,参数,调用方式和返回值我们已经伪装的很好了 

13.8 有参装饰器

当函数内部需要参数的时候,有两种方案可以实现,第一种方案是直接通过参数传递进去.第二种方案是通过闭包函数的概念包给它.如果是第一种方案可以解决,就不要使用第二种方案,因为第二种方案比第一种方案麻烦的多

wrapper的参数是原封不动的传给了被装饰对象,所以wrapper内部需要func的时候,我们不能直接通过wrapper的参数来传.所以只能使用闭包的方式,通过outer把func包给wrapper.现在又有新的需求就是wrapper的内部,需要从外面传递一个参数进来.如何做?

第一种方案是在outer里面在增加一个参数,这样很明显是不可行的

因为@outer本质上就是outer = outer(home),就是将outer的指向换为了wrapper的内存地址,但是语法糖传递给outer的参数只有被装饰对象的内存地址,我们可以将语法糖换为

outer = outer(home,"libai"),但是很明显这样还是太麻烦,我们还是需要使用语法糖,所以第一种方案不可行

def outer(func,name):
    print(name)

    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(end - start)
        return res

    return wrapper

def home():
    print("this is my home")

outer = outer(home,"libai")

home()

所以还是得使用第二种方案使用闭包传参 ,这样解决了outer内部需要参数的问题.但是这样还是带来了一个问题,给outer内部传递不同的参数,就会需要不同的outer= g_outer(name).这样还是很麻烦

def g_outer(name):
    def outer(func):
        print(name)

        def wrapper(*args, **kwargs):
            start = time.time()
            res = func(*args, **kwargs)
            end = time.time()
            print(end - start)
            return res

        return wrapper

    return outer

# 之前的outer是在全局的,但是现在缩进之后,在全局就访问不到了,所以需要将outer放回全局
# 现在这个g_outer的返回值就是ouetr的内存地址
outer = g_outer("libai")


@outer
def home():
    print("this is my home")


home()

有参装饰器最终解决方案:

@outer的outer本质上就是一个变量名,那么我们可以直接g_outer("libai") 

def g_outer(name):
    def outer(func):
        print(name)

        def wrapper(*args, **kwargs):
            start = time.time()
            res = func(*args, **kwargs)
            end = time.time()
            print(end - start)
            return res

        return wrapper

    return outer

# 之前的outer是在全局的,但是现在缩进之后,在全局就访问不到了,所以需要将outer放回全局
# 现在这个g_outer的返回值就是ouetr的内存地址
# outer = g_outer("libai")


@g_outer("libai") # 这里调用g_outer就相当于在这里放置了outer的内存地址,@g_outer("libai")=>@outer ,@outer => home = outer(home)
def home():
    print("this is my home")


home()

 

13.9 有参装饰器的应用 

# _*_ coding utf-8 _*_
# george
# time: 2024/3/5上午11:22
# name: 有参装饰器的应用.py
# comment:
def login_auth(pwd_type):
    def auth(func):
        def wrapper(*args, **kwargs):
            name = input("输入账号>>>").strip()
            pwd = input("输入姓名>>>").strip()
            if name == "libai" and pwd == "123":
                if pwd_type == "file":
                    print("基于文件的登陆验证")
                elif pwd_type == "mysql":
                    print("基于mysql的登陆验证")
                else:
                    print("基于ldap的登陆验证")
                res = func(*args, **kwargs)
                return res
            print("账号密码错误")
        return wrapper

    return auth


@login_auth("file")
def home():
    print("this is my home")


@login_auth("mysql")
def index():
    print("this is my index")


@login_auth("ldap")
def default():
    print("this is my default")

home()
index()
default()

13.10 装饰器叠加

# _*_ coding utf-8 _*_
# george
# time: 2024/3/5下午3:38
# name: 装饰器叠加.py
# comment:
def outer1(func):
    def wrapper(*args, **kwargs):
        print("outer1 开始执行")
        res = func(*args, **kwargs)
        print("outer1执行完毕")
        return res

    return wrapper


def outer2(x):
    def outer(func):
        def wrapper(*args, **kwargs):
            print("outer2 开始执行")
            res = func(*args, **kwargs)
            print("outer2 执行结束")
            return res

        return wrapper

    return outer


def outer3(func):
    def wrapper(*args, **kwargs):
        print("outer3开始执行")
        res = func(*args, **kwargs)
        print("outer3执行结束")
        return res

    return wrapper


@outer3  # home = outer3(home) # outer3.wrapper
@outer2("10")  # outer = outer2(10)=>outer, @outer=> home = outer(home) # outer2.wrapper
@outer1  # home = outer1(home) # outer1.wrapper
def home():
    print("this is my home")


home()

我们每一个装饰器的作用都是将全局的home,替换为wrapper 

执行顺序分析:

#执行顺序为@outer1 -> @outer2 -> @outer3
"""
1) @outer1   home = outer1(home) # outer1.wrapper,

这个home返回值就是outer1内部的wrapper的内存地址,简单记为outer1.wrapper
2) @outer2("10")   outer = outer2(10)=>outer,@outer=> home = outer(home) # outer2.wrapper,
调用outer2("10")就会得到outer,调用@outer就会立即调用outer()将下面函数的内存地址传递进去,就是@outer1的home,也就是outer1.wrapper而现在的home就是outer2内部的内存地址了,也就是outer2.wrapper了
3) @outer3  home = outer3(home) # outer3.wrapper,

执行outer3()将下面的内存地址传递进去,就是home也就outer2.wrapper传递进去,现在的home就变为了outer3内部的wrapper了
"""

输出结果为:

输出结果分析:

 经过前面的执行顺序分析可知,现在在全局调用home,其实调用的就是outer3内部的wrapper,outer3.wrapper.所以会执行outer3内部的wrapper子代码

def wrapper(*args, **kwargs):
    print("outer3开始执行")  => 开始执行1
    res = func(*args, **kwargs) => 调用func,它是通过@outer3传递进来的,也就是说这里传递的是outer2内部wrpper的内存地址,记为outer2.wraper,所以现在开始执行 outer2内部的wrapper
    print("outer3执行结束") => 开始执行7
    return res
def wrapper(*args, **kwargs):
    print("outer2 开始执行") => 开始执行2
    res = func(*args, **kwargs) => 调用func,它是通过@outer2传递进来的,也就是说这里传递的是outer2内部wrpper的内存地址,记为outer2.wraper,所以现在开始执行 outer1内部的wrapper
    print("outer2 执行结束") => 开始执行6
    return res
def outer1(func):
    def wrapper(*args, **kwargs):
        print("outer1 开始执行") => 开始执行3
        res = func(*args, **kwargs)=> 调用func,它是通过@outer1传递进来的,也就是说这里home的内存地址,所以现在开始执行home
        print("outer1执行完毕")=> 开始执行5
        return res => 这个res的值就是func的返回值,那么wrapper的返回值就是res

    return wrapper
def home():
    print("this is my home") => 开始执行4

 执行的时候是从上往下依次执行,结束的时候是从下往上依次结束.

14.迭代器

迭代就是重复,但是每一次重复都是在上一次基础上面做的,并不是是单纯的重复,而迭代器其实就是迭代取值的工具,并且每一次取值都是和上一次有关联的.

迭代器:不依赖索引的迭代取值方式 

14.1 可迭代对象

只要是可以被for循环遍历的就是可迭代对象,但是这样的说法并不准去.而是只要内置__iter__()方法的就可以被称之为可迭代对象

# _*_ coding:utf-8 _*_
# @Time  :  19:51
# @Author: george
# @File  : 迭代对象.py
# @Comment: ***
l = [1, 2, 3]
print(l.__iter__())  # <list_iterator object at 0x000001D4D67BA3D0>
s = "张大仙"
print(s.__iter__())  # <str_iterator object at 0x0000021A0121CA00>
t = (1, 2, 3)
print(t.__iter__())  # <tuple_iterator object at 0x0000021A0121CA00>
d = {'key': 1}
print(d.__iter__())  # <dict_keyiterator object at 0x000001D0B1E57B80>
s2 = {1, 2, 3}
print(s2.__iter__())  # <set_iterator object at 0x000001D0B1E4D240>

with open("../main.py","r",encoding="utf-8") as f:
    print(f.__iter__()) # <_io.TextIOWrapper name='../main.py' mode='r' encoding='utf-8'>

可迭代对象调用__iter__方法之后就会变为将这个可迭代对象转换为迭代器对象iterator

可迭代对象:就是可以转换为迭代器的对象,就被称之为可迭代对象

python设计迭代器的原因,就是为了寻求一种不依赖索引,也能够进行迭代取值的方案,所以python给那些没有索引的数据类型都内置了一个功能__iter__().如果我们不想或是不能依赖索引进行迭代取值 .那么我们直接调用python提供的__iter__()功能即可.只要我们调用了__iter__功能就会将可迭代对象转换为迭代器对象.有了这个迭代器对象就可以不依赖索引进行取值了.所以这个迭代器对象是怎么不依赖索引进行取值的呢?

只要是迭代器对象下面就会有一个__next__()方法,我们调用这个方法就是在取它的值,每执行一次这个__next__()就会得到一个返回值.如果可迭代对象的值取完了,我们继续调用__next__()就会抛出

StopIteration异常.迭代器很是节省内存资源,它是生鸡蛋的母鸡,不是鸡蛋

14.2 迭代器对象

可迭代对象就是内置有__iter__()方法的对象,当可迭代对象调用__iter__()方法之后,就会得到一个它的迭代器版本,也就是迭代器对象

迭代器对象:内置__next__()方法,和__iter__()方法的对象.

迭代器调用__next__()方法就会得到迭代器的下一个值

迭代器对象调用__iter__()方法就会得到迭代器本身(相当于和没调没区别)

可迭代对象是内置有__iter__()方法的,迭代器对象是内置有__next__()方法和__iter__()方法,那么迭代器对象就是可迭代对象,但是可迭代对象不一定是迭代器对象 

14.3 for 循环原理

既然迭代器调用__iter__()和没调用没区别,python这样设计有什么用吗?

其实是为了让for循环的工作原理统一起来,就是说for循环后面接的无论是可迭代对象还是迭代器对象,都可以采用同一套运行机制

for i in 可迭代对象.__iter__():

for i in 迭代器对象.__iter__()

无论是可迭代对象还是迭代器对象进行for循环时,都会转化为迭代器.

#for循环背后的实现过程

1.这个dict调用它的__iter__(),得到一个它的迭代器版本

2.for循环会帮我们调用这个迭代器对象的__next__()方法,拿到一个返回值,将这个返回值赋值给key

3.循环执行第二步,直到抛出StopIteration异常,for循环会帮我们捕获异常,并结束循环

14.4 类型转换

tuple("张大仙")

list("张大仙")

之前说过类型转换的这两个功能,它背后其实就是相当于调用了一个for循环,将遍历出来的值放到列表和元组里面,for循环的原理如上,那么这两个类型转换功能的原理其实也是这三步:

1) 调用字符串下面的__iter__()方法得到一个它的迭代器版本

2) for循环会帮我们调用这个迭代器对象的__next__()方法,拿到一个返回值,将这个返回值放到列表或是元组里面

3) 循环执行第二步,直到抛出StopIteration异常,for循环会帮我们捕获异常,并结束循环

15.生成器

15.1 生成器

如果要生成一个迭代器目前的方法只有一种,就是先创建一个数据类型(可迭代对象),再调用它下面的__iter__()方法.但是如果要一个可以产生1亿个值的迭代器.还要先定义一个1亿值的数据类型?

使用迭代器的原因就是因为它节省内存资源,如果我们这样做的话就是本末倒置了.虽然我们可以使用range(100000000)再调用__iter__方法,但是range只能针对整型

所以我们需要一种不依赖range和其他数据类型,就可以自己定义出来迭代器的方法

而我们说的生成器就是迭代器,只不过它是一种我们自己定义的迭代器,并不是说我们定义一个函数在这个函数内部写了yield关键字,这个函数就是生成器,而是调用此函数时,并不会正常执行函数子代码,而是会返回给我们一个生成器对象

当调用了生成器对象的__next__方法之后才会触发函数的第一次执行,遇到yield关键字之后就会停止下来接着将yield后面跟着的值,作为本次__next__()方法调用的结果返回出来.

当然res.__next__(),y可以使用next(res)

def func():
    print("第一次执行")
    yield 1
    print("第2次执行")
    yield 2
    print("第3次执行")
    yield 3
    print("第4次执行")
    yield 4
    print("第5次执行")


res = func()
print(res)  # => <generator object func at 0x10d2c8930>
# res 同时具备__iter__和__next__方法
# 当调用了生成器对象的__next__方法之后才会触发函数的第一次执行,遇到yield关键字之后就会停止下来
# 接着将yield后面跟着的值,作为本次__next__()方法调用的结果返回出来
a = res.__next__()  # => 第一次执行
print(a)  # => 1
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())  # 第5次调用,触发函数第五次执行,但是没有yield,直接抛出异常StopIteration

15.2 yield表达式 

所谓yield表达式就是说y=yield,这样写完之后,yield的功能就不止一个了

1)将函数暂停在这里,同时返回一个值

def func(x):
    print(f"{x}开始执行")
    while True:
        y = yield None  # 不屑默认也是为None
        print(x, y)


# 只要函数的内部出现了yield,就和函数内部的代码没有任何关系了,只有当我们调用__next__()方法的时候
# 才会执行其内部的代码

g = func(1)  # 获得生成器对象
res = next(g)  # 开始执行函数子代码,直到遇到yield关键字,并将其值返回出来
print(res)  # => None
next(g)  # => 1 None

2) 给这个yield传值

def func(x):
    print(f"{x}开始执行")
    while True:
        y = yield None  # 不屑默认也是为None
        print(x, y)


g = func(1)  # 获得生成器对象
# yield可以接收send过来的值,然后赋值给一个变量,send就相当于在next的基础上多了一个传值的功能
g.send(10)  # 这个10就会被传递给yield,然后yiled又被赋值给y,所以y就为10

 y = yield None 可以看做是两步

1)  y=yield, 是在本次执行开始时起的作用,是用来接收send传过来的值的 => y=10

2) yield None,是本次执行结束后起的作用,用于返回一个值出去,返回None => None

生成器需要先send一个None,让函数停在有yield的地方

def func(x):
    print(f"{x}开始执行")
    while True:
        y = yield None  # 不屑默认也是为None, y=yield是在本次执行开始时起的作用,是用来接收send传过来的值的 yield是本次执行结束后起的作用,用于返回一个值出去
        print(x, y)


g = func(1)  # 获得生成器对象
res = g.send(None)  # 相当于next(g),并且代码是停在了yield关键字这里
print(res)  # => None
res2 = g.send(10)  # 给yield传值,同时触发子函数的执行
print(res2)  # => None

我们可以通过send()来实现一段代码的挂起和执行,以此来实现在多段代码之间来回切换执行

 此模块是Python标准库中推荐的命令行解析模块

16.argparse模块

16.1 位置参数

import argparse

parser = argparse.ArgumentParser()
# add_argument()用于指定程序能够接收哪些命令行选项,添加help调用-h时,能将echo位置参数的help信息显示出来
# args.echo默认为str类型,除非定义传递的位置参数的类型,type=int
parser.add_argument("echo",help="echo the string you use here",type=int)
# 返回从命令行中获取的数据 parse_args()
args = parser.parse_args()
print(args)  # => Namespace(echo='6')
print(args.echo,type(args.echo))

c75fcbe00b7d441988ed57e2a98ed4c9.png

16.2 可选参数

可选参数的实现是通过--方式添加

parser.add_argument("--ver",help="increase output verbosity")

2.1 因为是可选参数,当不附带该选项时运行将不会提示任何错误.可选参数违背使用时,则关联的变量args.ver值为None

2.2 帮助信息会出现在optional arguments位置

2.3 使用--ver选项时,必须指定一个值,但可以是任何值

import argparse

parser = argparse.ArgumentParser()
# add_argument()用于指定程序能够接收哪些命令行选项,添加help调用-h时,能将echo位置参数的help信息显示出来
# args.echo默认为str类型,除非定义传递的位置参数的类型,type=int
parser.add_argument("echo",help="echo the string you use here",type=int) # 添加位置参数
parser.add_argument("--ver",help="increase output verbosity") # 添加可选参数
# 返回从命令行中获取的数据 parse_args()
args = parser.parse_args()
print(args)  # => Namespace(echo='6')
print(args.echo,type(args.echo))
print(args.ver,type(args.ver))

95b0b72797c94f1ca18371cbd7c83ece.png

16.3 参数action 

ArgumentParser 对象将命令行参数与action相关联.大多数action只是简单的向 parse_args() 返回的对象上添加属性.action 命名参数指定了这个命令行参数应当如何处理

730609eccc1843b69e609115343d9dad.png

16.3.1 store

- 存储参数的值。这是默认的动作

16.3.2 store_true

- 现在的选项像是旗标而不需要接收特定的值 ,如果指定了该选项则args.ver的值为True,未指定时则该值为False.

- 当你为其指定一个值时,它会报错

- store只能使用在可选参数上面

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--ver",help="increase output verbosity",action="store_true") # 添加可选参数
# 返回从命令行中获取的数据 parse_args()
args = parser.parse_args()
print(args)  #
if args.ver:
    print("命令中存在-ver")
else:
    print("命令中不存在-ver")

105e8ad2a88748d983ad29bc695c11e0.png

16.3.3 count

- count action 用来统计特定选项出现的次数

- 和action= "store_true"相似,也像一个标志

- 如果不添加-v标志,这个标志的值为None

- 长形态和短形态的输出是相同的

- 此时可以显式的将default值设置为0,含义为在-v个数为0是verbosity值为0而不是为None.避免出现TypeError: '>=' not supported between instances of 'NoneType' and 'int

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square",type=int,help="output the square of a given number")
parser.add_argument("-v","--verbosity",action="count",help="increase output verbosity")
args = parser.parse_args()
print(args)
answer = args.square
if args.verbosity >= 2:
    print(f"the square of {args.square} equal {answer}")
elif args.verbosity >= 1:
    print(f"{args.square}^2 == {answer}")
else:
    print(answer)

6a96dae41b70400a95b0d34e10121e59.png

16.3.4 default 

 5d93cc91abf741c7b76e6990c7cd0aac.png

a38d0cdef2904563938c92d3a72db02b.png

16.4 命令缩写 

使用-v来指定对于长选项的缩写形式

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-v","--ver",help="increase output verbosity",action="store_true") # 添加可选参数
# 返回从命令行中获取的数据 parse_args()
args = parser.parse_args()
print(args)  #
if args.ver:
    print("命令中存在-ver")
else:
    print("命令中不存在-ver")

8f3fd9b3bc264ee4bc0fde5a318367e8.png

16.5 位置参数结合可选参数 

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square",type=int,help="display a square of a given number")
parser.add_argument("-v","--verbosity",type=int,help="increase output verbosity")
args = parser.parse_args()
answer = args.square**2
if args.verbosity == 2:
    print(f"the square of {args.square} equals {answer}")
elif args.verbosity == 1:
    print(f"{args.square}^2 equals {answer}")
else:
    print(answer)

d9943687926d4ddfa981897b4bf44a81.png

16.6 choice

通过choice限制--verbosity选项的可选值 ,如果--verbosity取的值不在choice的范围内,则会报错

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("square",type=int,help="output the square of a given number")
parser.add_argument("-v","--verbosity",type=int,choices=[0,1,2],help="increase output verbosity")
args = parser.parse_args()
print(args)
answer = args.square
if args.verbosity == 2:
    print(f"the square of {args.square} equal {answer}")
if args.verbosity == 1:
    print(f"{args.square}^2 == {answer}")
else:
    print(answer)

36a7e25d9864408e90713ed842279ff3.png

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_68716275/article/details/136008151

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签