05 装饰器 + 函数 + 模块与包

一、装饰器

装饰器的逐渐演变过程

1.1 闭包

def foo(func):
    def index():
        print('top')
        func()
        print('bottom')
    return index

1.2 可变长度参数

*args, **kwargs :在形参中表示可接受任意多个,任意形式的参数

  • *名字 :在作为形参时,表示可接受任意多个位置参数
  • *名字 :在作为实参时,表示炸开为多个位置参数
  • **名字 :在作为形参时,表示可接受任意多个关键字参数
  • **名字 :在作为实参时,表示炸开为多个关键字参数
def w(*args, **kwargs):
    print(args)		# 一个元组,传递进来的位置参数
    print(kwargs)	# 一个字典,传递进来的关键字参数
# 例
w()
w(1)
w(1, 2)
w(1, name='哒卟了')
x = [1, 2, 3]
y = {'name': '哒卟了', 'age': 18}
w(*x)
w(**y)

""" 结论:
()
{}
(1,)
{}
(1, 2)
{}
(1,)
{'name': '哒卟了'}
(1, 2, 3)
{}
()
{'name': '哒卟了', 'age': 18}
"""

1.3 装饰器模板⭐⭐⭐

def foo(func):
    def index(*args, **kwargs):		# 将函数名作为参数传递进来,实现动态变化
        # 先新增功能,再执行原功能
        print('先增功能')
        res = func(*args, **kwargs)	# 原函数实现的功能
        # 先执行原功能,再新增功能
        print('后增功能')
        return res
    return index

# 语法糖:需要哪个函数新增该装饰器功能,只要在该函数上一行  @装饰器名字
# 案例:登录后,观看芒台的综艺、电视剧、电影、动漫等
def show(types):
    def operation(*args, **kwargs):
        if login:   # 要求登录后方可观看
            res = types(*args, **kwargs)
            return res
    return operation


@show
def variety():      # 综艺
    print("I like variety~ ")


@show
def teleplay():     # 电视剧
    print("I'm watching teleplay. ")


@show
def movie(name):        # 电影
    print(f"The movie {name} is very good! ")
    

while True:
    is_login = input("是否登录(是1,否2):").strip()
    if is_login == '1':
        login = True
        option = input("请选择 1综艺,2电视剧,3电影,4退出:").strip()
        while option != '4':
            if option == '1':
                variety()
            elif option == '2':
                teleplay()
            elif option == '3':
                film = input("请输入电影名称:").strip()
                movie(film)
            option = input("请选择 1综艺,2电视剧,3电影,4退出:").strip()
    else:
        login = False
        print("请您先登录")
        
        
""" 结果:
是否登录(是1,否2):1
请选择 1综艺,2电视剧,3电影,4退出:1
I like variety~ 
请选择 1综艺,2电视剧,3电影,4退出:2
I'm watching teleplay. 
请选择 1综艺,2电视剧,3电影,4退出:3
请输入电影名称:Garfield
The movie Garfield is very good! 
请选择 1综艺,2电视剧,3电影,4退出:4
是否登录(是1,否2):2
请您先登录
"""

二、函数进阶

2.1 迭代器、可迭代对象、生成器

