名字重整:其实就是__name这样的名字解释器在前面加了_类名,所以直接访问不到,如下:
Python是可以调用其它语言的,Python使用多线程,若是其中一个线程是调用的C\C++的函数(视频“02-GIL-2.flv”有说明),它们是不受GIL锁的限制的,所以除了这个方法,去掉GIL锁限制的方法还有,不用Cpython解释器,用其它的解释器。
GIL锁与互斥锁的区别:
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))
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
一个使用场景就是,服务器运行起来了,不能轻易停下来,如果其中一个模块的代码进行了修改,在不停服务的情况下进行这个模块的热加载:
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结果是不一样的。
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调用结束")
子类中调用父类的方法有三种:
第一种:第12行:Parent.__init__(self, name)
这个写法,就是直接写父类名称,调用==指定==父类的___init__
方法,这种比较直观,但是用的不多,就是以为如上的==Grandson==的多继承,这会导致多次调用==Parent==中的方法,然而我们只想调用一次,如果是套接字这种,开销浪费就很大,c++中这种菱形继承的处理方式是虚继承;
第二种:super().__init__()
,大概示意是:(注意写法,里面也没self了)
class Parent(object):
# 为避免多继承报错,使用不定长参数来接收参数
def __init__(self, name, *args, **kwargs):
print("parent的init开始调用")
self.name = name
print("parent的init调用结束")
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs):
print("son1的init开始调用")
self.age = age
super().__init__(name, *args, **kwargs)
print("Son1的init调用结束")
class Son2(Parent):
# 为避免多继承报错,使用不定长参数来接收参数
def __init__(self, name, gender, *args, **kwargs):
print("son2的init开始调用")
self.gender = gender
super().__init__(name, *args, **kwargs)
print("Son2的init调用结束")
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print("Grandson的init开始调用")
super().__init__(name, age, gender) # 只用一行就调用了全部父类方法
print("Grandson的init调用结束")
print(Grandson.__mro__)
MRO
的顺序决定==,print(Grandson.__mro__)
就会看到,然后发现Parent类就只调用了一次(推荐这种嘛)
MRO的决定顺序是由Python解释器中的C3
算法决定的第三种,还是上面第26行:super(Grandson, self).__init__(name, age, gender)
,这样写就跟上面26行就是一个意思了,26行那相当于默认是填的Grandson;
但区别是,这里面还可以填别的,好比==super(Son2, self).__init__(name, age, gender)==,那么这种就是会根据类Grandson的MRO表查询,从Son2开始,执行及其往后的类,前面的就会跳过。
传递不定长参数的讲解:==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那样传参
类方法(@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("静态方法")
解读:
类方法比较直接的就是能比较简单的修改类属性,而实例对象不是那么好修改类属性:
a = Foo("zhans")
a.country = "daa" # 这只是相当于给自己实例这个属性赋了一个值
print(Foo.country) # 结果还是中国(实例对象不好改类属性)
# 但是可以下面这样改
a.__class__.country = "改了吗?"
print(Foo.country) # 这样就改了,加 .__class__ 就是代表修改的类属性
总之:
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去调用
python的各种魔术方法看这里。(页面最下面有一个表格)
魔法方法:
__init__ # 初始化方法
__del__ # 一般无需定义,垃圾回收时会自动回收
__call__ # 实例对象() 加()时自动调用
__dict__ # 后去类或对象中的所有属性: t.__dict__ T.__dict__
__doc__ # 获取描述文档
__module__ # 表示当前操作的对象在哪个模块
__class__ # 表示当前操作的对象的类是什么(现在好像不是魔法方法了,定义里是property属性)
class T:
"""
这是一个描述信息,一般用三引号
"""
def __str__(self):
return "hello world!"
pass
print(T.__doc__) # 获取描述文档
print(help(T)) # 还可以这样获取描述
t = T()
print(t.__class__) # 表示当前操作的对象的类是什么,结果 <class '__main__.T'>
print(t.__module__) # 表示当前操作的对象在哪个模块,
# - 当前模块的类,结果就是 __main__
# - 当从其它模块导进来的类,输出就是其它模块的名字
__str__ 一个类中定义了此方法,那么在print(t)时,默认输出该方法的返回值 接着上面的代码:还可以 print(“还可以这样用:%s” % t)
__getitem__、__setitem__、__delitem__ ==# 用于索引操作,如字典,分别表示获取,设置,删除数据==
class FOO:
def __getitem__(self, key):
print("__getitem__", key)
def __setitem__(self, key, value):
print("__setitem__", key, value)
def __delitem__(self, key):
print("__delitem__", key)
obj = FOO() # obj并不是一个字典,但它里面实现了这三个魔法方法,就可以像字典这样操作
result = obj["k12"]
obj["k13"] = "world!" # 它会这样把对应的值传到相应位置
del obj["k14"]
# 下面这用于切片操作,如列表(这是Python2中的用法了,Python3中是把这整合到了上面的getitem三个中)
" __getslice__ __setslice __delslice__ "
定义: ==任何实现了__enter__()和__exit__()方法的对象都可称之为上下文管理器==。上下文管理器可以使用with关键字,我们自己也可以实现一个上下文管理器,一般有如下两种形式:
==类==:(其实就是魔法方法)
类里一定要实现__enter__
和__exit__
两个方法;
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)
这个主要是在学习WSGI-mini-web框架时学到的,比如写了很多个web框架,然后用命令行传参数决定使用哪个web框架的话,那就没办法事先导入,如 from my_dynamic import application ,那么就可以使用这来动态导包:
Tips:可能单单这么看,不是很方便理解,如果后续要用到,可以去看(”就业班\06 mini-web框架v3.1\01-WSGI-mini-web框架\10-给程序传递参数、添加web服务器的配置文件、添加shell功能.flv” 这个视频中大概第18分钟左右的讲解)
更对类似 getattr hasattr 属性的内容可以看这里。
__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
更多的使用可以看这里。
==闭包==:
思考:函数、匿名函数、闭包、对象当做实参时,有什么区别?
闭包的简单例子:求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))
解读:
==装饰器==:
代码要遵循==开放封闭==原则,即写好的代码尽量封闭,不要去改了,但是可以开放增加功能。
手动实现简单装饰器:即==装饰器原理==(重要)
def set_func(a_func):
print("开始装饰了-------")
def call_func():
print("这是权限验证1****")
a_func()
print("可以在不修改源函数的情况下,在其前面或者后面执行加东西,但不能中间加")
return call_func
@set_func # 这行就等于下面原理里的:my_func = set_func(my_func)
def my_func():
print("这是最开始的功能函数")
my_func() # 调用
"""
这是装饰器的原理:
res = set_func(my_func) # 把set_func这个函数结果(一个函数对象)返回给 res
res() # 然后这个调用就会执行
所以:
# 因为python中同名会覆盖
my_func = set_func(my_func) # 括号里的 my_func 函数还是上面单独定义的my_func
# 赋值后,这时 my_func 就是指向了 set_func 的返回值(也是一个函数引用),且它把前面单独定义的my_func覆盖了
my_func() # 这个结果就不会是执行单独定义的my_func函数的结果,(而这也就是装饰器的实现原理)
"""
特别注意:遇到装饰器,它就会开始执行,而并不是要等到调用时才执行:
不定长参数的函数装饰器:
def set_func(a_func):
def call_func(*args, **kwargs): # 这里就固定写法吧,这样多少个参数都能处理
print("加的权限验证代码!")
a_func(*args, **kwargs) # 下面这样原封不动的传进去(这其实就是拆包)
return call_func
@set_func # 这行就等于下面原理里的:my_func = set_func(my_func)
def my_func(num, *args, **kwargs):
print("这是最开始的功能函数 %d" % num)
print("有多的参数嘛:", args)
print("多个字典参数:", kwargs)
my_func(100)
my_func(100, 20)
my_func(100, 20, age=25)
有返回值的函数的装饰器:就借用上面这个代码
def set_func(a_func):
def call_func(*args, **kwargs):
print("加的权限验证代码!")
# 这里加个return就好了,怕万一被装饰的函数有返回值,这就是是通用的了
return a_func(*args, **kwargs) # 直接加个return就好了
return call_func
解读:
多个装饰器装饰一个函数:
def set_func(a_func):
print("这是加权限的装饰器") # (2)
def call_func(*args, **kwargs):
print("加的权限验证代码!") # (3)
return a_func(*args, **kwargs)
return call_func
def log_func(b_func):
print("这是加日志的装饰器") # (1)
def call_log_func(*args, **kwargs):
print("这是记录日志的代码") # (4)
return b_func(*args, **kwargs)
return call_log_func
@set_func
@log_func
def my_func(num, *args, **kwargs):
print("这是最开始的功能函数 %d" % num) # (5)
my_func(123)
Tips:注意这个执行的顺序啊,原理简单来说:
但是这个顺序啊,还是要注意:比如下面这个例子(这种还是用的少,知晓就好了)
def set_h1(func):
def local_get_str(*args, **kwargs):
return "<h1>" + func(*args, **kwargs) + "</h1>"
return local_get_str
def set_p(func):
def local_get_str(*args, **kwargs):
return "<p>" + func(*args, **kwargs) + "</p>"
return local_get_str
@set_h1
@set_p
def get_str():
return "hello world!"
print(get_str()) # <h1><p>hello world!</p></h1>
先执行的h1,但是第3行还要先去调用set_P()函数中的东西,所有p标签在里面,h1标签在外面,也确实是h1先执行的。
==类做装饰器==: 类也是可以做装饰器的,原理差不多一致:
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) # 就可以把这个函数属性打印出来
下面是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")
解读:(重要)
level:这个等级就代表了 logging.INFO及其以上的才会被记录;若不给这个,默认等级为 logging.WARNING
format:这参数不是必须的,但是这样会更加直观,定位了时间,错误位置,错误内容设置。可再自定义格式。
对于%(levelname)s
这种东西,是logging模块内置的,更多的看最后的表;
%(asctime)s
默认显示使用ISO8601
格式,可以提供datefmt
参数进行更深的自定义:
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')
# 结果如下
12/12/2010 11:46:36 AM is when this event was logged.
datefmt
参数的定制和time模块的time.strftime()
一样!
==(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")
还可以通过添加Filter过滤器来完成比日志级别更负责的过滤,不会了,用得少,放这里吧。
再写一个通过外部传参来决定日志等级的,即:python 123.py ERROR # 或者其它等级
import logging
import sys
level = sys.argv[1] # 获得日志等级
numeric_level = getattr(logging, level)
logging.basicConfig(level=numeric_level) # 这样就动态传参决定等级
注意点:(多文件的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:(以上的注释都写很清楚了,这再来强调一下)
==配置中日志文件若给的是路径,则一定要把路径提前建立好,不然会直接报错==;
像root中总的权限应该是最低的,如DEBUG,然后再在具体handlers中,根据各个不同的需求,加进一步的设置更紧的权限,如WARNING;
==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}") # 这既会在控制台输出,也会写到日志文件中
首先,类也是对象,在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是python编程语言后端web框架Django的核心思想,“Object Relational Mapping”,即对象-关系映射,简称ORM。
有些绕,大概了解吧,就不花时间了,它的实现在这里:(”.\就业班\06 mini-web框架v3.1\07-orm\02-orm-实现.flv”)
先写一个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)
重点单元测试: 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:(重点是后面这个文件)