01-日志的全局配置
日志在 Python 中专门有一个库可以使用——logging
import logging
# 控制台输出日志
print("我是日志")
日志有五个级别:
logging.debug("我是debug")
logging.info("我是info")
logging.warning("我是warning")
logging.error("我是error")
logging.critical("我是critical")
out:
WARNING:root:我是warning
ERROR:root:我是error
CRITICAL:root:我是critical
root 是默认日志的名称,root 是一个对象。logging 就是在调用 root。
输出的时候会前面带上日志的级别和对象。
root 是默认日志的名称,只会输出 warning 级别以上的日志。
1.1 配置日志(修改配置项)
配置默认的 root 日志
| 配置项 | 意义 |
|---|---|
| format | 格式化输出 |
| level | 0、10、20、30、40、50 分别对应日志的五个级别。 直接使用常量 logging.DEBUG |
| Handler | 指定日志输出位置 |
| datefmt | 修改日期输出格式 |
| filename | 日志文件名称 |
import logging
logging.basicConfig(format="%(name)s - %(asctime)s - %(filename)s - %(lineno)s -%(message)s")
logging.debug("我是debug")
logging.info("我是info")
logging.warning("我是warning")
logging.error("我是error")
logging.critical("我是critical")
out:
root - 2019-08-07 14:26:04,058 - 01-日志的全局配置.py - 7 -我是warning
root - 2019-08-07 14:26:04,059 - 01-日志的全局配置.py - 8 -我是error
root - 2019-08-07 14:26:04,059 - 01-日志的全局配置.py - 9 -我是critical
1.2Formatter 参数
| 参数 | 含义 |
|---|---|
| %(message)s | 用户自定义要输出的信息 |
| %(asctime)s | 当前的日期时间 |
| %(name)s | logger 实例的名称 |
| %(module)s | 使用 logger 实例的模块名 |
| %(filename)s | 使用 logger 实例的模块的文件名 |
| %(funcName)s | 使用 logger 实例的函数名 |
| %(lineno)d | 使用 logger 实例的代码行号 |
| %(levelname)s | 日志级别名称 |
| %(levelno)s | 表示日志级别的数字形式 |
| %(threadName)s | 使用 logger 实例的线程名称(测试多线程时有用) |
| %(thread)d | 使用 logger 实例的线程号(测试多线程时有用) |
| %(process)d | 使用 logger 实例的进程号(测试多进程时有用) |
创建 Formatter:
formatter = logging.Formatter
('%(asctime)s - %(filename)s[line:%(lineno)d] - <%(threadName)s %(thread)d>' + '- <Process %(process)d> - %(levelname)s: %(message)s')
1.3Handler 参数
1.3.1 常用的 Handler
| 参数 | 意义 |
|---|---|
| logging.StreamHandler | 输出到控制台 |
| logging.FileHandler | 输出到指定的日志文件中 |
| logging.handlers.RotatingFileHandler | 也是输出到日志文件中,还可以指定日志文件的最大大小和副本数,当日志文件增长到设置的大小 后,会先将原日志文件 test.log 重命名,如 test.log.1,然后再创建一个 test.log 继续写入日志。如 果设置了副本数 N,则最多只能存在 N 个重命名的日志文件 |
| logging.handlers.TimedRotatingFileHandler | 按日期时间保存日志文件,如果设置了滚动周期,则只存在这个周期内的日志文件。比如,只保留 一周内的日志 |
| logging.handlers.SMTPHandler | 捕获到指定级别的日志后,给相应的邮箱发送邮件 |
1.3.2 创建 Handler
# 创建StreamHandler,输出日志到控制台
stream_handler = logging.StreamHandler()
1.3.2Handler 常用方法
设置日志的格式,调用 formatter
stream_handler.setFormatter(formatter)
设置日志级别
stream_handler.setLevel(logging.INFO)
1.4Logger 实例
如前面所述,直接使用 logging.basicConfig() 默认使用 root 这个 logger 实例。
basicConfig 默认输出是 warning 级别的。
我们也可以使用 logging.getLogger()创建一个自定义命令的 logger 实例:
import logging
# 1. 创建一个叫aiotest的logger实例,如果参数为空则返回root
logger = logging.getLogger('aiotest')
# 2. 设置总日志级别, 也可以给不同的handler设置不同的日志级别
logger.setLevel(logging.DEBUG)
# 3. 设置Formatter
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - <%(threadName)s %(thread)d>' + '- <Process %(process)d> - %(levelname)s: %(message)s')
# 4. 创建Handler
# 文件Handler
file_handler = logging.FileHandler("logger.log", encoding="utf8")
# 控制台输出Handler
stream_handler = logging.StreamHandler()
# 5. 给Handler设置属性
stream_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
stream_handler.setLevel(logging.DEBUG)
file_handler.setLevel(logging.WARNING)
# 6. 将Handler添加到logger实例上
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
# 输出日志
logger.debug("我是debug")
logger.info("我是info")
logger.warning("我是warning")
logger.error("我是error")
logger.critical("我是critical")
out:
2019-08-07 15:22:55,429 - 01-日志的全局配置.py[line:30] - <MainThread 15616>- <Process 15732> - DEBUG: 我是debug
2019-08-07 15:22:55,429 - 01-日志的全局配置.py[line:31] - <MainThread 15616>- <Process 15732> - INFO: 我是info
2019-08-07 15:22:55,430 - 01-日志的全局配置.py[line:32] - <MainThread 15616>- <Process 15732> - WARNING: 我是warning
2019-08-07 15:22:55,430 - 01-日志的全局配置.py[line:33] - <MainThread 15616>- <Process 15732> - ERROR: 我是error
2019-08-07 15:22:55,430 - 01-日志的全局配置.py[line:34] - <MainThread 15616>- <Process 15732> - CRITICAL: 我是critical
日志文件:
2019-08-07 15:23:37,297 - 01-日志的全局配置.py[line:32] - <MainThread 74852>- <Process 74856> - WARNING: 我是warning
2019-08-07 15:23:37,297 - 01-日志的全局配置.py[line:33] - <MainThread 74852>- <Process 74856> - ERROR: 我是error
2019-08-07 15:23:37,298 - 01-日志的全局配置.py[line:34] - <MainThread 74852>- <Process 74856> - CRITICAL: 我是critical
给 logger 设置最低的全局级别,优先级最高,最低的就是 logger.setLevel(logging.DEBUG)的级别,不会低于这个级别。
在其他地方使用自定义 logger,将上面的代码保存为 demo 文件。
from demo import logger
try:
print(1/0)
except:
logger.error("错误001")
1.4.1 配合 os 和 time 模块使用:
import os
import time
import logging
# 1. 创建logger实例,如果参数为空则返回 root logger
logger = logging.getLogger('aiotest')
# 设置总日志级别, 也可以给不同的handler设置不同的日志级别
logger.setLevel(logging.DEBUG)
# 2. 创建Handler, 输出日志到控制台和文件
# 控制台日志和日志文件使用同一个Formatter
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - <%(threadName)s %(thread)d>' + '- <Process %(process)d> - %(levelname)s: %(message)s' )
# 日志文件FileHandler
basedir = os.path.abspath(os.path.dirname(__file__))
log_dest = os.path.join(basedir, 'logs') # 日志文件所在目录
if not os.path.isdir(log_dest):
os.mkdir(log_dest)
filename = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime(time.time())) + '.log'
# 日志文件名,以当前时间命名
file_handler = logging.FileHandler(os.path.join(log_dest, filename), encoding='utf-8')
file_handler.setFormatter(formatter)
# 设置Formatter
file_handler.setLevel(logging.WARNING)
# 单独设置日志文件的日志级别
# 控制台日志StreamHandler
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
# stream_handler.setLevel(logging.INFO)
# 单独设置控制台日志的日志级别,注释掉则使用总日志 级别
# 3. 将handler添加到logger中
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
02-深拷贝和浅拷贝
Python 赋值操作或函数参数传递,传递的永远是对象引用(即内存地址),而不是对象内容。在 Python 中一切皆对象,对象又分为可变(mutable)和不可变(immutable)两种类型。对象拷贝是指在内存中创建新的对象,产生新的内存地址。当顶层对象和它的子元素对象全都是 immutable 不可变对象时,不存在被拷贝,因为没有产生新对象。浅拷贝(Shallow Copy),拷贝顶层对象,但不会拷贝内部的子元素对象。深拷贝(Deep Copy),递归拷贝顶层对象,以及它内部的子元素对象。
可变对象与不可变对象
Python 中一切皆对象,对象就像一个塑料盒子,里面装的是数据。对象有不同类型,例如布尔型和整型,类型决定了可以对它进行的操作。现实生活中的"陶器"会暗含一些信息(例如它可能很重且易碎,注意不要掉到地上)。 对象的类型还决定了它装着的数据是允许被修改的变量(可变的 mutable)还是不可被修改的常量(不可变的 immutable)。你可以把不可变对象想象成一个透明但封闭的盒子:你可以看到里面装的数据,但是无法改变它。类似地,可变对象就像一个开着口的盒子,你不仅可以看到里面的数据,还可以拿出来修改它,但你无法改变这个盒子本身,即你无法改变对象的类型。
- mutable:可变对象,如 List、Dict 和 Set
- immutable:不可变对象,如 Number、String、Tuple、Frozenset
注意:
Python 赋值操作或函数参数传递,传递的永远是对象引用(即内存地址),而不是对象内容。
In [1]: a = 1
In [2]: b = a
In [3]: id(a)
Out[3]: 9164864
In [4]: id(b)
Out[4]: 9164864
In [5]: b += 1
In [6]: a
Out[6]: 1
In [7]: b
Out[7]: 2
In [8]: id(a) # 对象引用a还是指向Number对象1
Out[8]: 9164864
In [9]: id(b) # 对象引用b指向了Number对象2
Out[9]: 9164896
Python 会缓存使用非常频繁的小整数-5 至 256 、 ISO/IEC 8859-1 单字符 、 只包含大小写英文字 母的字符串 ,以对其复用,不会创建新的对象:
1. 不会创建新对象 In [1]: a = 10
In [2]: b = 10
In [3]: id(a)
Out[3]: 9165152
In [4]: id(b)
Out[4]: 9165152
In [5]: a = '@'
In [6]: b = '@'
In [7]: id(a)
Out[7]: 139812844740424
In [8]: id(b)
Out[8]: 139812844740424
In [9]: a = 'HELLOWORLDhelloworld'
In [10]: b = 'HELLOWORLDhelloworld'
In [11]: id(a)
Out[11]: 139812785036792
In [12]: id(b)
Out[12]: 139812785036792
2. 会创建新的对象
In [1]: a = 1000
In [2]: b = 1000
In [3]: id(a)
Out[3]: 140528314730384
In [4]: id(b)
Out[4]: 140528314731824
In [5]: a = 'x*y'
In [6]: b = 'x*y'
In [7]: id(a)
Out[7]: 139897777405880
In [8]: id(b)
Out[8]: 139897777403808
In [9]: a = 'Hello World'
In [10]: b = 'Hello World'
In [11]: id(a)
Out[11]: 139897789146096
In [12]: id(b)
Out[12]: 139897789179568
copy 是浅拷贝 (只拷贝内存地址)
deepcopy 是深拷贝 (内容重新分配)
03-对象属性管理
3.1__dict__方法
__dict__方法可以获取类或者对象的所有属性和方法。
类.__dict__可以直接获取到类定义时所有的方法和属性
实例对象.__dict__可以直接获取到实例的所有的方法和属性,不能获取到类中的。
但是实例对象的方法指向了类对象的方法,所以实例对象能调用类方法。
(对象添加属性或方法不影响类)
对象.__dict__['key']可以直接获取到 value
不存在的 key 会报错 KeyError
class Person(object):
name = 'python'
age = 18
def __init__(self):
self.sex = "boy"
self.like = "papapa"
@staticmethod
def stat_func():
print('this is stat_func')
@classmethod
def class_func(cls):
print('class_func')
person = Person()
print('Person.__dict__: ', Person.__dict__)
print('person.__dict__: ', person.__dict__)
out:
Person.__dict__: {'__module__': '__main__', 'name': 'python', 'age': 18, '__init__': <function Person.__init__ at 0x000002C518993950>, 'stat_func': <staticmethod object at 0x000002C518996978>, 'class_func': <classmethod object at 0x000002C5189969B0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
person.__dict__: {'sex': 'boy', 'like': 'papapa'}
由此可见, 类的普通方法、类方法、静态方法、全局变量以及一些内置的属性都是放在类对 象 dict 里 而实例对象中存储了一些 self.xxx 的一些东西
3.2 继承
在类的继承中,子类有自己的 dict, 父类也有自己的 dict,子类的全局变量和方法放在子类的 dict 中, 父类的放在父类 dict 中。
3.3 动态语言限制属性的修改
现在我们终于明白了,动态语言与静态语言的不同
-
动态语言:可以在运行的过程中,修改代码
-
静态语言:编译时已经确定好代码,运行过程中不能修改
如果我们想要限制实例的属性怎么办?比如,只允许对 Person 实例添加 name 和 age 属性。
class Person:
__slots__ = ("name", "age")
def __init__(self,name,age):
self.name = name
self.age = age
p = Person("老王",20)
p.score = 100
out:
Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/test/app.py", line 8, in <module>
p.score = 100
AttributeError: 'Person' object has no attribute 'score'
使用
__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用
当你定义__slots__后,Python 就会为实例使用一种更加紧凑的内部表示。
实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典。所以__slots__是创建大量对象时节省内存的方法。
__slots__的副作用是作为一个封装工具来防止用户给实例增加新的属性。 尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。
3.4 一些方法
hasattr()函数用于判断对象是否包含对应的属性
getattr()函数用于返回一个对象属性值
setattr 函数,用于设置属性值,该属性必须存在
delattr 函数用于删除属性,delattr(x,'foobar)相当于 del x.foobar
04-__call__魔法方法
可以使得函数可以直接被调用
class Person(object):
def __call__(self, *args, **kwargs):
return "ToString()"
person = Person()
print(person())
out:
ToString()
05-闭包
闭包就是,内层函数调用了外层函数的方法或变量,并返回内层方法。
闭包会保存局部作用域的变量。
def outer(num):
num = num
def inner():
nonlocal num
num += 1
print(num)
return inner
func = outer(100)
func()
func()
func()
func()
out:
101
102
103
104
闭包的内部函数不可以使用外部循环的变量,或者会变化的变量
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())
out:
9
9
9
原因是,调用的时候,i 已经变为 3 了,def 定义的函数不会立即执行。
进行一些修改:
def count():
def f(j):
return lambda: j * j
fs = []
for i in range(1, 4):
fs.append(f(i))
# f(i)立刻被执行,因此i的当前值被传入闭包lambda: j * j
return fs
f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())
out:
1
4
9
f(i)立刻被执行,因此 i 的当前值被传入闭包 lambda: j * j,调用的时候只是在调用 lambda:j*j
也可以这么写:
def count():
def f(j):
def double():
return j*j
return double
fs = []
for i in range(1, 4):
fs.append(f(i))
return fs
06-装饰器
装饰器(decorator)接受一个 callable 对象 (可以是函数或者实现了 call 方法的类)作为参数,并返回一个 callable 对象 它经常用于有切面需求的场景,比如:插入日志、性能测试(函数执行时间统计)、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。 举个实例,假设你写好了 100 个 Flask 中的路由函数,现在要在访问这些路由函数前,先判断用户 是否有权限,你不可能去这 100 个路由函数中都添加一遍权限判断的代码(如果权限判断代码为 5 行,你得加 500 行)。那么,你可能想把权限认证的代码抽离为一个函数,然后去那 100 个路由函 数中调用这个权限认证函数,这样只要加 100 行。但是,根据开放封闭原则,对于已经实现的功能代码建议不能修改, 但可以扩展,因为可能你在这些路由函数中直接添加代码会导致原函数出 现问题,那么最佳实践是使用装饰器
def my_func():
print("step 2 : my_func")
def outer(my_func):
print("step 1 : outer")
def inner():
my_func()
print("step 3 : inner")
return inner
# 传入my_func的引用
# 方法名即变量名,是引用,也就是改变了my_func的指向。即可在不改变原有功能下,增加新功能
my_func = outer(my_func)
my_func()
out:
step 1 : outer
step 2 : my_func
step 3 : inner
传入 my_func 的引用,方法名即变量名,是引用,也就是改变了 my_func 的指向。即可在不改变原有功能下,增加新功能
使用 Python 语法糖@,
@也就是封装了一行语句:my_func = outer(my_func),将这行代码简化
def outer(my_func):
print("step 1 : outer")
def inner():
my_func()
print("step 3 : inner")
return inner
@outer
def my_func():
print("step 2 : my_func")
my_func()#等价于inner()
out:
step 1 : outer
step 2 : my_func
step 3 : inner
被装饰器修饰的代码块一定是在下面的,是原来的功能。
新的方法应当写在老方法的上面,然后在老方法用
@新方法名修饰,然后老代码无需修改,即可调用新的功能
6.1 装饰器的几种
对原函数而言
6.1.1 没有参数、没有返回值
同上述的例子
6.1.2 有参数、没有返回值
def outer(my_func):
print("step 1 : outer")
def inner(num):
my_func(num)
print("step 3 : inner")
return inner
@outer
def my_func(num):
print("step 2 : my_func 传入的参数",num)
my_func(100) #等价于inner(100)
out:
step 1 : outer
step 2 : my_func 传入的参数 100
step 3 : inner
6.1.3 没有参数、有返回值
def outer(my_func):
print("step 1 : outer")
def inner():
print("step 2 : 进入内部函数")
return my_func()
return inner
@outer
def my_func():
print("step 3 : my_func")
return "初始函数返回值"
print(my_func())
out:
step 1 : outer
step 2 : 进入内部函数
step 3 : my_func
初始函数返回值
6.1.4 有参数、有返回值
def outer(my_func):
print("step 1 : outer")
def inner(num):
print("step 2 : 进入内部函数")
return my_func(num)
return inner
@outer
def my_func(num):
print("step 3 : my_func,传入的参数:", num)
num += 1
return "加1之后:" + str(num)
print(my_func(num=100))
out:
step 1 : outer
step 2 : 进入内部函数
step 3 : my_func,传入的参数: 100
加1之后:101
6.2 万能装饰器
利用*args,*kwargs传参
需要注意的是,传入到 inner 内部调用的函数的时候,需要解包
即还是写成
*args,*kwargs
6.3 多装饰器
def outer1(func):
print("outer1")
def inner():
print("inner1")
return func()
return inner
def outer2(func):
print("outer2")
def inner():
print("inner2")
return func()
return inner
@outer1
@outer2
def func():
print("func")
func()
out:
outer2
outer1
inner1
inner2
func
6.4 带参数的装饰器
def outer(str):
print("outer")
def outer1(func):
print("outer1")
def inner():
print(str)
print("inner")
return func()
return inner
return outer1
@outer("哈哈哈")
def func():
print("func")
func()
out:
outer
outer1 # 到此处都是装饰的时候生成的,下面才是调用的时候生成的
哈哈哈
inner
func
@函数名是装饰器
@函数名()是在调用函数后装饰
则在此处,outer()应当返回一个函数引用。
三层嵌套的函数可以为装饰器接受参数。
二层嵌套的函数不可以接受参数。
6.5 还原函数名称
def outer(func):
print("outer")
def inner():
print("inner")
print(func.__name__)
return func()
return inner
@outer
def func():
print("func in")
func() # 就是innner()
# 获取当前函数的名称
print(func.__name__)
out:
outer
inner
func
func in
inner
被装饰器修饰过的方法,打印的方法名称都是 inner,在打印日志的时候就无法追踪了。
在此需要还原方法名称
使用 Python 的@wraps帮助还原被装饰器修饰的函数名
from functools import wraps
def outer(func):
print("outer")
# 将方法名传入wraps
@wraps(func)
def inner():
print("inner")
print(func.__name__)
return func()
return inner
@outer
def func():
print("func in")
@outer
def func1():
print("func1 in")
func() # 就是innner()
func1()
# 获取当前函数的名称
print(func.__name__)
print(func1.__name__)
out:
outer
outer
inner
func
func in
inner
func1
func1 in
func
func1
用@wraps(函数名)修饰 inner 的内部方法。即可还原函数名
6.6 类装饰器
用类装饰的函数
class A:
# outer
def __init__(self,func):
print("开始装饰")
self.func = func
# inner
def __call__(self, *args, **kwargs):
print("类装饰器")
return self.func()
@A # func = A(func) 创建一个对象,func-->A类的实例对象。
def func():
print("this is func")
func()
out:
开始装饰
类装饰器
this is func
7-案例
1.函数执行时间的统计
import time
from functools import wraps
def outer(func):
@wraps(func)
def inner(num):
start = time.time()
ret = func(num)
end = time.time()
print("执行时间为:", end-start)
return ret
return inner
@outer
def func(num):
ret = 0
for i in range(num+1):
ret += i
return ret
print(func(100000000))
out:
执行时间为: 7.979628324508667
5000000050000000
2.日志记录
首先是之前的日志模块,这里重新贴一遍,略作修改
import logging
# 1. 创建一个叫aiotest的logger实例,如果参数为空则返回root
logger = logging.getLogger('aiotest')
# 2. 设置总日志级别, 也可以给不同的handler设置不同的日志级别
logger.setLevel(logging.DEBUG)
# 3. 设置Formatter
formatter = logging.Formatter('%(asctime)s - %(filename)s [line:%(lineno)d] - %(func)s - <%(threadName)s %(thread)d>' +
' - <Process %(process)d> - %(levelname)s: %(message)s')
# 4. 创建Handler
# 文件Handler
file_handler = logging.FileHandler("logger.log", encoding="utf8")
# 控制台输出Handler
stream_handler = logging.StreamHandler()
# 5. 给Handler设置属性
stream_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
stream_handler.setLevel(logging.DEBUG)
file_handler.setLevel(logging.WARNING)
# 6. 将Handler添加到logger实例上
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
因为%(funcName)s永远获取的是调用函数的 inner
的名字,所以这里传入新的自定义变量,将正确的函数名通过@wraps(函数引用)来传入,并通过.__name__方法获取正确的函数名称。
from functools import wraps
from myLog import logger
def outer(func):
@wraps(func)
def inner(*args, **kwargs):
try:
logger.info("当前执行方法为:" + func.__name__, extra={'func': func.__name__})
return func(*args, **kwargs)
except Exception as e:
logger.error(e.__repr__(), extra={'func': func.__name__})
return inner
@outer
def func1(*args, **kwargs):
return 1 / 1
@outer
def func2(*args, **kwargs):
return 1 / 0
print(func1())
print(func2())
out:
2019-08-08 23:37:10,982 - 02-对象.py [line:8] - func1 - <MainThread 14556> - <Process 18380> - INFO: 当前执行方法为:func1
1.0
2019-08-08 23:37:10,983 - 02-对象.py [line:8] - func2 - <MainThread 14556> - <Process 18380> - INFO: 当前执行方法为:func2
None
2019-08-08 23:37:10,983 - 02-对象.py [line:11] - func2 - <MainThread 14556> - <Process 18380> - ERROR: ZeroDivisionError('division by zero',)
日志文件中的记录:
2019-08-08 23:41:24,756 - 02-对象.py [line:12] - func2 - <MainThread 16856> - <Process 8564> - ERROR: ZeroDivisionError('division by zero',)
疑问:
执行顺序问题。多次执行打印的顺序不一样?