小知识点笔记二(原始版)

小知识点笔记二(原始版)

1、高阶函数(函数即变量)

'''
一个函数add()接收另一个函数abs作为参数,add称为高阶函数
'''
def add(a, b, f):
    return f(a) + f(b)
res = add(-1,-3,abs)
print(res)

2、嵌套函数-->闭包

def outer():
    x = 1
    def inner():
        print(x)
    inner()

outer() # 1
'''
inner做为嵌套函数,它可以访问外部函数的变量,调用 outer 函数时,发生了3件事:
1、给 变量 x 赋值为1
2、定义嵌套函数 inner,此时并不会执行 inner 中的代码,因为该函数还没被调用,直到第3步
3、调用 inner 函数,执行 inner 中的代码逻辑。
'''
def outer(x):
    def inner():
        print(x)

    return inner
closure = outer(2)
closure() # 2

'''
同样是嵌套函数,只是稍改动一下,把局部变量 x 作为参数了传递进来,
嵌套函数不再直接在函数里被调用,而是作为返回值返回,
这里的 closure就是一个闭包,本质上它还是函数,闭包是引用了自由变量(x)的函数(inner)。
'''
View Code

 

 3、装饰器

装饰器:本质是函数,功能是用来装饰其他函数
为其他函数添加附加功能
原则:1、不修改被装饰函数的源代码
2、不修改被装饰函数的调用方式
即:装饰器对于被装饰函数来说是透明的

 普通装饰器

'''
普通的装饰器
'''
def decorator(func):
    def wrapper(*args, **kwargs):
        print("前面加的代码")
        old_result = func(*args, **kwargs)
        print("后面加的代码")
        return old_result + "+后面加的返回值"
    return wrapper

@decorator    # 等价于  foo = decorator(foo)
def foo(name, age):
    print("{0}'s age is {1}".format(name, age))
    return "OK"
result = foo("Lee", 23)
print(result)

# 前面加的代码
# Lee's age is 23
# 后面加的代码
# OK+后面加的返回值
View Code

 装饰器叠加

'''
装饰器叠加,
类似包装套包装,距离函数最近的为最内层包装,
运行时解包装,从外到内再到外
'''
def decorator1(func):
    def wrapper(*args, **kwargs):
        print("decorator1.......begin")
        func()
        print("decorator1.......end")
    return wrapper
def decorator2(func):
    def wrapper(*args, **kwargs):
        print("decorator2........begin")
        func()
        print("decorator2........end")
    return wrapper

@decorator1
@decorator2
def foo():
    print("in the foo")
foo()

# decorator1.......begin
# decorator2........begin
# in the foo
# decorator2........end
# decorator1.......end
View Code

 装饰器工厂

'''
装饰器工厂
输入1返回decorator1
输入2返回decorator2
其他返回decorator3
'''
def closure(flag):
    def decorator1(func):
        def wrapper(*args, **kwargs):
            print("decorator1..begin")
            func(*args, **kwargs)
            print("decorator1..end")
        return wrapper
    def decorator2(func):
        def wrapper(*args, **kwargs):
            print("decorator2..begin")
            func(*args, **kwargs)
            print("decorator2..end")
        return wrapper
    def decorator3(func):
        def wrapper(*args, **kwargs):
            func(*args, **kwargs)
        return wrapper
    if flag == 1:
        return decorator1
    elif flag == 2:
        return decorator2
    else:
        return decorator3

@closure(2)  # 等价于  foo = closure(2)(foo)
def foo():
    print("in the foo")
foo()

# decorator2..begin
# in the foo
# decorator2..end
View Code

 给装饰器传参

'''
1、给index页面和page页面添加认证方式
2、index的认证方式为本地,page的认证方式为ldap
3、此处用获取的不同用户名和密码代替
'''
def auth(auth_type):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if auth_type == "local":   #本地验证方式的用户名密码
                username = "superadmin"
                password = "123456"
            elif auth_type == "ldap":  #ldap验证方式的用户名和密码
                username = "admin"
                password = "123"
            input_username = input("输入用户名:").strip()
            input_password = input("输入密码:").strip()
            if username == input_username and input_password == password:
                print("Success Login!")
                func(*args, **kwargs)
            else:
                print("Invalid username or password!")
        return wrapper
    return decorator

