什么是装饰器

在Python中,装饰器是一种高级功能,它允许你在不修改已有代码的情况下,动态地修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数作为参数,并返回一个新的函数,通常也会在内部定义一个函数来实现这个功能。

为什么需要装饰器

我们假设你的程序实现了say_hello()say_goodbye()两个函数

1
2
3
4
5
6
7
8
9
10
11
def say_hello():
print("hello!")


def say_goodbye():
print("hello!") # 此处应打印goodbye


if __name__ == '__main__':
say_hello()
say_goodbye()

假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要调用方法前记录函数调用名称,以定位出错位置。
在不使用装饰器的情况下我们可以用以下方法实现

1
2
3
4
5
6
7
8
9
10
11
def say_hello():
print("[DEBUG]: enter say_hello()")
print("hello!")

def say_goodbye():
print("[DEBUG]: enter say_goodbye()")
print("hello!")

if __name__ == '__main__':
say_hello()
say_goodbye()

我们在上述的两个函数内部都加上了对应的打印信息,这样的话就修改了函数内容的代码。如果有多个函数的话,那么每个函数的内部都需要去增加对应的打印信息,当不需要时又要逐个去除,这里就显得非常难受。装饰器能很好的解决这个问题。

装饰器的简单实现

在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能是用纯闭包的方式实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper


def say_hello():
print("hello!")


say_hello = debug(say_hello)

say_hello()

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()

return wrapper

@debug
def say_hello():
print("hello!")


say_hello()

但是上述的例子中,被装饰器装饰的函数无法携带参数,所以可以在内部函数wrapper中声明参数被装饰函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
def debug(func):
def wrapper(something):
print("[DEBUG]: enter {}()".format(func.__name__))
return func(something)

return wrapper

@debug
def say_hello(something):
print("hello {}!".format(something))


say_hello('theone')

上述例子中虽然解决了被装饰函数的参数问题,但是我们的装饰器所装饰的函数可能拥有多个参数,这个时候上面这种方法就显然行不通,这个时候我们需要将内部函数wrapper的参数列表改成不定长参数,这样就可以轻松应对不同参数个数的函数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
def debug(func):
def wrapper(*args, **kwargs):
print("[DEBUG]: enter {}()".format(func.__name__))
return func(*args, **kwargs)
return wrapper


@debug
def say(something):
print("hello {}!".format(something))


say('theone')

实现一个带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么我们的装饰器就要有支持传递参数的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return wrapper


@logging(level='INFO')
def say(something):
print("say {}!".format(something))


# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)

@logging(level='DEBUG')
def do(something):
print("do {}...".format(something))


if __name__ == '__main__':
say('hello')
do("my work")

基于类的装饰器

我们通过类的方式实现装饰器我们需要借助python中的魔术方法__call__,该魔术方法可以使类的实例对象像函数一一样被调用。

1
2
3
4
5
6
class Demo:
def __call__(*args, **kwargs):
print('实例方法被执行')

demo = Demo()
demo() # 实例方法被执行

接下来我们通过类的方式来实现装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Logging:
def __init__(self, func):
self.func = func

def __call__(self, *args, **kwargs):
print("[DEBUG]: enter function {func}()".format(
func=self.func.__name__))
return self.func(*args, **kwargs)


@Logging
def say(something):
print("say {}!".format(something))


say('木木')

基于类的方式实现一个带参数的装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接收一个函数并返回一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Logging:
def __init__(self, level='INFO'):
self.level = level

def __call__(self, func): # 接收函数
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__))
func(*args, **kwargs)

return wrapper # 返回函数


@Logging(level='INFO')
def say(something):
print("say {}!".format(something))


say('木木')

什么是闭包

在Python中,闭包(Closure)是指一个函数(称为内部函数)捕获了其外部作用域中的变量,并且可以在其内部函数范围之外被调用。换句话说,闭包可以访问其定义时所处环境中的变量,即使在其定义之后这些变量的作用域已经消失了。

闭包的代码结构

1
2
3
4
5
6
7
8
9
10
11
def send_message(message):
def print_message():
print(message) # 获取的是外部函数的形式参数

# 在外部函数中返回内部函数的地址
return print_message