迭代器

  • 迭代取值的工具,涉及可取多值,可循环取出数据(str, list, dict, tuple, set, file
  • 内置 __next__() 方法:逐一取出内容,取完后再取会报错(如母鸡下蛋)
  • 内置 __iter__() 方法:迭代器 – 经该方法后 --> 迭代器

可迭代对象

  • 可转化为迭代器的对象 str, list, dict, tuple, set, file
  • 内置 __iter__() 方法 :可迭代对象 – 经该方法后 --> 迭代器
# 案列
str_a = '123'
list_a = [1, 2, 3]
dict_a = {'name': '哒卟了', 'age': 18}
tuple_a = (1, 2, 3)
set_a = {1, 22, 3}

# 内置 __iter__ 方法
print(str_a, id(str_a))     # 字符串, 内存地址     123 1660694400752
b = iter(str_a)             # 可迭代对象 --> 迭代器
print(b, id(b))             # 迭代器 内存地址      <str_iterator object at 0x00000182AADA3308> 1660723802888
c = iter(b)                 # 迭代器 --> 迭代器
print(c, id(c))             # 迭代器 内存地址(同b)<str_iterator object at 0x00000182AADA3308> 1660723802888
# 内置 __next__ 方法
print(next(b))              # 1
print(next(b))              # 2

生成器

  • 自定义的迭代器

  • 一旦函数内部出现 yield 关键字,这个函数就会变成 生成器,执行该函数得到 一个迭代器(即得到地址,不会执行函数内部代码)

  • 该类函数通常与for循环结合使用,for循环可依次获取 yield 后的数据

    • for 变量 in 生成器函数名():
          代码
          
      # 不加()是名字,内存地址
      # 加了()是执行函数,得到一个迭代器
      
    • # 案例
      def test_a():
          print(1)
          yield 11
          print(2)
          yield 22
      
      
      print(test_a)		
      print(test_a())
      for x in test_a():
          print(x)
          
      """ 结果:
      <function test_a at 0x00000141F6CB5678>
      <generator object test_a at 0x00000141F6C386C8>
      1
      11
      2
      22
      """
      
  • 扩展 send (了解): 生成器中yield 为变量,不能定值

    • def test_b():
          print(1)
          received = yield
          yield received
          print(2)
          received2 = yield
          yield received2
      
      
      gen = test_b()
      gen.send(None)  # next(gen) 启动生成器
      print(gen.send('new data'))     # 发送数据到生成器,输出1 new data
      next(gen)       # gen.send(None) 再次启动等待接收状态
      print(gen.send('new data2'))    # 发送数据到生成器,输出 2 new data2
      
  • yieldreturn 区别:

    • return 函数一旦执行到它,就会立即退出
    • yield 每执行一次 next() 就会执行函数内部的代码,直到遇到 yield 暂停在那里,等下一次 next() 时,继续执行代码,将 yield 后的数据求出去,然后继续执行至下一个 yield

2.2 迭代循环

for 循环可称之为迭代循环,for 循环的本质原理是基于迭代器的next()方法

for 变量 in iter(可迭代对象, 迭代器): # 会依次调用iter()

  • 1)d.__iter__() 得到一个迭代器对象
  • 2)迭代器对象.__next__() 拿到一个返回值,然后将该返回值赋值给k
  • 3)循环往复步骤2,直到抛出异常,for循环会捕捉异常然后结束循环

三、模块与包

模块:一组功能的集合,多个功能的结合。一份python文件就是一个模块

模块的作用:提升效率,方便管理

3.1 模块的分类

内置模块 < 自定义模块 < 第三方模块 (最多)

3.2 自定义模块的使用

导入模块时,做的几件事:

  1. 导入模块时 会执行那个模块里面的代码
  2. 产生一个和模块名字相同的名称空间,将那个模块里产生的名字丢到那个名称空间去
  3. 我们需要使用那个模块里的名字时,通过 模块.名字 即可使用
# 导入单个模块(推荐)
import 模块名字/py文件名		# 调用  模块名.名字
from 模块名 import 名字		 # 可直接使用名字调用
import 模块名 as 别名

from 模块名 import *
	"""
	当模块里有个名叫 __all__ 的列表时,就只能导入这个列表里的字符串元素的名字;
	若没有 __all__ 时,不能导入以 下划线开头的名字,其他名字可直接使用。
	很容易产生名字冲突的情况,所以一般不使用
	"""

# 导入多个模块
import 模块名1, 模块名2


"""书写顺序规范:
内置模块

第三方模块

自定义模块
"""

导入位置区别:

  • 在外面导入:全局的,任意地方都可使用
  • 在函数内导入:局部的,只能在函数内部使用

📌tips:运行一半报错,因为python是解释型语言,即执行到哪算哪,哪里错了就报错。

3.3 循环导入问题(设计代码阶段)

问题描述:

# a.py

print('正在导入a')
from b import y

x = 'aa'
# b.py

print('正在导入b')
from a import x

y = 'bb'
# test.py

import a


"""运行结果:
正在导入a
正在导入b
ERROR报错....
"""

正确解决方案:

  • 一开始编写代码时应避免循环导入问题

  • 加入第三方,多个模块需要用到的名字,就放在一份文件中,作为公用

3.4 搜索模块的路径

模块查找问题:

  1. 先查找内存,从 sys.path 这个列表中的元素文件夹内部去找模块,按先后顺序来(第一个元素即当前文件夹 动态变化)
  2. 都找不到就报错。若必须导入某模块,但路径不在 sys.path 的列表中,可通过 先在 sys 中添加路径的方式,即: sys.path.append('该模块路径') ,再调用该模块
  3. 第三方模块 下载存放在python文件夹下的 lib\site-packages

报错问题:

  • Module Not Found :模块找不到
  • Name Not defined : 名字没定义
  • SyntaxError :尖括号在下方,表语法错误
__name__

在Python文件执行时,结果永远是 __main__ ,换言之,若 __name__ == '__main__' ,则表示正在执行该文件;

