python周报第十周

0.本周知识点预览

  • python作用域
  • 浅谈py2和py3的多继承
  • socketserver源码简析
  • IO多路复用
  • 初识多线程

1.python作用域

先看一个简单的例子

例子1:

def func():
    name = "lk"

func()
print(name)

执行结果如下:

Traceback (most recent call last):
  File "/Users/liukai/PycharmProjects/s13/day10/test.py", line 12, in <module>
    print(name)
NameError: name 'name' is not defined

例子2:

if 1==1:
    name = "lk"

print(name)

执行结果如下:

lk

例子3:

name = 'lk'

def f1():
    print(name)

def f2():
    name = "liukai"
    return f1

ret = f2()
ret()

执行结果如下:

lk

例子4:

li = [lambda :x for x in range(10)]
print(li[0],li[1])
print(li[0]())

执行结果如下:

<function <listcomp>.<lambda> at 0x1079470d0> <function <listcomp>.<lambda> at 0x107947158>
9

 代码解析:

例子1:变量name定义在函数func()内,当print(name)的时候,报变量未定义,可推断出,python至少作用域为函数级。

例子2:在if else代码块内,定义了变量name,可以成功print,可见,python也并不是代码块级作用域。PS:java、c#是代码块级作用域。

例子3:python为函数级作用域,而且内部函数未找到变量时,会去外层函数找,但例子3中,f1函数在f2函数内,不过输出的并不是我们想要的liukai,说明,python的作用域在代码执行之前就确定了,和函数如何嵌套是没关系的,代码从上到下执行,到加载到f1函数时,作用域就为函数体内,如若找不到变量,便去函数体外找,并不是在f2内。

例子4:li列表的最终结果为10个lambda函数,如li[0],li[1]...,不过为什么li[0]不等于0呢?这是因为,for x in range(10),已经让x等于9,并且,函数如若不加括号,便不会执行,也不会加载函数体内的内容。所有函数体内的x值一直不变,当执行li[0]()的时候,便会输出此时已经等于9的x。

综上:python的作用域为函数级,包括class,def,lambda;if else / while / for 不会改变作用域,搜索顺序为先搜索本地局部变量,在搜索上层变量直至全局变量。

2.py2和py3的多继承

1.py2

这里先说python2.7的版本。python2.7版本的类分为两种。

一、经典类

class A:
    def fuck(self):
        print("我是A类")

class B(A):
    def haha(self):
        print("我是B类")

class C(A):
    def haha(self):
        print("我是C类")

class D(B):
    def haha(self):
        print("我是D类")

class E(C):
    def fuck(self):
        print("我是E类")

class F(D,E):
    def haha(self):
        print("我是F类")

f = F()
f.fuck()

执行结果如下:

我是A类

代码解析:python2.7的经典类的继承关系为深度优先,一直找到最顶层的父类,也就是一条道走到黑的模式。

如图:

二、新式类

class A(object):
    def fuck(self):
        print("我是A类")

class B(A):
    def haha(self):
        print("我是B类")

class C(A):
    def haha(self):
        print("我是C类")

class D(B):
    def haha(self):
        print("我是D类")

class E(C):
    def fuck(self):
        print("我是E类")

class F(D,E):
    def haha(self):
        print("我是F类")

f = F()
f.fuck()

执行结果如下:

我是E类

代码解析:经典类和新式类在写法上的区别为 class A 和 class A (object),新式类的继承方式为广度优先。

如图:

2.py3

python3的类继承举例:

class A:
    def fuck(self):
        print("我是A类")

class B(A):
    def haha(self):
        print("我是B类")

class C(A):
    def haha(self):
        print("我是C类")

class D(B):
    def haha(self):
        print("我是D类")

class E(C):
    def fuck(self):
        print("我是E类")

class F(D,E):
    def haha(self):
        print("我是F类")

f = F()
f.fuck()

执行结果如下:

我是E类

代码解析:python3的类继承和python2.7的新式类的继承方式一样,都是广度优先。

3.socketserver 源码简析

socketserver源码举例:

import socketserver         ###导入socketserver模块


class MySocketServer(socketserver.BaseRequestHandler):      ###创建一个类继承socketserver.BaseRequestHandler
    def handle(self):
        pass

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(("127.0.0.1",8007),MySocketServer)     ###实例化socketserver.ThreadingTCPServer的对象,参数一是地址和端口,参数二是创建的类
    server.serve_forever()

以下为socketserver源码的执行顺序

###找sockerserver 源码  -> socketserver.ThreadingTCPServer类
###1. TCPServer __init__

###2. BaseServer __init__    封装变量  self.server_address = server_address = ("127.0.0.1",8007)
###                                   self.RequestHandlerClass = RequestHandlerClass = MySocketServer

###3. server.serve_forever()

###4. 找BaseServer的serve_forever()  serve_forever()执行self._handle_request_noblock

###5. self._handle_request_noblock  执行  self.process_request()

###6. process_request 去 ThreadingMixIn 类里找,并执行了self.process_request_thread