# 创建一个变量去接收内部函数地址
func_obj = send_message('python天下第一...')
func_obj()

上面这段代码是闭包的基本结构,在send_message函数的内部定义了一个print_message函数,send_message函数返回的是内部函数print_message的地址,并且print_message函数可以访问到外部函数send_message的形式参数。func_obj = send_message(‘python天下第一…’),这一段代码是将send_message函数中的内部函数print_message的函数地址值赋值给了func_obj,所以func_obj()实际上就是执行了内部函数print_message。

利用闭包的特性完成代码的复用性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def create_calculator(operation):
def add(x, y):
return x + y

def subtract(x, y):
return x - y

def multiply(x, y):
return x * y

def divide(x, y):
if y != 0:
return x / y
else:
return "Error: Division by zero!"

if operation == "add":
return add
elif operation == "subtract":
return subtract
elif operation == "multiply":
return multiply
elif operation == "divide":
return divide
else:
return None

# 创建加法器
add_calculator = create_calculator("add")
result = add_calculator(5, 3)
print(result) # 输出 8

# 创建减法器
subtract_calculator = create_calculator("subtract")
result = subtract_calculator(10, 4)
print(result) # 输出 6

# 创建乘法器
multiply_calculator = create_calculator("multiply")
result = multiply_calculator(7, 2)
print(result) # 输出 14

# 创建除法器
divide_calculator = create_calculator("divide")
result = divide_calculator(8, 2)
print(result) # 输出 4.0

在上面的代码中,create_calculator函数返回了不同的内部函数(add、subtract、multiply、divide),这些内部函数捕获了外部函数中的变量operation。通过调用create_calculator并传递不同的操作类型,我们可以创建不同功能的计算器,实现了代码的复用。

修改闭包中的形式参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def counter(start=0):
def add():
nonlocal start # 针对函数参数
start += 1
return start

return add


obj = counter(10)
print(obj())

"""
global: 作用于不可变对象且这个对象是一个全局的
nonlocal: 作用于不可变对象且这个对象具有作用域的
"""

在上面这段代码中,我们定义了一个函数counter,在函数counter的内部定义了一个函数add,内部函数可以访问到外部函数的形式参数,但是如果内部函数想要修改外部函数的形式参数那就必须使用nonlocal关键字。

多个闭包是内存隔离的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def wrapper(name):
def message(content):
print(f'{name}: {content}')

print(f'内部函数引用的地址为:', id(message))
return message


func_1 = wrapper('安娜')
func_2 = wrapper('双双')

# 闭包多次运行返回的内部函数的地址是不一样的,所以闭包具有内存隔离的特性,与面向对象中的实例对象类似
print(id(func_1)) # 2610767972528
print(id(func_2)) # 2610767970944

什么是生成器

在Python中,生成器(generator)是一种特殊的迭代器,它可以动态地生成值,而不是一次性将所有值存储在内存中。生成器使用yield关键字来产生值,每次调用生成器的__next__()方法(或者直接使用next(generator)函数)时,生成器会执行,直到遇到一个yield语句,然后返回yield语句后面的值,并暂停执行。下次调用时,生成器会从上次暂停的地方继续执行。

生成器的一个常见用法是在处理大量数据或者无限序列时,节省内存开销。因为它们是惰性计算的,只有在需要时才会生成值。

声明一个生成器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_num(number):  # 生成器函数
index = 0
while index < number:
yield index # yield可以返回一个结果并且不会终止一个函数的运行
index += 1


# 生成器函数不能直接使用小括号的方式获取结果
# 生成器函数调用后返回的是一个生成器对象
print(get_num(5).__next__())

# 生成器对象中的结果可以使用next函数进行获取,和迭代器类似
obj = get_num(5)
print(next(obj))
print(next(obj))
print(next(obj))

在python中使用yield关键字来定义一个生成器函数,在上面的例子中get_num函数声明了yield关键字,如果我们要打印get_num函数返回的值不能直接使用get_num(5),get_num函数是一个生成器函数,它返回的是一个生成器对象,生成器对象是一种特殊的迭代器,所以需要调用迭代器对象的next()方法来获取生成器函数的返回值。

