note

01. 私有属性

名字重整:其实就是__name这样的名字解释器在前面加了_类名,所以直接访问不到,如下:

02. GIL锁

​ Python是可以调用其它语言的,Python使用多线程,若是其中一个线程是调用的C\C++的函数(视频“02-GIL-2.flv”有说明),它们是不受GIL锁的限制的,所以除了这个方法,去掉GIL锁限制的方法还有,不用Cpython解释器,用其它的解释器。

GIL锁与互斥锁的区别:

03. 深浅拷贝

​ C\C++中 一般的复制 a=b 是真的复制出一个叫b的空间,里面的额内容跟a是一样的

python中的复制,都是引用,a是一个列表,b=a,那么是b也指向了a(会发现id(a)和id(b)是一样的) ,所以b更改时,a也会更改

深拷贝:

import copy
b = copy.deepcopy(a)  # 就是真正的复制了一份

一般的赋值都是浅拷贝,copy模块中也还有浅拷贝:(这个少用吧,尽量就用深拷贝或者直接复制)

a, b = [11], [22]
c = [a, b]
e = copy.copy(c)
id(c) != id(e)      # 这俩是不等的
但是
id(c[0]) == id(e[0])  # true

也就是说copy.copy()的浅拷贝是把这个对象完全拷贝一份,至于对象里面的东西是什么样就是什么样,原封不动

Python中,函数传递的时候都是引用,

def app_nums(temp):
	temp_.append(123)
 
 nums = [11, 22]
 app_nums(nums)
 print(nums)  # 此时结果是 [11, 22, 123]
 
 # 要是想让其不改变列表,最好用:
 app_nums(copy.deepcopy(nums))

04. import全局变量使用

import 包的一个用法:

​ Python多个py文件是可以在运行时进行互相通信里的。最简单的例子,有a.py,里面有一个全局变量IF_GPU=True,这时候b.py是从a.py导入了这个全局变量的,它可以加一些条件后,将IF_GPU设置为False,同时有一个c.py也从a.py中导入了这个全局变量,它比b.py后调用全局变量IF_GPU,那么它得到的就是False。 简而言之,不同模块之间有联动时,且一个模块a里代码执行需要另一个模块b的结果,那就可以再设置一个全局变量,一旦b出来结果,就改变这个全局变量的值,然后a再去判断全局变量的状态,然后再执行。

一个注意点:

from a import IF_GPU     # 假定IF_GPU在a模块中的值是True
def func1():
	IF_GPU = False       # 这是创建了一个名为IF_GPU的局部变量,从a导进来的全局变量IF_GPU还是True,要想改的是全局变量就需要在前面声明 global IF_GPU
	
	
# 假设是如下导包:
import a
def func2():
	a.IF_GPU = False      # 这就是改的a中的全局变量IF_GPU

包热加载 reload

​ 一个使用场景就是,服务器运行起来了,不能轻易停下来,如果其中一个模块的代码进行了修改,在不停服务的情况下进行这个模块的热加载:

from importlib import reload    # 基本即将弃用 from imp import reload
import my_new    # 这是我写的模块,里面有一个叫IF_GPU的全局变量
import time

print(my_new.IF_GPU)
time.sleep(10)
reload(my_new)      # 核心在这里,热加载
print(my_new.IF_GPU)

让这个运行起来,在sleep期间去修改my_new.py中IF_GPU的值,会发现两个print结果是不一样的。

05. 多继承super|MRO

python中多继承,以及 MRO 顺序

主要是介绍一下 super的使用:

class Parent(object):
    def __init__(self, name):
        print("parent的init开始调用")
        self.name = name
        print("parent的init调用结束")


class Son1(Parent):
    def __init__(self, name, age):
        print("son1的init开始调用")
        self.age = age
        Parent.__init__(self, name)
        print("Son1的init调用结束")


class Son2(Parent):
    def __init__(self, name, gender):
        print("son2的init开始调用")
        self.gender = gender
        Parent.__init__(self, name)
        print("Son2的init调用结束")