@auth(auth_type="local")
def index():
    print("Welcom to index page.")
@auth(auth_type="ldap")
def home():
    print("Welcom to home page.")

index()  # superadmin 123456
home()   # admin 123
View Code

 4、生成器&迭代器

生成器的特性
1、生成器是一个有yield关键字的函数对象,yield暂停并保存并返回调用结果
2、第一次通过next开始运行这个函数,以后每次next就从yield开始继续运行函数
3、可以用send给生成器的yield关键字传值并激活函数
'''
生成器:循环n次找出第n个元素
可以由上一个数据推算出下一个数据。
一边循环一边计算的机制叫做生成器
'''
# 列表生成式,列表的初始化[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l = [abs(i) for i in range(10)]
# 变成生成器,相当于准备了一个算法,
# 不访问不会生成数据,取数据只能通过for循环一个一个取
# 生成器有g.__next__()方法,
# 取出当前存储值的下一个值,不能往后退,
# 一次只能迈一步,不能迈大步子
g = (i*2 for i in range(10))

'''
自定义生成器
用函数实现斐波那契数列长度10
1,1,2,3,5,8,13,21,34,55..
'''
def fib(length):
    n, a, b = 0, 0, 1
    while(n < length):
        print(b)
        a, b = b, a+b
        n += 1

# 把fib变为生成器
def fib(length):
    n, a, b = 0, 0, 1
    while(n < length):
        yield b
        a, b = b, a+b
        n += 1
    return "生成器结束"
fib_generator = fib(10)
print(fib_generator.__next__())
print(fib_generator.__next__())
# 有了生成器后可以让函数停在某个地方,
# 做点其他事之后回来继续运行
# 让主线程可以来回调用不同的函数,
# 在各个工作之间不停的切换
print("做点其他的事情.........")
for i in fib_generator:
    print(i)

# 让生成器运行到结束(如果能结束的话)
while True:
    try:
        print("斐波那契:",next(fib_generator))
    except StopIteration as e:
        print("生成器函数结束后的返回值:",e.value)
        break
# 生成器第一次调用next方法就开始执行生成器函数,执行到yield返回给next方法。
# 第二次执行next就接着上一次的yield后执行,执行到yield又返回,依次类推
# yield的作用是暂停并记住当前状态,并返回
# 生成器中return的作用是异常的时候打印消息
View Code
通过生成器并行计算实现单线程下的异步=单线程下的并行效果=协程
协程是比线程更小的单位
因为CPU效率高,可以很快的运行和在不同的任务中来回切换
生产者消费者模式:
'''
生产者消费者模型:一个负责生产producer包子,一个负责吃consumer包子
__next__()只是启动yield,send(value)即启动yield又给yield传值value
消费者不停的吃,生产者不停的喂,
生产者可以有很多,消费者也可以有很多,喂的方式也是灵活的
'''
def consumer(name):#消费者
    while True:
        print("[%s]准备吃包子了" %name)
        baozi = yield
        # print(baozi,name)
        print("包子[%s]来了,被[%s]吃了" %(baozi,name))
def producer(con):
    for i in range(4):
        print("开始做包子喂给消费者了----------------")
        con.send(i)
c = consumer("张三")
c.__next__()
producer(c)
View Code
迭代器的概念:
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
所以生成器一定是迭代器,可迭代对象不一定是迭代器
'''
铺垫:
一类是集合数据类型:如list、tuple、dict、set、str等
一类是生成器generator,包括带yield的生成器方法
这些可以直接作用于for循环的对象称为Iterable可迭代对象
可以用isinstance('abc',Iterable)判断一个对象是否是Iterable对象
迭代器的概念:
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
所以生成器一定是迭代器,可迭代对象不一定是迭代器
通过iter("adcde")可以吧"abcde"转换成迭代器
可以用isinstance('abc',Iterator)判断一个对象是否是迭代器
'''
View Code

 5、内置函数

