python拓展知识
类方法self的作用
未绑定的类方法:没有self,通过类来引用方法返回一个未绑定方法对象。要调用它,你必须显示地提供一个实例作为第一个参数。
绑定的实例方法:有self,通过实例访问方法返回一个绑定的方法对象。Python自动地给方法绑定一个实例,所以我们调用它时不用再传一个实例参数。
class Test(object): def func(self, message): print message obj = Test() x = obj.func # x 是一个绑定的方法对象, 不需要传递一个实例参数 x('hi') # fn 是一个未绑定的方法对象, 需要传递一个实例参数 fn = Test.func fn(obj, 'hi') # fn('hi') # 错误的调用
type关键字
既可以查看一个对象的类型 ,也可以创建类对象
查看对象类型
a = 1 type(a) # <type 'int'> b = 'hello' type(b) # <type 'str'>
创建类对象
# 需要给一个参数来绑定类 def fn(self): print 'hello world' Hello = type('Hello', (object,), dict(hello=fn)) h = Hello() h.hello()
locals() 和 globals() 的区别
locals()返回局部所有类型的变量的字典, globals()返回当前位置的全部全局变量的字典
def test(arg): z = 1 print locals() test(4) # {'z': 1, 'arg': 4} print globals() # {'__builtins__': <module '__builtin__' (built-in)>, '__file__': '...', '__package__': None, 'test': <function test at 0x10ea970c8>, '__name__': '__main__', '__doc__': None}
字符串title函数
将字符串的每个单词的首字母大写,下划线连接的单词首字母也要大写
str = 'my_str' print str.title() # My_Str
字符串的rpartition函数
以某个分隔符将字符串分成两段,取最后一个匹配的位置的分隔符分隔,返回一个三元组(head, sep, tail)
print 'django.core.app'.rpartition('.') # ('django.core', '.', 'app')
python特殊函数__call__
所有的函数都是可调用对象,一个类实例也可以变成一个可调用对象,只需实现一个特殊方法__call__()
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __call__(self, job): print 'My name is %s' % self.name print 'My gender is %s' % self.gender print 'My job is %s' % job p = Person('Bob', 'male') p('student')
python strip(), lstrip(), rstrip()函数
strip(): 返回字符串的副本,并删除前导和后缀字符
lstrip(): 返回字符串的副本,删除前导字符
rstrip(): 返回字符串的副本,删除后缀字符
str = ' hello ' print len(str) # 5 print len(str.strip()) # 7 str = 'rhellor' print str.strip('r') # hello print str.lstrip('r') # hellor print str.rstrip('r') # rhello
random.shuffle()
random.shuffle 将列表随机排序
import random li = [1, 2, 3, 4, 5] random.shuffle(li) for i in li: print i
os.path模块
os.path.basename(path):获取对应路径下的文件名
os.path.abspath(path):返回path规范化的绝对路径
os.path.split(path): 将path分割成目录和文件名二元组返回
os.path.dirname(path): 返回path的目录
os.path.exists(path): 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path): 如果path是绝对路径,返回True
os.path.isfile(path): 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path): 如果path是一个存在的目录,则返回True。否则返回False
os.path.splitext(path): 分离文件名与扩展名;默认返回(fname,fextension)元组,可做分片操作
os.path.getatime(path): 返回path所指向的文件或者目录的最后存取时间
os.path.getmtime(path): 返回path所指向的文件或者目录的最后修改时间
os.path.join(path1[, path2[, path3..]]):
os.path.join 可以传入多个参数
- 会从第一个以”/”开头的参数开始拼接,之前的参数全部丢弃;
- 以上一种情况为先。在上一种情况确保情况下,若出现”./”开头的参数,会从”./”开头的参数的上一个参数开始拼接。
import os print "1:", os.path.join('aaaa', '/bbbb', 'ccccc.txt') # 1: /bbbb/ccccc.txt print "2:", os.path.join('/aaaa', '/bbbb', '/ccccc.txt') # 2: /ccccc.txt print "3:", os.path.join('aaaa', './bbb', 'ccccc.txt') # 3: aaaa/./bbb/ccccc.txt
os.stat()
方法用于在给定的路径上执行一个系统 stat 的调用
import os print os.stat("/root/python/zip.py") # (33188, 2033080, 26626L, 1, 0, 0, 864, 1297653596, 1275528102, 1292892895) print os.stat("/root/python/zip.py").st_mode #权限模式 # 33188 print os.stat("/root/python/zip.py").st_ino #inode number # 2033080 print os.stat("/root/python/zip.py").st_dev #device # 26626 print os.stat("/root/python/zip.py").st_nlink #number of hard links # 1 print os.stat("/root/python/zip.py").st_uid #所有用户的user id # 0 print os.stat("/root/python/zip.py").st_gid #所有用户的group id # 0 print os.stat("/root/python/zip.py").st_size #文件的大小,以位为单位 # 864 print os.stat("/root/python/zip.py").st_atime #文件最后访问时间 # 1297653596 print os.stat("/root/python/zip.py").st_mtime #文件最后修改时间 # 1275528102 print os.stat("/root/python/zip.py").st_ctime #文件创建时间 # 1292892895
socket.getaddrinfo()
函数原型:socket.getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]])
返回值:[(family, socktype, proto, canonname, sockaddr)]有元组组成的列表,元组里面包含5个元素,其中sockaddr是(host, port)
- family:表示socket使用的协议簇,常用的协议簇包括AF_UNIX(本机通信) 、AF_INET(TCP/IP协议簇中的IPv4协议)、AF_INET6(TCP/IP协议簇中的IPv4协议)。python的socket包中AF_UNIX=1, AF_INET=2, AF_INET6=10
- sockettype:表示socket的类型,常见的socket类型包括SOCK_STREAM(TCP流)、SOCK_DGRAM(UDP数据报)、SOCK_RAW(原始套接字),SOCK_STREAM=1, SOCK_DGRAM=2, SOCK_RAW=3
- proto:套接口所用的协议,若不指定,可用0,常用的协议有IPPROTO_TCP(6)和IPPTOTO_UDP(17),他们分别对应TCP传输协议、UDP传输协议
import socket print socket.getaddrinfo('www.baidu.com', 80) # [(2, 2, 17, '', ('180.97.33.108', 80)), (2, 1, 6, '', ('180.97.33.108', 80)), (2, 2, 17, '', ('180.97.33.107', 80)), (2, 1, 6, '', ('180.97.33.107', 80))]
struct模块pack和unpack
pack(fmt, v1, v2….):按照给定格式将数据封装成字符串(实际类似于c结构体的字节流)
unpack(fmt, string ):按照给定格式解析字节流,返回解析出的tuple
calcsize(fmt):计算给定的格式占用多少字节的内存
参考博客: https://blog.csdn.net/w83761456/article/details/21171085
# coding: utf-8 import struct a = 'hello' b = 'world!' c = 2 d = 45.123 bytes = struct.pack('5s6sif', a, b, c, d) print bytes # helloworld!�}4B a, b, c, d = struct.unpack('5s6sif', bytes) print a, b, c, d # hello world! 2 45.1230010986
metaclass
允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。多喜欢自定义__new__方法
class Trick(FlyToSky):
pass
当我们在创建上面的类的时候,python做了如下的操作:
Trick中有__metaclass__这个属性吗?如果有,那么Python会在内存中通过__metaclass__创建一个名字为Trick的类对象,也就是Trick这个东西。如果Python没有找到__metaclass__,它会继续在自己的父类FlyToSky中寻找__metaclass__属性,并且尝试以__metaclass__指定的方法创建一个Trick类对象。如果Python在任何一个父类中都找不到__metaclass__,它也不会就此放弃,而是去模块中搜寻是否有__metaclass__的指定。如果还是找不到,那就是使用默认的type来创建Trick。
那么问题来了,我们要在__metaclass__中放置什么呢?答案是可以创建一个类的东西,type,或者任何用到type或子类化type的东西都行。
参考博客: https://www.cnblogs.com/piperck/p/5840443.html
class UpperAttrMetaClass(type): def __new__(mcs, class_name, class_parents, class_attr): attrs = ((name, value) for name, value in class_attr.items() if not name.startswith('__')) uppercase_attrs = dict((name.upper(), value) for name, value in attrs) return super(UpperAttrMetaClass, mcs).__new__(mcs, class_name, class_parents, uppercase_attrs) class Trick(object): __metaclass__ = UpperAttrMetaClass bar = 12 money = 'unlimited' print Trick.BAR print Trick.MONEY
设置守护进程 setDaemon(flag)
当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束,当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。
线程join方法
join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程再终止。
join有一个timeout参数:
- 设置守护线程,主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。所以说,如果有10个子线程,全部的等待时间就是每个timeout的累加和。简单的来说,就是给每个子线程一个timeout的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。
- 没有设置守护线程,主线程将会等待timeout的累加和这样的一段时间,时间一到,主线程结束,但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。
参考博客:https://www.cnblogs.com/cnkai/p/7504980.html
functools.wraps
在使用装饰器时难免会损失一些东西,使用functools.wraps可以将原函数对象的指定属性复制给包装函数对象, 默认有 __module__、__name__、__doc__,或者通过参数选择。
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ + " was called" return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x print f.__name__ # 不加wraps: with_logging # 加wraps: f print f.__doc__ # 不加wraps: None # 加wraps: does some math
孤儿进程和僵尸进程
什么是孤儿进程?
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
什么是僵尸进程?
首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。
而僵尸进程就是指:一个进程执行了exit系统调用退出,而其父进程并没有为它收尸(调用wait或waitpid来获得它的结束状态)的进程。
任何一个子进程(init除外)在exit后并非马上就消失,而是留下一个称为僵尸进程的数据结构,等待父进程处理。这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号。
如何避免僵尸进程?
- 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
- 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
- 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
- 通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
参考博客:https://www.cnblogs.com/Anker/p/3271773.html
select、poll、epoll三者的区别
为何使用select 单进程实现并发?
因为一个进程实现多并发比多线程是实现多并发的效率还要高,因为启动多线程会有很多的开销,而且CPU要不断的检查每个线程的状态,确定哪个线程是否可以执行。
那么单进程是如何实现多并发的呢?
之前的socket一个进程只能接收一个连接,当接收新的连接的时候产生阻塞,因为这个socket进程要先和客户端进行通信,二者是彼此互相等待的【客户端发一条消息,服务端收到,客户端等着返回....服务端等着接收.........】一直在阻塞着,这个时候如果再来一个连接,要等之前的那个连接断了,这个才可以连进来。也就是说用基本的socket实现多进程是阻塞的。为了解决这个问题采用每来一个连接产生一个线程,是不阻塞了,但是当线程数量过多的时候,对于cpu来说开销和压力是比较大的。
客户端发起一个连接,会在服务端注册一个文件句柄,服务端会不断轮询这些文件句柄的列表,主进程和客户端建立连接而没有启动线程,这个时候主进程和客户端进行交互,其他的客户端是无法连接主进程的,为了实现主进程既能和已连接的客户端收发消息,又能和新的客户端建立连接,就把轮询变的非常快(死循环)去刷客户端连接进来的文件句柄的列表,只要客户端发消息了,服务端读取了消息之后,有另一个列表去接收给客户端返回的消息,也不断的去刷这个列表,刷出来后返回给客户端,这样和客户端的这次通信就完成了,但是跟客户端的连接还没有断,但是就进入了下一次的轮询。
三者区别
select:单进程实现并发
poll: 它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
epoll: 直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法, epoll可以同时支持水平触发和边缘触发, epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表 就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了 这些文件描述符在系统调用时复制的开销, 另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描 述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调 机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
水平触发和边缘触发
水平触发:select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll() 的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发。
边缘触发:只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发。
参考博客:https://www.cnblogs.com/qianyuliang/p/6551553.html
atexit模块
atexit只定义了一个register函数用于注册程序退出时的回调函数,我们可以在这个回调函数中做一些资源清理的操作
import atexit def exit0(*args, **kwarg): print 'exit0' for arg in args: print arg for item in kwarg.items(): print item def exit1(): print 'exit1' atexit.register(exit0, *[1, 2, 3], **{"a": 1, "b": 2, }) atexit.register(exit1) @atexit.register def exit2(): print 'exit2' # 回调函数执行的顺序与它们被注册的顺序刚才相反 # exit2 # exit1 # exit0 # 1 # 2 # 3 # ('a', 1) # ('b', 2)
shutil模块
高级的 文件、文件夹、压缩包处理模块
常用函数
copyfileobj(文件1,文件2):将文件1的数据覆盖copy给文件2
copyfile(文件1,文件2):不用打开文件,直接用文件名进行覆盖copy
copymode(文件1,文件2):之拷贝权限,内容组,用户,均不变
copystat(文件1,文件):只拷贝了权限
copy(文件1,文件2):拷贝文件和权限都进行copy
copy2(文件1,文件2):拷贝了文件和状态信息
copytree(源目录,目标目录):可以递归copy多个目录到指定目录下
rmtree(目标目录):可以递归删除目录下的目录及文件
move(源文件,指定路径):递归移动一个文件
make_archive():可以压缩,打包文件
psutil模块
psutil是一个跨平台库(http://pythonhosted.org/psutil/)能够轻松实现获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息。它主要用来做系统监控,性能分析,进程管理。它实现了同等命令行工具提供的功能,如ps、top、lsof、netstat、ifconfig、who、df、kill、free、nice、ionice、iostat、iotop、uptime、pidof、tty、taskset、pmap等。目前支持32位和64位的Linux、Windows、OS X、FreeBSD和Sun Solaris等操作系统
参考博客:https://www.cnblogs.com/saneri/p/7528283.html
inspect模块
主要用处
- 对是否是模块、框架、函数进行类型检查
- 获取源码
- 获取类或者函数的参数信息
- 解析堆栈
常用函数
getargspec(func):返回一个命名元组ArgSpect(args, varargs, keywords, defaults),args是函数位置参数名列表,varargs是*参数名,keywords是**参数名,defaults是默认参数值的元组。
getmembers(object[, predicate]):返回一个包含对象的所有成员的(name, value)列表。返回的内容比对象的__dict__包含的内容多,源码是通过dir()实现的。predicate是一个可选的函数参数,被此函数判断为True的成员才被返回。
getmodule(object):返回定义对象的模块
getsource(object):返回对象的源代码
getsourcelines(object):返回一个元组,元组第一项为对象源代码行的列表,第二项是第一行源代码的行号
stack():第一列是对象名,第二列是当前脚本文件名,第三列是行数,第四列是函数名,第五列是具体执行的程序。第一行是当前函数,第二行是父级函数,以此往上推
threading.local()
这个方法的特点用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问,localVal.val = name这条语句可以储存一个变量到当前线程,如果在另外一个线程里面再次对localVal.val进行赋值,那么会在另外一个线程单独创建内存空间来存储,也就是说在不同的线程里面赋值 不会覆盖之前的值,因为每个线程里面都有一个单独的空间来保存这个数据,而且这个数据是隔离的,其他线程无法访问
import threading # 创建全局ThreadLocal对象: localVal = threading.local() localVal.val = "Main-Thread" def process_student(): print '%s (in %s)' % (localVal.val, threading.current_thread().name) def process_thread(name): #赋值 localVal.val = name process_student() t1 = threading.Thread(target=process_thread, args=('One',), name='Thread-A') t2 = threading.Thread(target=process_thread, args=('Two',), name='Thread-B') t1.start() t2.start() t1.join() t2.join() print localVal.val # One (in Thread-A) # Two (in Thread-B) # Main-Thread
logging模块
logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比print,具备如下优点:
- 可以通过设置不同的日志等级,在release版本中只输出重要信息,而不必显示大量的调试信息
- print将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging则可以由开发者决定将信息输出到什么地方,以及怎么输出
# 基本使用 import logging logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) logger.info("Start print log") logger.debug("Do something") logger.warning("Something maybe fail.") logger.info("Finish")
参考博客:https://www.cnblogs.com/liujiacai/p/7804848.html
日志高级版zipkin
参考博客:https://blog.csdn.net/liumiaocn/article/details/80657943