Python学习笔记第四周

it2025-01-29  17

目录

  一、基础概念

    1、装饰器

      1、装饰器本质就是函数

      2、实现装饰器知识储备

        1、函数即变量

        2、高阶函数

        3、嵌套函数

      例子

        1、将一个函数名作为实参传递给另外一个函数

        2、返回值为函数名

        3、函数嵌套函数

        4、完整装饰器例子

        5、装饰器高级版本

    2、列表生成器

    3、生成器

    4、斐波那契数列

    5、迭代器

    6、内置函数

    7、json、pickle序列化

    8、软件目录结构规范

一、基础概念

1、装饰器:

  1、装饰器本质上就是函数,用来装饰其他函数

  原则:装饰器写好后,原则上不能修改其他函数源代码

    1、不能修改被装饰函数源代码

    2、不能修改被装饰函数调用方式

    总结成一点就是装饰器对被装饰函数完全透明,用这个函数的人完全不知道装饰器的存在

  2、实现装饰器的知识储备

    1、函数即变量

     在python中,变量定义值,在内存中开辟一块空间存放该变量值,当变量名不再引用该变量值后,python解释器会自动回收该值所在内存,函数也是如此,通过函数名调用函数体,当函数名不再引用该函数体,函数体从内存中被解释器回收。

          2、高阶函数

         a)把一个函数名作为实参传递给另外一个函数,在不修改被装饰函数源代码情况下为其添加功能

         b)返回值为该函数名,不修改函数的调用方式

    3、嵌套函数

      在一个函数内通过def声明一个函数

  装饰器=高阶函数+嵌套函数

 例子

  1、将一个函数名作为实参传递给另外一个函数

def test1(func): #传入函数名作为实参 func() def bar(): print('in the bar!') test1(bar) #输出: in the bar! import time def bar(): print('in the bar!') def test1(func): start_time = time.time() func()# 运行bar函数 stop_time = time.time() print('the func running time is %s' %(stop_time-start_time)) test1(bar) #输出: in the bar! the func running time is 8.58306884765625e-05

  2、返回值为函数名

import time def bar(): print('in the bar!') def test1(func): print(func) return func bar = test1(bar) #将bar函数名作为参数传递给函数test1内,同时返回值为bar函数名而不是bar函数执行结果,并将其重新赋值给bar bar() #执行bar()会先执行test1,打印bar对应的内存地址,然后执行bar函数对应的函数体内容 #输出: <function bar at 0x289a464> in the bar!

  3、函数嵌套函数

def foo(): print('in the foo') def bar(): print('in the bar') return bar() #执行foo时会返回bar函数执行结果 foo() #输出: in the foo in the bar

  4、完整装饰器例子

import time def deco(func): def wrapper(*args,**kwargs): #无论原始函数自身带任何参数均可以在这包含 start_time = time.time() func(*args,**kwargs) #当test1传入时,执行test1的返回结果,如果源函数携带参数,这里可以在执行原函数时带源函数所带参数 stop_time = time.time() print('the func time is %s' %(stop_time-start_time)) return wrapper #直接返回函数名 @deco #@deco 等价于 test1 = deco(test1) def test1(name): time.sleep(3) print('in the test1') print('the name is %s' %name) test1('gavin') #输出: in the test1 the name is gavin the func time is 3.0022261142730713 import time def deco(func): def wrapper(*args,**kwargs): #无论原始函数自身带任何参数均可以在这包含 start_time = time.time() func(*args,**kwargs) #当test1传入时,执行test1的返回结果,如果源函数携带参数,这里可以在执行原函数时带源函数所带参数 stop_time = time.time() print('the func time is %s' %(stop_time-start_time)) return wrapper #直接返回函数名 @deco #@deco 等价于 test1 = deco(test1) def test1(name): time.sleep(3) #print('in the test1') #print('the name is %s' %name) return name #返回name值 print(test1('gavin')) #输出: the func time is 3.0015320777893066 None #用该方法没法返回源函数需要返回的参数 import time def deco(func): def wrapper(*args,**kwargs): #无论原始函数自身带任何参数均可以在这包含 start_time = time.time() res = func(*args,**kwargs) #当test1传入时,执行test1的返回结果,如果源函数携带参数,这里可以在执行原函数时带源函数所带参数 stop_time = time.time() print('the func time is %s' %(stop_time-start_time)) return res #当需要被修饰函数有返回值时,可以在装饰器中将其返回 return wrapper #直接返回函数名 @deco #@deco 等价于 test1 = deco(test1) def test1(name): time.sleep(3) #print('in the test1') #print('the name is %s' %name) return name print(test1('gavin')) #输出 the func time is 3.00175404548645 gavin

   5、装饰器高级版本:通过装饰器来划分不同的登录认证界面