# 求绝对值
abs(-1)  
# 等价于x**y(带两个参数)或 x**y % z(带三个参数)有些类型
# 如整数,能够用一种更高效的算法使用三个参数的形式调用时。
pow()
# round(number[, ndigits]) -> number
# 把小数四舍五入到给定的位数(默认是0位)
round()
# Iterable中所有值x,bool(x)都为True则返回True,
# 可迭代对象为null返回True
all([1, 2, 3])  # True
# Iterable中所有值x,bool(x)任何一个为True则返回True,
# 可迭代对象为null返回False
any([0, 0, 0])  # False
# 返回int对象的二进制表示。
bin(2796202)  # 0b1010101010101010101010
# 返回int对象的八进制表示。
oct(342391)  # 0o1234567
# 返回int对象的十六进制表示。
hex(12648430)  # 0xc0ffee
# 返回一个对象是否能被调用。类可以被调用
# 注意,假设有实例f,那么f()调用__call__()方法,
# 可以判断是否有__call__()方法
callable(f)    # f对象如果重写__call__()方法返回True,因为可以f()
# 返回元祖(x//y, x%y). 不变式: div*y + mod == x.
divmod(7,3)   # (2,1)
# dir() -> 不给参数,返回当前名字空间列表
# 其他情况,返回一个字母顺序的列表,
# 名字包括(一些)给定对象的属性,和从它可获得的属性
# 如果某对象x支持__dir__(self)方法,该方法将被调用得出dir(x)结果。# 否则默认dir()逻辑被使用并且返回:
# 模块对象:模块的属性
# 类对象:它的属性,和它递归所有基类的属性
# 任何其他对象:它的属性,它的类的属性,以及它递归所有的基类的属性
# dir()在方法中:['self']
''' dir()在模块中:['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', 
'__name__', '__package__', '__spec__'(还有类名方法名,变量名等)]
 dir(f)f是对象:['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__',
 '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', 
'__subclasshook__', '__weakref__', (还有f对象的class自带属性和方法)]
# dir(f)f是模块:['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', 
'__spec__', (还有模块中的类名方法名,变量名等)]'''
dir()

# 对于Unicode编码i范围(0 <= i <= 0x10ffff)的字符
# 返回该Unicode字符串
chr(97)       # a
# 对单个字符,返回Unicode码
ord("")     # 25105
# 编译源代码到可以被exec()和eval()执行的对象
# compile(source,filename,mode,flags,dont_inherit)
# source可以代表一个Python模块,语句或表达式
# filename将用于运行时错误消息。
# mode必须是"exec"编译一个模块,"single"编译一个交互式语句
# 或者"eval"编译一个表达式,
# flags参数(如果存在)控制哪一个future语句影响代码编译,
# 如果dont_inherit=True停止这个编译继承任何future语句的影响代码调# 用编译中的结果
# 如果缺失或者为False那些语句影响编译,除了显示指定的特性
# compile("for i in range(10): print(i)", '', 'exec')   # at 0x10141e0b0, file "", line 1>
# exec(object, globals, locals)
# 对给的source在全局字典或局部字典环境下执行,source可以是一个字# 符串代表一个或多个Python语句,也可以是compile()
# 函数返回的代码对象
# globals必须是字典locals可以是任意映射,默认值为当前的全局或局部,# 如果只给了全局,那局部默认也是它
exec()

# eval(object, globals, locals)
# 对给的源在全局字典或局部字典环境下求值,源可以是Python表达式或
# 者compile()函数返回的代码对象
# 全局必须是字典局部可以是任意映射,默认值为当前的全局或局部,如果# 只给了全局,那局部默认也是它
eval()
'''返回一个包含当前名字空间全局变量的字典,类似`dir()`在模块中执行:['__annotations__', '__builtins__', 

'__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'(还有类名方法名,变量名等)]
# 注:本词典的更新会影响在当前全局范围的名称查找,反之亦然。'''
globals()