class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print("Grandson的init开始调用")
        Son1.__init__(self, name, age)
        Son2.__init__(self, name, gender)
        print("Grandson的init调用结束")

06. *args|**kwargs

传递不定长参数的讲解:==args==和==*kwargs== 简单来说就是,当传递参数时,超过给定参数时,单个的就以元祖的形式给到args,键值对的就给到kwargs

def func(name, age, *args, **kwargs):
    print(name)  # "lisi"
    print(age)   # 13
    print(args)   # (14, 15, 16) 元祖
    print(kwargs)  # {'gender': 'male', 'addr': 'chengdu', 'arg3': 3, 'arg2': 'two'} 字典
    
info = {"arg3": 3, "arg2": "two", "arg1": 5}
func("lisi", 13, 14, 15, 16, gender="male", addr="chengdu", **info)  # 可以**info这样传参

a = (11, 22, 33)
print(*a)   # 这样解引用是可以
print(**info)  # 不能这样,会直接报错,虽然可以上面**info那样传参

07. @classmethod | @staticmethod

类方法(@classmethod)、静态方法(@staticmethod)、实例方法

class Foo(object):
    country = "中国"  # 类属性
    def __init__(self, name):
        self.name = name  
    def ord_func(self):  # 实例方法至少有一个self参数
        print("实例方法")
    
    @classmethod
    def class_func(cls):  # 类方法至少一个cls参数
        print("类方法")
        
    @staticmethod
    def static_func():  # 静态方法,无默认参数
        print("静态方法")

解读:

08.property属性

property属性的两种方式:

  1. 装饰器:在方法上应用@property装饰器
  2. 类属性:在类中定义值为property对象的类属性

这3点是这两种方式都通用的:

==装饰器==: 只有新式类有可以如下用property的三种形式:(注意这==3个函数名都是一样的==;且(2)(3)Python2的新式类也是没有的)

class Image:  # python3这里加不加(Object)都是一样的,Python2却有很大的区别
    def __init__(self, width, height):
        self.res = width * height

    @property  # (1)获取属性
    def size(self):
        return self.res    # 这个也一定要有返回值

    @size.setter     # (2)设置属性,必须是上面的@property的 函数名.setter
    def size(self, value):  # 因为设置属性,一定要传参数
        self.res = value

    @size.deleter     # (3)删除属性,必须是上面的 函数名.deleter
    def size(self):    
        print("执行的这个函数")    # 这里面不一定放删除语句,可以是其它的逻辑语句
 	

img = Image(460, 480)
print(img.size)             # 自动执行 @property 修饰的 size 方法,获得返回值
img.size = 45678            # 自动执行 @size.setter 修饰的 size 方法,必须要传参
print(img.size)
del img.size                # 自动执行 @size.deleter 修饰的 size 方法

==类属性==:(Python2、2,经典类、新式类没有差别) 有四个参数(可以址填前面一个或者两个,一般来说前面两个是有的,即获取属性,设置属性)(Django框架中就有使用这种方法,很有意思,视频”.\就业班\04 Python高级语法v3.1\05-类对象和实例对象访问属性的区别和property属性\05-创建property属性的方式-类属性.flv”大概11分左右就有说明)

class FOO:
    def get_bar(self):
        print("1.执行的这个get_bar")
        return "必须要有返回值"

    def set_bar(self, value):
        print("2.执行的这个set_bar")
        if isinstance(value, str):       # 用这种方法的意义:就可以在设置值时进行一些检查(这就是实际意义)
            print("执行了赋值操作")       # 用@装饰器的方式也一样
        else:
            print("输入有误,不是str,")

    def del_bar(self):
        print("3.deleter**********")

    # 这是类属性(四个参数不一定都要给,后面的课根据自己情况不要)
    BAR = property(get_bar, set_bar, del_bar, "4.放一些string的说明文档")  # 四个参数不是必须传满


