python基础笔记2

(6). python赋值语句的注意事项

在本例子中会报错,找不到x, 因为赋值即定义(在函数定义时,已经解释了语句 x += 1, 所以左侧的x被重新定义成局部变量)

x = 5
def test():
  x += 1 # x = x + 1
  print(x)

解决上诉问题的方法:

  x = 5
  def test():
    global x  # x不可以被定义为局部变量 
    x += 1 # x = x + 1
    print(x)

打印x则为100

x = 5
def test():
    global x  # x不可以被定义为局部变量 
    x = 100
    x += 1  # x = x + 1
    print(x)  # 101

test()
print(x)  # 101 函数外部的x也被改成了101,因为现在x已经是全局变量了

def test2():
    print(x)  # 101
test2()

使用函数时尽量使用形参定义局部变量

# method1  方法不好
x = 1
y = 2
def add():
  print(x + y)
  
# method2 方法好
def add(x, y):
  print(x + y)

(7). lambda表达式

  • lambda表达式记匿名函数,格式: lambda[参数列表] :表达式

  • 参数列表中参数的个数>=0, 参数可以是形参的任意一种, 后面的冒号必须写,表达式相当于函数返回值,不能出现赋值语句=,表达式可以返回列表,元组,字典,集合,迭代器,生成器,也可以再包含一个lambda表达式。

    python中的lambda表达式只能有一行,称为单行函数

    例如:

    def fn():
      return 0
    # 等价于
    lambda : 0  # 省略了函数名字,和return,更简洁
    如何调用?(lambda : 0)()
    
    # 返回0的函数在字典中的应用1
    from collections import defaultdict
    dict1 = defaultdict(lambda : 0)
    for i in 'abc':
        for j in range(4):
            dict1[i]+= 1 # 不会报KeyError  相当于先调用一次dict1[i] = (lambda : 0)(),键i的值先设置为0了,所以在0的基础上加了1,才不会报错
    print(dict1)
    
    # 返回0的函数在字典中的应用1
    from collections import defaultdict
    dict1 = defaultdict(int)
    for i in 'abc':
        for j in range(4):
            dict1[i]+= 1
    print(dict1)
    
  • 用途:一般用在高阶函数传参时

    例如:

    sorted([4, 2, 3, 'a'], key=str, reverse=True)
    # 等价于
    sorted([1, 2, 3, 'a'], key=lambda x: str(x))
    
    # 普通传参
    lambda x, y=1: x+y  # 等价于fn,返回函数对象
    (lambda x, y=1: x+y)(2) # 等价于fn(2)  2
    
    
    # keyword_only参数
    (lambda x, *, y: x + y)(1, y=2)
    (lambda x, *, y: x + y)(x=1, y=2)
    
    (lambda x, *, y=2: x + y)(1)
    (lambda x, *, y=2: x + y)(x=1)
    (lambda x, *, y=2: x + y)(1, y=2)
    (lambda x, *, y=2: x + y)(x=1, y=2)
    
    
    # 可变位置参数,表达式可以返回迭代器,列表,集合,元组等
    # 思考如果参数传入range会返回什么?
    (lambda *args: (x for x in args))(*range(5))
    (lambda *args: [x for x in args])(*range(5))
    (lambda *args: {x%2 for x in args})(*range(5))
    (lambda *args: [(x,x) for x in args])(*range(5))
    
    
    # 可变关键字参数, 注意结构的星号
    (lambda *kwargs: {i:0 for i in kwargs})(*{'a':1, 'b':2})
    (lambda *kwargs: {i:0 for i in kwargs})(*dict(a=1, b=2))
    
    
    # 列表生成式与lambda表达式混合使用,lambda表达式里面还有
    # 一个lambda,并使用map高阶函数
    [i for i in (lambda *args: map(lambda x: x+1, args))(*range(3))]
    [i for i in (lambda *args: map(lambda x: (x+1, args), args))(*range(3))]
    # [(1, (0, 1, 2)), (2, (0, 1, 2)), (3, (0, 1, 2))]
    

    (8). 递归

  • 递归是指函数直接或者间接的调用自身(在同一个函数内部,或者多个函数之间相互调用),递归一定要有边界条件,当边界条件满足时递归返回,当边界条件不满足时,递归前进。递归的深度不宜太深,python中对递归的调用深度做了限制,以保护解释器(不要超过1000)。在写代码的过程中,要避免函数间接调用导致的循环递归。递归的效率相对较低,因为每一次调用都要开辟新的栈桢,所以即使递归很简洁,能不用就不用。

  • 大部分递归都可以通过循环实现

    例子:

    # 斐波那切数列
    # 循环实现
    a = 0
    b =1
    

(9). 高阶函数

高阶函数:如果一个函数的参数或者返回值中有函数
则这个函数是高阶函数

例如:

# counter函数是高阶函数,返回值指向内层函数
# 如果返回inc(), 即counter函数返回函数调用,也是高阶函数
def counter(base):
    def inc(step=1):
        nonlocal base
        base += step  # 使用外层函数中的base变量
        return base
    return inc

fn = counter(2)
print(fn())  # 3


# def counter(base):
#     def inc(step=1):
#         # nonlocal base
#         base += step  # UnboundLocalError: local variable 'base' referenced before assignment
#         return base
#     return inc
#
# fn = counter(2)
# print(fn())


# f1和f2是函数调用的结果,无法比较大小
# 所以 = 用来判断地址是否相等,每次函数
# 调用都创建新的栈桢,所以地址不同,但是
# 标识符 counter 的地址没有变。
f1 = counter(5)
f2 = counter(5)
f1 = f2 f1 is f2  #(False, False)