# 返回一个包含当前名字空间局部变量的字典
# 注:是否更新词典都会影响本地范围的名字查找,反之亦然是*实现依赖*
#而不是包含任何向后兼容性保证。
locals()
# 返回给定对象的hash值,两个比较相等的对象必须具有相同的散列值,
# 但反过来不一定成立。MD5是hash的一种算法
hash()

# help() -> 启动交互式会话
# help(thing) -> 查看thing的帮助
help()
# 返回对象的标识,保证在同时存在的对象之间是唯一的。
#(CPython用内存地址做标识)
id()
# 从标准输入读取字符串,忽略换行。如果给定的提示符字符串被打印到标# 准输出中,则在不使用换行之前阅读输入
# 如果用户点击EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return)抛出
# EOFError在*nix系统,readline可被使用
input()
# 返回一个对象是一个类的实例还是它的一个子类
# 一个元组,在``isinstance(x,(A,B,…)``可作为目标对照。
# 这相当于``isinstance(X,A)or isinstance(x,b)or ...``
isinstance()
# 返回“cls”是否来自另一个类或是同一个类。
# 一个元组,在``issubclass(x,(A,B,…)``可作为目标对照。
# 这相当于``issubclass(X,A)or issubclass(x,b)or ...``
issubclass()
# iter(iterable) -> iterator
# iter(callable, sentinel) -> iterator
# 从对象获取迭代器。在第一种形式中,参数必须提供自己的迭代器,
# 或者是一个序列。
# 在第二种形式中,调用可调用的方法,直到它返回标记为止
iter()
# max(iterable, *[, default=obj, key=func]) -> value
# max(arg1, arg2, *args, *[, key=func]) -> value
# 单一可迭代对象参数,返回它最大的项,有两个以上参数,
# 返回最大的参数,default参数用来返回迭代对象为空时的返回值
max()
# 和max一样
min()
# next(iterator[, default])
# 从迭代器返回下一项,如果给定default参数,在迭代器耗尽的时候会
# 返回default参数而不抛出StopIteration异常
next()
#
open()
# print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
# 打印输出到流,或者默认的sys.stdout
# 关键字选项参数:
# file:  一个类文件对象(stream); 默认是当前的sys.stdout.
# sep:   几个值中间的字符串默认为空格
# end:   追加在最后一个值后面的字符串默认为换行
# flush: 是否强制刷到流中
print()
# 返回一个从iterable创建的包含所有项的升序排列的list,
# 可设置flag进行降序排列,用key去定制排序规则
sorted()
# 返回start参数(默认为0)和一系列可迭代数字之和。
# 当迭代对象为空,返回start的值
# 这个方法是专门为使用数字值和可以阻止非数字型值
sum()
# vars([object]) -> dictionary
# 没有参数, 相当于locals()  有一个参数, 相当于 object.__dict__.vars()
# __import__(name, globals=None, locals=None, 
# fromlist=(),level=0) -> module
# 导入一个模块。因为这个方法意味着被Python解释器使用而不是一般的
# 使用最好使用importlib
# import_module()以编程方式导入模块。
# globals参数仅仅用于确定上下文;
# 它们没有被修改,locals参数没有用,from list应该是一个list of 
# names类似"from name import..."或者一个
# 空list类似"import name"
# 当从包导入一个模块,注意__import__('A.B',...)
# 返回包A当fromlist为空,返回它的子模块B当fromlist不为空
# level用来决定是否执行绝对或者相对的imports. 0是绝对,正数是相对
# 于当前模块搜索的父目录的数目
__import__()
View Code
#-------------类----------------
# bool(x)当x为True返回True否则返回False。内置的True或者False仅仅# 是bool类的两个实例,bool是int类的子类并且不能被继承
bool(0)  # False
# 构建可变字节数组对象从:
# 1-range(256)范围内的可迭代对象
# 2-使用指定编码的文本字符串
# 3-一个bytes或者一个buffer对象
# 4-任意实现了bufferAPI的对象
# 5-一个integer对象
# bytearray(iterable_of_ints) -> bytearray
# bytearray(string, encoding[, errors]) -> bytearray
# bytearray(bytes_or_buffer) -> mutable copy of bytes_or_buffer
# bytearray(int) -> bytes array of size given by the parameter 
# initialized with null bytes
# bytearray() -> empty bytes array
# bytearray("你好", encoding="utf8")  
bytearray(b'\xe4\xbd\xa0\xe5\xa5\xbd')
# 构建不可改变的字节数组对象从:
# 1-range(256)范围内的可迭代对象
# 2-使用指定编码的文本字符串
# 3-任意实现了bufferAPI的对象
# 4-一个integer对象
# bytes(iterable_of_ints) -> bytes
# bytes(string, encoding[, errors]) -> bytes
# bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
# bytes(int) -> bytes object of size given by the parameter 
# initialized with null bytes
# bytes() -> empty bytes object
bytes("你好", encoding="utf8")  # b'\xe4\xbd\xa0\xe5\xa5\xbd'
# 创建一个复数从一个实部和一个可选的虚部,相当于(real + imag*1j)
# 其中image默认为0
complex(5, 2)  # 5+2j
# dict() -> {}
# dict(mapping映射对象) -> 从一个映射对象的(key,value)初始化字典,
# 一个mapping对象将可哈希的值映射为任意对象 
# python中标准映射类型是字典
# dict(iterable) -> d = {}; for k, v in iterable: d[k] = v
# dict(**kwargs) -> 初始化字典对象使用name=value键值对例
# 如:dict(one=1, two=2)
dict()
# 返回一个enumerate对象(有索引的序列),对象必须支持可迭代
# enumerate对象的一对yield包括一个下标(默认从0开始)和一个值(被自# 己的迭代参数中断)
# 获取一个有下标的列表时enumerate是有用的
# (0, seq[0]), (1, seq[1]), (2, seq[2]), ...
enumerate("abcdef")         #  (0, 'a'), (1, 'b'), (2, 'c'), ...
# filter(function or None, iterable) --> filter object
# 过滤器,返回一个迭代器保留那些func(item)之后为True的项,
# 如果function为none,返回为True的项
# 例如:filter(lambda n:n>5,range(10))
# 派生:map(lambda n:n*2,range(10))
# 派生:[abs(i) : for i in range(10)]
# 派生:functools.reduce(lambda x,y:x*y,range(10))
filter()
# map(func, *iterables) --> map object
# 制作一个迭代器,从每个可迭代对象获取参数用来计算函数。
# 当短迭代耗尽的时候停止
# 把一个或多个迭代器当成参数喂给函数,返回一个函数运行后的迭代器。
map()         
# 字符串或数字转换为浮点数
float()
# frozenset() -> empty frozenset object
# frozenset(iterable) -> frozenset object
# 创建一个不可变的无序的唯一元素集合,对可迭代对象去重,并且不可变
frozenset()
# 将数字或字符串转换为整数,如果没有参数,则返回0。如果x是一个数,# 返回x.__int__()。对于浮点数,向零截断。
# 如果x不是一个数或如果给定base,那么x必须是一个字符串或者字节,
# 或ByteArray实例表示字面整数的给出的base。
# 字面上之前可以是“+”或“-”,然后被包围。base0意味着将字符串从基部# 解释为整数文字。>>> int('0b100', base=0)
int()
# list() -> [] 空list
# list(iterable) -> 从可迭代对象初始化的list
list()
# 创建一个新的参考给定对象的memoryview内存查看对象
memoryview()
# 最古老的基类,object是一个类
object()