foo = FOO()
# 以下是调用
print(foo.BAR)  # 这就自动调用类属性BAR中第一个参数,即get_bar,,不能是foo.get_bar
foo.BAR = "new_strings"  # 自动调用第二个参数
del foo.BAR  # 自动调用第三个参数
print(FOO.BAR.__doc__)  # 获取第四个参数中设置的值,注意因为是类属性,用类FOO去调用

09. 魔法方法

python的各种魔术方法看这里。(页面最下面有一个表格)

魔法方法:

10. 上下文管理器

定义: ==任何实现了__enter__()和__exit__()方法的对象都可称之为上下文管理器==。上下文管理器可以使用with关键字,我们自己也可以实现一个上下文管理器,一般有如下两种形式:

==类==:(其实就是魔法方法)

class MyFile:
    def __init__(self, file_name, mode):
        self.name = file_name
        self.mode = mode

    def __enter__(self):  # 没有参数
        print("entering")
        self.fp = open(self.name, self.mode)  # 用self.fp是方便下面的函数直接调用
        "一些其它逻辑处理代码"
        return self.fp   # 相当于把一开始的结果存起来了,方便后面调close时使用

    # 无论哪种情况,异常与否,系统都会调用这个方法
    def __exit__(self, exc_type, exc_val, exc_tb): #这是系统默认的方式,系统会自己在发生异常时传进来,exc_type异常类型,exc_val异常的错误信息,异常的一个存放内存位置(应该是)
    def __exit__(self, *args):    # *args是我自己写的,并没用到,默认是有三个参数
        print("will exit")
        self.fp.close()

# 这里的fp就是 __enter__ 方法的返回值
with MyFile("README.txt", "w") as fp:
    fp.write("hello world!")

# 这样就不用显示地调用close方法了,有系统自动去调用,哪怕中间遇到异常,close也会调用

==方法==:Python还提供了一个==contextmanager==的装饰器

from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
    f = open(path, mode)
    yield f
    f.close()

# 调用:这样 my_open 函数也可以用with了
with my_open("README.txt", "r") as fp:
    data = fp.readlines()
    print(data)

11. __import__动态导包 | getattr

这个主要是在学习WSGI-mini-web框架时学到的,比如写了很多个web框架,然后用命令行传参数决定使用哪个web框架的话,那就没办法事先导入,如 from my_dynamic import application ,那么就可以使用这来动态导包:

  1. 添加my_dynamicpy包所在的搜索路径:sys.path.append(“./dynamic”)
  2. 导入这个包:frame = __import__(my_dynamic) # 返回值标记这,导入这个模板
  3. 拿到处理函数的引用:app = getattr(frame, application) # 此时app就指向了 dynamic/my_dynamic中的application这个函数了(这就不用提前导包,就可以通过命令传参来指定)

Tips:可能单单这么看,不是很方便理解,如果后续要用到,可以去看(”就业班\06 mini-web框架v3.1\01-WSGI-mini-web框架\10-给程序传递参数、添加web服务器的配置文件、添加shell功能.flv” 这个视频中大概第18分钟左右的讲解)

更对类似 getattr hasattr 属性的内容可以看这里

12. __init__.py | __all__

__init__.py:当目录里有这个文件时,这个目录就成为包 作用:import导入这个包时,它就会自动执行这个.py文件里的内容

例如当有如下路径时:

user ├── __init__.py ├── fms └── summ_key └── document_type_handle.py # 这是一个.py文件,里面有一个类叫做==Category== 123.py # 这个123.py跟目录 user 是平级的

要是123.py要用 Category 这个类,一般就是==from user.fms.summ_key.document_type_handle import Category==,就很长,那么:(以下两种方式都可以)

"那么只要在 user 目录里的__init__.py里写上 
from user.fms.summ_key.document_type_handle import Category  # 注意也要完整
from .fms.summ_key.document_type_handle import Category   # fms前面的.就代表了user目录

那么 123.py中要再使用 Category 类就是:(也是两种方式)


关于__all__,它是与 from mypackage import * 这种语法相关联的,不建议使用,当作为了解放这里嘛。

有关_all__,__init__.py更多的使用可以看这里