三个高阶函数都会生成新的对象,不改变原来的可迭代对象

a = [0, 1, 2, 3, 4, 5]
b = list(filter(lambda x: x % 2 == 0, a))
print(b)  # 生成新的b
print(a)


a = [0, 1, 2, 3, 4, 5]
b = sorted(a, reverse=True)
print(b)  # 生成新的b
print(a)

a = [0, 1, 2, 3, 4, 5]
b = list(map(lambda x: str(x), a))
print(b)  # 生成新的b
print(a)

(1). sorted()

sorted(可迭代对象, key=函数,reverse)

返回list

插入排序实现sorted()高阶函数,不可以用while循环的原因,思考?

思考? ['12', '1', '3', '0']如何排序

a = ['12', '1', '3', '0']
print(sorted(a, reverse=True))  # ['3', '12', '1', '0']

nums = ['a', 9, 0, 8, 6, 2, 2, 2, 1, 7]
# 使用内键str函数排序
print(sorted(nums, key=str, reverse=True))

sorted函数的内部实现原理,手动实现sorted函数

def sorted(iterable, *, key=None, reverse=False):
    new_list = []
    for value in iterable:
        cvalue = key(value) if key else value
        for j, value1 in enumerate(new_list):
            cvalue1 = key(value1) if key else value1
            flag = cvalue < cvalue1 if not reverse else cvalue > cvalue1
            if flag:
                new_list.insert(j, value)
                break
        else:
            # new_list.insert(i, value)
            new_list.append(value)
    return new_list

(2). filter()

filter(function, iterable)

返回惰性对象

function是一个参数的函数,且返回值应当是bool或者等效布尔值
function参数如果是None, 可迭代对象的每一个元素自身等效bool

# function 不是None, 则返回表达式对应True的可迭代元素
# function 过滤掉部分元素,但是最终表达式返回True,元素才可以返回
list(filter(lambda x: x%3==0, range(10)))
# [0, 3, 6, 9]

print(list(filter(lambda x: x % 2 == 0, range(10))))
# 返回偶数[0, 2, 4, 6, 8]

list(filter(lambda x: x%3, range(10)))
# [1, 2, 4, 5, 7, 8]

# 将等效于None的元素过滤掉
list(filter(None, range(5)))
# [1, 2, 3, 4]

list(filter(None, range(-5, 5)))
# [-5, -4, -3, -2, -1, 1, 2, 3, 4]

(3). map()

可以代替zip()

map(function, *iterables)

返回一个惰性对象

dict(map(lambda x: (x%5, x), range(500)))
# {0: 495, 1: 496, 2: 497, 3: 498, 4: 499}
  • 给两个序列构建成一个字典

    # 直接map
    dict(map(lambda x,y: (x, y), 'abc', range(3)))
    # {'a': 0, 'b': 1, 'c': 2}
    
    # 直接zip
    print(dict(zip('abc', range(1, 3))))
    
    #  先zip,再map
    dict(map(lambda x:x, zip('abc', range(3))))
    
  • 将字典键值互换

    c = {'a': 1, 'b': 2}
    print(dict(map(lambda x: (x[1], x[0]), c.items())))
    
    print(dict(map(lambda x, y: (y, x), c.keys(), c.values())))
    
    print(dict(zip(c.values(), c.keys())))
    

(4).zip()

1.4zip(iter1, iter2)

返回惰性对象

print(zip([1,2,3], 'abc'))

(10). 生成器函数

生成器表达式:(i for i in range(3))

生成器函数:在函数中出现yield关键字

生成器函数的执行过程 :

# 被驱动一次,遇到一个yield时函数暂停,yield返回一个值,被驱动第二次
# 遇到第二个yield函数暂停,返回第2个值,第三次驱动遇到return,函数返回
# 因为没有yield了,所以驱动时会报错,但可以添加缺省值避免包报错,next(g, None),next()
# 是一个函数,不是g的属性

def gen():
    print(1)
    yield 2
    print(3)
    yield 4
    print(5)
    return 6
  
g = gen() # 直接调用函数是个生成器,没有打印和输出,函数体不会立即执行
next(g) # 驱动一次打印1,返回2,停在第3行

next(g) # 驱动第二次打印3,返回4,停在第5行

next(g) # 驱动第二次打印5,报错,在错误中可以看的返回的6

next(g, 6)# 打印5,返回缺省值6

# 没有显式的return
def gen():
    print(1)
    yield 2
    print(3)
    yield 4
    print(5)
    
g = gen()
next(g)
next(g)
next(g) # 打印5,报错,错误中返回一个看不到的None
next(g, None) # 打印5,不会报错,返回一个看不到的None

无限次循环例子:

# 无限循环, 每一次循环遇到yield会暂停
def counter():
    i = 0
    while True:
        i += 1
        yield i
c = counter()
next(c) # 返回1,停在yield
next(c) # 返回2,停在yield

计数器例子:

# 计数器
def inc():
    def counter():
        i = 0
        while True:
            i += 1
            yield i
    c = counter()
    def fn():    # return lambda : next(c)  后3行可以用lambda表达式代替
        return next(c) 
    return fn 
g = inc()

计数器改进例子:

思考c.send(0)

def inc():
    def counter():
        i = 1
        while True:
            i += 1            
            reponse = yield i
            if reponse is not None:
                i = reponse
            
#         def a():
#             nonlocal reponse            
    c = counter()        
    return lambda reset=False: next(c) if not reset else c.send(0)
g = inc()
g()
g()
g(True) # 需要调用几次g(),等待生成器启动,才可以使用重置功能

生成器实现斐波那契数列:

# 斐波那契数列,生成器实现
def fib():
    a1 = 0
    a2 = 1
    while True:
        a1, a2 = a2, a1+a2
        yield a1