生成器的运行过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_num(number):
print('开始迭代...')
i = 0
while i < number:
print('迭代中...')
yield i
print('迭代结束')
i += 1


obj = get_num(5)
print(next(obj))
print(next(obj))

"""
打印结果为:
开始迭代...
迭代中...
0
迭代结束
迭代中...
1
"""

在上一段代码中定义了一个生成器函数,在执行生成器函数的时候也是和普通函数一样从上往下执行,第一次调用函数的时候满足while循环条件进入到循环体内,生成器函数第一次遇到yield将结果返回出去,并保存当前的运行状态[保存了代码运行的位置],第二次运行则根据上一次运行的位置继续向下执行。

生成器表达式

1
nums = (i for i in range(1, 11))

上面这段代码就是生成器表达式,那么为什么要使用生成器表达式呢?
假设现在我们需要生成100万条数据,我们使用列表推导式生成的话会很慢,因为列表推导式生成100万条数据的时候会一次性把所有数据全部生成,数据没有生成完程序是不会退出的,而生成器表达式并不会直接给你生成数据,而是给你返回一个生成器对象,这个对象占用的内存并不大,生成器具有懒加载机制,调用next方法才可以获取到结果。

生成器的send和close方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def get_num(number):
i = 0
while i < number:
data = yield i
if data == '这是测试时传递的一个值':
print(123)
print('data变量的值:', data)
i += 1


obj = get_num(5)
# print(next(obj))
print(obj.send(None)) # 第一次执行生成器时不能使用send方法去传递任何对象,除了None对象
print(obj.send('这是测试时传递的一个值')) # 可以在运行生成器对象的过程中传递一个值

obj.close() # 关闭生成器对象,后续代码无法运行生成器对象
print(next(obj))

"""
运行结果为:
0
123
data变量的值: 这是测试时传递的一个值
1
"""

生成器的send方法可以给生成器发送一个信号,在上面的实例中我们让data = yield i,在第一次执行生成器函数时执行到yield i程序会停止并返回一个生成器对象,此时还没有声明data变量,所以此时我们的内存中并没有加载data这个变量,如果此时调用send方法传递一个值会出现报错,但是传递None对象除外,我们调用send方法传递一个值之前会调用一次next方法,随后就会将send方法中的参数值传递给我们生成器对象。close方法用来关闭生成器对象,生成器对象一旦关闭,后续代码无法运行生成器对象。

什么是可迭代对象

可迭代对象:在Python中,可迭代对象是指可以被迭代的对象,即可以使用 for 循环遍历其元素的对象。

1
2
3
4
5
6
7
8
from collections.abc import Iterable

nums = [1, 2, 3]

for num in nums:
print(num)

print(isinstance(nums, Iterable)) # True

在上述代码中我们创建了一个列表,这个列表中的元素可以被我们的for循环所遍历,那么nums就是一个可迭代对象。最下边我们通过一个python内置方法instance来检查nums是否是Iterable类的实例,打印了True说明nums是Iterable类的一个实例,要是Iterable的子类一定是一个可迭代对象。

instance内置方法

isinstance是python的内置函数,判断一个类的实例是否是指定类的实例,支持继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A:
pass


class B(A):
pass


a = A()
b = B()

print(isinstance(a, A)) # True
print(isinstance(b, A)) # True
print(isinstance(a, B)) # False

什么是迭代器对象

迭代器是一种对象,它实现了迭代器协议,即包含 iter() 和 next() 方法。迭代器对象用于在迭代过程中逐个访问元素,每次调用 next() 方法都会返回序列中的下一个元素,直到序列中没有元素可返回,此时会引发 StopIteration 异常。
iter函数是python中的一个内置函数,传递一个可迭代对象将这个对象转为一个迭代器对象

1
2
3
4
5
6
7
8
9
10
11
12
nums = [1, 2, 3]
print(next(nums)) # 这里会报错,因为只有迭代器对象才拥有next方法
"""
在python中iter()和__iter__的作用是一样的,因为__iter__是比较底层的方法,所以推荐使用iter()
下面这段代码将nums从一个可迭代对象转为一个迭代器对象
"""
obj = iter(nums) # 此时obj对象将拥有next方法