13. 闭包、@装饰器(及路由)

==闭包==:

思考:函数、匿名函数、闭包、对象当做实参时,有什么区别?

  1. 匿名函数通常很简单,方便,传递的是这个函数的引用,只有功能;
  2. 普通函数能够完成较为复杂的功能,传递的是这个函数的引用,只有功能;
  3. 闭包能够完成较为复杂的功能,相对对象来说占用空间较小,传递的是闭包中的函数以及数据,因此是功能+数据;
  4. 对象能完成最为复杂的功能,Python3默认继承Object对象,会有很多魔法方法的继承,较简单功能用对象的话,占空间就很大,传递的是很多数据+很多功能,因此是功能+数据。

闭包的简单例子:求y=k*x+b的一次函数值

def line(k, b):
    def cal(x):
        res = k * x + b
        return res
    return cal
# 一条函数线
line1 = line(2, 3)
print(line1(1))    # 这个的使用就很像对象了,上面初始化,这里调用__call__()方法
print(line1(3))
# 另外一条函数线
line2 = line(5, 6)
print(line2(2))
print(line2(3))

解读:


==装饰器==:

代码要遵循==开放封闭==原则,即写好的代码尽量封闭,不要去改了,但是可以开放增加功能。


==类做装饰器==: 类也是可以做装饰器的,原理差不多一致:

class MyDec(object):
    def __init__(self, func):
        self.func = func  # 传进来的是一个函数引用

    def __call__(self, *args, **kwargs):
        print("这是类做装饰器打印出来的")
        return self.func(*args, **kwargs)

@MyDec     # 它的原理其实也是 get_str = MyDec(get_str)
def get_str():
    return "hello world!"

print(get_str())  # 所以此时的 get_str 是一个实例对象,用()就会调用__call__魔法方法

Tips: 第9行,既然MyDec是类名,那也是可以用调用静态方法的,如 @MyDec.静态方法 (只是写在这里,就不作深入的研究了。以后看到能理解为什么)


==装饰器里带参数==:(重要,后面做路由映射会大量用到)

def set_level(level_num):
    print(789, level_num)

    def set_func(func):
        print(456)

        def call_func(*args, **kwargs):
            print(123)
            # 开始级别验证
            if level_num == 1:
                print("这是权限级别***1****")
            elif level_num == 2:
                print("这是权限级别***2****")
            else:
                print("没有权限")
            return func(*args, **kwargs)

        return call_func

    return set_func

# 这是装饰器里带了参数,
@set_level(2)
def my_func():
    print("这是写好的函数功能实现!")

# my_func()

原理解读:(这种去做路由是最好的)(先写简单装饰器,再加带参数的,一层层来写,别想着直接一次到位)

示例:==装饰器做路由映射==

import time
now_time = time.ctime()

# 创建一个字典用于存映射关系
URL_FUNC_DICT = dict()

"这个一导包进来,带@route的行就会执行"
def route(url):
    def set_func(func):
        URL_FUNC_DICT[url] = func  # 这里在@route时就会执行,上面装饰器带参数讲原理时讲过
		# 因为一开始字典里就拿到了 func的引用,其实这下面三行可以不要的,
        def call_func(*args, **kwargs):  
            return func(*args, **kwargs)  # 因为不会单独再去使用类似 register() 的单独调用,没有这三行,这样的单独调用就会报错
        return call_func

    return set_func


# 这里函数假设都是获取一些动态数据
@route("/register.py")
def register():
    return "hello world!  {}".format(now_time)


@route("/logging.py")
def logging():
    return "这是一个登录页面! {}".format(now_time)


@route("/exiting.py")
def exiting():
    return "这是注销页面! {}".format(now_time)


