day07

闭包函数

  • 函数对象:可以将定义在函数内的函数返回到全局使用,从而打破函数的层级限制。
  • 名称空间与作用域:作用域关系在函数定义阶段时就已经固定死了,与调用位置无关,即在任意位置调用函数都需要跑到定义函数时找到作用域关系。
def f1():
    x = 1

    def inner():
        print(x)
    return inner


func = f1()

x = 2


def f2():
    x = 3
    func()


f2()
1

什么是闭包

闭包:闭是封闭(函数内部函数),包是包含(该内部函数对外部作用域而非全局作用域的变量的引用)。闭包指的是:函数内部函数对外部作用域而非全局作用域的引用。

提示:之前我们都是通过参数将外部的值传给函数,闭包提供了另外一种思路,包起来喽,包起呦,包起来哇。

def outter():
    x = 1

    def inner():
        print(x)
    return inner


f = outter()


def f2():
    x = 2
    f()


f2()
1

两种为函数传参的方式

为函数传参的方式一:使用参数的形式

def func(x):
    print(x)


func(1)
func(1)
func(1)
1
1
1

为函数传参的方式二:包给函数

def outter(x):
    x = 1

    def inner():
        print(x)
    return inner


f = outter(1)
f()
f()
f()
# 查看闭包的元素
print(F"f.__closure__[0].cell_contents: {f.__closure__[0].cell_contents}")
1
1
1
f.__closure__[0].cell_contents: 1

闭包函数的应用

闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。

应用领域:延迟计算(原来我们是传参,现在我们是包起来)、爬虫领域。

import requests


def get(url):
    response = requests.get(url)
    print(f"done: {url}")
    
get('https://www.baidu.com')
get('https://www.baidu.com')
get('https://www.baidu.com')


get('https://www.cnblogs.com/linhaifeng')
get('https://www.cnblogs.com/linhaifeng')
get('https://www.cnblogs.com/linhaifeng')
done: https://www.baidu.com
done: https://www.baidu.com
done: https://www.baidu.com
done: https://www.cnblogs.com/linhaifeng
done: https://www.cnblogs.com/linhaifeng
done: https://www.cnblogs.com/linhaifeng
import requests


def outter(url):
    def get():
        response = requests.get(url)
        print(f"done: {url}")
    return get

baidu=outter('https://www.baidu.com')
python = outter('https://www.python.org')

baidu()
baidu()

python()
python()
done: https://www.baidu.com
done: https://www.baidu.com
done: https://www.python.org
done: https://www.python.org

装饰器

无参装饰器

什么是装饰器?

器指的是工具,而程序中的函数就是具备某一功能的工具,所以装饰器指的是为被装饰器对象添加额外功能。因此定义装饰器就是定义一个函数,只不过该函数的功能是用来为其他函数添加额外的功能。

需要注意的是:

  • 装饰器本身其实是可以任意可调用的对象
  • 被装饰的对象也可以是任意可调用的对象

为什么要用装饰器?

如果我们已经上线了一个项目,我们需要修改某一个方法,但是我们不想修改方法的使用方法,这个时候可以使用装饰器。因为软件的维护应该遵循开放封闭原则,即软件一旦上线运行后,软件的维护对修改源代码是封闭的,对扩展功能指的是开放的。

装饰器的实现必须遵循两大原则:

  1. 不修改被装饰对象的源代码
  2. 不修改被装饰对象的调用方式

装饰器其实就是在遵循以上两个原则的前提下为被装饰对象添加新功能。

怎么用装饰器?

改变源代码

import time


def index():
    start = time.time()
    print('welcome to index')
    time.sleep(1)
    end = time.time()
    print(F"index run time is {start-end}")


index()
welcome to index
index run time is -1.0126011371612549

编写重复代码

import time


def index():
    print('welcome to index')
    time.sleep(1)


def f2():
    print('welcome to index')
    time.sleep(1)


start = time.time()
index()
end = time.time()
print(F"index run time is {start-end}")

start = time.time()
f2()
end = time.time()
print(F"f2 run time is {start-end}")
welcome to index
index run time is -1.011988639831543
welcome to index
f2 run time is -1.0087692737579346

定义了两个函数 index 和 f2,它们都输出一条欢迎信息并执行一个耗时 1 秒的操作。

然后,我们使用 time 模块的 time 函数获取当前时间的时间戳,并将其赋值给变量 start。

接着,我们分别调用了 index 和 f2 函数。

在每个函数调用后,我们再次使用 time 函数获取当前时间的时间戳,并将其赋值给变量 end。

通过计算 end - start,我们得到了函数运行的时间,并打印出来。

请注意,代码中的字符串格式化表达式有一个错误。应该是 end - start 而不是 start - end,因为我们想要计算时间差。

另外,我们在控制台输出的字符串中使用了单引号和双引号,但这不会影响代码的执行。

这样,你可以分别获得 index 函数和 f2 函数运行的时间,并打印出来。

你可以根据需要修改代码,添加其他函数和计时逻辑来满足你的实际需求。

第一种传参方式:改变调用方式

import time


def index():
    print('welcome to index')
    time.sleep(1)


def time_count(func):
    start = time.time()
    func()
    end = time.time()
    print(f"{func} time is {start-end}")


time_count(index)
welcome to index
<function index at 0x00000201FE6A8C80> time is -1.000319004058838

定义了一个 index 函数,它输出一条欢迎信息并执行一个耗时 1 秒的操作。

我们还定义了一个 time_count 函数,它接受一个函数作为参数,并计算并打印该函数的运行时间。

在 time_count 函数内部,我们使用 time 模块的 time 函数获取当前时间的时间戳,并将其赋值给变量 start。

然后,我们调用传入的函数 func。

在函数调用后,我们再次使用 time 函数获取当前时间的时间戳,并将其赋值给变量 end。

通过计算 end - start,我们得到了函数运行的时间,并打印出来。

最后,我们调用 time_count 函数,并传入 index 函数作为参数,以计算并打印 index 函数的运行时间。

通过这种方式,你可以封装一个通用的计时函数,在调用其他函数时,可以方便地获取它们的运行时间。

你可以根据需要修改代码,并在 time_count 函数中添加其他逻辑来满足你的实际需求。

第二种传参方式:包给函数-外包

import time


def index():
    print('welcome to index')
    time.sleep(1)


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"{func} time is {start-end}")
    return wrapper

# f = time_count(index)
# f()


index = time_count(index)  # index为被装饰函数的内存地址,即index = wrapper
index()  # wrapper()
welcome to index
<function index at 0x00000201FE8F7378> time is -1.009307861328125