print(next(nums)) # 1
print(next(nums)) # 2
print(next(nums)) # 3
print(next(nums)) # 如果元素取完则引发报错:StopIteration

通过instance方法来判断对象是否为迭代器对象

1
2
3
4
5
from collections.abc import Iterator

nums = [1, 2, 3]
print(isinstance(nums, Iterator)) # False,列表对象不是迭代器对象
print(isinstance(iter(nums), Iterator)) # True

自定义类实例化返回迭代器对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ListNode:
def __init__(self):
self.person_list = list()

# 如果一个类内部定义了__iter__方法,那么创建出来的实例一定是一个可迭代对象
def __iter__(self):
pass

def __next__(self):
pass


list_node = ListNode()
print(isinstance(list_node, Iterable)) # True
print(isinstance(list_node, Iterator)) # True

在上面这段代码中我们自定义了一个类,该类拥有__iter__和__next__方法,那么该类的实例对象就是一个迭代器对象,如果将该类的__next__方法注释,那么该类的实例对象就是一个可迭代对象。可迭代对象不一定是迭代器对象,迭代器对象一定是一个可迭代对象。

__iter__和__next__具体的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ListNode:
def __init__(self):
self.index = 0
self.stu_list = list()

def __iter__(self):
return self

def __next__(self):
if self.index < len(self.stu_list):
item = self.stu_list[self.index]
self.index += 1
return item
else:
print('元素被取完了')
raise StopIteration

def add(self, name):
self.stu_list.append(name)


list_node = ListNode()
list_node.add('安娜')
list_node.add('双双')

for stu in list_node:
print(stu)

在上面这段代码中我们定义了一个ListNode类,该类中声明了__iter__和__next__方法,先实例化了ListNode类对象,随后通过add方法添加了两个学员,那么我们使用for循环直接去遍历ListNode类的实例对象是可以正常将类中列表的内容打印出来,因为for循环实质上是先将调用iter方法将需要遍历的对象转为迭代器对象,然后迭代器对象去执行next方法,从而将元素遍历出来,next方法的作用就是定义如何在一个迭代器对象中获取元素

什么是存储过程

存储过程(Stored Procedure)是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对象。
存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。

存储过程的优点

  • 提高性能: 存储过程在数据库服务器上预先编译和存储,可以减少每次执行的解析和编译时间,从而提高执行速度。
  • 减少网络流量: 客户端只需发送存储过程的调用请求,而不是发送大量的SQL语句,从而减少了网络流量和延迟。
  • 增强安全性: 可以限制用户直接访问数据库表,只允许他们通过存储过程进行数据操作,从而提高了数据的安全性和完整性。
  • 提高可维护性: 存储过程可以实现业务逻辑的封装,使得逻辑在数据库层面统一管理,易于维护和更新。
  • 提高代码复用: 存储过程可以被多个应用程序调用,实现了代码的复用,避免了重复编写相同的逻辑。

存储过程的缺点

  • 难以调试: 存储过程的调试相对复杂,特别是当存储过程逻辑较为复杂时,调试可能会变得非常困难。
  • 数据库厂商依赖性: 存储过程的语法和特性在不同的数据库管理系统中可能有所不同,导致代码的移植性受到影响。
  • 增加数据库复杂性: 过度使用存储过程可能会增加数据库的复杂性,使得数据库结构难以理解和维护。
  • 限制了平台独立性: 使用存储过程可能限制了应用程序的平台独立性,因为存储过程通常是特定数据库管理系统的特性。
  • 可能导致性能问题: 如果存储过程编写不当,可能会导致性能问题,例如过度的循环、大量的参数传递等都可能影响存储过程的性能表现。

存储过程的创建和调用

存储过程通过procedure关键字来进行定义,存储过程的开始和结束符号由BEGIN…END来标识

1
2
3
4
create procedure 存储过程名(参数)
BEGIN
...
END

声明语句结束符

1
2
3
DELIMITER $$

DELIMITER //

调用存储过程时通过call关键字来进行调用

1
call 存储过程名(参数)

变量赋值

1
SET @p_in=1

变量定义