# application中不能再写那么多 if else,如果有很多函数,就会写很多,不好看
def application(env: dict, start_response):
    start_response("200 OK", [("Content-Type", "text/html;charset=utf-8")])

    file_name = env["file_name"]  # 基本为 /index.html、/logging.py这种
    # 假定以.py结尾的是访问动态资源,,若不想这样,就要有一个字典存起来对应关系,然后直接取
    if file_name.endswith(".py"):
        try:
            ret = URL_FUNC_DICT[file_name]  # 这里返回的就是对应的最开始的函数引用
            return ret()  # 这是调用函数     (注意这两行)
        except:
            return "这个页面找不到了! 404 Not Found!"
    else:  # 否则就是访问静态资源
        try:
            # 一定注意,这里./static_sources这个相对路径是相对的server.py(因为是从server.py启动的),而不是dynamic.py
            fp = open(r"./static_sources" + file_name, "r")
        except Exception:
            response_body = "这个页面找不到了! 404 Not Found!"
        else:
            response_body = fp.read()
            fp.close()
        return response_body

在微软开源的visual-chatgpt这个项目里看到一个装饰器来做说明的用法,还不错:

def prompts(name, description):
    def decorator(func):
        func.name = name
        func.description = description
        return func

    return decorator


@prompts(name="函数名啊,之类的", description="放一些此函数的描述之类的")
def show(path):
    img = cv2.imread(path)
    cv2.imshow("1", img)
    cv2.waitKey(0)

if __name__ == '__main__':
    # show("./123.jpg")
    print(show.name)  # 就可以把这个函数属性打印出来

14. logging日志模块

下面是Python官方推荐的使用方法:

任务场景 最佳工具
普通情况下,在控制台显示输出 print()
报告正常程序操作过程中发生的事件 logging.info()(或者更详细的logging.debug())
发出有关特定事件的警告 warnings.warn()或者logging.warning()
报告错误 弹出异常
在不引发异常的情况下报告错误 logging.error(), logging.exception()或者logging.critical()

logging模块定义了下表所示的日志级别,按事件严重程度由低到高排列(注意是全部大写!因为它们是常量。):

级别 级别数值 使用时机
DEBUG 10 详细信息,常用于调试。
INFO 20 程序正常运行过程中产生的一些信息。
WARNING 30 警告用户,虽然程序还在正常工作,但有可能发生错误。
ERROR 40 由于更严重的问题,程序已不能执行一些功能了。
CRITICAL 50 严重错误,程序已不能继续运行。

==(1)、直接输出到控制台==:

import logging

logging.basicConfig(level=logging.INFO,
       format="%(asctime)s - %(filename)s:[line:%(lineno)d] - %(levelname)s: %(message)s")

logging.debug("这是 logging debug message")   # 这就不会被输出
logging.info("这是 logging info message")
logging.warning("这是 logging warning message")
logging.error("这是 logging error message")
logging.critical("这是 logging critical message")

解读:(重要)

==(2)、保存到日志文件==:

logging.basicConfig(level=logging.WARNING,
                    filename="./log.txt",
                    filemode="w",       # 这个跟open的模式是一样的,it defaults to 'a'
        format="%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")

注意:win上默认用的gbk,好像无法指定encoding编码方式


==(3)、既要把日志输出到控制台,也要写入到日志文件==:

# 第一步:创建一个logger记录器
# logger = logging.getLogger("songhui")  # 可传一个str参数
logger = logging.getLogger()  # 不给,默认就是root,可在格式中用 %(name)s 获取
logger.setLevel(logging.INFO)  # 这是log等级总开关

# 第二步:创建一个handler,用于写入日志文件
logfile = "./log.txt"
fh = logging.FileHandler(logfile, mode="a", encoding="utf-8")  # 注意模式和编码格式
fh.setLevel(logging.DEBUG)     # 输出到file中的log等级开关

# 第三步:再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING)   # 输出到console中的log等级开关

# 第四步:定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s:[line:%(lineno)d] - %(levelname)s: %(message)s")   # 这里是可以指定 datefmt 参数的,设置时间的格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)

# 第五步:将logger添加到handler里面
logger.addHandler(fh)     # 有add就有remove,logger.removeHandler(fh)就是把这踢出去
logger.addHandler(ch)