g = fib()
count = 1
for i in g:    
    if count == 101:
        print(i)
        break
    count += 1
       

生成器函数与生成器表达式:

# 前3个例子为函数
# 调用函数,使用g()
g = lambda : (i for i in range(5))

def foo():
    yield from range(5)   # from后面加可迭代对象
        
# 生成器函数与生成器表达式比较,生成器函数可以写出更复杂的功能
def foo():
    for i in range(5):
        yield i
        
# 第4个例子为generator       
m = (i for i in range(5))

(11). 装饰器

装饰器,一定要有返回值(指内层的wrapper函数,有返回值)

装饰器:无参装饰器、有参装饰器
无参装饰器:是一个函数,无参实际上是单参函数,接受一个函数作为参数,
而且装饰器返回一个函数。

无参装饰器

# 给函数增加统计运行时间功能
import datetime
def run_time(f):
    def wrapper(*args, **kwargs):
        start = datetime.datetime.now()
        ret = f(*args, **kwargs)
        interval = (datetime.datetime.now() - start).total_seconds()
        print('run time is {}'.format(interval))
        return ret
    return wrapper

@run_time
def add(x, y):
    return x + y

print(add(1, 2))
# 验证用户登陆功能
from functools import wraps
session = {}

def login_required(fn):
    @wraps(fn)
    def wrapper():
        name = session.get('name')
        if not name:
            return 'please login'
        return fn()
    return wrapper


@login_required
def index():
    return 'index'

print(index())

带参装饰器

  1. 偏函数实现,更新文档字符串
# 更新函数的名字,文档字符串等信息(无参装饰器内部使用带参装饰器)
from functools import wraps,partial


def update(f):
    # 自己写的wraps函数与内键函数功能相同
    # 通过偏函数实现更新,只有2层,没有通过3层装饰器实现
    # wrapper_assignments = ('__doc__', '__name__', '__annotations__')
    # wrapper_updates = ('__dict__',)
    #
    # def update_wrapper(wrapper, wrapped):
    #     for attr in wrapper_assignments:
    #         try:
    #             value = getattr(wrapped, attr)
    #         except Exception:
    #             pass
    #         else:
    #             setattr(wrapper, attr, value)
    #
    #     for attr in wrapper_updates:
    #         getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    #
    #     wrapper.__wrapped__ = wrapped
    #     return wrapper
    #
    # def wraps(wrapped):
    #     return partial(update_wrapper, wrapped=wrapped)

    @wraps(f)
    def wrapper(*args, **kwargs):
        """
        wrapper doc
        :param args:
        :param kwargs:
        :return:
        """
        return f(*args, **kwargs)
    return wrapper

@update
def test():
    """
    test doc
    :return:
    """
    pass

print(test.__doc__)
print(test.__name__)
print(test.__dict__)
print(test.__wrapped__)
  1. flask路由系统, 实现路由映射原理

    带参数装饰器,如果第一个参数不是fn, 可以通过2层实现

    class FlaskTest:
        def route(self, rule, **options):
            def decorator(f):
                self.add_url_rule(rule, view_func=f, **options)
            return decorator
        
    app = FlaskTest()
    
    @app.route("/", methods=['GET', 'POST'])
    def index():
        return "main page"
    
  2. 缓存装饰器

    from functools import wraps
    
    
    def cached(timeout=20):
        def _wrapper(fn):
            cach_dict = {}
    
            @wraps(fn)
            def wrapper(n):
                if n in cach_dict:
                    return cach_dict.get(n)
                ret = fn(n)
                cach_dict[n] = {'value': ret, 'timeout': timeout}
                return ret
            return wrapper
    
        return _wrapper
    
    
    @cached(timeout=20)
    def factorial(n):
        ret = 1
        for i in range(1, n+1):
            ret *= i
        return ret
    
    print(factorial(100000))
    print('*' * 40)
    print(factorial(100000))
    