def run():
	print('我要开始运行这个功能啦')
    
if __name__ == '__main__':
    # 此处代码一般作为程序的启动入口,只有直接运行该文件时才会执行
    run()

但导入该文件时,结果是该文件的 模块名

3.5 包

包 本质上还是一个模块,一个放着python的模块文件的文件夹叫包

重复执行一段代码 -----> 循环

重复使用一个功能 -----> 函数

多个功能的集合 -----> 模块 -----> 文件

多个模块的集合 -----> 包 -----> 文件夹

📌tips:python2 版本中文件夹里必须有 __init__.py 文件才可称之为包;但python3版本中没有该限制,有无都可,约定会放该文件

3.5.1 创建包

方法一:自己新建文件夹,在文件夹中创建python文件

方法二:利用 pycharm 创建,鼠标右击 NEW —> python Package (会自动创建 __init.py 文件)

3.5.2 导入包

# 1.导入
import 包的名字(文件夹的名字)		# 导入包后会执行 __init__.py 中的代码,其他文件代码不执行
import 包名.模块名			   # 调用:包名.模块名.名字
from 包名 import 模块名		   # 调用:模块名.名字
import 包名1.模块名, 包名2.模块名
from 包名 import 模块名1, 模块名2

# 2.取别名
import 包名.模块名 as 别名		  # 调用:别名.名字
from 包名 import 模块名 as 别名  # 调用:别名.名字
import 包名.模块名 as 别名, 包名.模块名 as 别名

from 包名.xxx import yyy as zzz	# 推荐


from . import xxx

"""
a
	b.py
	c.py
		from .b import name 	# 导入a文件夹下的b.py中的name
		# 直接运行该文件会报错
		
index.py
	from a import c		# 不会报错
"""

3.6 第三方模块(第三方库/包)

下载别人写好的文件,放在 sys.path 的列表中,即 ..\Python39\Lib\site-packages

  • 下载第三方库,在cmd中执行该命令即可自动下载

    • pip install 第三方库名 第三方官方网站:pypi.python.org/

    • 下载很慢问题:因为官网是国外的,所以可用国内镜像网站(换源)

      • 临时换源:(下载速度快,但每次下载都要加 源址)

        • 豆瓣:pip install 第三方库名字 -i https://pypi.douban.com/simple
        • 阿里云:pip install 第三方库名字 -i https://mirrors.aliyun.com/simple
      • 永久换源:(下载快,且不用每次都加 源址)

        • 本质是更换pip的网址

        • """
          1. 文件管理器文件路径地址栏敲:%APPDATA% 回车,快速进入 C:\Users\电脑用户\AppData\Roaming 文件夹中
          2. 新建 pip 文件夹并在文件夹中新建 pip.ini 配置文件
          3. 新增 pip.ini 配置文件内容
          """
          # 配置文件内容
          [global]
          index-url = http://pypi.douban.com/simple
          [install]
          use-mirrors = true
          mirrors = http://pypi.douban.com/simple/
          trusted-host = pypi.douban.com
          
    • pip 安装第三方库报错

      • 最简单粗暴的方式:重装python
      • 复杂点儿的方式: 将 D:\Tool\python\Scripts 加入环境变量即可
  • 卸载第三方库:pip uninstall 第三方库名字

四、常用的内置模块

4.1 和时间相关

time.time()
time.sleep(a)
import time

print(time.time())  # 得到一个时间戳,此时此刻距离 1970.1.1.00:00:00 的秒数
# 主要用于计算时间差
a = time.time()
time.sleep(1)   # 让程序休息几秒钟,再执行下面的代码
b = time.time()
time_stamp = b - a
print(f"时间戳:{time_stamp}秒")


"""结果:
1725440838.9463332
时间戳:1.0119984149932861秒
"""

4.2 随机数

random.random()
random.randint(a, b)
import random

print(random.random())          # (0,1) 浮点数
print(random.randint(1, 3))     # [1,3] 整数
print(random.randrange(1, 3))   # [1,3) 整数


"""结果:
0.5068625100520493
3
2
"""
# 案例:随机验证码 --> 6个随机码 0~9 a~Z
# tips:A == chr(65)	查ASCII表 65代表的字符

import random

res = ''
for i in range(6):
    str1 = chr(random.randint(65, 90))			# A-Z(65-90)
    str2 = chr(random.randint(97, 122))			# a-z(97-122)
    str3 = str(random.randint(0, 9))			# 0-9
    res += random.choice([str1, str2, str3])

print("验证码:", res)


