Python_oldboy_自动化运维之路_函数,装饰器,模块,包(六)
本节内容
- 上节内容回顾(函数)
- 装饰器
- 模块
- 包
1.上节内容回顾(函数)
函数
1.为什么要用函数?
使用函数之模块化程序设计,定义一个函数就相当于定义了一个工具,需要用的话直接拿过来调用。
不使用模块化程序设计的缺点:
- 体系结构不清晰,可读写差
- 可扩展性差
- 程序冗长
2.定义函数分三种
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ # #1.无参函数 def foo(): print('in the foo') foo() #2.有参函数 def bar(x,y): print('in the bar') bar(1,2) #3.定义空函数 def func(): pass def get(): pass def put(): pass
3.调用函数分三种
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ # #1.语句形式 def foo(): print('in the foo') foo() #2.表达式的形式 def my_max(x,y): if x > y: return x else: return y res = my_max(1,2) res = 10*my_max(1,2) #3.作为另外一个函数的参数 my_max(1,my_max(2,3))
4.函数返回值的三种形式
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ # #1.如果不定义返回值,则返回值为空。 def foo(): pass res = foo() print(res) #2.返回一个值,返回值可以是任意的数据类型 def foo(): return 1 res = foo() print(res) #3.返回多个值 def foo(): return 1,'w',[1,2,3] res = foo() print(res)
5.函数的参数
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ # #形参和实参的对应关系: #实参是真是占用内存空间的func(1,2) #形参只有在函数调用的时候才会接收值,占用内存空间,等函数调用完就会释放。 def func(x,y): print(x) print(y) func(1,2) func(1,'a') #形参和实参的动态性:定义形参的时候不用指定数据类型 #坏处:不指定数据类型,若执行以下代码会报错 def test(x,y): return x+y test(1,'a') #解决方法一: def test(x,y): if type(x) is int and type(y) is int: return x+y test(1,'a') #解决方法二:添加注释 def test(x,y): ''' :param x: int :param y: int :return: ''' return x+y test(1,'a')
6.位置参数,默认参数,*args,**kwargs
#从实参的角度:针对同一个形参,我们要么按照位置要么按照关键字为形参传值,不能对同一个形参赋值 # def foo(x,y): # print(x,y) # # foo(1,2) #按位置 # foo(y=2,x=1) #按照关键字的形式,必须写在位置关系的后边 #从形参的角度:位置参数,默认参数,可变长参数*args,**kwargs # def test(x,y,z): #位置参数,必传值参数 # print(x) # print(y) # print(z) # # test(1,y=2,z=3) # def test2(x,y=1): #默认参数 # print(x) # print(y) # # test2(1,2) # test2(1) # def test3(x,y=1,*args): #*args 必须放在在默认参数后面,会将多余的组成一个元组的形式 # print(x) # print(y) # print(args) # # test3(1,2,3,4,5,6,7,8,9,) # l = ['a','b'] # test3(1,2,*l) #*args的形式就等于1,2,3,4,5.l # test3(1,2,'a','b') #*args的形式就等于1,2,3,4,5 # l = ('a','b') # test3(1,2,*l) #l可以是列表,可以是元组,结果都一样 # def test3(x,y,z): #*的用法 # print(x,y,z) # # l=[1,2,3] # test3(*l) #等同于test3(1,2,3) # def foo(x,**kwargs): #**kwargs的用法 # print(x) # print(kwargs) # # foo(1,y=2,z=3) # dic = {'a':1,'b':2} # foo(1,**dic) #等同于foo(1,a=1,b=2) #位置参数>默认参数>*args>**kwargs def func(x,*args,**kwargs): #这样定义就表示函数可以传任意的参数。 print(x) print(args) print(kwargs)
总结:
*sym 等同于展开按照位置的方式去写
**sym 等同于把sym展开按照关键字的方式去写
7.函数是第一类对象:意思就是函数可以被当做数据来传递
这样执行为什么不会报错?因为函数就和变量一样,将qasd...赋值给了fun,只有在调用的时候才会检查其中的语法是否正确。
结论:
- 函数的定义和变量的定义是一回事,变量有什么特性,函数就有什么特性
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ # #函数是第一类对象 def func(): print('in the func') # #1.可以被引用 # f1=func # f1() #2.可以作为参数(高阶函数) def foo(x): x() foo(func) #3.返回值可以是函数 def foo1(): return func res=foo1() res() #4.可作为容器类型的元素 func_dic={ 'func':func } func_dic['func']()
8.函数的嵌套
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ # #嵌套调用:在一个函数的内部调用另外一个函数 #举列:比较四个数的大小 def my_max4(a,b,c,d): res1=my_max2(a,b) res2=my_max2(res1,c) res3=my_max2(res2,d) return res3 def my_max2(x,y): if x > y: return x else: return y print(my_max4(1,2,100,-1)) #嵌套定义 x=1 def f1(): x=10 def f2(): x=100 print(x) return f2 func=f1() func()
9.闭包函数
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ #闭包函数:打包内部函数(f2)的时候包含对外部作用域名字的引用(x=1),f2就被称为闭包函数 #用处:什么使用用,什么时候执行就可以了 def f1(): x=1 def f2(): print(x) return f2 f=f1() x=100000000000 f() #举列:获取baidu的页面 from urllib.request import urlopen #导入模块 def page(url): # url='http://www.baidu.com' def get(): return urlopen(url).read().decode('utf-8') return get baidu=page('http://www.baidu.com') print(baidu())
2.装饰器
装饰器:在遵循下面两个原则的前提下为装饰者添加新功能
必须遵循两个原则:
- 一定不能修改源代码
- 不能修改调用方式
#语法如下:就相当于将index传递给func,然后wrapper的结果给了timer()函数,然后timer函数就等于index #举列:给原始的函数index显示出运行的时间 import time def timer(func): def wrapper(): start_time = time.time() func() #这个就是在调用最原始的函数 stop_time = time.time() print('run time is %s' %(start_time-start_time)) return wrapper @timer #index=time(index) def index(): print('in the index') index()
#举列:传人多个参数和返回值的用法 import time def timer(func): def wrapper(*args,**kwargs): #('test'),{'msg'='tom'} start_time = time.time() res=func(*args,**kwargs) #home('test',msg='tom')---->home(user,msg) # func(msg) #这个就是在调用最原始的函数 stop_time = time.time() print('run time is %s' %(start_time-start_time)) return res return wrapper @timer #index=time(index(msg)) def index(msg): print('in the index',msg) @timer def home(user,msg): print('in the home %s %s' %(user,msg)) return 1 index('hello world') home('test',msg='tom') res=home('test',msg='tom') print(res)
#装饰器小知识:默认被装饰器的注释信息不会被调用,只会调用wrapper里的信息 #解决方法:导入functools,然后在wrapper上面在装饰 import time,functools def timer(func): @functools.wraps(func) def wrapper(*args,**kwargs): #('test'),{'msg'='tom'} ''' wrapper func :param args: :param kwargs: :return: ''' start_time = time.time() res=func(*args,**kwargs) #home('test',msg='tom')---->home(user,msg) stop_time = time.time() print('run time is %s' %(start_time-start_time)) return res return wrapper @timer #index=time(index(msg)) def index(msg): ''' 注释信息 :param msg: :return: ''' print('in the index',msg) return 1 res=index('hello world') print(res) print(help(index))
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ #需求:我要想执行index,必须先让我输入正确的用户名和密码才能让你执行 # def auth(func): # def wrapper(*args,**kwargs): # user = 'root' # passwd = 'root123' # newuser = input('user:') # newpasswd = input('passwd:') # if user == newuser and passwd == newpasswd: # return func(*args,*kwargs) # return wrapper # # @auth # def index(msg): # print("helo : %s" %(msg)) # # # index('lijun') #有参数的装饰器 #需求:现在我的index函数程序基于两种的验证方式,我的用户名和密码有可能是保存在本地,有可能是保存在数据库 def auth_test(type): def auth(func): def wrapper(*args,**kwargs): user = 'root' passwd = 'root123' newuser = input('user:') newpasswd = input('passwd:') if type == 'mysql': print('这个是mysql的验证方式') return func(*args,**kwargs) elif type == 'file': if user == newuser and passwd == newpasswd: return func(*args,*kwargs) else: pass #可以是其他的验证方式 return wrapper return auth @auth_test('file') #等同于auth_test(mysql)-->执行的结果假如为res---->@res index=auth(index) def index(msg): print("helo : %s" %(msg)) index('lijun')
#需求:假如我还有个函数,我第一次已经登陆成功过,在运行这个函数就不需要我在输入用户名和密码 #应用场景:假如你已经登陆了京东,在打开第二个页面是否还让你输入用户名和密码 accounts = {} #先定义一个字典,登陆成功后就加进去 current_login_user = None #定义一个全局变量,登陆成功后改这个变量 def auth_test(type): def auth(func): def wrapper(*args,**kwargs): if current_login_user not in accounts: #判断是否已经认证成功 user = 'root' passwd = 'root123' newuser = input('user:') newpasswd = input('passwd:') if type == 'mysql': print('这个是mysql的验证方式') return func(*args,**kwargs) elif type == 'file': if user == newuser and passwd == newpasswd: accounts[user] = passwd #给字典传值{'root':'root123'} global current_login_user #先声明改这个全局变量 current_login_user = user return func(*args,*kwargs) else: return func(*args,**kwargs) return wrapper return auth @auth_test('file') #等同于auth_test(mysql)-->执行的结果假如为res---->@res index=auth(index) def index(msg): print("helo : %s" %(msg)) @auth_test('mysql') def home(): print('This is home') index('lijun') #运行第一个函数 home() #运行第二个函数
3.模块
什么是模块?
一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀
为什么要用模块?
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用.
如何使用模块?
3.1.import
>示例:spam.py,文件名spam.py,模块名spam
#spam.py文件 print('There is spam') mony = 100 def read(): print(mony) ################################### #在另外一个python脚本操作 #import spam #import spam #只会执行spam import spam print(spam.mony) spam.read() ''' 第一次导入模块做了三件事: 1.创建新的作用域 2.在该作用域中执行顶级代码 3.得到一个模块名,绑定到该模块内的代码 ''' #输出: There is spam 100 100
>导入的模块只对该模块的作用域有效
#spam.py money = 100 def change(): #修改全局变量 global money money = 0 #另外一个脚本操作 import spam mony = 100000 spam.change() print(mony) #输出: 100000
>为模块名起别名,相当于m1=1;m2=m1
import spam as sm print(sm.money)
为已经导入的模块起别名的方式对编写可扩展的代码很有用,假设有两个模块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)
>一行当中导入多个模块
import sys,os,re
3.2.from...import.... 缺点:容易重名
>用法,直接调用,不需要前面加模块名
#spam.py money = 100 def change(): #修改全局变量 global money money = 0 #另外一个脚本操作 from spam import change mony = 100000 change() print(mony) #输出: 100000
>若函数和当前的脚本重名,会执行当前的函数
#spam.py def change(): print("spam里的函数") #另外一个脚本 from spam import change def change(): print("当前的脚本") change() #输出 当前的脚本
>也支持as,也支持导入多行
from spam import read1 as read from spam import (read1, read2, money)
>from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置
#spam.py def change(): print("spam里的函数") def read1(): print("from read1") def _read2(): print('from read2') #另外一个脚本执行 from spam import * change() read1() _read2() #输出:_read2会报错 Traceback (most recent call last): File "D:/pycharm/s16/day4/模块导入.py", line 32, in <module> _read2() NameError: name '_read2' is not defined spam里的函数 from read1
>__all__ 的用法,注意是两个下划线。
#spam.py __all__ = ['read1','read3'] #只有用from spam import *的方法导入模块的时候,只允许调用里面的函数 def change(): print("spam里的函数") def read1(): print("from read1") def _read2(): print('from read2') def read3(): print('from read3') #另外一台脚本操作 from spam import * read1() read3() #change () #若要导入change模块会报错
3.3把模块当做脚本执行
我们可以通过模块的全局变量__name__来查看模块名:
当做脚本运行:
__name__ 等于'__main__'
当做模块导入:
__name__等于'spam'
作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
if __name__ == '__main__':
#spam.py print(__name__) if __name__ == '__main__': print("文件被当做脚本执行时触发的代码") else: print("文件被当做模块模块时触发的代码") #当做脚本执行的结果 __main__ 文件被当做脚本执行时触发的代码 #当做模块执行的结果,在另外一个脚本执行 from spam import * #输出 spam 文件被当做模块模块时触发的代码
3.4 模块搜索路径
#在D:\pycharm\s16\day4\dri'的目录下有个test_path.py的脚本 print("这个是test_path模块") #在D:\pycharm\s16\day4的目录下执行 # -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ ''' import test 导入模块时首先要做的事情: 第一步:先去python内置的模块去找,看是否有该模块名。 第二步:然后去sys.path的目录按照顺序当中找是否有test的模块。 ''' #举列,假如我在当前目录下建立个dir目录,想要在dir目录下执行test_path脚本 import sys #print(sys.path) sys.path.append(r'D:\pycharm\s16\day4\dri') print(sys.path) #sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索 import test_path #输出 ['D:\\pycharm\\s16\\day4', 'D:\\pycharm\\s16', 'C:\\Python35\\python35.zip', 'C:\\Python35\\DLLs', 'C:\\Python35\\lib', 'C:\\Python35', 'C:\\Python35\\lib\\site-packages', 'D:\\pycharm\\s16\\day4\\dri'] 这个是test_path模块
至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。
需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。
4.包
Packages are a way of structuring Python’s module namespace by using “dotted module names”
包是一种通过使用‘.模块名’来组织python模块名称空间的方式。
无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
包的本质就是一个包含__init__.py文件的目录。
包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
1 #文件内容 2 3 #policy.py 4 def get(): 5 print('from policy.py') 6 7 #versions.py 8 def create_resource(conf): 9 print('from version.py: ',conf) 10 11 #manage.py 12 def main(): 13 print('from manage.py') 14 15 #models.py 16 def register_models(engine): 17 print('from models.py: ',engine)
4.1注意事项
1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。
2.对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
3.对比import item 和from item import name的应用场景:
如果我们想直接使用name那必须使用后者。
4.2 import和from....import导入包
# -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ #验证的脚本是在glance同级别目录下 #import的方法导入,或者可以起别名 import glance.db.models glance.db.models.register_models('mysql') import glance.db.models as a a.register_models('起别名后的') #from ... import ...的方法导入 #需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法 from glance.db.models import register_models register_models('from的方法')
#输出 from models.py: mysql from models.py: 起别名后的 from models.py: from的方法
4.3 __init__.py文件
不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码。
#分别在glance下的__init_.py和db下的__init__.py写入内容 #输出 --------------glance package------------ --------------db package------ from models.py: mysql from models.py: 起别名后的 from models.py: from的方法
4.4 from....import * 的用法
#api/__init__.py文件 # -*- coding: UTF-8 -*- #blog:http://www.cnblogs.com/linux-chenyang/ print('--------------api package------') __all__=['versions','policy'] #包的导入文件,和glance同级 #运行from import *就是代表运行api包下的__init__.py的文件 #若想要运行api下的其他模块,在api下的__init__.py用__all__ = []来指定 from glance.api import * policy.get() versions.create_resource('versions函数') #输出 --------------glance package------------ --------------api package------ from policy.py from version.py: versions函数
4.5 绝对导入和相对导入
#需求1:policy.py文件调用versions.py里的create_resource函数,在glance同级目录下执行 #policy文件 def get(): print('from policy.py') from glance.api import versions #要想在最外层调用模块,必须用from import的方式,在当前脚本执行会报错,除非加上环境变量 versions.create_resource('这是versions的函数') #glance同级目录下执行 import glance.api.policy glance.api.policy.get() #输出: --------------glance package------------ --------------api package------ from version.py: 这是versions的函数 from policy.py #需求2:在glance/api/version.py中想要导入glance/cmd/manage.py,在glance同级目录下执行 #version文件 def create_resource(conf): print('from version.py: ', conf) #绝对导入 from glance.cmd.manage import main main() #相对导入 from ..cmd.manage import main #一个.表示上一级目录,两个点表示上上级目录 main() #glance同级目录下执行 import glance.api.versions glance.api.versions.create_resource('aa') 输出: --------------glance package------------ --------------api package------ from manage.py from manage.py from version.py: aa
总结:from....import...的方法用来导入子包,import的方法用来导入内置的和第三方模块
4.6 单独导入包
#在与glance同级的test.py中 import glance glance.db.models.register_models('单独导入') ''' 执行结果: AttributeError: module 'glance' has no attribute 'db' ''' #解决方法 #glance/__init__.py from . import db #glance/db/__init__.py from . import models #输出结果: --------------glance package------------ --------------db package------ from models.py: 单独导入
千万别问:__all__不能解决吗,__all__是用于控制from...import * ,fuck
补充:from import的用法
包可以用“from 目录 import 模块名”这种方式来导入,但是目录不行,只能用‘from 模块 import 函数’