#版本一,通过不带参数的auth装饰器来完成home与bbs认证 user,passwd = 'gavin','123' def auth(func): def wrapper(*args,**kwargs): username = input('usernmae: ').strip() password = input('password: ').strip() if user == username and passwd == password: print('\033[32;1mauthticatin passed\033[32;0m'.center(50,'@')) func(*args,**kwargs) else: exit('wrong input!') return wrapper @auth def home(): print('in the home page!') @auth def bbs(): print('in the bbs page!') home() bbs() #输出: usernmae: gavin password: 123 @@@@@@@@@authticatin passed@@@@@@@@@ in the home page! usernmae: gavin password: a wrong input! user,passwd = 'gavin','123' def auth(func): def wrapper(*args,**kwargs): username = input('usernmae: ').strip() password = input('password: ').strip() if user == username and passwd == password: print('\033[32;1mauthticatin passed\033[32;0m'.center(50,'@')) res = func(*args,**kwargs) #将home函数对应的运行结果返回 return res else: exit('\033[31;1mwrong input!\033[32;0m') return wrapper @auth def home(): return 'home page' #需要装饰器可以返回该返回值 @auth def bbs(): print('in the bbs page!') print(home()) bbs() #输出: usernmae: gavin password: 123 @@@@@@@@@authticatin passed@@@@@@@@@ home page usernmae: gavin password: 123 @@@@@@@@@authticatin passed@@@@@@@@@ in the bbs page! user,passwd = 'gavin','123' def auth(auth_type): #装饰器携带的参数会在第一层传入装饰器 def outer(func): #装饰器要装饰的源函数会在第二层传入到装饰器 def wrapper(*args,**kwargs): if auth_type == 'local': username = input('usernmae: ').strip() password = input('password: ').strip() if user == username and passwd == password: print('\033[32;1mauthticatin passed\033[32;0m'.center(50,'@')) res = func(*args,**kwargs) #将home函数对应的运行结果返回 return res else: exit('\033[31;1mwrong input!\033[32;0m') elif auth_type == 'ldap': print('暂时不支持') func(*args,**kwargs) return wrapper return outer @auth(auth_type = 'local') def home(): return 'home page' #需要装饰器可以返回该返回值 @auth(auth_type = 'ldap') def bbs(): print('in the bbs page!') print(home()) bbs() #输出 usernmae: gavin password: 123 @@@@@@@@@authticatin passed@@@@@@@@@ home page 暂时不支持 in the bbs page!

 

 

2、列表生成器