"""结果:
验证码: 6CrnTI
"""

4.3 os模块

if not os.path.exists():
os.mkdir()
os.path.join()
import os

os.mkdir('新建一个文件夹')     # 创建一个文件夹,若该文件夹已存在,则报错

res = os.path.exists('../folder')  # 判断一个路径是否存在,存在True,不存在False
print(res)	# True 表存在该路径

if not os.path.exists('新建文件夹'):		# 经常结合使用:若无该文件夹则新建
    os.mkdir('新建文件夹')
    
res = os.path.join('code', '新建文件夹', 'test.py')  # 拼接形成适配操作系统格式的文件路径
print(res)	# code\新建文件夹\test.py

4.4 sys模块

sys.path
import sys

sys.path.append('路径')	# 将某个路径加入sys.path 可避免模块找不到的问题

4.5 json

json 用于解决跨语言、跨平台的问题 == 相当于 ==> 不同地区各有方言,但大家可通过普通话沟通

json.dumps()
json.dump()
json.loads()
json.load()
import json

# json.dumps(python的数据)   将python数据转成json数据,集合类型不能转成json
a = {'name': '哒卟了', 'age': 18}  # <class 'dict'>
print(type(a))
b = json.dumps(a)   # <class 'str'>
print(b)    # {"name": "\u54d2\u535f\u4e86", "age": 18}
print(type(b))
# json.loads(json的数据)   将json数据转成python数据
c = json.loads(b)
print(c)    # {'name': '哒卟了', 'age': 18}
print(type(c))

json 文件 .json 里面存放的是json数据类型

data = {'name': '哒卟了', 'age': 18}

# 写入json文件
with open('test.json', 'w', encoding='utf-8') as f:
    f.write(json.dumps(data))   # 先将python数据转成json数据,再写入

# 读取json文件
with open('test.json', 'r', encoding='utf-8') as f:
    data = f.read()         # 此时读出的为json数据
data = json.loads(data)     # 将json数据转为python数据
print(data, data['name'])   # {'name': '哒卟了', 'age': 18} 哒卟了
# 简便写法 (无s, 仅限于文件中)
# with open() as 文件对象:
#     json.dump(python数据, 打开的文件对象)
with open('test.json', 'w', encoding='utf-8') as f:
    json.dump(data, f)

# with open() as 文件对象:
#     data = json.load(打开的文件对象)
with open('test.json', 'r', encoding='utf-8') as f:
    data = json.load(f)
print(data)

4.6 加密模块 hash哈希内置模块

hash特点:

  1. 只要传入的内容一样,得到的hash值必然一样
    • 应用场景:验证文件的完整性,加密思想
  2. 不能由hash值反解出内容
    • 应用场景:密码加密
  3. 只要使用的hash算法不变,无论校验内容有多大,得到的hash值长度是固定的
    • 内部算法实现
import hashlib

# 1. 选择一个算法,常用md5算法,其他算法:sha1 -- sha256 -- sha512 (运算难度更大,对CPU要求更大)
m = hashlib.md5()
# 2. 使用算法对数据进行加密,数据必须是二进制,字符串要先编码
m.update('哒卟了'.encode('utf8'))
# 3. 得到加密后的结果
b = m.hexdigest()
print(b, "\t长度:", len(b), "位数字")     # 7b1349528e92a0615422502bd58381ce 	长度: 32 位数字

⭐⭐⭐加密必加盐:增加破译难度,提高破译成本

import hashlib

# 方法一:先加盐再加密
pwd = '678678'                      # 密码
salt = 'I am 哒卟了'                 # 盐
new_pwd = pwd + salt                # 加盐
m = hashlib.md5()
m.update(new_pwd.encode('utf8'))    # 加密
b = m.hexdigest()
print(b, "\t长度:", len(b), "位数字")     # 3b0ebc595f58f3d111caea84011984c7 	长度: 32 位数字


# 方法二:先加密再加盐
pwd = '678678'                  # 密码
salt = 'I am 哒卟了'             # 盐
m = hashlib.md5()
m.update(pwd.encode('utf8'))    # 加密
m.update(salt.encode('utf8'))   # 加盐
b = m.hexdigest()
print(b, "\t长度:", len(b), "位数字")     # 3b0ebc595f58f3d111caea84011984c7 	长度: 32 位数字

📌总结:

  • 登录、注册等 涉及到安全的一定要加密

  • 注册时将密码加密(要加盐值),将最后结果保存

  • 登录时将密码加密(要加盐值),将最后结果与保存结果进行比对

  • 只需对密码进行加密,账户不需要

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值