Day 27:Python小知识+logging 日志管理模块

列表与 * 操作(浅拷贝)

深浅拷贝的操作还可以看:https://www.cnblogs.com/PiaYie/p/13722263.html#_label1

a = [[]]*7 # [[], [], [], [], [], [], []]
id(a[0]) == id(a[1]) # True

由 * 操作复制出来的list,内存中的标识符是相等的,也就是说仅仅是浅复制,

在这种场景下,希望实现 id[0]、id[1] 不相等,修改 a[1] 不会影响 a[0]。

不使用 * 来生成其他列表,改用列表生成式即可:

b = [[] for _ in range(7)] # [[], [], [], [], [], [], []]
id(b[0]) == id(b[1]) # False

删除列表元素

复制代码
# 删除列表中的某个元素 # del_item_f_list([],del)
def del_item_from_list(lst:list,e):
    for i in lst:
        if i == e:
            lst.remove(i)
    return lst


del_item_from_list([1,3,3,3,5],3)

output:
[1, 3, 5]
复制代码

可以看到,3并没有完全被remove,是因为在remove第一个三的之后,索引还在往前,但是remove操作又把第一个三之后的元素一起往前移动了一格.

解决办法:找到被删除元素后,删除,同时下次遍历索引不加一;若未找到,遍历索引加一。

复制代码
# 删除列表中的某个元素 # del_item_f_list([],del)
def del_item_from_list(lst:list,e):
    i = 0
    while i < len(lst):
        if lst[i] == e:
            lst.remove(lst[i])
        else:
            i += 1
    return lst

del_item_from_list([1,3,3,3,3,5],3)

output:
[1, 5]
复制代码

函数默认参数为空

Python 函数的参数可设为默认值。如果一个默认参数类型为 list,默认值为设置为 []。

这种默认赋值,会有问题吗?

复制代码
# 默认参数为空
# 函数功能,把volume的内容替换为步长加初始值 如果为空则返回空list
def delta_val(val, volume=[]):
    if volume is None:
        volume = []
    size = len(volume)
    for i in range(size):
        volume[i] = i + val
    return volume

ret = delta_val(10)# []
ret.append(1) # [1]
ret.append(2) # [1,2]
ret = delta_val(10)# [10,11]  
复制代码

问题出在为什么第二次调用ret = delta_val(10)的时候是返回[10,11]???!

调用函数 delta_val 时,默认参数 volume 取值为默认值时,并且 volume 作为函数的返回值。再在函数外面做一些操作,再次按照默认值调用,并返回。整个过程,默认参数 volume 的 id 始终未变。

所以第二次调用时,volumn的取值是[1,2]而不是[].

为了避免这个问题,一是希望不要用默认参数返回,接受处理再调用,二是函数的默认参数不要设置为[],而是设置为None.

复制代码
def delta_val(val, volume= None):
    if volume is None:
        volume = []
    size = len(volume)
    for i in range(size):
        volume[i] = i + val
    return volume

ret = delta_val(10)
ret.append(1)
ret.append(2)
ret = delta_val(10)
ret

output:
[]
复制代码

None对象,具有唯一、不变的标识号(当前环境下)

{} 和 ()

之前一直疑惑为什么经常看到(1.1,)这种跟了一个逗号的情况:

() 在python中用来表征元组,

mytuple = (1.1,1.2)
print(type(mytuple))

output:
<class 'tuple'>

然🦢,

a = (1.1)
print(type(a))

output:
<class 'float'>

创建元组的时候,如果只有一个对象,那么仅有一个()时,python会把整个对象去括号处理,所以需要在后面加一逗号,这样对象才会被python解释成元组

这个问题经常在函数传参的时候体现,如果一不注意,就会改变参数类型

栗子:

复制代码
def fix_points(pts):
    for i in range(len(pts)):
        t = pts[i]
        if isinstance(t,tuple):
            t = t if len(t) == 2 else (t[0],0.0)
            pts[i] = t
        else:
            raise TypeError('pts 的元素类型要求为元组')
    return pts