# range(stop) -> range object
# range(start, stop[, step]) -> range object
# 返回一个Integer序列(顾首不顾尾)range(start,stop,step) start默
# 认为0,step默认为1(步长可增可减)
range()
# 返回一个倒序的迭代器
reversed()
# set() -> new empty set object
# set(iterable) -> new set object
# 构建一个无序的唯一元素集合
set()
# 创建切片对象。这是用于扩展的切片(例如 a[0:10:2])
# slice(stop)
# slice(start, stop[, step])
slice()
# tuple() -> 空元祖
# tuple(iterable) -> 从可迭代对象的项去初始化元祖
# 如果参数是元祖,返回值是同样的对象
tuple()
# type(object_or_name, bases, dict)
# type(object) -> 对象的类型
# type(name, bases, dict) -> 新类型
type()
# zip(iter1 [,iter2 [...]]) --> zip object
# 返回一个拉链对象,它的__next__()方法返回一个元祖第i个元素来自第i
# 个迭代对象参数。
# __next__()方法继续直到最短的可迭代对象在参数序列中被耗尽,抛出# StopIteration异常
zip()
View Code
#-----------面向对象-----------
# classmethod(function) -> method把一个方法转换成类方法
# 类方法将类本身接收为隐式第一个参数,就像实例方法接收self一样。要声明类方法,请使用这个成语:
# class C:
#    @classmethod
#    def f(cls, arg1, arg2, ...):
#    ...
# 类方法可以被类调用(C.f())或被实例调用(C().f())除它的类之外实例被忽略。派生类调用类方法的时候会隐式的把派