对 time_count 函数进行了修改,使用装饰器的方式来计算函数的运行时间。

在 time_count 函数内部,我们定义了一个名为 wrapper 的嵌套函数。wrapper 函数用于实际执行被装饰的函数,并在执行前后计算时间差并打印出来。

通过 @time_count 语法,我们将 time_count 装饰器应用到了 index 函数上,相当于执行了 index = time_count(index)。

这样,当我们调用 index() 时,实际上是调用了 wrapper() 函数。在 wrapper 函数内部,会先计算起始时间戳,然后执行被装饰的函数 func(),最后计算结束时间戳,并打印出函数运行的时间。

通过这种方式,我们可以方便地为多个函数添加相同的额外逻辑,例如计时、日志记录等。

最后的调用 index() 将执行被装饰后的函数,并计算并打印其运行时间。

你可以根据需要修改代码,并为其他函数应用装饰器来满足你的实际需求。

完善装饰器

上述的装饰器,最后调用index()的时候,其实是在调用wrapper(),因此如果原始的index()有返回值的时候,wrapper()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和wrapper()方法的返回值。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        res = func()
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


index = time_count(index)
res = index()
print(f"res: {res}")
welcome to index
<function index at 0x00000201FE8F7268> time is -1.011932134628296
res: 123

如果原始的index()方法需要传参,那么我们之前的装饰器是无法实现该功能的,由于有wrapper()=index(),所以给wrapper()方法传参即可。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123


def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


home = time_count(home)

res = home('egon')
print(f"res: {res}")
welcome egon to home page
<function home at 0x00000201FE6A8C80> time is -1.0066509246826172
res: egon

装饰器语法糖

在被装饰函数正上方,并且是单独一行写上 @装饰器名

import time


def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


@time_count  # home = time_count(home)
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


@time_count  # index = time_count(index)
def index():
    print('welcome to index')
    time.sleep(1)

    return 123


res = home('egon')
print(f"res: {res}")
welcome egon to home page
<function home at 0x00000201FE8F7268> time is -1.00465726852417
res: egon

装饰器模板

def deco(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper

有参装饰器

无参装饰器只套了两层,本节将讲一个套三层的装饰器——有参装饰器,但现在我们先实现一个用户登录注册的装饰器。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        if current_user['username']:
            res = func(*args, **kwargs)

            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()

        if user == 'nick' and pwd == '123':
            print('login successful')
            current_uesr['usre'] = user
            res = func(*args, **kwargs)

            return res
        else:
            print('user or password error')

    return wrapper


@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


@login
def index():
    print('welcome to index')
    time.sleep(1)

    return 123


res = index()
username: nick
password: 123
login successful



---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-15-e1ee5d99915a> in <module>
     43 
     44 
---> 45 res = index()


<ipython-input-15-e1ee5d99915a> in wrapper(*args, **kwargs)
     17         if user == 'nick' and pwd == '123':
     18             print('login successful')
---> 19             current_uesr['usre'] = user
     20             res = func(*args, **kwargs)
     21 


NameError: name 'current_uesr' is not defined

对于上面的登录注册,我们把用户登录成功的信息写入内存当中。但是在工业上,用户信息可以存在文本中、mysql中、mongodb当中,但是我们只让用户信息来自于file的用户可以认证。因此我们可以改写上述的装饰器。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):

        if current_user['username']:
            res = func(*args, **kwargs)

            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()
        
        engine = 'file'

        if engine == 'file':
            print('base of file')
            if user == 'nick' and pwd == '123':
                print('login successful')
                current_uesr['usre'] = user
                res = func(*args, **kwargs)

                return res
            else:
                print('user or password error')
        elif engine == 'mysql':
            print('base of mysql')
        elif engine == 'mongodb':
            print('base of mongodb')
        else:
            print('default')

    return wrapper


@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)


@login
def index():
    print('welcome to index')
    time.sleep(1)


res = index()
username: nick
password: 123
base of file
login successful



---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-1-c13bda2a903d> in <module>
     50 
     51 
---> 52 res = index()


<ipython-input-1-c13bda2a903d> in wrapper(*args, **kwargs)
     22             if user == 'nick' and pwd == '123':
     23                 print('login successful')
---> 24                 current_uesr['usre'] = user
     25                 res = func(*args, **kwargs)
     26 


NameError: name 'current_uesr' is not defined

三层闭包

def f1(y):

    def f2():
        x = 1

        def f3():
            print(f"x: {x}")
            print(f"y: {y}")
        return f3
    return f2


f2 = f1(2)
f3 = f2()
f3()
x: 1
y: 2

现在需求改了,我们需要判断用户动态的获取用户密码的方式,如果是file类型的,我们则让用户进行认证。因此我们可以使用有参装饰器。

import time

current_uesr = {'username': None}


def auth(engine='file'):

    def login(func):
        # func = 最原始的index
        def wrapper(*args, **kwargs):

            if current_user['username']:
                res = func(*args, **kwargs)

                return res

            user = input('username: ').strip()
            pwd = input('password: ').strip()

            if engine == 'file':
                print('base of file')
                if user == 'nick' and pwd == '123':
                    print('login successful')
                    current_uesr['usre'] = user
                    res = func(*args, **kwargs)

                    return res
                else:
                    print('user or password error')
            elif engine == 'mysql':
                print('base of mysql, please base of file')
            elif engine == 'mongodb':
                print('base of mongodb, please base of file')
            else:
                print('please base of file')

        return wrapper

    return login


@auth(engine='mysql')
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)


@auth(engine='file')
def index():
    print('welcome to index')
    time.sleep(1)


res = index()
username: nick
password: 123
base of file
login successful
welcome to index

由于两层的装饰器,参数必须得固定位func,但是三层的装饰器解除了这个限制。我们不仅仅可以使用上述单个参数的三层装饰器,多个参数的只需要在三层装饰器中多加入几个参数即可。也就是说装饰器三层即可,多加一层反倒无用。

迭代器

迭代器:迭代的工具。迭代是更新换代,如你爷爷生了你爹,你爹生了你,迭代也可以说成是重复,并且但每一次的重复都是基于上一次的结果来的。如计算机中的迭代开发,就是基于软件的上一个版本更新。以下代码就不是迭代,它只是单纯的重复

while True:
    print('*'*10)

可迭代对象

# python中一切皆对象
x = 1
name = 'nick'
lis = [1, 2]
tup = (1, 2)
dic = {'name': 'nick'}
s1 = {'a', 'b'}


def func():
    pass