多个装饰器例子

  • 由下到上执行
  1. 路由加登陆验证,双装饰器

    login_required放在下面,app.route('/')放在上面(正常例子):

    因为需要先验证,再完成路由映射

    from flask import Flask, request, render_template, Request, session
    from functools import wraps
    app = Flask(__name__)
    app.secret_key = 'ioooooo'
    
    def login_required(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            try:
                name = session['name']
            except KeyError:
                return 'please login'
            return fn(*args, **kwargs)
        return wrapper
    
    @app.route('/')
    @login_required
    def index():
        return 'index'
    

如果login_required放在上面(测试例子):

理论上可以,已经被app.route('/')装饰过的函数返回index函数本身, login_required直接装饰index函数没有问题,但是index视图函数被调用时,先执行路由映射,如果已经有返回值了,那么相当于login_required没有执行。

from flask import Flask, request, render_template, Request, session
from functools import wraps
app = Flask(__name__)
app.secret_key = 'ioooooo'

def login_required(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        try:
            name = session['name']
        except KeyError:
            return 'please login'
        print('name:', name)
        return fn(*args, **kwargs)
    return wrapper

@login_required
@app.route('/')
def index():
     return 'index'

同一个login_required 装饰器,如果装饰多个视图函数,需要加wraps或者给视图函数重新取个名字endpoint='xxxx':

原因是:一个视图函数默认的endpoint名字是函数名字,在flask内部维护一个字典,每个endpoint,都对应一个视图函数,并且一个endpoint只能对应一个视图函数,如果不加wraps装饰器,则视图函数都用同一个名字,所以程序运行不起来。

# 加wraps例子(推荐)
from flask import Flask, request, render_template, Request, session
from functools import wraps
app = Flask(__name__)
app.secret_key = 'ioooooo'

def login_required(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        try:
            name = session['name']
        except KeyError:
            return 'please login'
        print('name:', name)
        return fn(*args, **kwargs)
    return wrapper


@app.route('/')
@login_required
def index():
    return 'index'


@app.route('/detail')
@login_required
def detail():
    """ detail doc """
    return 'detail'


if __name__ == "__main__":
    app.run(debug=True)
    print(app.url_map)

# 加endpoint例子(不推荐)
from flask import Flask, request, render_template, Request, session
from functools import wraps
app = Flask(__name__)
app.secret_key = 'ioooooo'

def login_required(fn):
    def wrapper(*args, **kwargs):
        try:
            name = session['name']
        except KeyError:
            return 'please login'
        print('name:', name)
        return fn(*args, **kwargs)
    return wrapper


@app.route('/', endpoint="index")
@login_required
def index():
    return 'index'


@app.route('/detail', endpoint="detail")
@login_required
def detail():
    """ detail doc """
    return 'detail'


if __name__ == "__main__":
    app.run(debug=True)
    print(app.url_map)

装饰器加深理解例子

def logger(fn):
    @wraps(fn)
    def wrapper(*args):
        ret = fn(*args)
        return ret
    return wrapper
#print('logger:', logger)  两个圈先执行哪一个?先装饰@wraps,先执行@logger,
# 如果一个函数上面有2个装饰器的装饰和执行顺序,离add距离近的先装饰,离add近的后执行
@logger  # add = logger(add)
def add(x, y):return x + y
    
@logger
def sub(x, y):pass
# print(logger.__defaults__) None
# print(add.__name__, sub.__name__)
# logger什么时候执行? 在第8行开始执行,11行执行第二次,一共执行两次
# wraps装饰器执行了几次? 2次
# wapper的name属性被覆盖过几次? 2次
# add.__name__打印什么名字? add,因为wapper name虽然被覆盖了2次但是两个不同的wapper,装饰时执行logger(add)或者logger(sub), 是2个不同的栈。
# sub.__name__打印什么? sub

类装饰器

装饰器基础例子:

# 给函数增加统计时间的功能
import datetime
def add(a, b):
    return a + b

def logger(fn, *args, **kwargs): # 同时传入另一个函数的参数和变量
    start = datetime.datetime.now()
    x = fn(*args, **kwargs)
    
    delta = (datetime.datetime.now()-start).total_seconds()
    print(delta)
    return x
    
logger(add, 1, 2) # 三种调用方式等价
logger(add, 1, b=2)
logger(add, a=1, b=2)



# 第一次修改,在logger中先传入add, 再传入2个参数(柯里化)
import datetime

def add(a, b):
    return a + b

def logger(fn):
    def wrapper(*args, **kwargs):     
        start = datetime.datetime.now()
        x = fn(*args, **kwargs)
        delta = (datetime.datetime.now()-start).total_seconds()
        print(delta)
        return x
    return wrapper
logger(add)(1, 2) 


# 进一步修改,变成装饰器(背)
import datetime

def logger(fn):
    def wrapper(*args, **kwargs):     
        start = datetime.datetime.now()
        x = fn(*args, **kwargs)
        delta = (datetime.datetime.now()-start).total_seconds()
        print(delta)
        return x
    return wrapper

@logger # add = logger(add)
def add(a, b):
    return a + b

# logger(add)(1, 2) 在add上面加一个装饰器后,add函数的调用,等价于这个式子。
add(1, 2)



# 在上面装饰器的基础上增加修改函数fun.__name__ 、fun.__dic__的功能
def update_add():
    wrapper.__name__ = fn.__name__
    wrapper.__doc__ = fn.__doc__

def logger(fn):
    def wrapper(*args, **kwargs):
        'wrapper doc'
#         wrapper.__name__ = fn.__name__ # 写到里面只有函数执行时才会被修改
#         wrapper.__doc__ = fn.__doc__   
        return fn(*args, **kwargs)
    wrapper.__name__ = fn.__name__  # 写到外面在函数定义时就可以被修改
    wrapper.__doc__ = fn.__doc__
    
    #update_add(fn, wrapper)
    return wrapper

@logger # add = logger(add)
def add(a, b):
    'add doc'
    return a + b    
# 调用add, 先执行装饰器,在返回来的add基础上再接着执行,调用logger(add),返回内层的wrapper函数,该函数用到,外层函数的fn变量,形成闭包,一般来看装饰器,装饰完成时,内层函数使用的fn,就没有人记住了,但是因为外面的add标识符,已经指向了里面的wrapper,所以fn不会消亡。
# add指向wrapper函数,所以,当获取name和dict属性时,得到的是wrapper的名字和字典
add(1, 2)


# 第一次修改将更新操作提取成一个函数
def update_add(wrapper, fn):
    wrapper.__name__ = fn.__name__
    wrapper.__doc__ = fn.__doc__
    
def logger(fn):
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs)
   
    update_add(wrapper, fn)
    return wrapper
    
@logger # add = logger(add)
def add(a, b):
    return a + b
#add(1, 2)
add.__name__

第二次修改,柯里化实现修改属性的功能

实现了这个例子之后,应该可以实现给任何一个函数增加带参数装饰器的过程

def update_properties(src):
    def _copy(dest):
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest # 装饰器的内层函数需要有返回值
    return _copy    
  
def logger(fn):
    #wrapper =update_properties(fn)(wrapper)
    @update_properties(fn)  
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs) # 装饰器的内层函数需要有返回值   
    return wrapper
  
@logger # add = logger(add)
def add(a, b):
    return a + b


#add(1, 2)
add.__name__

不带参数装饰器另一个例子(实际编程就用写好的wraps,来更新属性):

# 不带参数的装饰器,一般只有两层(例子:修改属性,打印函数运行时间等)
def logger(fn):
    @wraps(fn) #  wraps(add)(wrapper)  wraps(add)返回一个只能接受一个单参数的函数,函数的名字叫update_wrapper
    def wrapper(*args):
        ret = fn(*args)
        return ret
    return wrapper
@logger
def add(x, y):
    return x + y
add(1, 2)

带参数装饰器例子:

题目:将函数运行时间超值了,就提取出来

# 带参数装饰器,是一个函数(例如:下面例子中的logger,wraps等函数),接受的参数也是一个函数,返回值是一个不带参数的装饰器(隐形等价式),可以看做在无参装饰器外层再加一层函数(不一定是3层也可能是2层,例如wraps),这个函数(最外层,即第3层函数)可以接受多个参数。
# 带参数装饰器可以总结为,本身是一个函数,接受一个函数作为参数,并且返回一个函数


# 分析:在最外层增加了时间间隔参数和一个打印函数,打印函数也可以换成写入文件的函数
import datetime, time, functools
def logger(duration, func = lambda name, delta: print('{} took {} s.'.format(name, delta))):
    def _logger(fn):
        @wraps(fn) #wraps(fn)(wrapper)
        def wrapper(*args):
            start = datetime.datetime.now()
            ret = fn(*args)
            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > duration:
                 func(fn.__name__, delta)
                    
            return ret
        return wrapper
    return _logger
@logger(2)  # add = logger(2)(add)
def add(x, y):
    time.sleep(2)
    return x + y
add(1, 2)

记录函数运行时间非柯里化写法

import time
import datetime
from functools import wraps
import json


def record_logging(logs):
    with open('test_logging', 'w', encoding='utf8') as f:
        f.write(json.dumps(logs))


def logger(fn, duration=2, record_logging=record_logging):

    @wraps(fn)
    def wrapper(*args, **kwargs):
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        if delta > duration:
            record_logging({fn.__name__: delta})
        return ret
    return wrapper


@logger
def add(x, y):
    time.sleep(3)
    return x + y


@logger
def sub(x, y):
    time.sleep(2)
    return x - y


print(add(1, 2))

(11.1)柯里化

定义:将原来接受多个参数的函数,改成接受一个参数的过程(不同于partial(类))

def add(x, y):
return x + y

add(1, 2)

def add(x):
def _add(y):
return x + y
return _add

add(1)(2)

12. 正则表达式

基础规则

正则表达式(Regular Expresion): regex,regexp,RE

  • 基本正则表达式:(BRE), grep、sed、vi等软件支持。vim有扩展

  • 扩展正则表达式:(ERE) egrep(grep-E)、sed-r等

  • PCRE:几乎所有高级语言都是PCRE的方言或者变种

  • 正则表达式中的元字符(metacharat er):

    [图片上传失败...(image-dcc64-1630076717257)]


    re元字符.png

\w : 可以匹配字母,数字,下划线,中文

  • 控制匹配次数的字符:

    [图片上传失败...(image-725a10-1630076717257)]


    re匹配数量.png
  • 捕获(分组匹配):

    [图片上传失败...(image-3fa1a9-1630076717257)]


    re捕获分组匹配.png
  • 贪婪匹配:

    [图片上传失败...(image-774e39-1630076717257)]


    re贪婪匹配.png
  • 匹配反义字符

    [图片上传失败...(image-82b377-1630076717257)]


    re 反义字符.png

转义字符:正则表达式中有特殊意义的字符用\转义,例如: \ 表示反斜杠 \r \n \r\n表示换行 或: x|y

re模块

# 一般先编译再匹配

re.M|re.S(DOTALL)|re.I

位运算多行,单行,忽略大小写,默认是单行模式

re.compile(pattern, flags=0)

单次匹配match search fullmatch

# match:从pos位置开始匹配,默认从第一个位置开始匹配,如果匹配不到返回None

编译匹配

regex = re.compile(pattern, flags)
regex.match(string[,pos[, endpos]])

直接匹配

re.match(pattern, string, flag=0)

search:从头开始搜素,如果第一个单词没搜索到,则搜素第二个单词,一直向下搜索,找不到返回

regex.search(string[, pos[,endpos]])
re.search(pattern, string, flags=0)

fullsearch

regex.fullsearch(string[, pos[, endpos]])
re.fullsearch(pattern, string, flags=0)

全文搜索finditer findall

#搜索整个字符串,所有匹配项返回列表,没找到返回空列表
regex.findall(string[, pos[, endpos]])
re.findall(pattern, string, flags=0)

返回迭代器,每次迭代返回match对象

regex.finditer(string[, pos[, endpos]])
re.finditer(pattern, string, flags=0)

匹配替换sub, subn

全局搜索,对匹配项使用repl替换,replacement可以是
string,bytes,function
默认count=0,全部替换
regex.sub(replacement, string, count=0)
re.sub(pattern, replacement, string, count=0,flags=0)

返回一个元组
regex.subn(replacement, string, count=0)
re.subn(pattern, replacement, string, count=0)

分割字符串split

regex.split(string)
re.split(pattern, string, maxsplit=0, flags=0)

分组匹配

match,seatch,finditer返回值并迭代,都返回match对象
findall返回字符串列表
先匹配,如果匹配成功,会在match对象中生成分组
matchobject.groups() 返回一个元组,元素为组名
matchobject.group(0) 返回match的具体内容
matchobject.group(1) 返回元组中的第一个元素
如果使用了命名分组
matchobject.groupdict()

例子:

# match例子(用的多):
s = '''bottle\nbag\nbig\napple'''
regex = re.compile('(b\w+)') # b在组里面,编译匹配
result = regex.match(s)
print(result) # 匹配上了bottle,返回一个match对象
print(result.groups()) # ('bottle',)
print(result.group(0)) # ’bottle'
print(result.group(1)) # 'bottle'

regex = re.compile('b(\w+)') # b 在组外面,编译匹配
result = regex.match(s)
print(result) # 匹配上了bottle,返回一个match对象
print(result.groups()) # ('ottle',)
print(result.group(0)) # ’bottle'第0个位置是match对象匹配的内容,不是分组里面的内容
print(result.group(1)) # 'ottle'

regex = re.compile('b\w+') # 没有分组,编译匹配
result = regex.match(s)
print(result) # 匹配上了bottle,返回一个match对象
print(result.group(0)) # bottle, 可以通过第0个位置获取匹配到的内容

result = re.match('b\w+', s)# 不编译匹配,匹配上了bottle,返回一个match对象
print(result)
print(result.group(0)) # bottle, 可以通过第0个位置获取匹配到的内容

命名分组

s = '''bottle\nbag\nbig\napple'''
regex = re.compile('b(\w+)\n(?Pb\w+)\n(?Pb\w+)')
result = regex.match(s)
print(result.groupdict()) # {'age': 'bag', 'sex': 'big'}
print(result.groups()) # ('ottle', 'bag', 'big')
print(result.group(0)) # 'bottle\nbag\nbig
print(result.group(1)) # 'ottle'
print(result.group(2)) # 'bag'

print(result.group(3)) # 'big'
print(result.group('sex')) # 'big',两个相同,感觉group里面也存了一个dict

# rearch例子(多)
s = '''bottle\nbag\nbig\napple'''
regex = re.compile('(b\w+)')
result = regex.search(s, 1)
print(result) # 匹配上了bag,因为是从第1个位置开始匹配,返回一个match对象
print(result.groups()) # ('bag',)
print(result.group(0)) # ’bag'
print(result.group(1)) # 'bag'

result = re.search('(b\w+)', s)# 匹配上了bottle,返回一个match对象,不能匹配bag,因为不能指定位置pos
print(result)
print(result.group(0)) # bottle, 可以通过第0个位置获取匹配到的内容

# fullmatch例子(少)
s = '''bottle\nbag\nbig\napple'''
regex = re.compile('bag')
result = regex.fullmatch(s)
print(result) # None

regex = re.compile('bottle')
result = regex.fullmatch(s)
print(result) # None

regex = re.compile('^b.+', re.S)
result = regex.fullmatch(s)
print(result) # 全部匹配返回match对象

# finditer例子
s = '''bottle\nbag\nbig\napple'''
regex = re.compile('(?Pb\w+)')
result = regex.finditer(s)
for i in result:
print(i.groups(), i.group('head'))

finditer全局匹配,匹配到第一个分组后,

接着匹配形成第2个分组,所以最后有3个分组

i.group('head')等价于i.group(0)

('bottle',) bottle

('bag',) bag

('big',) big

# findall例子
s = '''bottle\nbag\nbig\napple'''
regex = re.compile('b(\w+)\n(?Pb\w+)\n(?Pb\w+)')
result = regex.findall(s)
print(result) # [('ottle', 'bag', 'big')]

注意findall返回一个list,里面的元素是包含分组内容的元组

不能获得match真正匹配的内容,用的少,如果想获得match匹配的内容

可以使用finditer,去出match对象的group(0)

for i in result:
print(i)

regex = re.compile('^b')
result = regex.findall(s)
print(result) #['b'],findall虽然匹配多次但是默认只有一行

以b开头的只有一个

regex = re.compile('^b\w+')
result = regex.findall(s)
print(result) #['bottle']

regex = re.compile('^b.', re.S)
result = regex.findall(s)
print(result) #['bo'] .表示除了空格以外的任意字符,

但是只匹配一个字符

regex = re.compile('^b.+', re.S|re.M|re.I)
result = regex.findall(s)
print(result) # ['bottle\nbag\nbig\napple']

单行模式下,.可以穿透\n

regex = re.compile('^b.+')
result = regex.findall(s)
print('default:',result) #['bottle']

默认模式遇到.不会穿透

regex = re.compile('^b\w+', re.S|re.M|re.I)
result = regex.findall(s)
print(result) # ['bottle', 'bag', 'big']

regex = re.compile('^b\w+', re.S|re.M|re.I)
result = regex.findall(s, 1)
print(result) # 指定pos从第一个位置之后开始匹配['bag', 'big']

# 分割字符串例子
s = """
os.path.abspath(path)
normpath(join(os.getcwd(), path)).
"""

中括号里面的. ()不需要加转义字符

加上+ 字符串里面的,和空格会被一起切断,

不然会产生好多的空串

print(re.split('[.()\s,]+', s))

['', 'os', 'path', 'abspath', 'path',

'normpath', 'join', 'os', 'getcwd', 'path', '']

# 匹配替换练习

sub返回一个str,不是一个match对象

s = '''bottle\nbag\nbig\napple'''
regex = re.compile(r'\n')
result = regex.sub('#', s)
print(result, type(result)) # 'bottle#bag#big#apple'
result = regex.subn('#', s, count=2)
print(result) # ('bottle#bag#big\napple', 2)

正则表达式在文档中的应用

s = '''
tom 20\t\tmagedu\tM
jerry 30 pku
'''

迭代文件的方法

fns = ('name', 'age', 'school')
for line in s.splitlines():
fields = line.split()
if fields:

print(dict(zip(fns, fields)))

print(dict(map(lambda x,y: (x,y), fns, fields)))

正则匹配的方法(先分成单个行,再匹配)

pattern = r'(?P\w+)\s+(?P\d+)\s+(?P\w+)'
regex = re.compile(pattern)
for line in s.splitlines():
if line:
matchobj = regex.match(line)
print(matchobj.groupdict())

正则匹配的方法(整篇文章一起匹配)

pattern = r'(?P\w+)\s+(?P\d+)\s+(?P\w+)'
regex = re.compile(pattern)
for matchobj in regex.finditer(s):
print(matchobj.groupdict())

将字符串改造成元组的方法

import re
s = ''' bottle\nbag\nbig\napple'''
a = ''.join(map(str, enumerate(s)))
print(a, type(a))
regex = re.compile(r'[\n ]+')
b = regex.sub('', a)
print(b, type(b))

五、知识点杂记

  • 剩余内存够用不代表连续的内存够用

  • 字节码也是可以被虚拟机识别的二进制

  • js可以动态生成网页,但是消耗内存

  • 代码都放在内存中,以便cpu使用

  • 二进制的机器语言 到cpu的指令集一定要有几行汇编语言

  • 动态语言运行时才知道变量类型,静态语言编译时就知道变量类型

  • 赋值即定义,赋值同时重新指向

  • Jupyter notebook

    • 快捷键:

    • m 切换文字

    • ‘#’后面加空格

    • ‘*’ 表示正在运行 两种停止办法

    • shift + enter 运行

    • ctrl + enter 生成新行加运行

    • a b 新建cell

    • 找个文件夹打开 jupyter notebook

    • ~/.pip/pip.conf 里面填写[阿里云镜像](mirrors.aliyun.com) 里面pypi 的内容

    • dd 删除单元格

    • _ 下划线返回上一次输出,__两个下划线表示前2次输出

    • 可以使用 ls cd cd- (减号表示)上一次目录

    • _dh 可以到处切换历史

    • !touch test.txt表示当前操作系统中的命令

    • %%timeit 语句块的时间,%timeit primenum()函数执行时间

    • 在jupyter notebook里面无out无返回值

  • 程序放在内存中分为几个进程

  • 几个核就是几个cup

  • 充分利用内存可以优化程序

  • python中大写字母一般用在类中,或者表示常量

  • python常量无法定义,但是可以全大写字母表示常量

  • 字面常量:例如12,不可以更改,但可以通过运算生成新的常量13

  • 三引号可以表示多行字符串,也可以识别里面的单引号和双引号

  • 不要同时使用多个if, 尽量使用if elif else

  • 除法的计算量很大

  • [1,2] +[4,5]表示新生成一个列表(会单独占用空间)

  • str(任何对象都可以转换成字符串)

  • if else 判断语句如果多,对效率的影响不大,但是如果下面的交换次数多则会影响效率

  • 容器与遍历互为充要条件,可以遍历的不一定有索引,但是有索引的一定可以遍历

  • reversed 函数底层实现,与倒着取索引-(i+1), 原理相同

  • referenced 引用

六、平时练习注意事项:

  • 多查看官方文档,自己整理

  • 练习时现有基本思路,写出代码再修改

  • 自己debug, 学会单元测试,注意多线程都会在哪里出现bug

  • 在学框架之前,需要熟悉标准库

七、待解决的问题:

  • 8086原理?

  • 固态硬盘与硬盘?

  • 标识符与变量?

  • c =3 >5 返回false,先计算右侧再进行左侧赋值

  • priorityqueue源码

  • threading.local类,大小字典源码

八、文件操作

文件操作:IO操作一般指文件IO,如果是网络IO就会直接说网络IO。 磁盘:目前仍是文件持久化最重要的设备 磁盘结构示意图:

[图片上传失败...(image-a221a9-1630076920518)]

等弧长分区示意图:

[图片上传失败...(image-3e0e6f-1630076920518)]

  • 打开文件

    • 一般写文档时只使用'\n', 读文档时使用默认的None模式读取

open函数常用的参数:file mode encoding

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) 文件的访问模式分为:文本模式和二进制模式

  • mode

    r: 只读模式,不可以写入文件,如果文件不存在FileNotFoundError,rb rt(默认) rb+ rt+ r+

    w: 只写模式,不可以读文件,如果文件存在会清空原来的内容,从头开始写入,不存在会创建文件,wb wt wb+ wt+ w+

    x: 只写模式,文件存在会FileExitsError, 文件不存在会新创建一个文件,xb xt xb+ xt+ x+

    a: 只写模式,文件存在打开追加内容,文件不存在创建文件,写入内容,ab at ab+ at+ a+

    r: 只读,wxa: 只写,并且会产生新文件

  • encoding:打开或者创建文件时都需要指定编码,因为可能会在不同的操作系统上使用文件,Windows中使用codepage代码页,可以认为每一个代码页就是一张编码表,cp936等同于GBK,这也是Windows中默认的编码,linux中默认的编码是utf-8。

  • errors: 如果值为NONE或strict,有编码错误会抛出ValueError, 如果值为ignore表示忽略编码错

  • newline: 一般文本模式会使用,只可以填5种None, "", '\r', '\n', '\r\n'

    写时:

    • None模式下,'\n'会被替换成系统的缺省行分隔符,例如:windows系统为'\r\n', mac系统'\r',linux系统'\n'

    • ""空串模式下常见的换行符都会换行

    • 其他3种换行符,按照指定换行符换行