fix_points([(1.0,3.0),(2.0),(5.0,4.0)])

output:
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-27-f57f6c888e07> in <module>()
      9     return pts
     10 
---> 11 fix_points([(1.0,3.0),(2.0),(5.0,4.0)])

<ipython-input-27-f57f6c888e07> in fix_points(pts)
      6             pts[i] = t
      7         else:
----> 8             raise TypeError('pts 的元素类型要求为元组')
      9     return pts
     10 

TypeError: pts 的元素类型要求为元组
复制代码

正确的调用是在第二个参数(2.0)后面加上逗号(2.0,)

  • 还要注意的是集合和字典,他们都用{},创建空集合和空字典:
复制代码
# 创建空字典
mydict = {}
print(type(mydict))

# 创建空集合
myset = set()
print(type(myset))


output:
<class 'dict'>
<class 'set'>
复制代码

解包

Python 中,支持多值赋值给多变量的操作。

弄清计算顺序是很重要的,多值赋值,(在基础不变的情况下)先计算出等号右侧的所有变量值后,再赋值给等号左侧变量。

# 多值赋值
a, b = 5, 7
a, b = b+7, a*2+b
print(a,b)

output:
14 17

多值赋值,是一种解包(unpack)操作。

那么打包是由何而来?等号右侧的多个变量,会被打包(pack)为一个可迭代对象。

这样说有点迷糊,那么只要只要可以用 = 操作方便地赋值。

比如说,getvalue 函数返回一个 list,如下:

def getvalue():
    result = [1234568,'xiaoming','address','telephone',['','','...']]
    return result 

但是我们只要前两项,而不关心其他的返回。那么只需要把其他不想要的项放到 others 变量中,并在前加一个 *,如下所示:

复制代码
id, name,*others = getvalue()
print(id)
print(name)
print(others)

output:
1
xiaoming
['address', 'telephone', ['', '', '...']] # 把之后的打成了一个包,还是可迭代啊
复制代码
  • 如果不用*,那么参数个数就会不匹配,报ValueError错误。
  • *others 会被单独解析为一个 list

访问控制

 Python 是一门动态语言,支持属性的动态添加和删除。

而 Python 面向对象编程(OOP)中,提供很多双划线开头和结尾的函数,它们是系统内置方法,被称为魔法方法。

  • __getattr__ 和 __setattr__ 是关于控制属性访问的方法。(新知识)

如下,访问类不存在属性时,程序默认会抛出 AttributeError 异常。

复制代码
# 访问控制
class Student():
    def __init__(self,idt,name):
        self.id = idt
        self.name = name

piayie = Student(1234568,'piayie')
print(piayie.age )

output:
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-45-1ad34eedc7a4> in <module>()
----> 1 print(piayie.age )

AttributeError: 'Student' object has no attribute 'age'
复制代码

上面是默认访问某种不存在属性时的返回,现在想要自己定义,那么就重写 __getattr__ 方法,会定义如果不存在某种属性时的行为。

复制代码
class Student():
    def __init__(self, idt, name):
        self.id = idt
        self.name = name

    def __getattr__(self, prop_name):
        print('property %s not existed, would be set to None automatically' %
                  (prop_name,))
        self.prop_name = None

piayie = Student(1234568,'piayie')
print(piayie.age )


output:
property age not existed, would be set to None automatically
None
复制代码

__setattr__,不管属性是否存在,属性赋值前都会调用此函数。(默认的__setattr__好像是啥都不做)

复制代码
class Student():
    def __init__(self, idt, name):
        self.id = idt
        self.name = name

    def __getattr__(self, prop_name):
        print('property %s not existed, would be set to None automatically' %
                  (prop_name,))
        self.prop_name = None
        
    def __setattr__(self,prop_name,val):
        print('%s would be set ro %s'%(prop_name,str(val)))  

piayie = Student(1234568,'piayie')
print(piayie.age )