f = open('49w.txt', 'w', encoding='utf-8)

对于这一切的对象中,但凡有__iter__方法的对象,都是可迭代对象。

# x = 1.__iter__  # SyntaxError: invalid syntax

# 以下都是可迭代的对象

name = 'nick'.__iter__
lis = [1, 2].__iter__
tup = (1, 2).__iter__
dic = {'name': 'nick'}.__iter__
s1 = {'a', 'b'}.__iter__
f = open('49w.txt', 'w', encoding='utf-8')
f.__iter__
f.close()

可迭代的对象:Python内置str、list、tuple、dict、set、file都是可迭代对象。

特点:

  • 内置有__iter__方法的都叫可迭代的对象。

迭代器对象

只有字符串和列表都是依赖索引取值的,而其他的可迭代对象都是无法依赖索引取值的。因此我们得找到一个方法能让其他的可迭代对象不依赖索引取值。

在找到该方法前,首先我们给出迭代器对象的概念:可迭代的对象执行__iter__方法得到的返回值。并且可迭代对象会有一个__next__方法。

# 不依赖索引的数据类型迭代取值
dic = {'a': 1, 'b': 2, 'c': 3}
iter_dic = dic.__iter__()
print(iter_dic.__next__())
print(iter_dic.__next__())
print(iter_dic.__next__())
# print(iter_dic.__next__())  # StopIteration:
a
b
c
# 依赖索引的数据类型迭代取值
lis = [1, 2, 3]
iter_lis = lis.__iter__()
print(iter_lis.__next__())
print(iter_lis.__next__())
print(iter_lis.__next__())
# print(iter_lis.__next__())  # StopIteration:
1
2
3

上述的方法是非常繁琐的,我们可以使用while循环精简下。

s = 'hello'
iter_s = s.__iter__()

while True:
    try:
        print(iter_s.__next__())
    except StopIteration:
        break
h
e
l
l
o

迭代器对象:执行可迭代对象的__iter__方法,拿到的返回值就是迭代器对象。

特点:

  1. 内置__next__方法,执行该方法会拿到迭代器对象中的一个值
  2. 内置有__iter__方法,执行该方法会拿到迭代器本身
  3. 文件本身就是迭代器对象。

缺点:

  1. 取值麻烦,只能一个一个取,并且只能往后取,值取了就没了
  2. 无法使用len()方法获取长度

for循环原理

for循环称为迭代器循环,in后必须是可迭代的对象。

lis = [1, 2, 3]
for i in lis:
    print(i)
1
2
3

因为迭代器使用__iter__后还是迭代器本身,因此for循环不用考虑in后的对象是可迭代对象还是迭代器对象。

由于对可迭代对象使用__iter__方法后变成一个迭代器对象,这个迭代器对象只是占用了一小块内存空间,他只有使用__next__后才会吐出一个一个值。如lis = \[1,2,3,4,5,...\]相当于一个一个鸡蛋,而lis = \[1,2,3,4,5,...\].__iter__相当于一只老母鸡,如果你需要蛋,只需要__next__即可。

print(range(10))  # range(0, 10)

三元表达式

条件成立时的返回值 if 条件 else 条件不成立时的返回值

x = 10
y = 20

print(f"x if x > y else y: {x if x > y else y}")
x if x > y else y: 20

列表推导式

[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]
类似于
res=[]
for item1 in iterable1:
    if condition1:
        for item2 in iterable2:
            if condition2
                ...
                for itemN in iterableN:
                    if conditionN:
                        res.append(expression)
print(F"[i for i in range(10)]: {[i for i in range(10)]}")
[i for i in range(10)]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(F"[i**2 for i in range(10)]: {[i**2 for i in range(10)]}")
[i**2 for i in range(10)]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

字典生成式

print({i: i**2 for i in range(10)})
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

zip()方法

keys = ['name', 'age', 'gender']
values = ['nick', 19, 'male']

res = zip(keys, values)
print(F"zip(keys,values): {zip(keys,values)}")

info_dict = {k: v for k, v in res}
print(f"info_dict: {info_dict}")
zip(keys,values): <zip object at 0x00000259FAD16CC8>
info_dict: {'name': 'nick', 'age': 19, 'gender': 'male'}

通过解压缩函数生成一个字典

info_dict = {'name': 'nick', 'age': 19, 'gender': 'male'}
print(f"info_dict.keys(): {info_dict.keys()}")
print(f"info_dict.values(): {info_dict.values()}")

res = zip(info_dict.keys(), info_dict.values())
print(F"zip(keys,values): {zip(info_dict.keys(),info_dict.values())}")

info_dict = {k: v for k, v in res}
print(f"info_dict: {info_dict}")
info_dict.keys(): dict_keys(['name', 'age', 'gender'])
info_dict.values(): dict_values(['nick', 19, 'male'])
zip(keys,values): <zip object at 0x00000259FAD16408>
info_dict: {'name': 'nick', 'age': 19, 'gender': 'male'}

生成器

yield关键字

yield的英文单词意思是生产,在函数中但凡出现yield关键字,再调用函数,就不会继续执行函数体代码,而是会返回一个值。

def func():
    print(1)
    yield
    print(2)
    yield


g = func()
print(g)
<generator object func at 0x00000259FAD22A98>

生成器的本质就是迭代器,同时也并不仅仅是迭代器,不过迭代器之外的用途实在是不多,所以我们可以大声地说:生成器提供了非常方便的自定义迭代器的途径。并且从Python 2.5+开始,[PEP 342:通过增强生成器实现协同程序]的实现为生成器加入了更多的特性,这意味着生成器还可以完成更多的工作。这部分我们会在稍后的部分介绍。

def func():
    print('from func 1')
    yield  'a'
    print('from func 2')
    yield  'b'
    
g = func()
print(F"g.__iter__ == g: {g.__iter__() == g}")

res1 = g.__next__()
print(f"res1: {res1}")

res2 = next(g)
print(f"res2: {res2}")


g.__iter__ == g: True
from func 1
res1: a
from func 2
res2: b
g.__iter__ == g:True
from func 1
res1: a
from func 2
res2:b
def func():
    print('from func 1')
    yield 'a'
    print('from func 2')
    yield 'b'


g = func()
for i in g:
    print(i)

print(f"list(func()): {list(func())}")
from func 1
a
from func 2
b
from func 1
from func 2
list(func()): ['a', 'b']

yield+return

既然生成器函数也是函数,那么它可以使用return输出返回值吗?

既然你都选择自定义一个函数作为生成器,你还return干啥?如果这是在Python2中,Python解释器会赠送给你一个异常,但是在Python3中,他也不管你这种傻瓜行为了。

def i_wanna_return():
    yield 'a'
    yield 'b'
    return None
    yield 'c'
    
for i in i_wanna_return():
    print(i)
a
b

迭代器套迭代器

如果需要在生成器的迭代过程中接入另一个生成器的迭代怎么办?

协同程序

协同程序(协程)一般来说是指这样的函数:

  • 彼此间有不同的局部变量、指令指针,但仍共享全局变量;
  • 可以方便地挂起、恢复,并且有多个入口点和出口点;
  • 多个协同程序间表现为协作运行,如A的运行过程中需要B的结果才能继续执行。

协程的特点决定了同一时刻只能有一个协同程序正在运行(忽略多线程的情况)。得益于此,协程间可以直接传递对象而不需要考虑资源锁、或是直接唤醒其他协程而不需要主动休眠,就像是内置了锁的线程。在符合协程特点的应用场景,使用协程无疑比使用线程要更方便。

从另一方面说,协程无法并发其实也将它的应用场景限制在了一个很狭窄的范围,这个特点使得协程更多的被拿来与常规函数进行比较,而不是与线程。当然,线程比协程复杂许多,功能也更强大,所以我建议大家牢牢地掌握线程即可,是不是听了一脸懵逼,那么就别管他了,因为并发编程你会重新学习他。以下介绍的方法了解即可。

send(value):

send是除next外另一个恢复生成器的方法。Python2.5+中,yield语句变成了yield表达式,这意味着yield现在可以有一个值,而这个值就是在生成器的send方法被调用从而恢复执行时,调用send方法的参数。

def h():
    print('--start--')
    first = yield 5  # 等待接收 Fighting! 值
    print('1', first)
    second = yield 12  # 等待接收 hahaha! 值
    print('2', second)
    yield 13
    print('--end--')


g = h()
first = next(g)  # m 获取了yield 5 的参数值 5
# (yield 5)表达式被赋予了'Fighting!',  d 获取了yield 12 的参数值12
second = g.send('Fighting!')
third = g.send('hahaha!')  # (yield 12)表达式被赋予了'hahaha!'
print(f'--over--')
print(f"first:{first}, second:{second}, third:{third}")
--start--
1 Fighting!
2 hahaha!
--over--
first:5, second:12, third:13

close()

这个方法用于关闭生成器。对关闭的生成器后再次调用next或send将抛出StopIteration异常。

def repeater():
    n = 0
    while True:
        n = (yield n)
        
r = repeater()
r.close()
print(next(r))

throw(type, value=None, traceback=None)

中断Generator是一个非常灵活的技巧,可以通过throw抛出一个GeneratorExit异常来终止Generator。Close()方法作用是一样的,其实内部它是调用了throw(GeneratorExit)的。我们看close的源代码:

def close(self):
    try:
        self.throw(GeneratorExit)
    except(GeneratorExit,stopIteration):
        pass
    else:
        raise RuntimeError("generator ignored GeneratorExit")

自定义range()方法

def my_range(start,stop,step=1):
    while start < stop:
        yield start
        start += 1
        
        
g = my_range(0,3)
print(f"list(g):{list(g)}")
list(g):[0, 1, 2]

小总结

yield:

  1. 提供一种自定义迭代器的方式
  2. yield可以暂停住函数,并提供当前的返回值

yield和return:

  1. 相同点:两者都是在函数内部使用,都可以返回值,并且返回值没有类型和个数的限制
  2. 不同点:return只能返回一次之;yield可以返回多次值

生成器表达式

  • 把列表推导式的[]换成()就是生成器表达式
  • 优点:省内存,一次只产生一个值在内存中
t = (i for i in range(10))
print(t)
print(f"next(t):{next(t)}")
<generator object <genexpr> at 0x00000259FAD22D58>
next(t):0

生成器表达式和列表推导式

列表推导式相当于直接给你一筐蛋,而生成器表达式相当于给你一只老母鸡。

# 生成器表达式

with open('52.txt','r',encoding='utf8') as f:
    nums = [len(line) for line in f]

print(max(nums))


# 列表推导式

with open('52.txt','r',encoding='utf8') as f:
    nums = (len(line) for line in f)
    
print(max(nums))

自定义range方法(复杂版本)

def range(*args, **kwargs):
    if not kwargs:
        if len(args) == 1:
            count = 0
            while count < args[0]:
                yield count
                count += 1
        if len(args) == 2:
            start, stop = args
            while start < stop:
                yield start
                start += 1
        if len(args) == 3:
            start, stop, step = args
            while start < stop:
                yield start
                start += step

    else:
        step = 1

        if len(args) == 1:
            start = args[0]
        if len(args) == 2:
            start, stop = args

        for k, v in kwargs.items():
            if k not in ['start', 'step', 'stop']:
                raise ('参数名错误')

            if k == 'start':
                start = v
            elif k == 'stop':
                stop = v
            elif k == 'step':
                step = v

        while start < stop:
            yield start
            start += step


for i in range(3):
    print(i)
print('*' * 50)
for i in range(99, 101):
    print(i)
print('*' * 50)
for i in range(1, 10, 3):
    print(i)
print('*' * 50)
for i in range(1, step=2, stop=5):
    print(i)
print('*' * 50)
for i in range(1, 10, step=2):
    print(i)

递归

函数递归是什么

函数的嵌套调用是:函数嵌套函数。

函数的递归调用:它是一种特殊的嵌套调用,但是它在调用一个函数的过程中,又直接或间接地调用了它自身

def foo():
    print('from foo')
    foo()

foo()  #  直接进入死循环

如果递归函数不断地调用函数自身,那么这个递归函数将会进入一个死循环,因此我们应该给递归函数一个明确的结束条件。

直接调用

直接调用指的是:直接在函数内部调用函数自身。

import sys

print(f"最大递归层数: {sys.getrecursionlimit()}")
最大递归层数: 3000
import sys

# 修改递归层数
sys.setrecursionlimit(10000)
def foo(n):
    print('from foo',n)
    foo(n+1)
    
foo(0)

间接调用

间接调用指的是:不在原函数体内调用函数自身,而是通过其他的方法间接调用函数自身。

def bar():
    print('from bar')
    foo()
    
def foo():
    print('from foo')
    bar()
    
bar()

递归必须要有两个明确的阶段:

  1. 递推:一层一层递归调用下去,进入下一层递归的问题规模都将会减小
  2. 回溯:递归必须要有一个明确的结束条件,在满足该条件开始一层一层回溯。

递归的精髓在于通过不断地重复逼近一个最终的结果。

为什么要用递归

递归的本质就是干重复的活,但是仅仅是普通的重复,我们使用while循环就可以了。

lis = [1,[2,[3,[4,[5,[6,]]]]]]

def tell(lis):
    for i in lis:
        if type(i) is list:
            tell(i)
        else:
            print(i)
            
            
tell(lis)
1
2
3
4
5
6

如何用递归

nums = [1,3,7,11,22,34,55,78,111,115]

for item in nums:
    if item == 10:
        print('find it')
        break
else:
    print('not exists')
not exists

对于上述的列表我们可能可以通过一个for循环实现我们需要的功能,但是当我们的列表中的元素个数非常多时,我们还用这种方法,那是极其复杂的,因此我们可以考虑使用二分法的思想实现。

from random import randint
nums = [randint(1,100) for i in range(100)]
nums = sorted(nums)
print(nums)
[3, 3, 5, 5, 5, 8, 8, 9, 9, 10, 10, 10, 11, 12, 14, 14, 16, 17, 19, 19, 19, 20, 20, 20, 23, 23, 25, 25, 26, 28, 30, 31, 31, 31, 33, 35, 35, 35, 36, 39, 40, 40, 44, 44, 45, 45, 46, 47, 47, 48, 48, 52, 52, 53, 55, 58, 60, 62, 62, 62, 63, 64, 64, 65, 65, 66, 66, 70, 71, 71, 71, 71, 72, 74, 77, 77, 81, 82, 83, 84, 86, 86, 88, 88, 89, 89, 89, 90, 90, 91, 91, 91, 92, 92, 93, 93, 95, 96, 97, 98]
def search(search_num,nums):
    mid_index = len(nums)//2
    print(nums)
    if not nums:
        print('not exists')
        return
    if search_num > nums[mid_index]:
        nums = nums[mid_index+1:]
        search(search_nums,nums)
    elif search_num < nums[mid_index]:
        nums = nums[:mid_index]
        search(search_num,nums)
    else:
        print('find it')
        
search(7,nums)

[1, 2, 3, 4, 6, 6, 7, 11, 11, 12, 13, 15, 17, 19, 19, 20, 22, 23, 26, 27, 28, 28, 28, 30, 30, 30, 30, 30, 31, 31, 32, 32, 33, 33, 34, 35, 36, 37, 41, 41, 41, 43, 45, 45, 47, 50, 53, 54, 56, 56, 56, 57, 58, 59, 59, 59, 60, 61, 63, 64, 65, 66, 67, 67, 68, 68, 69, 69, 69, 69, 69, 71, 73, 73, 75, 76, 78, 79, 79, 80, 80, 81, 82, 83, 84, 84, 85, 86, 86, 86, 86, 89, 89, 90, 96, 97, 97, 97, 97, 100]
[1, 2, 3, 4, 6, 6, 7, 11, 11, 12, 13, 15, 17, 19, 19, 20, 22, 23, 26, 27, 28, 28, 28, 30, 30, 30, 30, 30, 31, 31, 32, 32, 33, 33, 34, 35, 36, 37, 41, 41, 41, 43, 45, 45, 47, 50, 53, 54, 56, 56]
[1, 2, 3, 4, 6, 6, 7, 11, 11, 12, 13, 15, 17, 19, 19, 20, 22, 23, 26, 27, 28, 28, 28, 30, 30]
[1, 2, 3, 4, 6, 6, 7, 11, 11, 12, 13, 15]
find it

定义了一个函数 search(),该函数接受两个参数 search_num 和 nums,分别表示要查找的数字和一个已经排序的列表。

在函数内部,你开始通过计算 mid_index(即 len(nums)//2)来找到列表的中间索引,并将其打印输出。

然后,你使用条件语句判断要查找的数字 search_num 和中间元素 nums[mid_index] 的大小关系。

如果 search_num 大于 nums[mid_index],则说明要查找的数字位于中间元素的右侧。因此,你更新 nums 列表为中间元素右侧的子列表,并调用 search() 函数进行递归查找。
如果 search_num 小于 nums[mid_index],则说明要查找的数字位于中间元素的左侧。因此,你更新 nums 列表为中间元素左侧的子列表,并调用 search() 函数进行递归查找。
如果 search_num 等于 nums[mid_index],则说明要查找的数字就是中间元素本身。在这种情况下,你打印输出 “find it”。
通过调用 search(7, nums) 函数进行测试,你在已排序的列表 nums 中查找数字 7。根据你提供的代码,它会输出每次递归调用后的列表 nums,然后输出 “find it”。

'''
2. 使用递归打印斐波那契数列,(前两个数的和得到第三个数,如:0 1 1 2 3 5 8...)
'''

# x = 0  # 1
# y = 1  # 2
# count = 0
# while count < 10:
#     print(x)  # 0,
#     x, y = y, x + y
#     count += 1
# import sys
# sys.setrecursionlimit(10)

# def feibo(x, y, end=999):
#     if x > end:
#         return
#
#     print(x)
#     x, y = y, x + y
#     feibo(x, y)
#
#
# feibo(0, 1)

'''
x = 0 # 1
y = 1 -->  # 2
x + y --> x # 3
x + 2y --> x + y --> y
'''

'''
一个嵌套很多层的列表,如l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]],用递归取出所有的值
'''

l = [1, 2, [3, [4, 5, 6, [7, 8, [9, 10, [11, 12, 13, [14, 15]]]]]]]

for i in l:

    if type(i) == list:
        for j in i:
            print(j)
    else:
        print(i)


def get(lis):
    for i in lis:
        if type(i) == list:
            get(i)
        else:
            print(i)


get(l)
1
2
3
[4, 5, 6, [7, 8, [9, 10, [11, 12, 13, [14, 15]]]]]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

定义了一个嵌套列表 l,包含多层嵌套。然后使用 for 循环遍历列表 l 中的每个元素。

在循环中,你检查每个元素的类型,如果是列表 (list) 类型,则使用另一个嵌套的 for 循环遍历该列表,并打印出其中的元素。如果元素不是列表类型,则直接打印该元素。

接着,你定义了一个名为 get() 的函数,该函数接受一个列表参数 lis。在函数内部,你使用递归方式遍历列表 lis 中的每个元素。如果元素是列表类型,则递归调用 get() 函数继续遍历子列表,否则直接打印该元素。

最后,你调用 get(l) 函数,传入嵌套列表 l 进行处理。

运行这段代码,它会逐层打印出嵌套列表 l 中的每个元素。

匿名函数

有名函数

我们之前定的函数都是有名函数,它是基于函数名使用。

def func():
    print('from func')

func()
func()
func()
print(func)
from func
from func
from func
<function func at 0x0000021E8306E730>

匿名函数

匿名函数,他没有绑定名字,使用一次即被收回,加括号即可以运行。

lambda x,y:x+y
<function __main__.<lambda>(x, y)>
add = lambda x, y: x+y
result = add(3, 4)
print(result)  # 输出:7
7
res = (lambda x,y: x+y)(1,2)
print(res)
3

与内置函数联用

匿名函数通常与max()、sorted()、filter()、sorted()方法联用。

salary_dict = {
    'nick': 3000,
    'jason': 100000,
    'tank': 5000,
    'sean': 2000
}

如果我们想从上述字典中取出薪资最高的人,我们可以使用max()方法,但是max()默认比较的是字典的key。

  1. 首先将可迭代对象变成迭代器对象
  2. res=next(迭代器对象),将res当做参数传给key指定的函数,然后将该函数的返回值当做判断依据
salary_dict = {
    'nick': 3000,
    'jason': 100000,
    'tank': 5000,
    'sean': 2000
}
print(f"max(salary_dict):{max(salary_dict)}")

def func(k):
    return salary_dict[k]

print(f"max(salary_dict,key=func()):{max(salary_dict,key=func)}")

print(f"max(salary_dict,key=lambda name: salary_dict[name]:{max(salary_dict,key=lambda name:salary_dict[name])})")
max(salary_dict):tank
max(salary_dict,key=func()):jason
max(salary_dict,key=lambda name: salary_dict[name]:jason)

定义了一个名为 salary_dict 的字典,其中包含了一些员工的名字及其对应的工资。

接下来,你使用 max() 函数尝试找到字典 salary_dict 中的最大值,并使用 f-string 打印输出结果。然而,你会发现打印出来的结果并不是你期望的。

问题出在 max() 函数的参数上。在这种情况下,如果你只是传入字典名 salary_dict 作为参数,max() 函数默认会在字典的键上进行比较操作,而不是对应的值。

为了解决这个问题,你定义了一个名为 func() 的函数,该函数接受一个参数 k,并返回 salary_dict 中对应键 k 的值。然后,你再次使用 max() 函数,并传入参数 key=func 来指定使用 func() 函数作为比较的关键字函数。这样做的目的是让 max() 函数基于工资值进行比较,而不是键。

print(f"max(salary_dict, key=lambda name: salary_dict[name]): {max(salary_dict, key=lambda name: salary_dict[name])}")

然后,你使用 Lambda 函数定义了一个匿名函数 (lambda name: salary_dict[name]),该函数接受一个参数 name,并返回 salary_dict 中对应键 name 的值。你再次使用 max() 函数,并使用这个匿名函数作为参数 key,以实现基于工资值的比较。

在你的代码中,使用了三种不同的方式来获取字典 salary_dict 中工资最高的员工:

max(salary_dict):这种方式基于字典的键进行比较,因此只会返回键的最大值,而不是对应的工资最高的员工。

max(salary_dict, key=func):这种方式使用了自定义的函数 func(),该函数基于工资值进行比较,从而返回对应的工资最高的员工的键。

max(salary_dict, key=lambda name: salary_dict[name]):这种方式使用了 Lambda 函数,基于工资值进行比较,从而返回对应的工资最高的员工的键。

如果我们想对上述字典中的人,按照薪资从大到小排序,可以使用sorted()方法。

sorted()工作原理:

  1. 首先将可迭代对象变成迭代器对象
  2. res=next(迭代器对象),将res当做参数传给第一个参数指定的函数,然后将该函数的返回值当做判断依据。
lis = [1,3,2,5,8,6]
sorted(lis)
print(f"lis:{lis}")
print(f"sorted(lis,reverse=True):{sorted(lis,reverse=True)}")
lis:[1, 3, 2, 5, 8, 6]
sorted(lis,reverse=True):[8, 6, 5, 3, 2, 1]

定义了一个列表 lis,并尝试对其进行排序操作。

首先,你使用 sorted() 函数对列表 lis 进行排序,但你没有将结果赋值给任何变量。这是因为 sorted() 函数返回一个新的已排序的列表,而不会改变原始列表。所以在这里,排序后的列表并没有被赋值给任何变量,也没有被保存下来。

接下来,你使用 f-string 打印输出原始列表 lis,注意到它没有发生改变。这是因为在之前的排序操作中,并没有修改原始列表。

然后,你使用 sorted() 函数对列表 lis 进行降序排序,并将结果保存在一个新的列表中。通过传入参数 reverse=True 给 sorted() 函数,可以实现降序排序。

最后,你使用 f-string 再次打印输出原始列表 lis 和降序排序后的列表。这会显示原始列表 lis 并未发生改变,而降序排序后的列表会按照降序排列的顺序打印出来。

salary_dict = {
    'nick': 3000,
    'jason': 100000,
    'tank': 5000,
    'sean': 2000
}
print(f"sorted(salary_dict, key=labbda name:salary_dict[name]:{sorted(salary_dict,key=lambda name:salary_dict[name])})")
sorted(salary_dict, key=labbda name:salary_dict[name]:['sean', 'nick', 'tank', 'jason'])

如果我们想对一个列表中的某个人名做处理,可以使用map()方法。

map()工作原理:

  1. 首先将可迭代对象变成迭代器对象
  2. res=next(迭代器对象),将res当做参数传给第一个参数指定的函数,然后将该函数的返回值作为map()方法的结果之一。
name_list = ['jason', 'tank', 'sean']

res = map(lambda name:f"{name} sb",name_list)
print(f"list(res):{list(res)}")
list(res):['jason sb', 'tank sb', 'sean sb']

定义了一个名为 name_list 的列表,其中包含了几个名字字符串。

接下来,你使用了 map() 函数来对 name_list 中的每个元素应用一个 Lambda 函数,并将结果作为一个迭代器返回。Lambda 函数的功能是在每个名字后面添加字符串 " sb"。

然后,你使用 list() 函数将 map() 返回的迭代器转换为一个列表,并将结果赋值给变量 res。通过打印 list(res),你展示了经过 Lambda 函数处理后的结果列表。

如果我们想筛选除名字中含有'sb'的名字,我们可以使用filter()方法。

filter() 函数用于对可迭代对象(如列表)中的每个元素应用指定的条件,并返回满足条件的元素作为一个迭代器。

filter()工作原理:

  1. 首先将可迭代对象变成迭代器对象
  2. res=next(迭代器对象),将res当做参数传给第一个参数指定的函数,然后filter会判断函数的返回值的真假,如果为真则留下
name_list = ['nick', 'jason sb', 'tank sb', 'sean sb']

filter_res = filter(lambda name:name.endswith('sb'),name_list)
print(f"list(filter_res):{list(filter_res)}")
list(filter_res):['jason sb', 'tank sb', 'sean sb']

定义了一个名为 name_list 的列表,其中包含了几个名字字符串。

接下来,你使用了 filter() 函数来筛选 name_list 中的元素,并只保留满足指定条件的元素。你传入了一个 Lambda 函数作为条件,该函数用于检查名字是否以 “sb” 结尾。

然后,你使用 list() 函数将 filter() 返回的迭代器转换为一个列表,并将结果赋值给变量 filter_res。通过打印 list(filter_res),你展示了符合条件的名字构成的结果列表。

自定义sorted方法

# 不太熟悉的同学不要看(有兴趣的可以看一看)
def sorted(iter, key=None, reverse=None):
    iter.sort()

    if key:
        # key 是一个函数对象
        lt = []
        lt2 = []
        for i in iter:
            res = key(i)
            lt.append(res)
            lt2.append(i)
        lt.sort()

        lt3 = []
        for i in lt:
            for j in lt2:
                if j[1] == i:
                    lt3.append(j)

        if reverse:
            lt3.reverse()
        return lt3

    if reverse:
        iter.reverse()
    return iter


salary_list = list(salary_dict.items())
print(salary_list) # [('nick', 3000), ('jason', 100000), ('tank', 5000), ('sean', 2000), ('z', 1000)]
print(sorted(salary_list, key=lambda i: i[1],
             reverse=None))  # [('sean', 2000), ('nick', 3000), ('tank', 5000), ('jason', 100000)]

内置函数

常用内置函数

更多内置函数

掌握

  1. bytes
  2. chr/ord
  3. divmod
  4. enumerate
  5. eval
  6. hash

bytes()——解码字符

res = '你好'.encode('utf8')
print(res)
b'\xe4\xbd\xa0\xe5\xa5\xbd'

使用了字符串的 encode() 方法来进行转换,将字符串 '你好' 编码为 UTF-8 字节序列。encode() 方法是在字符串对象上调用的,接受一个编码参数,并返回相应的字节序列。

res = bytes('你好',encoding='utf8')
print(res)
b'\xe4\xbd\xa0\xe5\xa5\xbd'

使用了 bytes() 类型的构造函数,以字符串 '你好' 和编码 'utf8' 作为参数。bytes() 函数接受一个字符串和一个编码参数,并返回相应的字节序列。

chr()/ord()——chr()参考ASCII码表将数字转成对应字符;ord()将字符转换成对应的数字。

print(chr(65))
A
print(ord('A'))
65

divmod()——分栏

print(divmod(10, 3))
(3, 1)

divmod() 函数是 Python 内置函数之一,用于获取除法运算的商和余数。它接受两个参数,分别代表被除数和除数。使用 divmod() 函数可以避免进行额外的除法和取模运算,方便获取商和余数的一次性操作。

enumerate()——带有索引的迭代

enumerate() 函数接受一个可迭代对象作为参数,并返回索引和值的元组。

l = ['a', 'b', 'c']
for i in enumerate(l):
    print(i)
(0, 'a')
(1, 'b')
(2, 'c')

在循环中,enumerate() 返回的元组被赋值给变量 i,其中第一个元素是索引,第二个元素是对应的值。通过打印 i,可以看到每个元素的索引和值的组合。

eval()——把字符串翻译成数据类型。

lis = '[1,2,3]'
lis_eval = eval(lis)
print(lis_eval)
[1, 2, 3]

eval() 函数将字符串作为Python代码进行求值,并返回结果。在这里,eval(lis) 会将字符串 '[1,2,3]' 解释为一个列表,并返回该列表对象。

hash()——是否可哈希。

print(hash(1))
1

hash() 是一个内置函数,用于返回给定对象的哈希值。

在你的代码中,hash(1) 表示将整数 1 的哈希值进行打印输出。

哈希值是一种用于快速定位数据的唯一标识。 它并不是一个加密算法,不能逆向计算得到原始输入。哈希函数的输出长度是固定的,无论输入数据的大小。因此,在使用哈希值时应小心冲突(不同的输入可能具有相同的哈希值)和安全性问题。

了解

  1. abs
  2. all
  3. any
  4. bin/oct/hex
  5. dir
  6. frozenset
  7. gloabals/locals
  8. pow
  9. round
  10. slice
  11. sum
  12. import

abs()——求绝对值。

print(abs(-13))
13

all()——可迭代对象内元素全为真,则返回真。

print(all([1,2,3,0]))
print(all([]))
False
True

print(all([1, 2, 3, 0])):

这行代码用于判断列表 [1, 2, 3, 0] 中的所有元素是否都为真。由于数字 0 在布尔运算中被视为假值,所以存在一个假值 (0),因此结果为 False。

print(all([])):

这行代码用于判断空列表 [] 中的所有元素是否都为真。由于空列表中没有任何元素,因此不存在假值,结果为 True。

需要注意的是,all() 函数只有在可迭代对象中的所有元素都为真时才返回 True,否则返回 False。在第一个例子中,由于存在假值 (0),结果为 False。而在第二个例子中,由于没有任何元素,也就不存在假值,结果为 True。

any()——可迭代对象中有一元素为真,则为真。

print(any([1,2,3,0]))
print(any([]))
True
False

bin()/oct()/hex()——二进制、八进制、十六进制转换。

print(bin(17))
print(oct(17))
print(hex(17))
0b10001
0o21
0x11

dir()——列举出所有time的功能

import time
print(dir(time))
['_STRUCT_TM_ITEMS', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'altzone', 'asctime', 'clock', 'ctime', 'daylight', 'get_clock_info', 'gmtime', 'localtime', 'mktime', 'monotonic', 'perf_counter', 'process_time', 'sleep', 'strftime', 'strptime', 'struct_time', 'time', 'timezone', 'tzname']

frozenset()——不可变集合

s = frozenset({1,2,3})
print(s)
frozenset({1, 2, 3})

frozenset 是 Python 中的内置类,用于创建不可变的集合对象。在代码中,frozenset({1, 2, 3}) 表示创建一个不可变的集合,其中包含元素 1、2 和 3。

这意味着 s 是一个不可变的集合,其中包含元素 1、2 和 3。由于 frozenset 是不可变的,因此无法对集合对象进行修改(添加、删除元素等),即使尝试也会引发错误。这在需要确保集合数据的不可变性时非常有用。

需要注意的是,frozenset 和普通的 set 之间的区别在于它们的可变性。普通的 set 对象是可变的,可以通过添加或删除元素来修改它们。而 frozenset 对象是不可变的,一旦创建就不能修改。

在打印 frozenset 对象时,输出的格式以 frozenset({}) 的形式显示,其中大括号 {} 包含集合中的元素。

globals()/loacals()——查看全局名字;查看局部名字。

def func():
    a = 1
    print(locals())
    
func()
{'a': 1}

pow()——用于计算幂运算后取模的结果

# pow(3, 2, 3) 表示计算 3 的幂和 2 的结果
print(pow(3, 2, 3))  # (3**2)%3
0

round()——用于对浮点数进行四舍五入操作

print(round(3.5))
4

需要注意的是,当有两个整数与浮点数之间距离相等时,round() 会选择离零更近的偶数作为结果。因此,round(3.5) 会返回 4 而不是 3。同样地,round(2.5) 会返回 2 而不是 3。

如果需要在四舍五入后返回浮点数而不是整数,可以使用 round(3.5, 1) 来指定保留小数点后的位数。例如,round(3.5, 1) 的结果将是 3.5。

slice()——切片对象

lis = ['a','b','c']
s = slice(1,4,1)
print(lis[s])
['b', 'c']

首先定义了一个名为lis的列表,其中包含了三个元素:‘a’、‘b’和’c’。

接下来,使用slice()函数创建了一个切片对象s,参数分别为起始索引1(包含)、结束索引4(不包含)以及步长1。这个切片对象s等同于切片表达式[1:4:1],表示从索引1开始(包含),到索引4结束(不包含),步长为1。

最后,使用切片对象s来获取lis列表中对应切片的值,并通过print()函数打印输出。即通过lis[s]或者lis[1:4:1]获取['b', 'c'],然后将其打印输出。结果就是 ['b', 'c']。

sum()

print(sum(range(100)))
4950

import()——通过字符串导入模块。

m = __import__('time')
print(m.time())
1690368307.9993165

使用__import__()函数动态地导入了名为time的模块,并将导入的模块赋值给变量m。

然后,通过调用m.time()来访问time模块中的time()函数,并将其结果打印输出。

面向对象知识点

  1. classmethod
  2. staticmethod
  3. property
  4. delattr
  5. hasattr
  6. getattr
  7. setattr
  8. isinstance()
  9. issubclass()
  10. object()
  11. super()

面向过程编程

面向过程编程是解决问题的一种思想,相当于武林门派,武林门派之间没有好坏之分,因此它与我们之后学习的面向对象编程其实没有好坏之分。
面向过程编程,核心是编程二字,过程指的是解决问题的步骤,即先干什么、后干什么、再干什么、然后干什么……
基于该思想编写程序就好比在设计一条流水线,面向对称编程其实是一种机械式的思维方式。

当我们写登录功能,我们首先需要输入账号、密码,然后认证两次密码是否相同,然后从数据库中读取密码验证用户密码输入是否正确,然后输入验证码……之后,我们就能够实现登录功能。这样把登录功能问题流程化,进而是解决问题的思路非常清晰。

  • 优点:复杂的问题流程化,进而简单化。

生产汽水瓶的流水线,没办法生产特斯拉。流水线下一个阶段的输入与上一个阶段的输出是有关联的。因此他的扩展性极差。

  • 缺点:扩展性差。

注册功能

接受用户输入用户名,进行合法性校验,拿到合法的用户名

def check_username():
    username = input('username>>>').strip()
    if username.isalpha():
        return username
    else:
        print('用户名必须为字母,傻叉')

接受用户输入密码,进行合法性校验,拿到合法的密码

def check_pwd():
    while True:
        pwd = input('password>>>').strip()
        if len(pwd) < 5:
            print('密码长度至少五位')
            continue
        re_pwd = input('re_password>>>').strip()
        if pwd == re_pwd:
            return pwd
        else:
            print('两次输入密码不一致')

将合法的用户名和密码写入文件

def insert(username, pwd, path='57.txt'):
    with open(path, 'a', encoding='utf8') as fa:
        fa.write(f'{username}:{pwd}\n')

注册

def register():
    username = check_username()
    pwd = check_pwd()
    insert(username, pwd)
    print(f'{username}注册成功')


register()

如果现在我们需要校验用户的年龄,因此我们需要增加一个check_age()方法,并且其他有牵连的地方都需要修改,因此它的扩展性极差。

封装文件读写功能

# def register():
#     while True:
#         username = input('username>>>').strip()
#         # 检测用户是否重复,如果重复则重新输入
#         with open('db.txt', 'r', encoding='utf8') as fr:
#             for line in fr:
#                 info = line.strip('\n').split(':')
#                 if username == info[0]:
#                     print('用户名已经存在')
#                     break
#             else:
#                 # 用户名不存在
#                 # 跳出循环,不用重复输入用户名字
#         res =

# def tell_info():
#     username = input('username>>>').strip()
#     with open('db.txt', 'r', encoding='utf8') as fr:
#         for line in fr:
#             info = line.strip('\n').split(':')
#             if username == info[0]:
#                 return info

数据处理层

def select(username):
    with open('db.txt', 'r', encoding='utf8') as fr:
        for line in fr:
            info = line.strip('\n').split(':')
            if username == info[0]:
                return info


def tell_info():
    username = input('username>>>').strip()
    info = select(username)
    print(info)
用户功能层
def register():
    while True:
        username = input('username>>>').strip()
        # 检测用户是否重复,如果重复则重新输入
        res = select(username)
        if res:
            print('用户名已经存在')
        else:
            break

    while True:
        pwd = input('password>>>').strip()
        re_pwd = input('re_password>>>').strip()
        if pwd != re_pwd:
            print('两次输入密码不一致,请重新输入')
        else:
            break
把注册功能分开之后,功能与功能直接解耦合,复杂的问题流程化,更加清晰。

分层实现功能

  • 用户功能层:实现用户具体的功能。
  • 接口层:连接数据处理层和用户功能层。
  • 数据处理层:处理数据后把结果交给接口层。

分层实现功能的好处:当我们需要实现web端和app端的软件,我们只要把数据处理层和接口层写好,然后实现不同的用户功能层即可,web端使用web端的用户功能层,app端使用app端的用户功能层,但是接口层和数据处理层是通用的。


posted @   阿K进阶路  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示