# 生类作为第一个参数传递
classmethod()   #
# property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
# fget是一个方法用作得到一个属性值,同样的,fset是一个方法用来设置,
# fdel用来删除,一个属性。 典型的用法是定义一个使用过的属性:
'''
class C(object):
        def getx(self): return self._x
        def setx(self, value): self._x = value
        def delx(self): del self._x
        x = property(getx, setx, delx, "I'm the 'x' property.")

    Decorators make defining new properties or modifying existing ones easy:

    class C(object):
        @property
        def x(self):
            "I am the 'x' property."
            return self._x
        @x.setter
        def x(self, value):
            self._x = value
        @x.deleter
        def x(self):
            del self._x
'''
property()     #
# staticmethod(function) -> method
# 转换一个方法到静态方法
# 静态方法不会隐式接收第一个参数,用下面的语法声明一个静态方法:
# class C:
#   @staticmethod
#   def f(arg1, arg2, ...):
# 静态方法可以被类或者实例调用,除了类之外,该实例将被忽略
staticmethod()   #
# super() -> 和super(__class__, )一样
# super(type) -> 不绑定父类object
# super(type, obj) -> 绑定父类object; 要求 isinstance(obj, type)
# super(type, type2) -> 绑定父类object; 要求 issubclass(type2, type)
# 典型调用父类的方法:
"""
class C(B):
    def meth(self, arg):
        super().meth(arg)
This works for class methods too:
class C(B):
    @classmethod
    def cmeth(cls, arg):
        super().cmeth(arg)
"""
super()   #
# 从给定对象中删除命名的属性delattr(x, 'y')相当于 ``del x.y''
delattr()
# 从对象获取一个已命名的属性getattr(x, 'y')相当于x.y
# 当给定一个默认参数,如果属性不存在会返回,没有它将会在那种情况抛出一个异常
getattr()
# 返回是否对象有给定名字的属性,这是通过调用getattr(obj, name)和捕获AttributeError异常
hasattr()
# 将给定对象上的命名属性设置为指定的值。
# setattr(x, 'y', v)相当于 ``x.y = v``
setattr()
View Code
#-----------有对应关系------------------------
# 返回对象的标准字符串表示形式。
# 对于许多类型的对象,包括大多数内置对象,eval(repr(obj))== obj。
# 重写__repr__()方法可以改变结果
repr("你好")  # '你好'
# 返回对象的ASCII表示,对象变为可打印的字符串,非ASCII字符用\\x, \\u或 \\U转义。repr()返回对象的可打印表示的字