# 日志  (一定注意,这用的是上面创建的对象logger,而不是模块logging)
logger.debug("这是 logging debug message")
logger.info("这是 logging info message")
logger.warning("这是 logging warning message")
logger.error("这是 logging error message")
logger.critical("这是 logging critical message")

注意点:(多文件的log日志)

logging模块内置的keys:

属性 格式 描述
asctime %(asctime)s 日志产生的时间,默认格式为2003-07-08 16:49:45,896
created %(created)f time.time()生成的日志创建时间戳
filename %(filename)s 生成日志的程序名
funcName %(funcName)s 调用日志的函数名
levelname %(levelname)s 日志级别 (‘DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’)
levelno %(levelno)s 日志级别对应的数值
lineno %(lineno)d 日志所针对的代码行号(如果可用的话)
module %(module)s 生成日志的模块名
msecs %(msecs)d 日志生成时间的毫秒部分
message %(message)s 具体的日志信息
name %(name)s 日志调用者
pathname %(pathname)s 生成日志的文件的完整路径
process %(process)d 生成日志的进程ID(如果可用)
processName %(processName)s 进程名(如果可用)
thread %(thread)d 生成日志的线程ID(如果可用)
threadName %(threadName)s 线程名(如果可用)

还有更多高级的用法,看这里的后面。


使用:(相当重要)

一般来说,简单的话,就像上面那种配置了就行,一般大型项目还是配置成.yaml文件来加载:

==logging.conf.yaml==:

version: 1
# (一):这是设置格式,可以设置几种,不同的情况用不同的格式
formatters:
	# (1.1)可选的一种格式
    simple:
        format: " %(asctime)s - %(filename)s:[line:%(lineno)d] - %(levelname)s: %(message)s"
        datefmt: "%Y-%m-%d %H:%M:%S"
    # (1.2)可选的另一种格式
    upgrade:
        format: " %(name)s: %(asctime)s - %(filename)s:[line:%(lineno)d] - %(levelname)s: %(message)s"

# (二):这是控制log日志打印到屏幕以及保存到磁盘文件(一样可以写不同场景的配置)
handlers:
    # (2.1)输出到控制台
    console:
        class: logging.StreamHandler   # 这就是代表输出到控制台
        level: WARNING
        formatter: upgrade           # 用的上面formatters中的哪种格式
        stream: ext://sys.stdout
    # (2.2)记录到文件
    all_file_handler:
        # 当日志文件的大小达到指定值的时候,RotatingFileHandler 会将日志文件重命名存档,然后打开一个新的日志文件
        class: logging.handlers.RotatingFileHandler
        level: DEBUG
        formatter: simple    # 一样选择上面一种格式
        filename: ./logs/all_log.log
        maxBytes: 10485760   # 10MB(单个文件)
        backupCount: 50      # 保留50个log文件
        encoding: utf8
    # (2.2)这也是记录到文件,代表不同的场景配置
    server_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: INFO
        formatter: upgrade
        filename: ./loggs/server.log   # 若是给了文件路径,一定要提前建立好,不然会报错
        maxBytes: 10485760
        backupCount: 20
        encoding: utf8

# (三)这也是第三大点,二者选其中一个 my_logger = logging.getLogger()
root:
    level: DEBUG
    # 由这来决定输出控制台、保存文件(同时保存不同的格式)、都要
    handlers: [console, all_file_handler, server_file_handler]

# (三):这是设置 my_logger = logging.getLogger("server")时传递的名字,
#loggers:
#	# (3.1)代表指定的name使用的配置
#    server:
#        level: DEBUG
#        handlers: [console, server_file_handler]   # 这就是既可以打印又会保存日志文件
#        propagate: True  # 设为false则禁止将日志消息传递给父级记录器的处理程序中
#	# (3.1)另一个人的配置
#    songhui:
#        level: INFO
#        handlers: [console, all_file_handler, server_file_handler]
#        propagate: True

Tips:(以上的注释都写很清楚了,这再来强调一下)

==log.py==:加载配置文件使用

import logging.config
import yaml