>>> a = [ i * 2 for i in range(10)] >>> a [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

 

3、生成器:generator

为了避免列表中数据过多占用太多内存空间,导致系统不可用,使用生成器来替代列表产生数据序列

>>> b = (i * 2 for i in range(10)) >>> b <generator object <genexpr> at 0x10223d7d8>

此时b为生成器,只有在调用的时候才会产生数据,所以使用通常的b[5]调用列表的方法是没法得到数据的

>>> b[2] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'generator' object is not subscriptable

生成器只有在调用时才会产生相应的数据,同时只记录当前的位置,只有通过__next__()方式或者next()内置函数才能调用

 

>>> b.__next__() 0 >>> b.__next__() 2 >>> b.__next__() 4 >>> c = next(b) >>> print(c) 6

>>> b = (i * 2 for i in range(10)) 

>>> for n in b:print(n) #也可以使用for循环一次性全部取出,这样可以不用使用next方法同时避免最后遇到StopIteration错误... 024681012141618

4、斐波那契数列

fibonacci除了第一个和第二个数外,其他任意数都是前面两个数相加得到的

def fib(max): n, a, b = 0, 0, 1 while n < max: yield b #yield类似断点,执行到这里会跳出生成器,然后下一次进入生成器会从这个位置进入继续执行 a, b = b, a + b #yield会保存函数的中断状态 n = n + 1 return 'Done' res = fib(6) for n in res: #同样斐波那契函数也可以使用for循环方式取到所有想要的数值,同时也避免使用next方法,但是有一点for循环没法做到,就是返回return的值 print(n) #输出 1 1 2 3 5 8

如果想要取到return返回的值,需要使用下面的方法

def fib(max): n, a, b = 0, 0, 1 while n < max: yield b #yield类似断点,执行到这里会跳出生成器,然后下一次进入生成器会从这个位置进入继续执行 a, b = b, a + b #yield会保存函数的中断状态 n = n + 1 return 'Done' res = fib(6) while True: try: x = next(res) #next()为内置函数,类似于__next__() print('res:', x) except StopIteration as e: #如果想要获得return返回的值,必须抓到StopIteration错误 print('Generator return valuel:', e.value) break #当结束后会进入exception,同时需要跳出该循环 #输出 res: 1 res: 1 res: 2 res: 3 res: 5 res: 8Generator return valuel: Done

通过协程方法达到多线程效果:

#版本一:单独传入next与send,查看yield与send方法使用 import time def consumer(name): print('%s准备吃包子啦' %name) while True: baozi = yield #yield保存当前状态,返回时直接返回到这个状态,但是单纯调用next不会给yield传值 print('%s包子来啦,被%s吃了!' %(baozi,name)) eat = consumer('gavin') eater = next(eat) #只唤醒yield,不会给它传值 baozi1 = '韭菜馅' eat.send(baozi1) #send可以调用yield,同时给yield传值,也就是唤醒yield同时给它传值 #输出 gavin准备吃包子啦 韭菜馅包子来啦,被gavin吃了! #版本二:通过协程方式达到并行效果 import time def consumer(name): print('%s准备吃包子啦' %name) while True: baozi = yield #yield保存当前状态,返回时直接返回到这个状态,但是单纯调用next不会给yield传值 print('%s包子来啦,被%s吃了!' %(baozi,name)) def producter(): c1 = consumer('gavin')#这个动作只是把它变成生成器 c2 = consumer('alex') c1.__next__()#需要执行next才会执行生成器 c2.__next__() print('老子开始做包子啦!') for i in range(1,5): time.sleep(1) print('做了一个包子,分两半,一个白菜馅,一个韭菜馅') c1.send(i) c2.send(i) producter() #输出 gavin准备吃包子啦 alex准备吃包子啦 老子开始做包子啦! 做了一个包子,分两半,一个白菜馅,一个韭菜馅 1包子来啦,被gavin吃了! 1包子来啦,被alex吃了! 做了一个包子,分两半,一个白菜馅,一个韭菜馅 2包子来啦,被gavin吃了! 2包子来啦,被alex吃了! 做了一个包子,分两半,一个白菜馅,一个韭菜馅 3包子来啦,被gavin吃了! 3包子来啦,被alex吃了! 做了一个包子,分两半,一个白菜馅,一个韭菜馅 4包子来啦,被gavin吃了! 4包子来啦,被alex吃了!

 

5、迭代器

可直接作用于for循环的数据结构包括:

  1:集合类型数据:list、tuple、dict、set、str

  2、generator,包括生成器和带yield的generator function函数

这些可以直接作用于for循环对象统称为可迭代对象Iterable

可以使用isinstance()判断一个对象是否是Iterable

>>> from collections import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance((), Iterable) True >>> isinstance((x for x in range(20)), Iterable) True

生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopItrable错误表示没法继续返回下一个值

可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator

>>> isinstance((x for x in range(20)), Iterator) True >>> isinstance({}, Iterator) False >>> isinstance([], Iterator) False >>> isinstance((), Iterator) False

生成器都是Iterator对象,但是list dict set虽然是Iterable但是不是Iterator,如果想把它们变为Iterator,可以使用iter()函数

>>> isinstance(iter(()), Iterator) True >>> isinstance(iter([]), Iterator) True >>> isinstance(iter({}), Iterator) True

 

Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

6、内置函数

>>> abs(-1) #取绝对值 1 >>> print(all([-1])) #如果里面包括0为false其余都为true,这个相当于and,只要有false就为false True >>> print(all([0,1])) False >>> print(any([0,1])) #只要有真就为true,不管有没有false True >>> print(any([0,-1])) True >>> print(ascii('ä¸中å午')) #将值转换为asii码 '\xe4\xb8\u4e2d\xe5\u5348' >>> print(bin(1)) #将十进制转为二进制 0b1 >>> bool([]) False >>> bool([1]) #布尔函数,判断真假 True >>> bool([-1]) True >>> a = bytearray('abcde', encoding='utf-8') #可以通过asii码的形式修改字符串 >>> print(a[1]) 98 >>> a[1] = 50 #将b替换为2 >>> print(a) bytearray(b'a2cde') >>> print(callable([])) #是否可以包含括号,列表不能包含括号 False >>> def func():print('1') ... >>> print(callable(func)) #函数可以包含括号 True >>> print(chr(98)) #通过数字得到对应的asii码 b >>> print(ord('b'))#通过asii码得到对应的数字 98 >>> code = "for i in range(5):print(i)" >>> exec(code) #可以将字符串对应的函数执行 0 1 2 3 4 >>> >>> >>> a = dict() #定义字典 >>> a {} >>> divmod(5,3) #取两个数字的商和余数 (1, 2) >>> a = '''{'a':1,'b':'aaaaa'}''' >>> c = eval(a) #取引号 >>> c {'a': 1, 'b': 'aaaaa'} >>> calc = lambda n:print(n) #匿名函数,如果不反复调用执行一次就结束可以使用匿名函数,执行完毕后立即回收内存 >>> >>> calc(5) 5 >>> calc = lambda n:print(n+1) >>> calc(5) 6 >>> res = filter(lambda n:n>5,range(10)) #过滤器:只取大于5的数值 >>> for i in res:print(i) ... 6 7 8 9 >>> res1 = map(lambda n:n*n,range(5)) #和匿名函数配合 >>> for i in res:print(i) >>> for i in res1:print(i) ... 0 1 4 9 16 >>> import functools #reduce函数从内置函数中去除 >>> res2 = functools.reduce(lambda x,y:x+y,range(10)) #操作x+赋值给x >>> print(res2) 45 >>> float(10) #将整数变为浮点数 10.0 >>> madlib = " I {verb} the {object} off the {place} ".format(verb="took", object="cheese", place="table") #format是准备替换%s的内置函数,匹配更加精确,通过{}与format方法进行引用,此例format通过变量名进行引用 >>> madlib ' I took the cheese off the table ' >>> '{},{}'.format('a','b') #通过顺序方式依次引用a b 'a,b' >>> a = set([1,1,1,1,33,4,1,45]) #将列表变为集合 >>> a {1, 4, 45, 33} >>> b = frozenset([1,1,1,1,33,4,1,45]) #被冻结的集合,没法添加 >>> b frozenset({1, 4, 45, 33}) >>> print(hash('I have a apple')) #做hash操作 7983704463637394503 >>> len('I have a apple') 14 >>> print(hex(255)) #将十进制转换为16进制 0xff >>> def test():local_var = 333,print(locals()) ... >>> test() {} >>> print(globals()) {'test': <function test at 0x101a479d8>, 'res': <filter object at 0x101945b38>, 'functools': <module 'functools' from '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/functools.py'>, '__name__': '__main__', 'a': {1, 4, 45, 33}, '__builtins__': <module 'builtins' (built-in)>, 'b': frozenset({1, 4, 45, 33}), 'res1': <map object at 0x101a534e0>, 'res2': 45, 'c': {'a': 1, 'b': 'aaaaa'}, '__doc__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, 'calc': <function <lambda> at 0x101a478c8>, '__spec__': None, 'i': 16, 'Iterable': <class 'collections.abc.Iterable'>, 'Iterator': <class 'collections.abc.Iterator'>, 'func': <function func at 0x101a477b8>, 'madlib': ' I took the cheese off the table ', 'code': 'for i in range(5):print(i)', '__package__': None} >>> print(globals().get('local_var')) None >>> print(max([1,2,3,4])) #打印最大 4 >>> print(min([1,2,3,4])) #打印最小 1 >>> print(oct(255)) #打印8进制 0o377 >>> a = 'Hello world' #str与repr的区别在于str与repr虽然都是显示字符串、但是str显示的更人性化、所以一般和print一起调用,而repr一般是给解释器使用所以和eval()一起使用的多一些 >>> b = str(a) >>> c = repr(a) #对该字符串再增加引号 >>> d = eval(c) #将c增加的引号脱去 >>> print(a==b) True >>> print(a==c) False >>> print(a==d) True >>> print(str(a)) Hello world >>> print(repr(a)) 'Hello world' >>> print(eval(repr(a))) Hello world >>> print(list(reversed([3,4,55,23,45]))) #对列表里的数据倒序排列 [45, 23, 55, 4, 3] >>> round(1.3345,3)#去小数点3位 1.335 >>> slice(2,6) slice(2, 6, None) >>> d = range(20) >>> d range(0, 20) >>> d[slice(2,5)] #分割 range(2, 5) >>> sum([1,44,32,11,45])#将列表中的数据相加 133 >>> type([1,44,32,11,45]) #打印数据类型 <class 'list'> >>> a = {6: 2,8:0, 1:4,-5:6,99: 11,4:22} >>> print(a) {1: 4, 99: 11, 4: 22, 6: 2, 8: 0, -5: 6} >>> print(sorted(a.items())) #对字典按照key值来排序 [(-5, 6), (1, 4), (4, 22), (6, 2), (8, 0), (99, 11)] >>> print(sorted(a.items(),key=lambda x:x[1])) #对字典按照value值来排序 [(8, 0), (6, 2), (1, 4), (-5, 6), (99, 11), (4, 22)] >>> print(sorted(a.items(),key=lambda x:x[1],reverse = False ))#对字典按照value值来排序,默认reverse = False采用正序排序 [(8, 0), (6, 2), (1, 4), (-5, 6), (99, 11), (4, 22)] >>> print(sorted(a.items(),key=lambda x:x[1],reverse = True ))#对字典按照value值来排序,reverse = True采用反序排序[(4, 22), (99, 11), (-5, 6), (1, 4), (6, 2), (8, 0)] >>> print(sorted(a.items(),reverse = True )) #对字典的key值进行反向排序[(99, 11), (8, 0), (6, 2), (4, 22), (1, 4), (-5, 6)] >>> a = [1,2,3,4] >>> b = ['a','b','c','d']>>> for i in zip(a,b):print(i) #合并打印 ... (1, 'a') (2, 'b') (3, 'c') (4, 'd') >>> c = ['a','b'] >>> for i in zip(a,c):print(i) #如果一方的值少,会按照少的那方的数据个数来打印 ... (1, 'a') (2, 'b') >>> __import__('os') #和import一样,但是是采用字符串方式来进行引用<module 'os' from '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/os.py'> >>> __import__('os',globals(),locals(),['path','pip'])<module 'os' from '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/os.py'>

 

 

7、json、pickle与序列化

用于序列化的两个模块

json 用于字符串与python数据类型间进行转换pickle用于python特有的类型与python数据类型间进行转换

Json模块提供了四个功能:dumps、dump、loads、load

pickle模块提供了四个功能:dumps、dump、loads、load

json默认情况下只字符串、字典、列表进行序列化,因为json是通用的,所有语言都支持json,在不进行特殊自定义情况下,不能序列化函数

import json info = { 'name': 'alex', 'age': 23, 'job': 'IT' } print(repr(info)) #输出 {'job': 'IT', 'name': 'alex', 'age': 23} info_json = json.dumps(info,sort_keys=True) #sort_keys用来对序列进行排序 print(info_json) #输出 {"age": 23, "job": "IT", "name": "alex"} info_json1 = json.dumps(info,sort_keys=True, indent=4) #indent可以对dumps的序列进行优化,表示数值离括号的距离 print(info_json1) #输出 { "age": 23, "job": "IT", "name": "alex" } info_json2 = json.dumps(info,sort_keys=True, separators=(',',':')) #在网络传输过程中为了节省传输带宽,可以对无用的字符进行压缩,这里是对,和:进行压缩以节省更多传输空间 print(info_json2) #输出 {"age":23,"job":"IT","name":"alex"}

 

 

import json info = { 'name': 'alex', 'age': 23, 'job': 'IT' } with open('seriliaztion.txt','w') as f: f.write(repr(info)) #按照普通方式写入文件,普通方式写入要求字符串格式进行写入 with open('seriliaztion.txt','r') as f: for line in f: print(type(line)) print(line) #对该形式存入的输出只能按照字符串方式进行打印 #输出 <class 'str'> {'age': 23, 'name': 'alex', 'job': 'IT'} with open('seriliaztion-json.txt','w') as f: f.write(json.dumps(info,sort_keys=True)) #在写入时对其进行json编码 with open('seriliaztion-json.txt','r') as f: for line in f: print(json.loads(line)['name']) #对此进行解码,在解码后可以按照字典方式针对key打印value print(json.loads(line)['age']) print(json.loads(line)['job']) #输出 alex 23 IT

 

with open('seriliaztion-json.txt','r') as f: data = json.load(f) #等同于data = json.loads(f.read()) print(data['job'])#输出IT

 

 

#pickle与json的区别在于pickle不需要特殊定义就默认支持将函数序列化,同时pickle生产的文件是二进制文件,需要加b import pickle def sayhi(name): print('hi {}'.format(name)) info = { 'name': 'gavin', 'age': 30, 'func': sayhi } with open('seriliaztion-pickle.txt','wb') as f: #需要加wb pickle.dump(info,f) #等同于f.write(pickle.dumps(info))

 

 8、软件目录结构规范

 (摘抄于http://www.cnblogs.com/alex3714/articles/5765046.html)

为什么要设计好目录结构?

"设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。对于这种风格上的规范,一直都存在两种态度:

这种个人风格问题"无关紧要"。理由是能让程序work就好,风格问题根本不是问题。规范化能更好的控制程序结构,让程序具有更高的可读性。

"项目目录结构"其实也是属于"可读性和可维护性"的范畴,设计一个层次清晰的目录结构,就是为了达到以下两点:

可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

所以,保持一个层次清晰的目录结构是有必要的。更何况组织一个良好的工程目录,其实是一件很简单的事儿。

目录组织方式

假设你的项目名为foo, 最方便快捷目录结构这样就足够了:

Foo/

|-- bin/ | |-- foo | |-- foo/ | |-- tests/ | | |-- __init__.py | | |-- test_main.py | | | |-- __init__.py | |-- main.py | |-- docs/ | |-- conf.py | |-- abc.rst | |-- setup.py |-- requirements.txt |-- README

简要解释一下:

bin/: 存放项目的一些可执行文件,当然你可以起名script/之类的也行。foo/: 存放项目的所有源代码。(1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录tests/存放单元测试代码; (3) 程序的入口最好命名为main.py。docs/: 存放一些文档。setup.py: 安装、部署、打包的脚本。requirements.txt: 存放软件依赖的外部Python包列表。README: 项目说明文件。

除此之外,有一些方案给出了更加多的内容。比如LICENSE.txt,ChangeLog.txt文件等

下面,再简单讲一下这些目录的理解和个人要求

关于README的内容

每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。

它需要说明以下几个事项:

软件定位,软件的基本功能。运行代码的方法: 安装环境、启动命令等。简要的使用说明。代码目录结构说明,更详细点可以说明软件的基本原理。常见问题说明。

有以上几点是比较好的一个README。在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。

关于requirements.txt和setup.py

setup.py

一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情。这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。

刚开始接触Python写项目的时候,安装环境、部署代码、运行程序这个过程全是手动完成,遇到过以下问题:

安装环境时经常忘了最近又添加了一个新的Python包,结果一到线上运行,程序就出错了。Python包的版本依赖问题,有时候我们程序中使用的是一个版本的Python包,但是官方的已经是最新的包了,通过手动安装就可能装错了。如果依赖的包很多的话,一个一个安装这些依赖是很费时的事情。新同学开始写项目的时候,将程序跑起来非常麻烦,因为可能经常忘了要怎么安装各种依赖。

setup.py可以将这些事情自动化起来,提高效率、减少出错的概率。"复杂的东西自动化,能自动化的东西一定要自动化。"是一个非常好的习惯。

setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,可以参考一下Python的一个Web框架,flask是如何写的: setup.py

当然,简单点自己写个安装脚本(deploy.sh)替代setup.py也未尝不可。

requirements.txt

这个文件存在的目的是:

方便开发者维护软件的包依赖。将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包。方便读者明确项目使用了哪些Python包。

这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以简单的通过 pip install -r requirements.txt来把所有Python包依赖都装好了。

 

关于配置文件的使用方法

注意,在上面的目录结构中,没有将conf.py放在源码目录下,而是放在docs/目录下。

很多项目对配置文件的使用做法是:

配置文件写在一个或多个python文件中,比如此处的conf.py。项目中哪个模块用到这个配置文件就直接通过import conf这种形式来在代码中使用配置。

这种做法我不太赞同:

这让单元测试变得困难(因为模块内部依赖了外部配置)另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。程序组件可复用性太差,因为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖conf.py这个文件。

所以,我认为配置的使用,更好的方式是,

模块的配置都是可以灵活配置的,不受外部配置文件的影响。程序的配置也是可以灵活控制的。

能够佐证这个思想的是,用过nginx和mysql的同学都知道,nginx、mysql这些程序都可以自由的指定用户配置。

所以,不应当在代码中直接import conf来使用配置文件。上面目录结构中的conf.py,是给出的一个配置样例,不是在写死在程序中直接引用的配置文件。可以通过给main.py启动参数指定配置路径的方式来让程序读取配置内容。当然,这里的conf.py你可以换个类似的名字,比如settings.py。或者你也可以使用其他格式的内容来编写配置文件,比如settings.yaml之类的。

    

  

转载于:https://www.cnblogs.com/xiaopi-python/p/6370150.html

最新回复(0)