读时:

  • None模式下,是在5种模式下唯一发生转换的模式'\r','\n','\r\n'都会被转换成'\n', 即'\n','\n','\n\n'

    • ""空串模式下常见的换行符都认为是换行
  • 其他3种换行符,按照指定换行符换行

  • closefd:默认为True,表示关闭文件时会关闭文件描述符。

    例子:

with open('test', 'w', newline=) as f:
f.write('python\rwww.python.org\nwww.magedu.com\r\npython3')

在Windows下相当于写入

'python\rwww.python.org\r\nwww.magedu.com\r\r\npython3'

在mac下相当写入???

'python\rwww.python.org\rwww.magedu.com\r\rpython3'

newlines = [None, '', '\r', '\n', '\r\n']
for nl in newlines:
f = open('test', newline=nl) # 分别替换不同换行符
print(f.readlines())
f.close()

['python\n', 'www.python.org\n', 'www.magedu.com\n', 'python3'] None模式下\r被替换为\n ???

['python\r', 'www.python.org\n', 'www.magedu.com\r\n', 'python3'] ''模式下会识别出来3中换行符,但是不会替换

['python\r', 'www.python.org\nwww.magedu.com\r', '\npython3'] 遇到'\r'换行

['python\rwww.python.org\n', 'www.magedu.com\r\n', 'python3'] 遇到'\n'换行