output:
id would be set ro 1234568
name would be set ro piayie
property age not existed, would be set to None automatically
prop_name would be set ro None
None
复制代码

但是,这里有一个坑,如果在 __setattr__ 方法中再做属性赋值,那么就会陷入循环。。。

师傅,使不得!

谨记,不要在 __setattr__ 方法中再做属性赋值。

中括号访问

我们经常使用中括号加索引的方法[index]来访问对象某个元素的值,有没有想过为什么可以这样?没有。

其实是对象正确定义了魔法方法 __getitem__,就能实现 [index] 功能。

复制代码
class Table(object):
    def __init__(self,df:dict):
        self.df = df
    def __getitem__(self,column_name):
        return self.df[column_name]

t = Table({'ids':list(range(5)),'names':'piayie ori atta bawbaw'.split()})
print(t['names'])
print(t['ids'])

output:
['piayie', 'ori', 'atta', 'bawbaw']
[0, 1, 2, 3, 4]
复制代码

鸭子类型

复制代码
# 鸭子类型
class Plane():
    def run(self):
        print('plane is flying...')
    
        
class Clock():
    def run(self):
        print('clock is rotating...')

def using_run(duck):
    print(duck.run())
复制代码

Plane和Clock都有方法run, 不像其他静态语言中duck.run(),函数体内使用此类型的方法或属性时,参数类型就必须为此类型或子类。

在python中不用,个个都是大哥

因都有 run 方法,Python 认为它们看起来就是 duck 类型,因此,Plane 对象和 Clock 对象就被看作 duck 类型。

元类type

Python 界的领袖 Tim Peters 说过:“元类就是深度的魔法,99% 的用户应该根本不必为此操心。”

 

 

 使用type创建的类和使用class 关键字创建的Student 类一模一样。

对象序列化

对象序列化,是指将内存中的对象转化为可存储或传输的过程。很多场景,直接一个类对象,传输不方便。但是,当对象序列化后,就会更加方便,因为约定俗成的,接口间的调用或者发起的 Web 请求,一般使用 JSON 串传输(JSON模块)。

复制代码
class Student():
    def __init__(self,**args):
        self.ids = args['ids']
        self.name = args['name']
        self.address = args['address']

# 创建两个实例
dabai = Student(ids = 111,name = 'dabai',address = '北京')
liuedan = Student(ids = 2222,name = 'liuedan',address = '南京')

import json

with open('json.txt','w') as f:
    json.dump([dabai,liuedan], f, default=lambda obj: obj.__dict__, ensure_ascii=False, indent=2, sort_keys=True)
复制代码

生成的json格式的文件为:

复制代码
[
  {
    "address": "北京",
    "ids": 111,
    "name": "dabai"
  },
  {
    "address": "南京",
    "ids": 2222,
    "name": "liuedan"
  }
]
json.txt
复制代码
  • 中括号 + 字典(每一个实例)
  • 双引号
  • 写法,按上面的生成写法就挺好的

logging日志模块的使用

-------------------------------------------------------随便说说-------------------------------------------------------

说起来,因为只学代码,也没有真正接受过上线的项目,对于日志的接触少之又少。

调试代码由最开始的print方法

到后面在IDE上可以直接看变量或是什么

不得不说都是好方法。

但是当程序上线后一般运行在linux服务器上,调试并不是那么容易。

一般的解决方案,在代码中想 print 的信息,也要写入到日志文件中,在磁盘上保存起来。此时,遇到问题后,找到并分析对应的日志文件就行,这种解决问题的方法更可取,效率也会更高。

那也不能一直写日志啊,总有一天磁盘会满,文件太大也没有看的欲望,

但是只要有一套合理的管理体系,涉及方方面面的考量,那么就会是强大的工具。

像大名鼎鼎的、适用于 Java 开发的 log4j,便是一套设计优秀的日志管理包。

Python 中,也有一个模块 logging,也能做到高效的日志管理。

