目录
4. 函数式编程 Functional Programming
5.4.4 LoggerAdapter —— 对标准logger的一个扩展
5.5.1 def basicConfig(**kwargs)
0. Python的一些考点
参考链接:https://www.nowcoder.com/discuss/117365
- 装饰器(什么是AOP/面向切面编程)、
- 迭代器与生成器的区别什么
- Python代码执行原理
- Python的int是怎么实现的
- 解释Python的对象
- 如何自己实现一个字典
- 什么是GIL、为什么要加GIL、如何理解Python多线程
- 什么是协程
- Python的IO多路复用是怎么实现的
- 什么是上下文管理器
- 你知道右加么(__radd__)
- 什么是闭包
- python中一般的类都继承object,那object的父类是什么(type)
- 谈谈元类、元类的应用
- 用Python写一个单例模式
- 谈谈super原理
- 什么是多重继承
- 浅复制和深复制有什么区别
1. 基础
- if条件判断: if判断条件还可以简写,比如写:
if x:
print('True')
# 只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False。
- 循环:
# 1. For x in …
# 2. while
# 3. Continue:可以通过continue语句,跳过当前的这次循环,直接开始下一次循环。
# 4. break语句,提前推出循环
- List: 数组
- Tuple:
tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!
- Dict: dictionary, 在其他语言中也称为map,使用key-value
注意:dic的key是不可变对象;通过key计算位置的算法成为Hash算法
# e.g 定义一个dict
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
# e.g 判断key存不存在:
>>> 'Thomas' in d
False
# e.g get方法
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
- Set:是一组key的集合,但不存储value;key不能重复。方法有:add(key) ; remove(key)
注意:set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。试试把list放入set,看看是否会报错。
- string:不可变对象
2. 函数
- help(function_name)查询函数帮助信息
- abs(),max()
- 数据类型转换int(),bool(),str(),float()
- 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1
-
2.1 定义函数
- 空函数 pass 如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass让代码能运行起来。
pass还可以用在其他语句里,比如:
if age >= 18:
pass
缺少了pass,代码运行就会有语法错误:(1)参数类型检查:isinstance() (2)返回多个值:实际上返回的是一个tuple
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
- 函数参数
- 默认参数 : 注意:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
- 可变参数:
-
>>> nums = [1, 2, 3] >>> calc(*nums) 14
-
- 关键字参数
-
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw)
函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
- 命名关键字参数:如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
-
def person(name, age, *, city, job): print(name, age, city, job)
- 和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。 调用方式如下:
-
>>> person('Jack', 24, city='Beijing', job='Engineer') Jack 24 Beijing Engineer
-
- 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
-
def person(name, age, *args, city, job): print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
-
-
-
参数组合顺序:必选参数,默认参数,可变参数,命名关键字参数,关键字参数
- 递归函数
3. 高级特性
- 切片
- 迭代
- 列表生成式:列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
- 生成器generator
- 迭代器
- yield关键字
4. 函数式编程 Functional Programming
- 函数式编程
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
- 高阶函数
变量可以指向函数 --> 如果一个变量只想一个函数可以通过变量来调用这个函数
函数名也是变量:他是指向函数的变量
高阶function:就是让函数能够接受函数作为参数
5. Python - logging module
参考链接:https://www.jianshu.com/p/e3abceb9ab43
5.1 开始 - 小案例
最开始,我们用最短的代码体验一下logging的基本功能。
- 第一步,通过logging.getLoger函数,获得一个loger对象,但这个对象暂时是无法使用的。
- 第二步,logging.basicConfig函数,进行一系列默认的配置,包括format、handler等。
- 第三步、loger调用setLevel函数定义日志级别为DEBUG
- 最后,logger调用debug函数,输出一条debug级别的message,显示在了标准输出上
import logging
loger=logging.getLoger()
logging.basicConfig()
loger.setLevel('DEBUG')
loger.debug('logsomething')
# 输出
# out>>DEBUG:root:logsomething
5.2 logging中的日志级别
logging在生成日志的时候,有一个日志级别的机制,默认有以下几个日志级别:
CRITICAL = 50
ERROR = 40
WARNING = 30
INFO = 20
DEBUG = 10
NOTSET = 0
每一个logger对象,都有一个日志级别,它只会输出高于它level的日志。
如果一个logger的level是INFO,那么调用logger.debug()是无法输出日志的,而logger.warning()能够输出。
一般来说,以上的6个日志级别完全满足我们日常使用了。
5.3 logging中的基础类
logging是python中的一个基础模块,它在python中的源码位置如下:
#主干代码
/usr/lib/python2.7/logging/__init__.py
#扩展的handler和config
/usr/lib/python2.7/logging/config.py
/usr/lib/python2.7/logging/handlers.py
组成logging的主干的几个基础类都在__init__.py中:
5.3.1 第一个基础类:LogRecord
一个LogRecord对象,对应了日志中的一行数据。通常包含:时间、日志级别、message信息、当前执行的模块、行号、函数名……这些信息都包含在一个LogRecord对象里。
LogRecord对象可以想象成一个大字典
class LogRecord(object):
#代表一条日志的类
def getMessage(self):
#获取self.msg
def makeLogRecord(dict):
#这个方法很重要,生成一个空的LogRecord,然后通过一个字典,直接更新LogRecord中的成员变量
rv = LogRecord(None, None, "", 0, "", (), None, None)
rv.__dict__.update(dict)
return rv
5.3.2 第二个基础类:Formatter
Formatter对象是用来定义日志格式的,LogRecord保存了很多信息,但是打日志的时候我们只需要其中几个,Formatter就提供了这样的功能,它依赖于python的一个功能:
#通过字典的方式,输出格式化字符串
print '%(name)s:%(num)d' % {'name':'my_name','num': 100}
out>>my_name:100
如果说LogRecord是后面的那个字典,那么Formatter就是前面的那个格式字符串……的抽象
重要的代码如下:
class Formatter(object):
def __init__(self,fmt=None,datefmt=None):
if fmt:
self._fmt = fmt
else:
#默认的format
self._fmt = "%(message)s"
def format(self,record)
#使用self._fmt进行格式化
s=self._fmt % record.__dict__
return s
5.3.3 第三个基础类:Filter和Filterer
Filter类,功能很简单。Flter.filter()函数传入一个LogRecord对象,通过筛选返回1,否则返回0。从代码中可以看到,其实是对LogRecord.name的筛选。
Filterer类中有一个Filter对象的列表,它是一组Filter的抽象。
重要的代码如下:
class Filter(object):
def __init__(self,name=''):
self.name=name
self.nlen=len(name)
def filter(self,record)
#返回1表示record通过,0表示record不通过
if self.nlen==0:
return 1
elif self.name==record.name:
return 1
#record.name不是以filter开头
elif record.name.find(self.name,0,self.nlen) !=0:
return 0
#最后一位是否为.
return (record.name[self.nlen] == ".")
class Filterer(object):
#这个类其实是定义了一个self.filters=[]的列表管理多个filter
def addFilter(self,filter)
def removeFilter(self,filter)
def filter(self,record):
#使用列表中所有的filter进行筛选,任何一个失败都会返回0
#例如:
#filter1.name='A',filter2.name='A.B',filter3.name='A.B.C'
#此时record.name='A.B.C.D'这样的record才能通过所有filter的筛选
5.4 logging中的高阶类
有了以上三个基础的类,就可以拼凑一些更重要的高级类了,高级类可以实现logging的重要功能。
5.4.1 Handler——抽象了log的输出过程
- Handler类继承自Filterer。Handler类是log输出这个过程的抽象。
- 同时Handler类具有一个成员变量self.level,在第二节讨论的日志级别的机制,就是在Handler中实现的。
- Handler有一个emit(record)函数,这个函数负责输出log,必须在Handler的子类中实现。
重要代码如下:
class Handler(Filterer):
def __init__(self,level=NOTSET):
#handler必须有level属性
self.level=_checkLevel(level)
def format(self,record):
#使用self.formatter,formatrecord
def handle(self,record):
#如果通过filter的筛选,则emit这条log
rv=self.filter(record)
self.emit(record)
def emit(self,record):
#等待子类去实现
接下来看两个简单的handler的子类,其实在logging源码中,有一个handler.py专门定义了很多更复杂的handler,有的可以将log缓存在内存中,有的可以将log做rotation等
5.4.1.1 StreamHandler
最简单的handler实现,将log写入一个流中,默认的stream是sys.stderr
重要的代码如下:
class StreamHandler(Handler):
def __init__(self, stream=None):
if stream is None:
stream=sys.stderr
self.stream=stream
def emit(self,record):
#将record的信息写入流中
#处理一些编码的异常
fs='%s\n' #每条日志都有换行
stream=self.stream
stream.write(fs % msg)
5.4.1.2 FileHandler
将log输出到文件的handler,继承自StreamHandler
重要代码如下:
class FileHandler(StreamHandler):
def __init__(self,filename, mode='a'):
#append方式,打开一个文件
StreamHandler.__init__(self, self._open())
def emit(self,record):
#和streamhandler保持一致
StreamHandler.emit(self, record)
5.4.2 Logger —— 一个独立的log管道
什么是logger?
- logger类继承自Filterer,
- logger对象有logger.level日志级别
- logger对象控制多个handler:logger.handlers=[]
- logger对象之间存在父子关系
简单的来说,logger这个类,集中了我们以上所有的LogRecord类、Filter类、Formatter类、handler类。首先,logger根据输入生成一个LogRecord对象,经过Filter和Formatter之后,再通过self.handlers列表中的所有handler,把log发送出去。一个logger中可能有多个handler,可以实现把一份log放到多个任意的位置。
重要代码:
class Logger(Filterer):
def __init__ (self,name,level=NONSET):
#handler列表
self.handlers=[]
self.level=_checkLevel(level)
def addHandler(self,hdlr):
def removeHandler(self,hdlr):
def _log(self, level, msg, args, exc_info=None, extra=None):
#在_log函数中创建了一个LogRecord对象
record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra)
#交给handle函数
self.handle(record)
def handle(self,record):
#进行filter,然后调用callHandlers
if (not self.disabled) and self.filter(record):
self.callHandlers(record)
def callHandlers(self, record):
#从当前logger到所有的父logger,递归的handl传入的record
c=self
while c:
for hdlr in c.handlers:
hdlr.handle(record) #进入handler的emit函数发送log
……
c=c.parent
5.4.3 Manager —— 管理logger的类
Manager这个类,对用户其实是不可见的,如果生成了Logger,Manager就会自动存在,Manager对象负责管理所有的Logger。
logger和manager的关系,总结了一下几条:
- Logger是输出log的对象,Manager类提供了管理多个Logger的功能。
- 一个程序中只能有一个manager对象,生成manager时,必定也会生成RootLogger,manager对象中的self.root指向了RootLogger
- manager对象中的self.loggerDict,这个字典保存了当前所有的logger对象(不包含rootlogger)
- 如果使用logging.getLogger的name为空,那么默认指向了name为'root'的RootLogger
- 如果使用logging.getLogger的name不为空,生成的logger会自动挂载到RootLogger下,除非指定其他的父logger
- 其他的logger通过name建立父子关系
父子关系示例:
loger1=logging.getLogger('A')
loger2=logging.getLogger('A.B')
#loger2的父loger是loger1
loger2.parent
# out>><logging.Logger object at 0xb7230d6c>
# loger1的父loger是rootlogger
# loger1.parent
# out>><logging.RootLogger object at 0xb7230b6c>
这些关系都在manager中进行管理
重要的代码:
class Manager(object):
def getLogger(self,name):
#生成一个logger,将logger中的manager指向self
#维护所有logger的父子关系
def _fixupParents(self,aloger):
def _fixupChildren(self,ph,aloger):
#修复所有logger的父子关系
5.4.4 LoggerAdapter —— 对标准logger的一个扩展
LogRecord这个大字典中提供的成员变量已经很多,但是,如果在输出log时候仍然希望能够夹带一些自己想要看到的更多信息,例如产生这个log的时候,调用某些函数去获得其他信息,那么就可以把这些添加到Logger中,LoggerAdapter这个类就起到这个作用。
LoggerAdapter这个类很有意思,如果不做什么改动,那么LoggerAdapter类和Logger并没有什么区别。LoggerAdapter只是对Logger类进行了一下包装。
LoggerAdapter的用法其实是在它的成员函数process()的注释中已经说明了:
def process(self, msg, kwargs):
"""
Normally, you'll only need to override this one method in a
LoggerAdapter subclass for your specific needs.
"""
也就是说重写process函数,以下是一个例子:
import logging
import random
L=logging.getLogger('name')
#定义一个函数,生成0~1000的随机数
def func():
return random.randint(1,1000)
class myLogger(logging.LoggerAdapter):
#继承LoggerAdapter,重写process,生成随机数添加到msg前面
def process(self,msg,kwargs):
return '(%d),%s' % (self.extra['name'](),msg) ,kwargs
# 函数对象放入字典中传入
LA=myLogger(L,{'name':func})
# now,do some logging
LA.debug('some_loging_messsage')
# out>>DEBUG:name:(167),some_loging_messsage
5.5 logging中的config函数
5.5.1 def basicConfig(**kwargs)
basicConfig函数将对各种参数进行配置,如果不传入参数,则进行默认配置:
- format配置
- handler配置
- level配置
6. session & Cookie
- 由于HTTP协议是无状态的协议(发送一次请求即断开),所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session. 典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
- 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
- Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。
- 总结一下:
- Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;
- Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
- Cookie是写在客户端的,很容易被篡改,不适合存放敏感信息;而session保存在服务器,客户端只是接收一个随机字符串,相对安全。
7. decorator
python中函数也是一个对象,所以可以将:
1. 函数复制给变量
2. 将函数当做参数
3. 返回一个函数
decorator就是一个:使用函数作参数并且返回函数的函数。通过改进我们可以得到:
- 更简短的代码,将结合点放在函数定义时
- 不改变原函数的函数名
在Python解释器发现login调用时,他会将login转换为printdebug(login)()。也就是说真正执行的是__decorator 函数
Decorators are a shortcut to applying wrapper functions. This is helpful to “wrap” functionality with the same code over and over again
装饰器是一个函数,其主要用途是包装另一个函数或类。这种包装的首要目的是透明地修改或增强被包装对象的行为。
8. 一些常用的库
8.1 pandas
Python Data Analysis Library 或 pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。pandas提供了大量能使我们快速便捷地处理数据的函数和方法。你很快就会发现,它是使Python成为强大而高效的数据分析环境的重要因素之一。
8.2 matplotlib
Matplotlib 是一个 Python 的 2D绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。
链接:http://python.jobbole.com/85106/
8.3 pickle
序列化和反序列化的库
参考:https://docs.python.org/3/library/pickle.html
8.4 numpy
NumPy系统是Python的一种开源的数值计算扩展。这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nested list structure)结构要高效的多(该结构也可以用来表示矩阵(matrix))。
9. 一些常问的问题总结
- Python中__all__的用法:它是一个string元素组成的list变量,定义了当使用 from <module> import * 导入某个模块的时候能导出的符号 (这个符号代表变量,函数,类等)
- Python class中__init__()方法的作用
- hasattr(object, name):判断对象是否包含对应的属性
- Python pop(key[,default])删除字典给定键key及对应的值,返回值为本删除的值
- python中*args,**kwargs
- *args 和 **kwargs主要用于函数定义,将不定数量的参数传递给一个函数
- *args 用来发送一个非键值对的可数变量的参数列表给一个函数
- **kwargs允许将不定长度的键值对,作为参数传给一个函数。如果想要在一个函数里处理带名字的参数,应使用**kwargs
- *会把list或者tuple分解为一个个参数传递给函数
- **会把dict转成关键字参数
- 函数定义和函数调用,可以同时出现*或者**,如foo3(*args,**kwargs),但是必须*在前,**在后
- 出现在函数调用时,一定要注意参数匹配问题 some_func(fargs, *args, **kwargs