['python\rwww.python.org\nwww.magedu.com\r\n', 'python3'] 遇到'\r\n'换行

  • 文件读取方法总结

    f.read(size=-1) # 从头开始读 f.readline(size=-1) # 一行一行读取,size可以设置读取行内几个字符或字节 f.readlines() # 读取所有行返回列表

    大多数情况都用下面的方式读取文件内容

    f = open('test') # 返回可迭代对象
    for line in f:
    print(line.encode().strip())
    f.close()

  • 写方法总结

    f = open('test', 'w+') f.writelines(['abc\n', '123']) #需要提供换行符

    常用的写入文件的方法

    f = open('test', 'w+')
    f.write('\n'.join(['abc', '123']))

  • 关闭方法总结

    f.close() # 文件关闭时自动调用一次flush() 为了避免因为异常而无法关闭文件,有两种处理方式 方式1:异常处理

    try:
    f = open('test', 'w')
    f.read()
    finally: # 无论try中是否报错都会执行finally 语句
    f.close()

    方式2:上下文管理的方式 上下文管理的语句不会开启新的作用域 with语句块执行完会自动关闭文件对象

    with open('test', 'w') as f:
    f.write('abc')
    f.closed

  • 其他方法总结

    f.seekable() f.readable() f.writable() f.closed 查看文件是否关闭,返回一个布尔值,不能加括号,不可调用 如果文件已经关闭则 f.fileno()会报ValueError

    cat test1 # 查看文件,如果文件中是字节b'abc',则也可以查看出'abc' f.read() # 如果文件中是字节b'abc', 则查看出b'abc'

  • 文本模式及二进制模式总结