# 通过配置文件配置logging
with open("./logging.conf.yaml", encoding="utf-8") as fp:
    dic = yaml.load(fp, Loader=yaml.FullLoader)
logging.config.dictConfig(dic)

# 创建logger
logger = logging.getLogger()

# 会按.yaml配置来处理日志内容
logger.debug("这是 logging debug message")
logger.info("这是 logging info message")
logger.warning("这是 logging warning message")
logger.error("这是 logging error message")
logger.critical("这是 logging critical message")

Tsip: 把以上封装成一个类,类也直接实例化,然后其它模块直接导包从log.py中导入实例对象logger,然后就直接调用logger的相关方法。


下面是我最简单的使用,并把日志设置成7天自动删除旧日志(最上面的==loguru==库实现起来更加简单)

import logging

# 日志类
class Logging:
    def __init__(self, user):
        self.user = user
        # 这里看自己情况把日志放那里,这里是放在启动的py文件上一级下的 logs 文件夹中
        self.log_path = str(self._get_log_dir() / f"{user}.log")
        self.logger = logging.getLogger(user)
        self.logger.setLevel(logging.INFO)  # 这里是把级别设成了INFO,自己可以酌情或传参

    def _get_log_dir(self):
        current_path = Path.cwd()
        logs_dir = current_path.parent / "logs" / f"{self.user}"
        logs_dir.mkdir(parents=True, exist_ok=True)
        return logs_dir

    def __call__(self):
        # 创建一个 handler,用于写入日志文件 (保存7天)
        """
        	midnight:表示日志文件在每天半夜时分滚动,当时间到达午夜时分,当前的日志文件会被关闭,并创建一个新的日志文件。
        	interval: 间隔时间单位的个数,指等待多少个 when 的时间后 Logger 会自动重建新闻继续进行日志记录.
        	backupCount: 表示日志文件的保留个数,假如为7,则会保留最近的7个日志文件
        """
        file_handler = TimedRotatingFileHandler(self.log_path, when="midnight", interval=1, backupCount=7)
        file_handler.suffix = "%Y-%m-%d"  # 设置日志文件名的时间戳格式,这就是后缀,会跟在self.log_name后面
        # extMatch是编译好正则表达式,用于匹配日志文件名后缀
        # 需要注意的是suffix和extMatch一定要匹配的上,如果不匹配,过期日志不会被删除。
        file_handler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}$")  # 所以删上面还可以设置其它后缀

        # 创建一个 handler,用于输出到控制台
        console_handler = logging.StreamHandler()
        console_handler.setLevel(logging.INFO)  # 设置 handler 级别为 INFO

        # 创建一个 formatter,用于设置日志格式(%H是代表24小时制,%I代表12小时制)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s', datefmt="%Y.%m.%d %H:%M:%S")

        # 设置 handler 的格式
        file_handler.setFormatter(formatter)
        console_handler.setFormatter(formatter)

        # 为 logger 添加 handler
        self.logger.addHandler(file_handler)
        self.logger.addHandler(console_handler)

        return self.logger
if __name == '__main__':
    # 日志
    logger = Logging("V05")()   # 随便给一个名字都行
    # 后面就在需要的地方加日志就好
    displayValue = 3.14
    logger.info(f"扭矩扳手的读数:{displayValue}")  # 这既会在控制台输出,也会写到日志文件中

14. 元类

​ 首先,类也是对象,在ipython中,可直接==globals()==,结果就是一个字典,里面包括了内建的模块以及自己定义的全局变量、函数、类等,那么==globals()[“_builtin__”]==就是通过key得到这个内建模块,再就可以通过.__dict__来查看它的属性,即==globals()[“_builtin__”].__dict__==就会再得到一个字典,里面就有各种内建函数,那么也可以通过字典key的方式来调用print,对应的value就是print的函数引用,那么==globals()[“__builtin__”].__dict__[“print”](123456)==就会打印123456出来。

元类 —> 类 —> 实例对象 # 这三个的创建关系,Test1是一个类,那么Test1.__class__ 的结果就是 <class ‘type’>