1
DECLARE l_int int unsigned default 4000000;

存储过程使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 创建matches表
CREATE TABLE matches (
MATCHNO INT,
TEAMNO INT,
PLAYERNO INT,
WON INT,
LOST INT
);
-- 表中插入数据
INSERT INTO matches ( MATCHNO, TEAMNO, PLAYERNO, WON, LOST )
VALUES
( 1, 1, 6, 3, 1 ),
( 7, 1, 57, 3, 0 ),
( 8, 1, 8, 0, 3 ),
( 9, 2, 27, 3, 2 ),
( 11, 2, 112, 2, 3 );

-- 定义存储过程,删除指定球员参加的所有比赛
delimiter $$ -- 声明语句结束符,可以自定义
create procedure delete_matches(in p_playerno INTEGER)
BEGIN
delete from matches where playerno = p_playerno;
END$$
delimiter ;

call delete_matches(57); -- 执行存储过程,删除playerno为57的所有记录

存储过程的参数

1、in输入参数

1
2
3
4
5
6
7
8
9
10
11
12
delimiter $$
create procedure in_param(in p_in int)
BEGIN
select p_in;
set p_in=2;
select p_in;
END$$
delimiter ;

set @p_in=1;
call in_param(@p_in); -- 这里会先打印1后打印2,p_in 在存储过程中被修改,但并不影响 @p_in 的值,因为前者为局部变量、后者为全局变量。
select @p_in; --这里会输出1

2、out输出参数

1
2
3
4
5
6
7
8
9
10
11
12
13
delimiter //
create procedure out_param(out p_out int)
BEGIN
select p_out;
set p_out=2;
select p_out;
END
//
delimiter ;

set @p_out=1;
call out_param(@p_out); -- 这里会先输出null,再输出2,因为out是向调用者输出参数,不接收输入的参数,所以存储过程里第一次select p_out的时候会打印null
select @p_out; -- 这里会输出2,因为我们调用了存储过程out_param并将参数@p_out传入,经过存储过程内部的处理将@p_out参数进行了修改并进行输出

3、inout输入输出参数

1
2
3
4
5
6
7
8
9
10
11
12
13
delimiter //
create procedure out_param(inout p_inout int)
BEGIN
select p_inout;
set p_inout=2;
select p_inout;
END
//
delimiter ;

set @p_inout=1;
call inout_param(@p_inout); -- 这里会先输出1再输出2,因为变量的类型为inout,所以既支持外部传入参数同时也会将参数输出出去
select @p_inout; -- 这里仍然会打印2

注意:
1.即使存储过程中没有参数也必须在括号后面写上小括号
2.确保参数的名字不等于列的名字,否则在过程体中,参数名被当做列名来处理

MySQL存储过程的查询、修改与删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select name from mysql.proc where db='数据库名';
或者
select routine_name from information_schema.routines where routine_schema='数据库名';
或者
show procedure status where db='数据库名';

如果我们想知道,某个存储过程的详细,那我们又该怎么做呢?是不是也可以像操作表一样用describe 表名进行查看呢?
SHOW CREATE PROCEDURE 数据库.存储过程名;

修改存储过程
ALTER PROCEDURE

删除存储过程
DROP PROCEDURE

MySQL存储过程的变量作用域及控制语句

1、变量的作用域

内部的变量在其作用域范围内享有更高的优先权,当执行到 end。变量时,内部变量消失,此时已经在其作用域外,变量不再可见了,应为在存储过程外再也不能找到这个声明的变量,但是你可以通过 out 参数或者将其值指派给会话变量来保存其值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DELIMITER //
CREATE PROCEDURE proc3()
begin
declare x1 varchar(5) default 'outer';
begin
declare x1 varchar(5) default 'inner';
select x1;
end;
select x1;
end;
//
DELIMITER ;

CALL proc3() -- 先输出inner,再输出outer

2、控制语句