方法: 文本模式 : 二进制模式 : 指针位置:
f.read(2) 按字符读取 按字节读取 指针起始在0,读完指针指向第2个位置
f.write() 按字符写入 按字节写入 从指针当前位置开始写,写完指针停在EOF
f.tell() - - 显示指针当前位置
f.seek(偏移量[,whence=0,1,2]) 按字节偏移,3种模式分别是指针在开头,也是缺省值 ,offset只能接受正整数; 当前指针位置,offset只能接受0; EOF offset只能接受0。 按字节偏移,3种模式是开头位置,也是缺省值 offset只能接受正整数; 当前指针位置,offset可正可负; EOF offset可正可负。 二进制模式支持任意起点的偏移,从头,从尾,从中间开始,向后 seek可以超界,向前超界则会抛异常
open中的参数buffer 可以看作是是一个先进先出的队列(FIFO)文件, 在缓冲区满了或者达到阈值时,数据会被flush到磁盘上 flush()将缓冲区数据写入到磁盘、 close()关闭前会自动第一次flush() -1 缺省模式可通过io.DEFAULT_BUFFERSIZE 查看大小(一般都用缺省)缺省值的大小一般是4096或8192KB >1使用缺省模式 1 行缓冲遇到换行符才flush(用得少 0不支持 -1 同文本模式 >1比如buffer=10,那么超过10缓冲区hui被flush 1不支持 0关闭缓冲区(一般不用)br />
open中的参数mode为a时 - - 指针会停在EOF

linux查看打开文件数量

[图片上传失败...(image-446708-1630076920517)]

file-like-object

  • 类文件对象,可以像文件对象一样操作

  • socket对象、输入输出对象(stdin、stdout)、StringIO()、BytesIO()都是类文件对象

import sys
f = sys.stderr # 不同于sys.stdout, 输出的内容会放到一个变颜色的

error区域,f是一个文件对象,可读可写,不可seek

print(f.seekable(), f.readable, f.writable, file=f)
sys.stdout.write('abc') # 标准输出打印内容需要调用写方法

StringIO()与BytesIO()例子:

from io import StringIO, BytesIO
f = StringIO() # 是一个类文件对象,ByteIO与这个类似

是内存中的一个缓冲区域(buffer),

文本模式的buffer

调用close()方法,bufffer会被释放

print(f.seekable(), f.readable(), f.writable()) # True True True
print(f.fileno()) # UnsupportedOperation
f.seek(0)
f.getvalue() # 无视指针,输出全部内容
f.close()

不同:

与文本模式不同的是,StringIO() 没有fileno,

但是多了一个getvalue方法

好处:

一般来讲。磁盘的操作比内存的操作要慢的多,内存足够的情况下

一般的优化思路是少落地,减少磁盘IO的过程,可以大大提高程序运行的效率

九、作业及练习

https://github.com/AprilJW/magedu/tree/master/python

你可能感兴趣的:(python基础笔记2)