使用==type==创建类: type还可以动态创建类,type可以接受一个类的描述作为参数,然后返回一个类,即: ==type(类名,由父类名称组成的元组(针对继承的情况们可以为空),包含类属性的字典)==,如下:

class Test1:
    num1 = 100
    num2 = 200

Test2 = type("Test2", (), {"num1": 100, "num2": 200})  # 后面这就是一个元类,因为没有继承,第二个参数就是空元组
# 这里相当于是起了名 Test2 指向 第一个参数 "Test2",当然可以不一样,那就没意义了啊,

aa = Test2()
print(aa.num1)   # 有类属性

# 元类的继承写法:
Test22 = type("Test22", (Test2, ), {})   # 属性可以是空字典

Tsip:

还可以元类type创建时加上方法(这里把方法也看做一个类属性就好了),提供提前定义函数的方式,如下:

​ 元类的用法是,在创建类时不用默认的type,而是用我的方法来创建,就可以在不改类代码的前提下,对类属性进行修改,有些函数装饰器的意思,(在视频:”.\就业班\06 mini-web框架v3.1\06-元类\03-通过type来创建复杂的类,元类应用demo.flv” 大概第19分钟的时候),大概也是这,一个原理:

上面 upper_attr 函数中都还可以改,第二个父类继承参数,可以自己加一些类,就实现了批量修改类的目的,所以像是前面说的私有属性,名字重整(就是把私有属性名字改了),就可以通过这种方式实现。可以上毕竟是一个函数实现,应当写一个类来代替这个函数,如下:

​ 要用自己的元类实现,Python3就是如上,写类时加参数==metaclass=你的实现==,pyton2是在类里面加一行属性==__metaclass__=你的实现==


ORM

定义: ORM是python编程语言后端web框架Django的核心思想,“Object Relational Mapping”,即对象-关系映射,简称ORM。

有些绕,大概了解吧,就不花时间了,它的实现在这里:(”.\就业班\06 mini-web框架v3.1\07-orm\02-orm-实现.flv”)

15. 单元测试

  1. 先写一个flask的服务 server.py

    from flask import Flask, request, jsonify
       
    app = Flask(__name__)
       
    @app.route("/login", methods=["POST"])  # 制宜设置了post才能访问
    def login():
        name = request.form.get("name")
        passwd = request.form.get("passwd")
       
        if not all([name, passwd]):
            data = dict(
                status=0,
                message="缺失登录的必要信息!"
            )
            return jsonify(data)
       
        if name != "sh" or passwd != "123456":
            data = dict(
                status=1,
                message="密码或用户名错误"
            )
        else:
            data = dict(
                status=2,
                message="登陆成功"
            )
        return jsonify(data)
       
    if __name__ == '__main__':
        app.run(debug=True)
    
  2. 重点单元测试: study.py

    import unittest
    import requests
       
    # 那这样的一个类就是测试案例
    class MyTestClass(unittest.TestCase):
        # 该方法会首先执行,相当于做测试前的准备工作
        def setUp(self) -> None:
            pass
       
        # 该方法会在测试代码执行完后执行(一般做测试完后新增数据的删除,如因为测试需要新增的测试数据库)
        def tearDown(self) -> None:
            pass
       
        # 测试代码,一定以 test_  开头,这些方法才会被测试
        def test_app_exists(self):
            # 模拟发送post请求
            data = requests.post("http://127.0.0.1:5000/login", 
                                 data={"name": "sh", "passwd": 123456})
            res = data.json()
            # assert res.get("status") == 2, "登陆失败"
            # # 这其实就是对断言进行了一个封装
            self.assertEqual(res.get("status"), 2, msg="登陆失败")  
       
        def test_other(self):
            data = requests.post("http://127.0.0.1:5000/login", 
                                 data={"name": "error_name", "passwd": 123456})
            res = data.json()
            self.assertIsNotNone(res.get("status"))
       
    if __name__ == '__main__':
        unittest.main()   # 注意这个启动方式
    

Tips:(重点是后面这个文件)