2.1 if-then-else 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DELIMITER //
CREATE PROCEDURE proc2(IN parameter int)
begin
declare var int;
set var=parameter+1;
if var=0 then
insert into MATCHES values(17,9,9,9,9);
end if;
if parameter=0 then
update MATCHES set LOST=LOST+1;
else
update MATCHES set LOST=LOST+2;
end if;
end;
//
DELIMITER ;
2.2 case语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DELIMITER //
CREATE PROCEDURE proc3 (in parameter int)
begin
declare var int;
set var=parameter+1;
case var
when 0 then
insert into matches values(17,9,9,9,9);
when 1 then
insert into matches values(18,9,9,9,9);
else
insert into matches values(19,9,9,9,9);
end case;
end;
//
DELIMITER ;
2.3 循环语句

2.3.1 while ···· end while

1
2
3
4
5
6
7
8
9
10
11
12
DELIMITER //
CREATE PROCEDURE proc4 (in parameter int)
begin
declare var int;
set var=0;
while var<6 do
insert into matches values(var,9,9,9,9);
set var=var+1;
end while;
end;
//
DELIMITER ;

2.3.2 repeat···· end repeat
它在执行操作后检查结果,而 while 则是执行前进行检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
DELIMITER //
CREATE PROCEDURE proc5()
begin
declare v int;
set v=0;
repeat
insert into matches values(v,8,8,8,8);
set v=v+1;
until v>=5
end repeat;
end;
//
DELIMITER ;

2.3.3 loop ·····endloop
loop 循环不需要初始条件,这点和 while 循环相似,同时和 repeat 循环一样不需要结束条件, leave 语句的意义是离开循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DELIMITER //
CREATE PROCEDURE proc6()
begin
declare v int;
set v=0;
LOOP_LABLE:loop
insert into matches values(v,7,7,7,7);
set v=v+1;
if v >=5 then
leave LOOP_LABLE;
end if;
end loop;
end;
//
DELIMITER ;

2.3.4 ITERATE迭代
ITERATE 通过引用复合语句的标号,来从新开始复合语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DELIMITER //
CREATE PROCEDURE proc10()
begin
declare v int;
set v=0;
LOOP_LABLE:loop
if v=3 then
set v=v+2;
ITERATE LOOP_LABLE;
end if;
insert into matches values(v,5,5,5,5);
set v=v+1;
if v>=5 then
leave LOOP_LABLE;
end if;
end loop;
end;
//
DELIMITER ;

通过数据存储过程批量创建用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
delimiter $$

create procedure insertUser(in insertCount int, in startUsername int)
begin
declare i int default 0; -- 初始化循环计数器
declare usernameno int default startUsername; -- 初始化用户名计数器
while i < insertCount do -- 循环插入数据直到达到指定的insertCount
insert into user(username, email, phone, sex, password, create_time) values(
concat('user_', usernameno),
concat('user', usernameno, '@gmail.com'),
concat('15', lpad(FLOOR(100000000 + RAND() * 900000000), 8, '0')),
if(mod(usernameno, 2) = 0, 1, 0),
'123456',
now()
);
insert into user_role(user_id, role_id) values(last_insert_id(), 2); -- 使用last_insert_id()获取刚刚插入的user_id
set i = i + 1;
set usernameno = usernameno + 1;
end while;
end$$

delimiter ;

call insertUser(2, 3);

Docker-compose简介

Docker-compose 是用于定义和运行多容器的 Docker 应用程序的工具。通过 Compose,可以使用 YAML 文件来配置应用程序的服务。

Linux下安装Docker-compose

https://github.com/docker/compose/releases

1
curl "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

更改权限:chmod +x /usr/local/bin/docker-compose
查看版本:docker-compose version

Docker-compose常用命令

  • 查看配置:docker-compose config
  • 后台启动:docker-compose up -d
  • 停止并删除容器、网络、卷、镜像: docker-compose down
  • 构建镜像:docker-compose build
  • 下载镜像:docker-compose pull
  • 展示当前docker-compose编排过的运行的所有容器:docker-compose ps
  • 进程:docker-compose top
  • 查看配置:docker-compose config
  • 启动:docker-compose start
  • 停止:docker-compose stop

Docker-compose.yml示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
version: "3"

service:
# 下面的配置等价于
# docker run -d -p 6001:6001 -v /app/microService:/data --network atguigu_net --name ms_01 docker_image:1.6
microService:
image: docker_image:1.6
container_name: ms_01
ports:
- "6001:6001"
volumes:
- /app/microService:/data
networks:
- atguigu_net
depends_on:
- redis
- mysql

redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- /app/redis/redis.conf:/etc/redis/redis.conf
networks:
- atguigu_net
command: redis-server /etc/redis/redis.conf

mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: '123456'
MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
MYSQL_DATABASE: 'db123'
MYSQL_USER: 'william'
MYSQL_PASSWORD: '123456'
ports:
- "3306:3306"
volumes:
- /app/mysql/mysql.conf:/etc/mysql/mysql.conf
networks:
- atguigu_net
command: --default-authentication-plugin=mysql_native_password # 解决外部无法访问
networks:
atguigu_net:

Docker常用命令

1.docker pull 镜像名称:镜像标签 拉取镜像
2.docker run 运行镜像

  • –rm 容器停止之后立即删除容器
  • -i 表示运行容器
  • -it 运行之后进入到交互模式
  • –name xxx 给容器进行命名
  • -e 配置环境变量
  • -d 后台运行
  • -p 33060:3306 将本地的33060端口映射到容器内部的3306端口
  • -v 本地路径:容器内部的路径 将本地的目录映射到容器内部
  • –privileged=true 解决挂载目录没有权限的问题
  • –network 网络组名称 容器之间进行相互通信
  • –privileged 使用该参数,container内的root拥有真正的root权限。
    否则,container内的root只是外部的一个普通用户权限。

3.docker ps 查看正在运行的容器
4.docker ps -a 查看所有的容器
5.docker logs 容器名称/容器id 查看启动日志
6.docker exec -it 容器名称/容器id bash 进入容器的内部
7.docker inspect 容器名称/容器id 分析容器的具体信息
8.docker run -d bash -c ‘要执行的命令1 && 要执行的命令2’ 运行镜像后要执行的命令(注意:启动dockerfile构建的镜像时在后面加上bash -c ‘要执行的命令1 && 要执行的命令2’,会忽略掉dockerfile中CMD的内容)
9.docker cp 本地的文件目录 镜像名称:镜像目录 ——将本地的文件拷贝到容器内
10.docker inspect 容器名称——查看容器的ip地址
11.docker load -i 镜像压缩文件——将压缩文件加载为docker镜像

镜像管理

1、docker images 查看所有镜像
2、docker search 镜像名 搜索镜像
3、docker pull 镜像名:版本号 拉取镜像
4、docker history 镜像名 查看镜像创建历史
5、docker rmi 镜像名:标签 删除镜像
6、docker tag busybox:latest busybox:test 更改镜像名

Docker镜像构建

一、Dockerfile参数详解

  • FROM –依赖于哪些镜像
  • WORKDIR –指定工作的上下文
  • COPY –把一些文件或目录添加到镜像中
  • RUN –要运行的相关命令
  • CMD –最后要运行的指令
  • ENV key value –设置环境变量
  • ADD –将本地的文件复制到镜像内部(如果是一个压缩文件,将会在复制后自动解压)
  • COPY –与ADD相似(但是如果复制的是压缩文件,不会自动解压)

二、docker build参数详解

  • -t:为构建的镜像设置标签
  • –build-arg:传递构建参数到Dockerfile中
  • -f:指定要使用的Dockerfile的名称,如果文件名默认叫做dockerfile可以不用写
  • –network:指定构建时使用的网络配置
  • –no-cache:忽略缓存,强制重新构建镜像
  • –pull:在构建之前尝试拉取最新的基础镜像
  • –target:指定构建过程中的目标阶段

docker build官方文档:https://docs.docker.com/engine/reference/builder/

docker网络组的使用

当你在Docker中使用网络组时,可以通过以下简单的例子来理解其作用。假设你有两个容器,一个运行Web应用程序,另一个运行数据库服务。

首先,创建一个网络组:

1
docker network create mynetwork

接下来,运行一个容器并将其连接到这个网络:

1
docker run --name webapp --network mynetwork -d -p 8080:80 your-webapp-image

这会运行一个名为 “webapp” 的容器,将其连接到 “mynetwork” 网络,并将容器的端口 80 映射到主机的端口 8080。

