python_10(模块与包)

第1章 模块
1.1 模块的种类
1.2 定义
1.3 作用
1.4 导入及使用
1.4.1 import
1.4.2 测试一:
1.4.3 测试二:
1.4.4 测试三:
1.4.5 小结
1.4.6 sys.modules
1.4.7 模块别名
1.4.8 选读读取模块
1.4.9 导入多模块
1.4.10 from ... import...
1.4.11 重名覆盖
1.4.12 强调
1.5 all/*
1.5.1 把模块当做脚本执行
第2章 包
2.1 目录结构
2.2 注意事项
2.3 对比import item 和from item import name的应用场景:
2.4 from glance.api import *
2.5 绝对导入和相对导入
2.6 测试结果:注意一定要在于glance同级的文件中测试
2.7 单独导入包
2.8 开发规范
第3章 异常处理
3.1 异常处理概要
3.2 异常类别统计表
3.3 处理语法
3.3.1 陈旧的处理方法
3.3.2 暴露的问题
3.3.3 if处理小结
3.4 python特制异常处理
3.4.1 处理的基础语法
3.5 处理的完整语法
3.6 finally
3.7 try语法介绍
3.7.1 整体语句逻辑
3.8 raise主动抛异常
3.9 异常处理注意事项
3.10 Exception应用场景
3.11 try..except的方式比较if的方式的好处
4.1 socket
4.2 tcp/udp
4.3 socket参数的详解
4.3.1 创建socket对象的参数说明:
4.4 基于Tcp协议的socket
4.5 基于UDP协议的socket
4.5.1 server端
4.5.2 套接字(socket)初使用
4.5.3 qq聊天案例
4.5.4 时间服务器
4.6 端口占用解决方法
第5章 黏包
5.1 基于tcp出现的现象
5.2 发生黏包的两种情况
5.3 小结
5.4 解决黏包问题
第6章 拾遗

第1章 模块

1.1 模块的种类

l  内置模块

l  扩展模块

       扩展模块安装:pip install Django 所有的web框架

       http:///www.pypi.org

l  自定义模块

       自己写的代码,放在一个py文件里

       特点:节省内存

1.2 定义

解释:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名加上.py的后缀

import加载的模块 分为四个通用类别:

1)使用python编写的代码(.py文件)

2)已被编译为共享库的代码(.py)

3)包好一组模块的包

4)使用c编写并连接到python解释器的内置模块

1.3 作用

写好的程序永久保存;结构清晰功能强大;方便管理,单独执行可以是脚本,模块倒入重复利用。

1.4 导入及使用

1.4.1 import

解释:模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句)

例:

示例文件:自定义模块my_module.py,文件名my_module.py,模块名my_module

#my_module.py

print('from the my_module.py')

 

money=1000

 

def read1():

    print('my_module->read1->money',money)

 

def read2():

    print('my_module->read2 calling read1')

    read1()

 

def change():

    global money

    money=0

import实际应用

#demo.py

import my_module #只在第一次导入时才执行my_module.py内代码,此处的显式效果是只打印一次'from the my_module.py',当然其他的顶级代码也都被执行了,只不过没有显示效果.

import my_module

1.4.2 测试一:

money与my_module.money不冲突

#demo.py

import my_module

money=10

print(my_module.money)

执行结果:

from the my_module.py

C:\python3\python3.exe D:/python/untitled2/python_10/lession.py

from the my_module.py

1000

'''

测试一:money与my_module.money不冲突

1.4.3 测试二:

read1与my_module.read1不冲突

#demo.py

import my_module

def read1():

    print('========')

my_module.read1()

'''

执行结果:

from the my_module.py

my_module->read1->money 1000

'''

1.4.4 测试三:

执行my_module.change()操作的全局变量money仍然是my_module中的

#demo.py

import my_module

money=1

my_module.change()

print(money)

执行结果:

C:\python3\python3.exe D:/python/untitled2/python_10/lession.py

from the my_module.py

1

1.4.5 小结

总结:首次导入模块my_module时会做三件事:

n  为源文件(my_module模块)创建新的名称空间,在my_module中定义的函数和方法若是使用到了global时访问的就是这个名称空间。

n  在新创建的命名空间中执行模块中包含的代码,见初始导入import my_module

       事实上函数定义也是“被执行”的语句,模块级别函数定义的执行将函数名放入模块全局名称空间表,用globals()可以查看

n  创建名字my_module来引用该命名空间

       这个名字和变量名没什么区别,都是‘第一类的’,且使用my_module.名字的方式可以访问my_module.py文件中定义的名字,my_module.名字与test.py中的名字来自两个完全不同的地方。

1.4.6 sys.modules

解释:

       从sys.modules中找到当前已经加载的模块,sys.modules是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。

我们可以从sys.modules中找到当前已经加载的模块,sys.modules是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。

执行结果:

from the my_module.py

1.4.7 模块别名

为模块名起别名,相当于m1=1;m2=m1 

1 import my_module as sm

2 print(sm.money)

例:

#mysql.py

def sqlparse():

    print('from mysql sqlparse')

#oracle.py

def sqlparse():

    print('from oracle sqlparse')

#test

db_type =input('>>')

if db_type == 'mysql':

    import mysql as db

    db.salparse()

elif db_type == 'oracle':

    import oracle as db

输出:

C:\python3\python3.exe D:/python/untitled2/python_10/lession.py

>>mysql

from mysql sqlparse

1.4.8 选读读取模块

       为已经导入的模块起别名的方式对编写可扩展的代码很有用,假设有两个模块xmlreader.py和csvreader.py,它们都定义了函数read_data(filename):用来从文件中读取一些数据,但采用不同的输入格式。可以编写代码来选择性地挑选读取模块,例如

if file_format == 'xml':

     import xmlreader as reader

elif file_format == 'csv':

     import csvreader as reader

data=reader.read_date(filename)

 

1.4.9 导入多模块

import sys

import os

import re

1.4.10 from ... import...

解释: 对比import my_module,会将源文件的名称空间'my_module'带到当前名称空间中,使用时必须是my_module.名字的方式

而from 语句相当于import,也会创建新的名称空间,但是将my_module中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字就可以了

例:

from my_module import read1,read2

#测试一:导入的函数read1,执行时仍然回到my_module.py中寻找全局变量money

#demo.py

from my_module import read1

money=1000

read1()

'''

执行结果:

from the my_module.py

spam->read1->money 1000

 

#测试二:导入的函数read2,执行时需要调用read1(),仍然回到my_module.py中找read1()

#demo.py

from my_module import read2

def read1():

    print('==========')

read2()

执行结果:

from the my_module.py

my_module->read2 calling read1

my_module->read1->money 1000

1.4.11 重名覆盖

如果当前有重名read1或者read2,那么会有覆盖效果

#测试三:导入的函数read1,被当前位置定义的read1覆盖掉了

#demo.py

from my_module import read1

def read1():

    print('==========')

read1()

'''

执行结果:

from the my_module.py

==========

1.4.12 强调

需要特别强调的一点是:python中的变量赋值不是一种存储操作,而只是一种绑定关系,如下:

from my_module import money,read1

money=100 #将当前位置的名字money绑定到了100

print(money) #打印当前的名字

read1() #读取my_module.py中的名字money,仍然为1000

from the my_module.py

my_module->read1->money 1000

'''

也支持as

1 from my_module import read1 as read

也支持导入多行

1 from my_module import (read1,

2                   read2,

3                   money)

from my_module  import *  把my_module 中所有的不是以下划线开头的名字导入到当前位置,大部分情况下我们的python程序不该使用这种导入方式,因为* 你不知道你导入的什么名字,很可能会覆盖掉之前已经定义好的名字,而且可读性及其差,在交互式环境中导入时没有问题。

例:

from my_module import * #将模块my_module中所有的名字都导入到当前名称空间

print(money)

print(read1)

print(read2)

print(change)

 

'''

执行结果:

from the my_module.py

<function read1 at 0x1012e8158>

<function read2 at 0x1012e81e0>

<function change at 0x1012e8268>

1.5 all/*

解释:
__all__=['money','read1'] #这样在另外一个文件中用from my_module import *就这能导入列表中规定的两个名字

*如果my_module.py中的名字前加_,_money,则from my_module import *,_money不能被导入

虑到性能的原因,每个模块只被导入一次,放入字典sys.modules中,如果你改变了模块的内容,你必须重启程序,python不支持重新加载或卸载之前导入的模块,

可能会想到直接从sys.modules中删除一个模块不就可以卸载了吗,注意了,你删了sys.modules中的模块对象仍然可能被其他程序的组件所引用,因而不会被清除。

特别的对于我们引用了这个模块中的一个类,用这个类产生了很多对象,因而这些对象都有关于这个模块的引用。

如果只是你想交互测试的一个模块,使用 importlib.reload(), e.g. import importlib;

模块的加载与修改

def func1():

    print('func1')

 

import time,importlib

import aa

 

time.sleep(20)

# importlib.reload(aa)

aa.func1()

 

测试代码

在20秒的等待时间里,修改aa.py中func1的内容,等待test.py的结果。

打开importlib注释,重新测试

1.5.1 把模块当做脚本执行 

我们可以通过模块的全局变量__name__来查看模块名:
当做脚本运行:
__name__ 等于'__main__'

当做模块导入:
__name__= 模块名

作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
if __name__ == '__main__':

def fib(n):  

    a, b = 0, 1

    while b < n:

        print(b, end=' ')

        a, b = b, a+b

    print()

 

if __name__ == "__main__":

    print(__name__)

    num = input('num :')

    fib(int(num))

模块搜索路径

python解释器在启动时会自动加载一些模块,可以使用sys.modules查看

在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用

如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。

所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块

sys.path的初始化的值来自于:

The directory containing the input script (or the current directory when no file is specified).

PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).

The installation-dependent default.

需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。

在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。

1 >>> import sys

2 >>> sys.path.append('/a/b/c/d')

3 >>> sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索

注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理。

#首先制作归档文件:zip module.zip foo.py bar.py

import sys

sys.path.append('module.zip')

import foo,bar

#也可以使用zip中目录结构的具体位置

sys.path.append('module.zip/lib/python')

 

#windows下的路径不加r开头,会语法错误

sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a')

至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。

需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。

第2章 包

定义:种通过使用‘.模块名’来组织python模块名称空间的方式

包的实质

n  无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法

n  包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)

n  import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件

强调:

n  在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错

n  创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块

2.1 目录结构

包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间

glance/                   #Top-level package

   

├── __init__.py      #Initialize the glance package

 

├── api                  #Subpackage for api

 

│   ├── __init__.py

 

│   ├── policy.py

 

│   └── versions.py

 

├── cmd                #Subpackage for cmd

 

│   ├── __init__.py

 

│   └── manage.py

 

└── db                  #Subpackage for db

 

    ├── __init__.py

 

    └── models.py

 

#policy.py

def get():

    print('from policy.py')

 

#versions.py

def create_resource(conf):

    print('from version.py: ',conf)

 

#manage.py

def main():

    print('from manage.py')

 

#models.py

def register_models(engine):

    print('from models.py: ',engine)

2.2 注意事项

.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,

n  在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。

n  对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。

2.3 对比import item 和from item import name的应用场景:

如果我们想直接使用name那必须使用后者。

def register_models(engine):

    print('from modes.py: ',engine)

输出:

C:\python3\python3.exe "E:\software\PyCharm 2016.2.1\helpers\pydev\pydevd.py" --multiproc --qt-support --client 127.0.0.1 --port 14724 --file D:/python/untitled2/python_10/lession.py

warning: Debugger speedups using cython not found. Run '"C:\python3\python3.exe" "E:\software\PyCharm 2016.2.1\helpers\pydev\setup_cython.py" build_ext --inplace' to build.

pydev debugger: process 6120 is connecting

 

Connected to pydev debugger (build 162.1628.8)

from modes.py:  mysql

2.4 from glance.api import *

在讲模块时,我们已经讨论过了从一个模块内导入所有*,此处我们研究从一个包导入所有*。

此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:

#在__init__.py中定义

x=10

 

def func():

    print('from api.__init.py')

 

__all__=['x','func','policy']

此时我们在于glance同级的文件中执行from glance.api import *就导入__all__中的内容(versions仍然不能导入)。

glance/                  

 

├── __init__.py     

 

├── api                 

 

│   ├── __init__.py   __all__ = ['policy','versions']

 

│   ├── policy.py

 

│   └── versions.py

 

├── cmd               __all__ = ['manage']   

 

│   ├── __init__.py

 

│   └── manage.py   

 

└── db                __all__ = ['models']             

 

    ├── __init__.py

 

    └── models.py

 

 

 

from glance.api import *

policy.get()

2.5 绝对导入和相对导入

我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:

l  绝对导入:以glance作为起始

l  相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)

例如:我们在glance/api/version.py中想要导入glance/cmd/manage.py

在glance/api/version.py

 

#绝对导入

from glance.cmd import manage

manage.main()

 

#相对导入

from ..cmd import manage

manage.main()

在glance/api/version.py

#绝对导入

from glance.cmd import manage

manage.main()

 

#相对导入

from ..cmd import manage

manage.main()

2.6 测试结果:注意一定要在于glance同级的文件中测试

fromglance.api import versions

注意:在使用pycharm时,有的情况会为你多做一些事情,这是软件相关的东西,会影响你对模块导入的理解,因而在测试时,要回到命令行去执行,模拟我们生产环境,

特别需要注意的是:可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式。

比如我们想在glance/api/versions.py中导入glance/api/policy.py,这俩模块是在同一个目录下

 #在version.py中

import policy

policy.get()

没错,我们单独运行version.py是一点问题没有的,运行version.py的路径搜索就是从当前路径开始的,于是在导入policy时能在当前目录下找到

但是你想啊,你子包中的模块version.py极有可能是被一个glance包同一级别的其他文件导入,比如我们在于glance同级下的一个test.py文件中导入version.py,如下

from glance.api import versions

执行结果:

ImportError: No module named 'policy'

分析:

此时我们导入versions在versions.py中执行

import policy需要找从sys.path也就是从当前目录找policy.py,

这必然是找不到的

绝对导入

glance/                  

 

├── __init__.py      from glance import api

                             from glance import cmd

                             from glance import db

 

├── api                 

 

│   ├── __init__.py  from glance.api import policy

                              from glance.api import versions

 

│   ├── policy.py

 

│   └── versions.py

 

├── cmd                 from glance.cmd import manage

 

│   ├── __init__.py

 

│   └── manage.py

 

└── db                   from glance.db import models

 

    ├── __init__.py

 

    └── models.py

 

相对导入

lance/                  

 

├── __init__.py      from . import api  #.表示当前目录

                     from . import cmd

                     from . import db

 

├── api                 

 

│   ├── __init__.py  from . import policy

                     from . import versions

 

│   ├── policy.py

 

│   └── versions.py

 

├── cmd              from . import manage

 

│   ├── __init__.py

 

│   └── manage.py    from ..api import policy  

                     #..表示上一级目录,想再manage中使用policy中的方法就需要回到上一级glance目录往下找api包,从api导入policy

 

└── db               from . import models

 

    ├── __init__.py

 

    └── models.py

2.7 单独导入包

单独导入包名称时不会导入包中所有包含的所有子模块,如

#在与glance同级的test.py中

import glance

glance.cmd.manage.main()

执行结果:

AttributeError: module 'glance' has no attribute 'cmd'

解决方法:

1 #glance/__init__.py

2 from . import cmd

3

4 #glance/cmd/__init__.py

5 from . import manage

执行

1 #在于glance同级的test.py中

2 import glance

3 glance.cmd.manage.main()

2.8 开发规范

v

#=============>bin目录:存放执行脚本

#start.py

import sys,os

 

BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

sys.path.append(BASE_DIR)

 

from core import core

from conf import my_log_settings

 

if __name__ == '__main__':

    my_log_settings.load_my_logging_cfg()

    core.run()

 

#=============>conf目录:存放配置文件

#config.ini

[DEFAULT]

user_timeout = 1000

 

[egon]

password = 123

money = 10000000

 

[alex]

password = alex3714

money=10000000000

 

[yuanhao]

password = ysb123

money=10

 

#settings.py

import os

config_path=r'%s\%s' %(os.path.dirname(os.path.abspath(__file__)),'config.ini')

user_timeout=10

user_db_path=r'%s\%s' %(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),\

                     'db')

 

 

#my_log_settings.py

"""

logging配置

"""

 

import os

import logging.config

# 定义三种日志输出格式 开始

standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \

                  '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字

 

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

 

id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'

 

# 定义日志输出格式 结束

 

logfile_dir = r'%s\log' %os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # log文件的目录

 

logfile_name = 'all2.log'  # log文件名

 

# 如果不存在定义的日志目录就创建一个

if not os.path.isdir(logfile_dir):

    os.mkdir(logfile_dir)

 

# log文件的全路径

logfile_path = os.path.join(logfile_dir, logfile_name)

 

# 配置字典

LOGGING_DIC = {

    'version': 1,

    'disable_existing_loggers': False,

    'formatters': {

        'standard': {

            'format': standard_format

        },

        'simple': {

            'format': simple_format

        },

    },

    'filters': {},

    'handlers': {

        #打印到终端的日志

        'console': {

            'level': 'DEBUG',

            'class': 'logging.StreamHandler',  # 打印到屏幕

            'formatter': 'simple'

        },

        #打印到文件的日志,收集info及以上的日志

        'default': {

            'level': 'DEBUG',

            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件

            'formatter': 'standard',

            'filename': logfile_path,  # 日志文件

            'maxBytes': 1024*1024*5,  # 日志大小 5M

            'backupCount': 5,

            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了

        },

    },

    'loggers': {

        #logging.getLogger(__name__)拿到的logger配置

        '': {

            'handlers': ['default', 'console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕

            'level': 'DEBUG',

            'propagate': True,  # 向上(更高level的logger)传递

        },

    },

}

 

 

def load_my_logging_cfg():

    logging.config.dictConfig(LOGGING_DIC)  # 导入上面定义的logging配置

    logger = logging.getLogger(__name__)  # 生成一个log实例

    logger.info('It works!')  # 记录该文件的运行状态

 

if __name__ == '__main__':

    load_my_logging_cfg()

 

#=============>core目录:存放核心逻辑

#core.py

import logging

import time

from conf import settings

from lib import read_ini

 

config=read_ini.read(settings.config_path)

logger=logging.getLogger(__name__)

 

current_user={'user':None,'login_time':None,'timeout':int(settings.user_timeout)}

def auth(func):

    def wrapper(*args,**kwargs):

        if current_user['user']:

            interval=time.time()-current_user['login_time']

            if interval < current_user['timeout']:

                return func(*args,**kwargs)

        name = input('name>>: ')

        password = input('password>>: ')

        if config.has_section(name):

            if password == config.get(name,'password'):

                logger.info('登录成功')

                current_user['user']=name

                current_user['login_time']=time.time()

                return func(*args,**kwargs)

        else:

            logger.error('用户名不存在')

 

    return wrapper

 

@auth

def buy():

    print('buy...')

 

@auth

def run():

 

    print('''

购物

查看余额

转账

    ''')

    while True:

        choice = input('>>: ').strip()

        if not choice:continue

        if choice == '1':

            buy()

if __name__ == '__main__':

    run()

#=============>db目录:存放数据库文件

#alex_json

#egon_json

 

#=============>lib目录:存放自定义的模块与包

#read_ini.py

import configparser

def read(config_file):

    config=configparser.ConfigParser()

    config.read(config_file)

    return config

#=============>log目录:存放日志

#all2.log

[2017-07-29 00:31:40,272][MainThread:11692][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!]

[2017-07-29 00:31:41,789][MainThread:11692][task_id:core.core][core.py:25][ERROR][用户名不存在]

[2017-07-29 00:31:46,394][MainThread:12348][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!]

[2017-07-29 00:31:47,629][MainThread:12348][task_id:core.core][core.py:25][ERROR][用户名不存在]

[2017-07-29 00:31:57,912][MainThread:10528][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!]

[2017-07-29 00:32:03,340][MainThread:12744][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!]

[2017-07-29 00:32:05,065][MainThread:12916][task_id:conf.my_log_settings][my_log_settings.py:75][INFO][It works!]

[2017-07-29 00:32:08,181][MainThread:12916][task_id:core.core][core.py:25][ERROR][用户名不存在]

第3章 异常处理

3.1 异常处理概要

解释:

l  python解释器检测到错误,触发异常(也允许程序员自己触发异常)

l  程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)

l  如果捕捉成功则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理

3.2 异常类别统计表

AttributeError

试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x

IOError

输入/输出异常;基本上是无法打开文件

ImportError

无法引入模块或包;基本上是路径问题或名称错误

IndentationError

语法错误(的子类) ;代码没有正确对齐

IndexError

下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]

KeyError

试图访问字典里不存在的键

KeyboardInterrupt

Ctrl+C被按下

NameError

使用一个还未被赋予对象的变量

SyntaxError Python

代码非法,代码不能编译(个人认为这是语法错误,写错了)

TypeError

传入对象类型与要求的不符合

UnboundLocalError

试图访问一个还未被设置的局部变量,基本是由于另有一个同名的全局变量导致你以为正在访问它

ValueError

传入一个调用者不期望的值,即使值的类型是正确的

3.3 处理语法

3.3.1 陈旧的处理方法:

#_*_coding:utf-8_*_

__author__ = 'Linhaifeng'

 

num1=input('>>: ') #输入一个字符串试试

if num1.isdigit():

    int(num1) #我们的正统程序放到了这里,其余的都属于异常处理范畴

elif num1.isspace():

    print('输入的是空格,就执行我这里的逻辑')

elif len(num1) == 0:

    print('输入的是空,就执行我这里的逻辑')

else:

    print('其他情情况,执行我这里的逻辑')

3.3.2 暴露的问题

l  问题一:

使用if的方式我们只为第一段代码加上了异常处理,但这些if,跟你的代码逻辑并无关系,这样你的代码会因为可读性差而不容易被看懂

l  问题二:

这只是我们代码中的一个小逻辑,如果类似的逻辑多,那么每一次都需要判断这些内容,就会倒置我们的代码特别冗长。

3.3.3 if处理小结

l  if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误你需要写重复的if来进行处理。

l  在你的程序中频繁的写与程序本身无关,与异常处理有关的if,会使得你的代码可读性极其的差

l  if是可以解决异常的,只是存在1,2的问题,所以,千万不要妄下定论if不能用来异常处理。

例:

def test():

    print('test running')

choice_dic={

    '1':test

}

while True:

    choice=input('>>: ').strip()

    if not choice or choice not in choice_dic:continue #这便是一种异常处理机制啊

    choice_dic[choice]()

3.4 python特制异常处理

解释:python为每一种异常定制了一个类型,然后提供了一种特定的语法结构用来进行异常处理

try:

     被检测的代码块

except 异常类型:

     try中一旦检测到异常,就执行这个位置的逻辑

3.4.1 处理的基础语法

解释:

next(g)会触发迭代f,依次next(g)就可以读取文件的一行行内容,无论文件a.txt有多大,同一时刻内存中只有一行内容。

提示:g是基于文件句柄f而存在的,因而只能在next(g)抛出异常StopIteration后才可以执行f.close()

f = open('a.txt')

 

g = (line.strip() for line in f)

for line in g:

    print(line)

else:

    f.close()

方法实例:

try:

    f = open('a.txt')

    g = (line.strip() for line in f)

    print(next(g))

    print(next(g))

    print(next(g))

    print(next(g))

    print(next(g))

except StopIteration:

    f.close()

3.5 处理的完整语法

try:

    # print('123')

    int('a')

except ValueError:#号码不对

    print('请输出一个数字')

except Exception as e:#信号不好

    print(e)

else:#短信发送成功

    print('打印else中的代码')

3.6 finally

try:

    # print('123')

    int('a')

except ValueError:#号码不对

    print('请输出一个数字')

except Exception as e:#信号不好

    print(e)

else:#短信发送成功

    print('打印else中的代码')

finally:#无论如何都会执行的代码,用来归还一些资源

    print('打印finally中的代码')

输出:

C:\python3\python3.exe D:/python/untitled2/python_10/client.py

请输出一个数字

打印finally中的代码

异常处理中的注意事项

def func():

    try:

        return 123

    finally:

        print('aaa')

ret = func()

print(ret)

输出

:\python3\python3.exe D:/python/untitled2/python_10/client.py

aaa

123

3.7 try语法介绍

try/except

try/finally

try/except/else

try/except/finally

try/except/else/finally

3.7.1 整体语句逻辑

try:

    #可能出异常的代码

except #(错误类型):

    # 针对这个错误的处理方式

except Exception as e:

    print(e)

else:

    print('如果try中的代码没有发现异常就执行')

finally:

    print('无论如何都要执行')

3.8 raise主动抛异常

当我自己完成一个功能,在完成的过程中有一些特定的异常,不在内置的异常类型范围内

class EvaException(BaseException):

    def __init__(self,msg):

        self.msg=msg

try:

    raise EvaException('类型错误')

except EvaException as e:

    print(e)

3.9 异常处理注意事项

l  细粒度的对个别行代码做异常处理

l  尽量针对特有的错误类型做处理

l  而不是直接用万能异常

l  一旦用万能异常一定要把错误信息打印出来

3.10 Exception应用场景

如果你想要的效果是,无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么骚年,大胆的去做吧,只有一个Exception就足够了。

l  Exception

s1 = 'hello'

try:

    int(s1)

except Exception,e:

    '丢弃或者执行其他逻辑'

    print(e)

 

#如果你统一用Exception,没错,是可以捕捉所有异常,但意味着你在处理所有异常时都使用同一个逻辑去处理(这里说的逻辑即当前expect下面跟的代码块)

 

Exception

l  多分支

.如果你想要的效果是,对于不同的异常我们需要定制不同的处理逻辑,那就需要用到多分支了

s1 = 'hello'

try:

    int(s1)

except IndexError as e:

    print(e)

except KeyError as e:

    print(e)

except ValueError as e:

    print(e)

l  多分支+Exception

s1 = 'hello'

try:

    int(s1)

except IndexError as e:

    print(e)

except KeyError as e:

    print(e)

except ValueError as e:

    print(e)

except Exception as e:

    print(e)

l  异常的其他

s1 = 'hello'

try:

    int(s1)

except IndexError as e:

    print(e)

except KeyError as e:

    print(e)

except ValueError as e:

    print(e)

#except Exception as e:

#    print(e)

else:

    print('try内代码块没有异常则执行我')

finally:

    print('无论异常与否,都会执行该模块,通常是进行清理工作')

l  主动触发

ry:

    raise TypeError('类型错误')

except Exception as e:

    print(e)

l  自定义部署

class EvaException(BaseException):

    def __init__(self,msg):

        self.msg=msg

    def __str__(self):

        return self.msg

 

try:

    raise EvaException('类型错误')

except EvaException as e:

    print(e)

l  断言

# assert 条件

 

assert 1 == 1

 

assert 1 == 2

3.11 try..except的方式比较if的方式的好处

try..except这种异常处理机制就是取代if那种方式,让你的程序在不牺牲可读性的前提下增强健壮性和容错性

异常处理中为每一个异常定制了异常类型(python中统一了类与类型,类型即类),对于同一种异常,一个except就可以捕捉到,可以同时处理多段代码的异常(无需‘写多个if判断式’)减少了代码,增强了可读性

使用try..except的方式

l  1:把错误处理和真正的工作分开来

l  2:代码更易组织,更清晰,复杂的工作任务更容易实现;

l  3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;

第4章 网络编程

定义:机器上的程序和另外一台机器上的程序通讯的工具

了解所有网络程序的底层

4.1 socket

解释:

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

4.2 tcp/udp

l  TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

l  UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)

4.3 socket参数的详解

socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)

保持客户端连接端口不被占用

4.3.1 创建socket对象的参数说明:

family   

地址系列应为AF_INET(默认值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。

(AF_UNIX 域实际上是使用本地 socket 文件来通信

type

套接字类型应为SOCK_STREAM(默认值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。

SOCK_STREAM 是基于TCP的,有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料传proto送。

SOCK_DGRAM 是基于UDP的,无保障的面向消息的socket,多用于在网络上发广播信息。

proto

协议号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应为CAN_RAW或CAN_BCM之一

fileno

如果指定了fileno,则其他参数将被忽略,导致带有指定文件描述符的套接字返回。

与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。

这可能有助于使用socket.close()关闭一个独立的插座

4.4 基于Tcp协议的socket

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

server端

import socket

sk = socket.socket()

sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字

sk.listen()          #监听链接

conn,addr = sk.accept() #接受客户端链接

ret = conn.recv(1024)  #接收客户端信息

print(ret)       #打印客户端信息

conn.send(b'hi')        #向客户端发送信息

conn.close()       #关闭客户端套接字

sk.close()        #关闭服务器套接字(可选)

client(端)

import socket

sk = socket.socket()

sk.connect(('127.0.0.1',8898))

sk.send(b'world')

print(sk.recv(1024))

sk.close()

4.5 基于UDP协议的socket

udp是无链接的,启动服务之后可以直接接受信息,不需要提前建立连接

简单使用

4.5.1 server端

import socket

udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字

udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字

msg,addr = udp_sk.recvfrom(1024)

print(msg)

udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)

udp_sk.close()                         # 关闭服务器套接字

client

import socket

ip_port=('127.0.0.1',9000)

udp_sk=socket.socket(type=socket.SOCK_DGRAM)

udp_sk.sendto(b'hello',ip_port)

back_msg,addr=udp_sk.recvfrom(1024)

print(back_msg.decode('utf-8'),addr)

4.5.2 套接字(socket)初使用

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

sever

import socket

sk = socket.socket()

sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字

sk.listen()          #监听链接

conn,addr = sk.accept() #接受客户端链接

ret = conn.recv(1024)  #接收客户端信息

print(ret)       #打印客户端信息

conn.send(b'hi')        #向客户端发送信息

conn.close()       #关闭客户端套接字

sk.close()        #关闭服务器套接字(可选)

4.5.3 qq聊天案例

(server端)

import socket

ip_port=('127.0.0.1',8081)

udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

udp_server_sock.bind(ip_port)

 

while True:

    qq_msg,addr=udp_server_sock.recvfrom(1024)

    print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))

    back_msg=input('回复消息: ').strip()

 

    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)

(client 端)

import socket

BUFSIZE=1024

udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

qq_name_dic={

    '小天涯':('127.0.0.1',8081),

    '凤凰':('127.0.0.1',8081),

    '鸡蛋':('127.0.0.1',8081),

    '样子':('127.0.0.1',8081)

}

 

while True:

    qq_name=input('请选择聊天对象:').strip()

    while True:

        msg=input('请输入消息,回车发送,输入q结束和他的聊天:').strip()

        if msg == 'q':break

        if not msg or not qq_name or qq_name not in qq_name_dic:continue

        udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])

        back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)

        print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' % (addr[0], addr[1], back_msg.decode('utf-8')))

 

    udp_client_socket.close()

4.5.4 时间服务器

server(服务器)

# _*_coding:utf-8_*_

from socket import *

from time import strftime

 

ip_port = ('127.0.0.1', 9000)

bufsize = 1024

 

tcp_server = socket(AF_INET, SOCK_DGRAM)

tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

tcp_server.bind(ip_port)

 

while True:

    msg, addr = tcp_server.recvfrom(bufsize)

    print('===>', msg)

 

    if not msg:

        time_fmt = '%Y-%m-%d %X'

    else:

        time_fmt = msg.decode('utf-8')

    back_msg = strftime(time_fmt)

 

    tcp_server.sendto(back_msg.encode('utf-8'), addr)

 

tcp_server.close()

client(客户端)

#_*_coding:utf-8_*_

from socket import *

ip_port=('127.0.0.1',9000)

bufsize=1024

 

tcp_client=socket(AF_INET,SOCK_DGRAM)

 

while True:

    msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()

    tcp_client.sendto(msg.encode('utf-8'),ip_port)

 

    data=tcp_client.recv(bufsize)

4.6 端口占用解决方法

server(端)

#加入一条socket配置,重用ip和端口

import  socket

sk = socket.socket()

sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

sk.bind('127.0.0.1',8899)

sk.listen()

while True:

    conn,addr  =  sk.accept()

    while True:

        msg = input('>>>').encode('utf-8')

        conn.send(msg)

        if  msg == b'q':break

        ret = conn.recv(1024)

        if  ret == b'q':break

        print(ret.decde('utf-8'))

    conn.close()

sk.close()

client(端)

import socket

sk = socket.socket()

sk.connect(('127.0.0.1',8899))

while True:

    msg = sk.recv(1024).decode('utf-8')

    if msg == 'q':break

    print(msg)

    inp = input('>>>')

    sk.send(inp.encode('utf-8'))

    if inp == 'q':break

sk.close()

第5章 黏包

5.1 基于tcp出现的现象

由于tcp协议对数据有拆包和包的机制,所以导致数据与数据之间是没有条与条之间的限制的

又由于我接受数据是按照字节接受的,所以如果接收端有需要字节数的数据,那么这些数据就都会一股脑的交给用户

5.2 发生黏包的两种情况

n  发送方的缓存机制

送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

例:

server端

#_*_coding:utf-8_*_

from socket import *

ip_port=('127.0.0.1',8080)

 

tcp_socket_server=socket(AF_INET,SOCK_STREAM)

tcp_socket_server.bind(ip_port)

tcp_socket_server.listen(5)

conn,addr=tcp_socket_server.accept()

data1=conn.recv(10)

data2=conn.recv(10)

print('----->',data1.decode('utf-8'))

print('----->',data2.decode('utf-8'))

conn.close()

client端

#_*_coding:utf-8_*_

import socket

BUFSIZE=1024

ip_port=('127.0.0.1',8080)

 

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

res=s.connect_ex(ip_port)

 

 

s.send('hello'.encode('utf-8'))

s.send('egg'.encode('utf-8'))

n  接收方的缓存机制

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

server端

#_*_coding:utf-8_*_

from socket import *

ip_port=('127.0.0.1',8080)

 

tcp_socket_server=socket(AF_INET,SOCK_STREAM)

tcp_socket_server.bind(ip_port)

tcp_socket_server.listen(5)

 

 

conn,addr=tcp_socket_server.accept()

 

 

data1=conn.recv(2) #一次没有收完整

data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的

 

print('----->',data1.decode('utf-8'))

print('----->',data2.decode('utf-8'))

 

conn.close()

client端

#_*_coding:utf-8_*_

import socket

BUFSIZE=1024

ip_port=('127.0.0.1',8080)

 

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

res=s.connect_ex(ip_port)

s.send('hello egg'.encode('utf-8'))

5.3 小结

黏包现象只发生在tcp协议中:

n  从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

n  实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

5.4 解决黏包问题

客户端

import struct

import socket

 

sk = socket.socket()

sk.connect(('127.0.0.1',8080))

 

num = struct.unpack('i',sk.recv(4))[0]

print(sk.recv(num).decode('utf-8'))

num = struct.unpack('i',sk.recv(4))[0]

print(sk.recv(num).decode('utf-8'))

sk.close()

# import struct

# import socket

#

# sk = socket.socket()

# sk.bind(('127.0.0.1',8080))

# sk.listen()

#

# conn,addr = sk.accept()

# a = input('>>>').encode('utf-8')

# b = input('>>>').encode('utf-8')

# lena = struct.pack('i',len(a))

# lenb = struct.pack('i',len(b))

# conn.send(lena+a)

# conn.send(lenb+b)

# conn.close()

# sk.close()

 

# import struct

# ret = struct.pack('i',172848762)

# print(ret)

# print(struct.unpack('i',ret)[0])

 

 

解决方法:

服务端

#_*_coding:utf-8_*_

import socket,subprocess

ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

 

s.bind(ip_port)

s.listen(5)

 

while True:

    conn,addr=s.accept()

    print('客户端',addr)

    while True:

        msg=conn.recv(1024)

        if not msg:break

        res=subprocess.Popen(msg.decode('utf-8'),shell=True,\

                            stdin=subprocess.PIPE,\

                         stderr=subprocess.PIPE,\

                         stdout=subprocess.PIPE)

        err=res.stderr.read()

        if err:

            ret=err

        else:

            ret=res.stdout.read()

        data_length=len(ret)

        conn.send(str(data_length).encode('utf-8'))

        data=conn.recv(1024).decode('utf-8')

        if data == 'recv_ready':

            conn.sendall(ret)

    conn.close()

客户端

#_*_coding:utf-8_*_

import socket,time

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

res=s.connect_ex(('127.0.0.1',8080))

 

while True:

    msg=input('>>: ').strip()

    if len(msg) == 0:continue

    if msg == 'quit':break

 

    s.send(msg.encode('utf-8'))

    length=int(s.recv(1024).decode('utf-8'))

    s.send('recv_ready'.encode('utf-8'))

    send_size=0

    recv_size=0

    data=b''

    while recv_size < length:

        data+=s.recv(1024)

        recv_size+=len(data)

 

 

    print(data.decode('utf-8'))

第6章 拾遗

错误提示:addrress already  inuse

解决方法:

#加入一条socket配置,重用ip和端口

import socket

from socket import SOL_SOCKET,SO_REUSEADDR

sk = socket.socket()

sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加

sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字

sk.listen()          #监听链接

conn,addr = sk.accept() #接受客户端链接

ret = conn.recv(1024)   #接收客户端信息

print(ret)              #打印客户端信息

conn.send(b'hi')        #向客户端发送信息

conn.close()       #关闭客户端套接字

sk.close()        #关闭服务器套接字(可选)

猜你喜欢

转载自www.cnblogs.com/wang-xd/p/9371783.html
今日推荐