-------------------------------------------------------华丽的分割线-------------------------------------------------------

 logging 库,和它的四大组件:记录器、处理器、过滤器和格式化器。

logging 模块能按照指定周期切分日志文件,下面是一个基本的日志类,同时将日志显示在控制台和写入文件中,同时按天为周期  切分  日志文件。

复制代码
import logging
from logging import handlers


class Logger(object):
    kv = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'crit': logging.CRITICAL
    }  # 日志级别关系映射

    def __init__(self, filename, level='info', when='D', backCount=3, fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)
        format_str = logging.Formatter(fmt)  # 设置日志格式
        self.logger.setLevel(self.kv.get(level))  # 设置日志级别
        sh = logging.StreamHandler()  # 往屏幕上输出
        sh.setFormatter(format_str)  # 设置屏幕上显示的格式
        th = handlers.TimedRotatingFileHandler(
            filename=filename, when=when, backupCount=backCount, encoding='utf-8')
        th.setFormatter(format_str)  # 设置文件里写入的格式
        self.logger.addHandler(sh)  # 把对象加到 logger 里
        self.logger.addHandler(th)
        
复制代码

实例化Logger为log对象,日志级别为 debug 及按以上设置写入日志文件:

log = Logger('all.log', level='debug').logger

决定往这个日志文件(log对象)中写入什么:

复制代码
class Student:
    def __init__(self, id, name):
        self.id = id
        self.name = name
        log.info('学生 id: %s, name: %s' % (str(id), str(name)))

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, score):
        if isinstance(score, int):
            self.__score = score
            log.info('%s得分:%d' % (self.name, self.score))
        else:
            log.error('学生分数类型为 %s,不是整型' % (str(type(score))))
            raise TypeError('学生分数类型为 %s,不是整型' % (str(type(score))))

xiaoming = Student(10010, 'xiaoming')
xiaoming.score = 88
xiaohong = Student('001', 'xiaohong')
xiaohong.score = 90.6

output:
2021-07-19 13:17:56,553 - <ipython-input-73-b70ed4c5ceeb>[line:5] - INFO: 学生 id: 10010, name: xiaoming
2021-07-19 13:17:56,567 - <ipython-input-73-b70ed4c5ceeb>[line:15] - INFO: xiaoming得分:88
2021-07-19 13:17:56,568 - <ipython-input-73-b70ed4c5ceeb>[line:5] - INFO: 学生 id: 001, name: xiaohong
2021-07-19 13:17:56,569 - <ipython-input-73-b70ed4c5ceeb>[line:17] - ERROR: 学生分数类型为 <class 'float'>,不是整型
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-77-d5cb24f28de5> in <module>()
      2 xiaoming.score = 88
      3 xiaohong = Student('001', 'xiaohong')
----> 4 xiaohong.score = 90.6

<ipython-input-73-b70ed4c5ceeb> in score(self, score)
     16         else:
     17             log.error('学生分数类型为 %s,不是整型' % (str(type(score))))
---> 18             raise TypeError('学生分数类型为 %s,不是整型' % (str(type(score))))

TypeError: 学生分数类型为 <class 'float'>,不是整型
复制代码

日志文件:

复制代码
2021-07-19 13:22:50,168 - <ipython-input-81-b70ed4c5ceeb>[line:5] - INFO: 学生 id: 10010, name: xiaoming
2021-07-19 13:22:50,185 - <ipython-input-81-b70ed4c5ceeb>[line:15] - INFO: xiaoming得分:88
2021-07-19 13:22:50,186 - <ipython-input-81-b70ed4c5ceeb>[line:5] - INFO: 学生 id: 001, name: xiaohong
2021-07-19 13:22:50,187 - <ipython-input-81-b70ed4c5ceeb>[line:17] - ERROR: 学生分数类型为 <class 'float'>,不是整型
all.log
复制代码

大概记住这个过程即可,具体的使用以后再详细学习。

  • 设置Logger -> 生成log对象 -> 往log里面写入内容
posted @   PiaYie  阅读(70)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示