然后,运行另一个容器,比如一个运行数据库的容器,并将其连接到相同的网络:

1
docker run --name database --network mynetwork -d -e MYSQL_ROOT_PASSWORD=password your-db-image

现在,你有两个容器运行在同一个网络中。它们可以通过容器名称相互通信,而无需关心它们各自的 IP 地址。

例如,在Web应用程序容器中,你可以配置数据库连接为:

1
mysql -h database -u root -p

这里,”database” 是另一个容器的名称,Docker网络组的内置 DNS 服务会将其解析为相应的 IP 地址。
通过这种方式,你可以轻松地创建多个容器,使它们能够在同一个网络中相互通信,而不受主机网络或其他容器的影响。这种隔离和组织方式使得构建和管理复杂的应用程序更为灵活和方便。

Docker简介

Docker 起源于 2013 年。Docker 是一个开源的应用容器引擎,基于 Go 语言开发,Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的系统

Docker优点

  • Docker 可以用来快速交付应用。加快打包时间,加快测试,加快发布,缩短开发及运行代码之间的周期。
  • 复杂环境管理,应用隔离:不同软件运行环境兼容依赖问题,开发环境/测试环境/线上环境保持一致。
  • 轻量级:对于系统内核来说,一个 docker 只是一个进程,一个系统可以运行上千个容器。

Docker与虚拟机的区别

  • 容器与容器之间只是进程的隔离,而虚拟机是完全的资源隔离。
  • 虚拟机的启动可能需要分钟级别,Docker 启动是秒级或者更短。
  • 容器使用宿主操作系统的内核,而虚拟机使用完全独立的内核。

Docker架构

centos下安装Docker

1
2
3
4
5
6
7
# 安装 Docker 依赖
yum install -y yum-utils device-mapper-persistent-data lvm2
# 配置依赖下载源
yum-config-manager --add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 安装 docker
yum makecache fast && yum -y install docker-ce

Docker配置阿里镜像源

https://cr.console.aliyun.com/cn-guangzhou/instances/mirrors(阿里云镜像加速地址获取)

1
2
3
4
5
6
7
8
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["这里替换成自己的加速地址"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

Docker容器介绍

容器是您机器上的沙盒进程,与主机上的所有其他进程隔离。这种隔离利用了内核命名空间和 cgroups,这些特性已经在 Linux 中存在了很长时间。 Docker 致力于使这些功能变得平易近人且易于使用。总而言之,一个容器: 是图像的可运行实例。您可以使用 DockerAPI 或 CLI 创建、启动、停止、移动或删除容器。 可以在本地机器、虚拟机上运行或部署到云端。 是可移植的(可以在任何操作系统上运行)。 与其他容器隔离并运行自己的软件、二进制文件和配置。

图片描述

docker官网

前置说明:假设现在打开一个网页,需要点击页面中的元素跳转到另一个页面,这个时候可能会出现页面没有渲染完成导致点击失败


这里拿企业微信做一个例子,现在我处于协作模块,我需要点击进入应用管理,假设浏览器打开页面后页面没有渲染完成导致我点击应用管理失败,这个时候我们可以通过显示等待的高级用法(使用复杂行为解决点击不生效的问题)。我们可以找到应用管理页面中特定元素来判断是否点击成功,我们可以看见应用管理页面中有一个微信客服的元素,那么我们可以通过selenium显示等待的高级用法进行循环点击。

1
2
3
4
5
def loop_click(driver):
self.driver.click(By.XPATH, "//span[text()='应用管理']/parent::a")
return len(self.driver.find_elements(By.XPATH, "/div[text()='微信客服']")) > 0

WebDriverWait(self.driver, 10).until(loop_click)

这里我们我们将点击应用管理的操作写在了loop_click的函数内,并且在函数的return中定义了判断结果,如果应用管理页面的元素一旦出现则loop_click函数就会返回true,证明元素点击成功。当应用管理页面的元素一直没有出现,那么len(self.driver.find_elements(By.XPATH, “/div[text()=’微信客服’]”)) > 0就会一直为false,则在显示等待设定的时间内会一直执行loop_click函数从而达到循环点击的作用。

0%