符串。Python2中,和repr()返回的字符串相似
# 重写__repr__()方法可以改变结果
ascii("你好")  # '\u4f60\u597d'
# str(object='') -> str
# str(bytes_or_buffer[, encoding[, errors]]) -> str
# 从给定对象创建一个新字符串对象,如果指定了encoding或errors参数
# 那么对象必须公开数据缓冲区(将被使用给定的编码和error handler解码)
# 否则,返回obj.__str__()的结果(如果有__str__()方法)或者repr(obj)(重写了__repr__()方法没有重写__str__()方法)
# 编码格式默认为系统编码,错误默认为"strict"
str("你好")  # 你好,  注意,str是一个class
# 返回容器中的项目数目,重写__len__()方法将被调用
len()
# 返回value.__format__(format_spec),类重写__format__方法可以修改该方法返回值
# format_spec默认值为"",help('FORMATTING')查看细节
format()
View Code

6、模块导入的本质

导包原则:
一、导完能直接使用import后的方法或变量
(import func 使用时:func(xxx)而不要func.func1(xxx))
二、测试单独写,运行是单独入口互不干扰
'''
一、模块:本质是.py结尾的Python文件,模块是一个对象
二、包:是一个带__init__.py的目录,包里放模块,可以看出包也是一个对象,
__init__.py是包初始化(即导入)的时候执行的
三、导入模块的方法:
1、import module_name(通过module_name.func调用)
2、from module_name import * (直接func调用,这样可能引起重名)
3、from module_name import logger as logger_alex(取个别名避免重名)
四、import的本质:
例如[import Alex]导入模块本质就是在sys.path里面找到Alex.py的路径并把模块Alex.py解释一遍,
可以把import后的模块看做一个对象,要通过这个对象去调用里面的方法
导入包只解释执行包下的__init__.py文件
五、导入优化:第二种方法好一点,不用再重复找模块
########第一种方法###########
import module
module.func(xx)
module.func(yy)
#############################
##########第二种方法#########
from module import func
func(xx)
func(yy)
#############################
'''
import sys,os
search_path = os.path.dirname(os.path.abspath(__file__))
sys.path.append(search_path)
View Code

7、软件目录结构规范

软件目录结构规范

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

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

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

我是比较偏向于后者的,因为我是前一类同学思想行为下的直接受害者。我曾经维护过一个非常不好读的项目,其实现的逻辑并不复杂,但是却耗费了我非常长的时间去理解它想表达的意思。从此我个人对于提高项目可读性、可维护性的要求就很高了。"项目目录结构"其实也是属于"可读性和可维护性"的范畴,我们设计一个层次清晰的目录结构,就是为了达到以下两点:

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

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

目录组织方式

关于如何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构。在Stackoverflow的这个问题上,能看到大家对Python目录结构的讨论。

这里面说的已经很好了,我也不打算重新造轮子列举各种不同的方式,这里面我说一下我的理解和体会。

假设你的项目名为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

简要解释一下:

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

除此之外,有一些方案给出了更加多的内容。比如LICENSE.txt,ChangeLog.txt文件等,我没有列在这里,因为这些东西主要是项目开源的时候需要用到。如果你想写一个开源软件,目录该如何组织,可以参考这篇文章。

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

关于README的内容

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

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

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

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

可以参考Redis源码中Readme的写法,这里面简洁但是清晰的描述了Redis功能和源码结构。

关于requirements.txt和setup.py

setup.py

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

这个我是踩过坑的。

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

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

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

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

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

requirements.txt

这个文件存在的目的是:

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

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

 

关于配置文件的使用方法

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

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

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

这种做法我不太赞同:

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

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

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

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

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

posted on 2018-07-06 09:24 要一直走下去 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/staff/p/9271858.html

你可能感兴趣的:(小知识点笔记二(原始版))