###7. self.process_request_thread 执行了 self.finish_request , finish_request 去BaseServer 找
###8. finish_request 里执行了self.RequestHandlerClass(request, client_address, self)

###9. self.RequestHandlerClass(request, client_address, self) -> MySocketServer(request, client_address, self)

###10.  MySocketServer(request, client_address, self) 会执行 MySocketServer 的__init__方法

###11. MySocketServer 中没有 __init__方法,便去父类找,咱们写的父类是socketserver.BaseRequestHandler

###12. 父类中执行self.handle(),便是执行MySocketServer的handle()

4.I/O多路复用 

 I/O多路复用,有三种方法,select、poll、epoll。

windows适用于select

mac 适用于select

linux 适用于select、poll、epoll

在这里只举例select的方法

服务端:

#!/usr/bin/env python3
# -*- coding=utf-8 -*-
# Author : LiuKai

## I/O 多路复用
import socket       ###导入socket模块
import select       ###导入select模块


s = socket.socket()     ###创建一个socket

s.bind(("127.0.0.1",9999,))     ###绑定地址


s.listen(5)         ###设置等待队列
input = [s,]        ###设置监听socket队列
output = []         ###设置将要发送数据的socket队列

while True:
    ###rlist 是当参数1 -> input中的socket发生变化时,就会将变化的socket加入到rlist中.变化是指当创建的socket接收请求(accept方法),或者创建连接的(conn方法)
    ###wlist 是只要参数2有值,就将socket对象传入wlist
    ###elist 是只要参数3的socket出现问题,就会把其加入到elist
    ###第四个参数1,代表超时时间,如若未设置,则select 会一直阻塞,直到文件句柄发生变化,如若设置为1,那么监听的socket无变化则阻塞1秒,返回空列表.
    rlist, wlist, elist = select.select(input,output,[],1)

    print(len(input),len(rlist),len(output),len(wlist))
    
    ###只要有新连接或者接收消息,则rlist就会添加元素
    for i in rlist:
        ###循环rlist,假如是新连接,则接受请求,把conn添加到input中,方便select 监听
        if i == s:
            conn, address = i.accept()
            input.append(conn)
            conn.sendall(bytes("hello",encoding="utf-8"))
        ###假如不是新连接,而是接收的信息(conn变化了),则如若接收没问题,则把连接(conn)假如到output列表中,方便wlist监听,发送消息.
        else:
            try:
                data = i.recv(1024)
                if not data:
                    raise Exception("收到空值")
                else:
                    output.append(i)
            except:
                ###假如客户端断开连接造成异常,则在监听队列中删除该conn
                input.remove(i)

    ###从监听的即将要发送的等待队列中(conn),遍历该列表,发送消息,消息发出后,从发送队列中删除该conn.
    for j in wlist:
        j.sendall(bytes("response",encoding="utf-8"))
        output.remove(j)

客户端:

#!/usr/bin/env python3
# -*- coding=utf-8 -*-
# Author : LiuKai

import socket

s = socket.socket()

s.connect(("127.0.0.1",9999,))

data = s.recv(1024)
print(data)

while True:
    inp = input(">>>")
    s.sendall(bytes(inp,encoding="utf-8"))
    data = s.recv(1024)
    print(data)
s.close()

执行服务端后,执行多个客户端,客户端结果如下:

b'hello'
>>>ll
b'response'
>>>ll
b'response'

服务端结果如下:

0 0 1 1
1 1 0 0
1 0 1 1
1 1 0 0
1 0 1 1
1 0 0 0

代码解析:服务端的结果为当创建一个连接,第一列和第二列为1,当新建的连接(第二列)发生变化后,连接用掉之后,会归0,当接收消息后,根据代码逻辑,会放入第三列一个socket待监听,致使第四列也会变为1.服务端发出消息后,第三列、第四列都归0.

5.初识多线程

简介:

0.线程是应用程序中工作的最小单元。

1.一个应用程序,可以有单、多进程,可以有单、多线程。

2.默认:单进程、单线程

3.当程序不怎么占用CPU时,I/O操作较频繁时,推荐用单进程、多线程。

4.当程序多进行计算性操作,利用CPU较频繁,I/O操作较少时,推荐用单线程、多进程。

5.由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。

不用多线程的代码:

import time

def f1(arg):
    time.sleep(1)
    print(arg)

print(time.ctime())
for i in range(5):
    f1(i)
print(time.ctime())

执行结果如下:

Wed Jul 13 22:19:55 2016
0
1
2
3
4
Wed Jul 13 22:20:00 2016

代码解析:可见,连续执行5次函数f1,执行的时间为5秒。

以下为利用多线程的代码:

import threading
import time

def f1(arg):
    time.sleep(1)
    print(arg)

print(time.ctime())
for i in range(5):
    t = threading.Thread(target=f1,args=(i,))
    t.start()
print(time.ctime())

执行结果如下:

Wed Jul 13 22:25:38 2016
Wed Jul 13 22:25:38 2016
0
2
3
4
1

代码解析:利用多线程,执行时间变为1秒。

原文地址:https://www.cnblogs.com/Caesary/p/5662661.html