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

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线

推荐文章

热门文章

相关标签