python高级用法笔记

Python高级用法笔记

1. 多任务编程-1

进程:在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发执行,就是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此进程就是为了在CPU上实现多道编程而提出来的

线程:进程有很多优点,它提供了多道编程,提高了CPU的利用率,而线程是为了提高进程资源的利用率而存在的,CPU分时给每一个进程,如果进程中只有一个线程,那在相对时间里所能处理的事情就相对简单和少

Python GIL(Global Interpreter Lock)
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行,擦。。。,那这还叫什么多线程呀?莫如此早的下结结论,听我现场讲。

'全局解释器锁[Global Interpreter Lock]是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。即便在多核处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程,常见的使用 GIL 的解释器有CPython与Ruby MRI。可以看到GIL并不是Python独有的特性,是解释型语言处理多线程问题的一种机制而非语言特性。'

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

这篇文章透彻的剖析了GIL对python多线程的影响,

强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf

Python自身作为一门编程语言,它有多种实现。这里的实现指的是符合Python语言规范的Python解释程序以及标准库等。这些实现虽然实现的是同一种语言,但是彼此之间,特别是与CPython之间还是有些差别的。

# 下面分别列出几个主要的实现。

1.CPython:这是Python的'官方版本',使用'C语言'实现,使用最为广泛,新的语言特性一般也最先出现在这里。CPython实现会将源文件(py文件)转换成字节码文件(pyc文件),然后运行在Python虚拟机上。

2. Jython:这是Python的'Java'实现,相比于CPython,它与Java语言之间的互操作性要远远高于CPython和C语言之间的互操作性。在Python中可以直接使用Java代码库,这使得使用Python可以方便地为Java程序写测试代码,更进一步,可以在Python中使用Swing等图形库编写GUI程序。Jython会将Python代码动态编译成Java字节码,然后在JVM上运行转换后的程序,这意味着此时Python程序与Java程序没有区别,只是源代码不一样。在Python 中写一个类,像使用Java 类一样使用这个类是很容易的事情。你甚至可以把Jython 脚本静态地编译为Java 字节码。


1.1 进程

# 进程的概念
一个正在运行的程序或软件就是一个进程,'它是操作系统进行资源分配的基本单位',也就是说没启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
注意:一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程。

1.2 多进程

# 导入进程包
import multiprocessing

# Process 进程类的说明
proce([group[,target[,name[,args[,kwargs]]]]])
	group: 指定进程组,目前只能使用None
  target: 执行的目标任务名
  name: 进程名字
  args: 以元组方式给执行任务传参
  kwargs: 以字典方式给执行任务传参
    
Process创建的实例对象的常用方法:
	start(): 启动子进程实例(创建子进程)
  join(): 等待子进程执行结束
  terminate(): 不管任务是否完成,立即终止子进程
  
Process创建的实例对象的常用属性:
	name:当前进程的别名, 默认为Process-N, N为从1开始递增的整数
  
# 实例
import multiprocessing
import time
import os
def dance():
    num1 = 0
    print("dance:", os.getpid())
    print("dance:", multiprocessing.current_process())
    print("dance_father:", os.getppid())
    for i in range(5):
        num1 += 1
        print(f"跳舞中。。。{num1}")
        time.sleep(0.2)
def sing():
    num2 = 0
    # 查看子进程号
    print("dance:", os.getpid())
    print("dance:", multiprocessing.current_process())
    # 显示父进程号
    print("dance_father:", os.getppid())
    for i in range(5):
        num2 += 1
        print(f"唱歌中{num2},,,")
        time.sleep(0.2)

if __name__ == '__main__':
    # 查看进程号 
    print("main:", os.getpid())
    print("main:", multiprocessing.current_process())
    # 创建多进程
    dance_process = multiprocessing.Process(target=dance, name="myproncess1")
    sing_process = multiprocessing.Process(target=sing)
    # 启动多进程
    dance_process.start()
    sing_process.start()


补充:
'''
python多进程实现方式一
下面代码开启了5个进程去执行函数,几乎同时打印,实现了并行执行操作,
就是多个cpu同时执行任务,
'''
from multiprocessing import Process
def fun1(name):
    print('测试%s多进程' % name)

if __name__ == "__main__":
    process_list = []
    for i in range(5):  # 开启5个子进程
        p = Process(target=fun1, args=('python',))
        p.start()
        # p.join()
        process_list.append(p)

    for i in range(50):
        p.join()

    print('结束')

"""
实现方法二:利用类的继承方式实现多进程
"""
class Myprocess(Process):  # 继承Process类
    def __init__(self, name):
        super(Myprocess, self).__init__()
        self.name = name

    def run(self):
        print("测试%s多进程" % self.name)


if __name__ == '__main__':
    process_list = []
    for i in range(5):     # 开启5个子进程执行run函数
        p = Myprocess('python')     # 实例化对象
        p.start()
        process_list.append(p)

    for i in process_list:
        p.join()

    print("结束测试")

1.3 获取多进程编号

1)获取多进程编号的目的
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的
获取进程的操作有两种:
 获取当前进程编号
		os.getpid()
 获取当前父进程编号
		os.getppid()

1.4 给进程传参

# 给执行任务的进程传递参数有两种方式
 args 表示以元组的方式给执行任务传参
 kwargs 表示以字典方式给执行任务传参

示例:
1. 元组传参
import multiprocessing
import time
# 带有参数的任务
def task(count):
    for i in range(count):
        print("任务开始了。。。")
    else:
        print(f"执行{count}次已完成")
if __name__ == "__main__":
    # 创建子进程,元组传参
    sub_task = multiprocessing.Process(target=task, args=(5, ))
    sub_task.start()
    
2. 字典传参
# 带有参数的任务
def task_two(count):
    for i in range(count):
        print("任务2开始了。。。")
    else:
        print(f"执行{count}次已完成")

if __name__ == "__main__":
    # 创建子进程,已字典方式传承
    sub_task_two = multiprocessing.Process(target=task_two, kwargs={"count": 5})
    sub_task_two.start()
    
'注:在创建子进程时 target 参数的任务名一定要对应到要执行任务的函数名,否则不会执行'

进程总结:

  1. 进程之间不共享全局变量

    # 解释:主进程创建子进程后,若主进程存在全局变量,子进程之间和主进程之间都 不共享此全局变量,都是独立的,互不干扰,只是变量名字相同而已。而各进程操作的都不是一个全局变量
    '''
    创建子进程会对主进程资源进程拷贝,也就是说子进程是主进程的一个副本,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量的名字相同而已
    '''
    
  2. 主进程会等待所有子进程执行完成再结束

为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是'主进程退出子进程销毁,不让主进程再等待子进程去执行'
设置守护主进程方式:'子进程对象.daemon = True'
销毁子进程方式:'子进程对象.terminate()'

# 示例:
# 1.如果父进程比子进程先执行完,如果不设置任何参数,父进程执行完子进程还是会继续执行,直到结束
# 2.如果要在主进程执行完成后直接结束掉没有执行完的子进程就需要加上函数 sub_process.terminate() 或者设置一个守护进程 sub_process.daemon = True 
def task():
    for i in range(10):
        print("任务执行中中中")
        time.sleep(0.2)
        
if __name__ == "__main__":
    # 创建子进程
    sub_process = multiprocessing.Process(target=task)
		#方法一:(建议)创建子进程的守护进程,守护着父进程一结束就结束子进程
    sub_process.daemon = True
    sub_process.start()

    # 主进程延时0.5秒钟
    time.sleep(0.5)
    print("over。。。")
		#方法二:子进程一被创建就销毁,创建后销毁
    # sub_process.terminate()  
    exit()

2. 多任务编程-2

2.1 线程

# 线程的概念
线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进程调度,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程

# 线程的作用:多线程可以完成多任务
说明:程序启动默认会有一个主线程,程序员自己创建的线程可以称为子线程,多线程可以完成多任务

2.2 多线程

# 多线程的使用- 导入线程模块
import threading

# 线程类 Thread 参数说明
Thread([group[,target[,name[,args[,kwargs]]]]])
	group: 线程组,目前只能使用 None
  target: 执行的目标任务名
  args: 以元组的方式给执行任务传参
  kwargs: 以字典的方式给执行任务传参
  name: 线程名, 一般不设置
    
# 线程的启动
		线程名.start()
  
  
多线程示例:
import threading
import time

my_list = []

# 向列表中写数据
def write_data():
		# 获取当前线程对象
    print("write:", threading.current_thread())
    for i in range(50):
        my_list.append(i)
        time.sleep(0.1)
    print("write_data:, ", my_list)

# 向列表中读数据函数
def read_data():
    print("read_data:", my_list)


if __name__ == '__main__':
    # 写线程
    write_thread = threading.Thread(target=write_data())
    # 读线程
    read_thread = threading.Thread(target=read_data())

    write_thread.start()
    write_thread.join()

    read_thread.start()
    print("开始读取数据了")

2.3 给线程传参

# Thread 类执行任务并给任务传参有以下两种方式:
 args 表示以元组的方式给执行任务传参
 kwargs 表示以字典方式给执行任务传参


# 传参是注意
 元组方式传参(args): 元组方式传参一定要和参数的顺序保持一致
 字典方式传参(kwargs): 字典方式传参字典中的key一定要和参数名保持一致
  

# 线程的注意点
1. 线程之间执行是无序的
2. 主线程会等待所有子线程执行结束再结束
3. 线程之间共享全局变量
4. 线程之间共享全局变量数据出错问题


# 1. 线程之间执行是无序的
 线程之间的执行是无序的,它是由cpu调度决定的,cpu调度那个线程就先执行那个,没有调度的线程不能执行
 进程之间的执行也是无序的,它是由操作系统调度决定的,操作系统调度那个进程,那个进程就先执行那个,没有调的的进程不能执行



# 2. 主线程会等待所有子线程执行结束再结束
通过实例可以得知,'主线程会等待所有子线程执行结束再结束'
但是如果需要主线程结束的同时也结束子线程,就需要设置'守护主线程'
守护主线程:就是主线程退出子线程立即销毁不再继续执行
设置守护进程的两种方式:
1) threading.Thread(target=show_info, daemon=True)
2) 线程对象.setDaemon(True)
示例:
import threading
import time
def task():
    time.sleep(1)
    print("当前线程",threading.current_thread().name)

if __name__ == '__main__':
    for i in range(5):
        sub_thread = threading.Thread(target=task)
        # print("主线程结束了")
        # sub_thread.daemon = True
        sub_thread.start()


# 3. 线程之间共享全局变量
'通过下列示例,可以知道,读写线程都调用一个全局变量列表时,使用的都是同一个变量,与进程不同'
示例:
import threading
import time
my_list = []
# 向列表中写数据函数:
def write_data():
    for i in range(5):
        my_list.append(i)
        time.sleep(0.1)
    print("write_data:, ", my_list)
# 向列表中读数据函数:
def read_data():
    print("read_data:", my_list)

if __name__ == '__main__':
    # 写线程
    write_thread = threading.Thread(target=write_data())
    # 读线程
    read_thread = threading.Thread(target=read_data())

    write_thread.start()
    write_thread.join()   # 写线程执行完成后再执行读线程
    print("开始读取数据了")
    read_thread.start()

执行结果:
write_data:,  [0, 1, 2, 3, 4]
read_data: [0, 1, 2, 3, 4]
开始读取数据了

# 4. 线程之间共享全局变量数据出错问题
思考:如果a,b两个线程同时操作一个全局变量 list ,而a,b线程的执行是有cpu时间片来调度的,这时 两个函数操作一个列表会带来混乱的列表结果
'解决':给其中一个线程加上 join 等待函数,即该线程执行完了之后才能执行其他线程
		线程名.join()
线程等待
 示例:加上join后,一个线程全部执行完毕后才执行下一个线程,同一时间只有一个线程执行
import threading
# 定义全局变量
ret_num = 0
# 创建全局变量的互斥锁
# 若没有互斥锁,range中相加的数越大出错的几率越大
def sum_num1():
    for i in range(500000):
        global ret_num
        ret_num += 1
    print("ret_num111= ", ret_num)
   
def sum_num2():
    for i in range(500000):
        global ret_num
        ret_num += 1
    print("ret_num222= ", ret_num)

if  __name__ == "__main__":
    sum1_thread = threading.Thread(target=sum_num1)
    sum2_thread = threading.Thread(target=sum_num2)
    sum1_thread.start()
    sum1_thread.join()  #等待执行完再执行下一个,sum1全部执行完后才执行sum2
    sum2_thread.start()
    sum2_thread.join()  #等待执行完再执行下一个

2.4 互斥锁

1. 互斥锁概念:对共享数据进程锁定,保证同一时间只有一个线程去操作。
2. 加上互斥锁的目的 : 是在多线程时,不同的线程访问一个全局变量时不会出现数据出错的问题

  注意:互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其他等待的线程再去抢这个锁

总结:
 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
 使用互锁的好处是确保某段关键代码只能由一个线程从头到尾完整地去执行
 使用互斥锁会影响代码的执行效率,多任务改成单任务执行了
 互斥锁如果没有使用好容易出现死锁的情况
1. 使用互斥锁:'threading 模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数获得互斥锁'

2. 互斥锁使用步骤:
    # 创建锁
    mutex = threading.Lock()

    # 上锁
    mutex.acquire()
    ...这里编写代码能保证同一时刻只能有一个线程去操作,对共享数据进程锁定...

    # 释放锁
    mutex.release()

# 注意点:acquire和release方法之间的代码同一时刻只能有一个线程去操作;如果在调用acquire方法的时候,其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁


示例:

import threading					
ret_num = 0  							# 定义全局变量
lock = threading.Lock()		# 创建 互斥锁的 全局变量

def sum_num1():
    lock.acquire()				# 上锁 
    for i in range(500):
        global ret_num
        ret_num += 1
    print("ret_num111= ", ret_num)
    lock.release()				# 释放锁

def sum_num2():
    lock.acquire()				# 上锁
    for i in range(500):
        global ret_num
        ret_num += 1
    print("ret_num222= ", ret_num)
    lock.release() 				# 释放锁

if  __name__ == "__main__":
    sum1_thread = threading.Thread(target=sum_num1)    # 定义多线程
    sum2_thread = threading.Thread(target=sum_num2)
    sum1_thread.start()
    sum2_thread.start()

    返回:
      ret_num111=  500
      ret_num222=  1000

'''注:
1. 加上互斥锁后,线程获得互斥锁是随机的,没有抢到互斥锁的线程需要等待释放后才能执行
2. 加上互斥锁的瞬间整个任务就变成了单线程,性能会下降,同一时刻只有一个线程在执行任务
'''


2.5 死锁

 死锁:一直等待对方释放锁的情景就是死锁
 死锁的结果:会造成应用程序的停止响应,不能再处理其他任务了
 使用互斥锁的时候注意死锁问题,要在合适的地方加上释放锁

示例:
import threading
import time
lock = threading.Lock()

# 要求:根据下标取值,保证同一时刻是有一个线程在取值
def get_value(index):
    lock.acquire()      # 上锁
    print(threading.current_thread())
    my_list = [1, 3, 5, 7, 9]
    if index >= len(my_list):
        print("下标越界了: ", index)
        lock.release()				'# 不在这里加释放锁时,任务执行取到 9 这个元素后就会阻塞'
        return
    value = my_list[index]
    print(value)
    time.sleep(0.2)
    lock.release()   # 释放锁

if __name__ == '__main__':
    for i in range(30):
        sub_thread = threading.Thread(target=get_value, args=(i,))
        sub_thread.start()

        部分返回:
        ...
          <Thread(Thread-4, started 123145507696640)>
          7
          <Thread(Thread-5, started 123145524486144)>
          9
          <Thread(Thread-6, started 123145541275648)>
          下标越界了:  5   # 释放锁后,尽管下标越界也会继续执行
          <Thread(Thread-7, started 123145558065152)>
          下标越界了:  6
          <Thread(Thread-8, started 123145574854656)>
          下标越界了:  7
        ...

  注:上述程序只有加两把释放锁任务才会正常执行,否则任务会阻塞

2.6 进程和线程的对比

# 进程和线程的对比的三个方向
1. 关系对比
2. 区别对比
3. 优缺点对比

# 1. 关系对比
 线程是依附在进程里面的,没有进程就没有线程
 一个进程默认提供一个线程,进程可以创建多个线程

# 2. 区别对比
 进程之间不共享全局变量
 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁或者线程同步
 创建进程的资源开销要比创建线程的资源开销大
 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
 线程不能够独立执行,必须依存在进程中
 多进程开发比单进程多线程开发稳定性要强

# 3. 优缺点对比
 进程优缺点:
	优点:可以用多核
  缺点:资源开销大
 线程优缺点:
	优点:资源开销小
  缺点:不能使用多核
  
  

2.7 多任务(扩展)---协程

协程,又称为微线程,纤程。英文名Coroutine

协程是啥?
	协程是python中另外一种实现多任务的方式
	只不过比线程占用更小的执行单位(理解为需要的ziyuan)
	通俗理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候切换到原来的函数都是有开发决定
	
协程的优点:
	最大的优势就是协程极高的执行效率。因为是函数切换而不是线程切换,是有程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
	第二大优势就是不需要多线程的锁机制,因为只有一个线程,不存在同时写变量的冲突,在协程中控制共享资源不用加锁,只需要判断状态就好了,所以执行效率要比多线程高很多。
	
gevent
	gevent是一个第三方库
  Python 中仅提供了对协程的基本支持,但功能不完全。而借助第三方库gevent为Python提供了比较完善的协程支持
 

3. 网络编程

IP地址的作用:网络上设备的唯一标识
端口和端口号的作用:
					端口:数据传输的通道
					端口号:端口的唯一标识
TCP 的特点:面向连接,可靠连接
socket的作用:进程间通信的工具

3.1 IP地址介绍

 ip地址的作用是标识网络中唯一的一台设备的
 IP地址的表现形式分为:IPv4 和 IPv6
	IPv4 是点分十进制数,IPv6是16进制冒号分割的数
 查看网卡信息:ifconfig
 检查网络:ping

3.2 端口和端口号的介绍

 端口的作用就是给运行的应用程序提供传输数据的通道
 端口号的作用是用来区分和管理不同端口的,通过端口号能找到唯一的一个端口
 端口号可以分为两类:知名端口号和动态端口号
		知名端口号的范围是0到1023
		动态端口号的范围是1024到65535
		

3.3 TCP的介绍

# 概念:TCP 协议的英文(Transmission Control Protocol)简称传输控制协议,他是一种面向连接的,可靠的,基于字节流的传输层通信协议


# TCP 的特点
1. 面向连接
		通信双方必须先建立好连接才能进行数据的传输,数据传输完成后,双方必须断开此连接,以释放系统资源
2. 可靠传输
		TCP 采用发送应答机制
  	超时重传
    错误校验
    流量控制和阻塞管理
    

3.4 socket的介绍

# socket 的概念
socket(简称 套接字)是进程之间通信的一个工具,进程之间的网络通信依靠于 socket;它是计算机之间进行通信的一种约定,通过此约定,计算机之间可以互相发送和接收数据。

3.5 TCP网络应用程序开发流程

# TCP 网络应用程序开发分为:
	TCP 客户端程序开发
  TCP 服务端程序开发

  
# 客户端程序开发流程:
	1. 创建客户端套接字对象
  2. 和服务端套接字建立连接
  3. 发送数据
  4. 接收数据
  5. 关闭客户端套接字
  
  
# TCP 服务端程序开发流程:
	1. 创建服务端端口套接字对象
  2. 绑定端口号
  3. 设置监听
  4. 等待接受客户端的连接请求
  5. 接收数据
  6. 发送数据
  7. 关闭套接字
  
  
# 总结
 TCP 网络应用程序开发分为客户端程序开发和服务端程序开发
 主动发起建立连接请求的是客户端程序
 等待接受连接请求的是服务端程序

3.6 TCP客户端程序开发

------------------------'重点记忆'---------------------------------
# 客户端程序开发流程:
	1. 创建客户端套接字对象 	socket()
  2. 和服务端套接字建立连接 	connect()
  3. 发送数据 	send()
  4. 接收数据 	recv()
  5. 关闭客户端套接字 	close()
------------------------------------------------------------------  
  
# socket 类的介绍
导入模块:import socket
创建客户端 socket 对象:socket.socket(AdderFamily, Type)

'模块参数说明:
	AdderssFamily 表示IP地址类型,分为IPv4和IPv6
  Type 表示传输协议类型

'模块方法说明:
	connect((host,port)) 表示和服务端套接字建立连接,host是服务器ip地址,port是应用程序的端口号
  send(data) 表示发送数九,data是二进制数据
  recv(buffersize) 表示接受数据,buffersize是每次接收数据的长度
  
  
  示例:
  '创建-连接-发送-接收-关闭'
  # 导包
import socket
if __name__ == '__main__':
    # socket.AF_INET 表示ipv4,   socket.SOCK_STREAM 表示tcp协议
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 和服务端应用程序建立连接
    # connect方法中只有一个参数,是一个元组,与那组中有两个元素IP+端口
    tcp_client_socket.connect(("192.168.31.143", 8080))
    # 准备发送的数据
		send_data = "hi , 我是客户端! ".encode("gbk")
    # 发送数据
    tcp_client_socket.send(send_data)
    # 接收数据recv方法,这次接收方法中的参数是规定最大字节1024
    recv_data = tcp_client_socket.recv(1024)
    # 测试得知返回的直接是服务端程序发送的二进制数据
    print(recv_data)
    # 读数据进行解码
    recv_content = recv_data.decode("gbk")
    print("接收服务端的数据为:", recv_content)
    # 关闭套接字
    tcp_client_socket.close()
    
    注意:
    	字符串发送时,是二进制格式的数据,按照一定的编码格式进行编码
      str.encode(编码格式)表示把字符串编码成为二进制
      data.decode(编码格式)表示把二进制解码成为字符串
         

3.7 TCP服务端程序开发

--------------------------'重点记忆'-------------------------------
# 开发TCP服务端程序开发步骤:
	1. 创建服务端套接字对象 	socket()
  2. 绑定端口		bind()
  3. 设置监听 	listen()
  4. 等待接收客户端的连接请求 	accept()
  5. 接收数据 	recv()
  6. 发送数据 	send()
  7. 关闭套接字 	close()
------------------------------------------------------------------  
  
# socket 类的介绍
导入 socket 模块:import socket
创建服务端 socket 对象:socket.socket(AddressFamily, Type)

# 参数说明:
	AddressFamily 表示IP地址类型,分为IPv4和IPv6
  Type 表示传输协议类型
  
# 类中方法说明:
	bind((host, port)) 表示绑定端口号,host是ip地址,port是端口号,ip地址一般不指定,表示本机的任何一个IP地址都可以
  listen(backlog) 表示设置监听,backlog参数表示最大等待建立连接的个数
  accpet() 表示等待接收客户端请求
  send(data) 表示发送数据,data是二进制数据
  recv(buffersize) 表示接收数据,buffersize 是每次接收数据的长度
------------------------TCP单任务版服务端---------------------------
'创建-绑定-监听-等待-接收-发送-关闭'
示例代码:
import socket
if __name__ == '__main__':
    #创建tcp套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用,让程序退出端口立即释放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 给程序绑定端口号
    tcp_server_socket.bind(("", 8989))
    # 设置监听: 参数128表示服务端最大等待连接个数
    tcp_server_socket.listen(128)
    # 等待客户端建立连接的请求,只有客户端和服务端建立连接成功代码才会阻塞,代码才能继续往下执行
    # accept:返回一个元组(IP,端口)
    # 1. 专门和客户端通讯的套接字:service_client_socket
    # 2. 客户端的ip地址和端口号:ip_port,拆包
    service_client_socket, ip_port = tcp_server_socket.accept()
    # 代码执行到此说明连接建立成功
    print("客户端的ip地址和端口号:", ip_port)
    # 接收客户端发送的数据,这次接收的最大字节数是1024
    recv_data = service_client_socket.recv(1024)
    # 获取数据的长度
    recv_data_lenght = len(recv_data)
    print("接收数据的长度为:", recv_data_lenght)
    # 对二进制数据进行解码
    recv_content = recv_data.decode("gbk")
    print("接收客户端的数据为:", recv_content)
    # 准备发送的数据
    send_data = "ok, 问题正在处理中。。。".encode("gbk")
    # 发送数据给客户端
    service_client_socket.send(send_data)
    # 关闭服务与客户端的套接字,终止和客户端通信服务
    service_client_socket.close()
    # 关闭服务端的套接字,终止和客户端提供建立连接请求的服务
    tcp_server_socket.close() '一般服务器监听套接字不会关闭,一直开机;这里会等待1-2分钟是因为,在tcp 4次挥手过程中,服务端发送完FIN确认断开标示符后time-wait状态的2MLS时间,就是2次超时时间大约1-2分钟'

说明:
	当客户端和服务端建立连接后,'服务端程序退出后端口号不会立即释放,需要等待大概1-2分钟'
  解决办法有两种:
  	1. 更换服务端端口
    2. 设置端口号复用(推荐),也就是说让服务端程序退出后端口号立即释放
端口复用代码如下:
	tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
  # 参数1:表示当前套接字
  # 参数2:设置端口号复用选项
  # 参数3:设置端口号复用选项对应的值
  
      
------------------多任务版TCP服务端代码示例--------------------------

import socket
import threading


def handle_client_request(service_client_socker, ip_port):
    # 循环接收客户端发送的数据
    while True:
        recv_data = service_client_socker.recv(1024)
        # if recv_data:
        #     print(recv_data.decode('gbk'), ip_port)
        #     # 回复
        #     service_client_socker.send('okkkkk,问题正在处理中。。。'.encode('gbk'))
        #
        # else:
        #     print("客户端已经下线了:", ip_port)
        #     break
        if len(recv_data) == 0:
            print(f"客户端{ip_port}已经下线了")
            break
        print(f"接收到客户端数据{ip_port}:", recv_data.decode('utf-8'))
        service_client_socker.send("您好,你的问题已收到,正在安排解决中...".encode('utf-8'))
    # 终止和客户端进行通信
    service_client_socker.close()


if __name__ == '__main__':
    # 创建socket对象,socket方法默认值是ipv4,和tcp协议
    tcp_server_socket = socket.socket()
    # 设置端口复用:关闭socket时立即释放端口,无需等待1-2min
    # 参数1:设置当前socket
    # 参数2:设置端口复用选项
    # 参数3:设置端口复用选项的值(True表示开启端口复用)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 端口绑定
    # bind(元组)
    # 元组格式(本地ip,端口号)
    tcp_server_socket.bind(("", 9091))
    # 设置监听模式
    # listen:
    # 1)设置当前tcp_server_socket为被动连接的socket,这个socket不能收发消息只负责监听
    # 2)128 表示允许最大等待连接的个数
    tcp_server_socket.listen(128)

    # 循环等待接收客户端的连接请求,等到后创建一个新线程
    while True:
        # accept 返回的是一个元组类型,元组中有两个元素(新的服务器客户端的socket,客户端ip和端口)
        service_client_socket, ip_port = tcp_server_socket.accept()
        print("客户端连接成功:", ip_port)
        # 当客户端和服务端建立连接成功以后,需要创建一个子线程,不同子线程负责接收不同客户端的请求
        sub_thread = threading.Thread(target=handle_client_request, args=(service_client_socket, ip_port))
        # 设置子线程的守护主线程
        sub_thread.setDaemon(True)
        # 启动子线程
        sub_thread.start()


3.8 TCP网络应用程序的注意点

1.当TCP客户端程序想要和TCP服务端程序进行通信的时候必须要先建立连接
2.TCP 客户端程序一般不需要绑定端口号,因为客户端是主动发起建立连接的
3.TCP 服务端程序必须绑定端口号,否则客户端找不到这个TCP服务端程序
4.listen后的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发消息
5.当TCP客户端程序和TCP服务端程序连接成功后,TCP 服务端程序会产生一个新的套接字,收发客户端消息使用该套接字。
6.关闭accept返回的套接字意味着和这个客户端已经通信完毕
7.关闭listen后的套接字意味着服务端的套接字关闭了,会导致新的客户端不能连接服务端,但是之前已经接成功的客户端还能正常通信
8.当客户端的套接字调用close后,服务器端的recv会解阻塞,返回的数据长度为0,服务端可以通过返回数据的长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv也会解阻塞,返回的数据长度也为0

3.9 socket之send和recv原理剖析

# send 发送原理
socket.send()->发送缓冲区->网卡->网卡->接收缓冲区->socket.recv(1024)
# recv 接收原理
socket.recv(1024)<-接收缓冲区<-网卡<-网卡<-发送缓冲区<-socket.send()

# 总结:
	不管是recv还是send都不是直接接收到对方的数据和发送数据到对方,发送数据会写入到发送缓冲区,接收数据是从接收缓冲区来读取,发送数据和接收数据最终是由操作系统控制网卡来完成

4. HTTP协议和静态Web服务器

4.1 HTTP协议

传输 HTTP 协议格式的数据是基于 TCP 的超文本传输协议,发送数据之前需要先建立连接

# HTTP协议的作用:
它规定了'浏览器和 Web 服务器通信数据的格式',web浏览器和服务器之间通信需要http协议


# 小结
 HTTP 协议是一个超文本传输协议
 HTTP 协议是一个基于TCP传输协议传输数据的
 HTTP 协议规定了浏览器和服务器通信数据的格式

4.2 HTTP 协议的通讯过程

谷歌浏览器的开发者工具是查看http协议的通信过程利器,通过Network标签选项可以查看每一次的请求和响应的通信过程,调出开发者工具的通用方法是在网页右击选择检查。
开发者工具Headers选项总共有三部分组成:
		1. General:主要信息
		2. Response Headers:响应头
		3. Request Headers:请求头
Response选项是查看响应体信息的

4.3 HTTP 请求报文

HTTP 常见的请求报文有两种:
1.GET方式的请求报文,获取web服务器数据
2.POST 方式的请求报文,向web服务器提交数据


总结:
一个HTTP请求报文可以由请求行,请求头,空行和请求体 4个部分组成
请求行的三个组成部分:
		1.请求方式
		2.请求资源路径
		3.HTTP协议版本
    
GET 方式的请求报文没有请求体,只有请求行,请求头,空行组成
POST 方式的请求报文可以有请求行,请求头,空行,请求体四部分组成,注意:POST方式可以允许没有请求体,但是这种格式很少见
	GET:请求行+请求头+空行
  POST:请求行+请求头+空行+请求体
  
Request Headers  # 请求报文头
---请求行---
GET(或者POST)/ HTTP/1.1		
# GET或者POST请求方式,/请求资源路径,HTTP协议版本
---请求头---
Accept: text/html,application/xhtml+xml,application/xml; # 可接收的数据类型
Accept-Encoding: gzip, deflate			# 可接收的压缩格式
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8		# 可接收的语言
Cache-Control: max-age=0 #缓存头:缓存有效期,过期后浏览器会再次发送请求
# Cache-Control:no-cache 可以在本地进行缓存,但每次发请求时,都要向服务器进行验证,如服务器允许,才能本地缓存
Connection: keep-alive  # 和服务端保持长连接
Cookie: username=admin; sessionid=p8jlt056qse07jr7czsumi6x	# 登录用户的身份标识
Host: mp-meiduo-python.itheima.net  #服务器的主机地址和端口号,默认80
Upgrade-Insecure-Requests: 1 # 让浏览器升级不安全请求,使用https请求
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.193 Safari/537.36 #用户代理,客户端的名称

4.4 HTTP 响应报文

Response Headers  # 服务器响应报文头
---响应行/状态行---
HTTP/1.1 200 OK			# HTTP协议版本,状态码,状态描述
---响应头---
Connection: keep-alive	# 和客户端保持长连接
Content-Length: 46  # 告诉浏览器报文中实体数据的大小
Content-Type: application/json		# 内容类型
Date: Tue, 17 Nov 2020 02:26:21 GMT	# 服务端响应时间
Server: nginx/1.16.1		# 服务器名称和版本号
Vary: Cookie, Origin		# 告诉下游代理是使用缓存响应还是从原始服务器请求
X-Frame-Options: SAMEORIGIN  # 页面只能被本站页面嵌入到iframe或者frame中(防止

4.5 示例代码

示例一:web服务器返回固定页面

import socket

if __name__ == '__main__':
    # 创建套接字
    server_socket = socket.socket()
    # 端口复用
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定端口
    server_socket.bind(("", 9000))
    # 设置监听
    server_socket.listen(128)

    while True:
        # 创建新套接字,循环等待客户端连接
        new_socket, ip_port = server_socket.accept()
        print("客户端ip和端口:", ip_port)

        recv_data = new_socket.recv(2048)
        print(recv_data.decode('utf-8'))

        with open("static/index.html", "rb") as f:
            file_data = f.read()

        response_line = 'HTTP/1.0 \r\n'
        response_head = 'Server: nginx/1.0 \r\n'
        # response_data = file_data

        response_data = (response_line + response_head + "\r\n").encode('utf-8') + file_data

        new_socket.send(response_data)

        new_socket.close()

示例2: 静态web服务器面向对象版-多线程-判断并返回请求的页面

import socket
import threading

"""
创建套接字
端口复用
绑定
监听
接收发送关闭


"""


class WebServer(object):
    """
    面向对象:
      静态web服务器
    抽象类:
      类名: HttpWebServer
      属性: tcp_server_socket
      方法: __init__, handler_client_request, start
    """

    def __init__(self):
        # 创建套接字对象
        self.new_socket = socket.socket()
        self.new_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        self.new_socket.bind(("", 8080))
        self.new_socket.listen(128)

    @staticmethod
    # 接收用户数数据和发送用户数据
    def handle_client_request(client_socket, ip_port):
        recv_data = client_socket.recv(1024)
        if len(recv_data) == 0:
            print(f"客户端{ip_port}已经下线了")
            client_socket.close()
            return
        recv_str = recv_data.decode('utf-8')
        recv_request = recv_str.split(" ", maxsplit=2)
        request_path = recv_request[1]
        print("客户端请求资源的路径:", request_path)
        # 对用户的请求头剪切等到请求的目标文件名
        if request_path == "/":
            request_path = "/index.html"

        # 异常判断,打开资源路径的文件
        try:
            with open("static" + request_path, "rb") as file:
                file_data = file.read()
        except Exception as f:  # 如果异常则打开404页面
            request_head = "Http/1.1 404\r\n"
            request_leng = "Server: NBA/1.0\r\n"
            with open("static/error.html", "rb") as f:
                file_data = f.read()
            request_file = file_data

            request_data = (request_head + request_leng + "\r\n").encode("utf-8") + request_file

            client_socket.send(request_data)

        else:       # 如果异常没有执行就打开用户请求的页面内容,发送给用户
            request_head = "Http/1.1 200 ok\r\n"
            request_leng = "Server: NBA/1.0\r\n"
            request_file = file_data

            request_data = (request_head + request_leng + "\r\n").encode("utf-8") + request_file

            client_socket.send(request_data)

        finally:        # 不管异常是否捕获都关闭此套接字
            client_socket.close()

    def start(self):
        # 循环创建新客户端的套接字,并且创建多线程,主线程负责循环监听创建客户端套接字
        while True:
            client_socket, ip_port = self.new_socket.accept()
            print("客户端ip和端口是:", ip_port)
            # 创建子线程处理用户请求
            sub_thread = threading.Thread(target=self.handle_client_request, args=(client_socket, ip_port))
            # 守护主线程,主线程结束子线程就结束
            sub_thread.setDaemon(True)
            sub_thread.start()


if __name__ == "__main__":
    wb = WebServer()
    wb.start()


示例3: 静态web服务器-多线程-返回请求页面-获取命令行参数再启动

# -*- confing: utf-8 -*-
import socket
import threading
import sys
"""
服务端:
创建套接字,设置端口复用,绑定端口,设置监听
接收数据和发送给客户端数据

"""
class WebServer(object):
    """
        面向对象:
          静态web服务器
        抽象类:
          类名: HttpWebServer
          属性: tcp_server_socket
          方法: __init__, handler_client_request, start
    """

    def __init__(self, port):
        self.server_socket = socket.socket()
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        self.server_socket.bind(("", port))
        self.server_socket.listen(128)

    @staticmethod
    def handler_client_request(new_socket, ip_port):
        # 接收和发送用户数据
        recv_data = new_socket.recv(1024)
        if len(recv_data) == 0:
            print(ip_port, "客户端已断开连接")
            new_socket.close()
            return

        recv_str = recv_data.decode("utf-8")
        print(recv_str)
        recv_list = recv_str.split(" ", maxsplit=2)
        request_path = recv_list[1]
        print("请求资源路径是:", request_path)
        if request_path == "/":
            request_path = "/index.html"

        try:
            with open("static" + request_path, "rb") as file:
                file_data = file.read()

        except Exception as e:
            request_head = "HTTP/1.1 404 \r\n"
            request_server = "Server NBA/1.0\r\n"
            with open("static/error.html", "rb") as file:
                file_data = file.read()
            request_data = (request_head + request_server + "\r\n").encode("utf-8") + file_data
            new_socket.send(request_data)

        else:
            request_head = "HTTP/1.1 200 ok\r\n"
            request_server = "Server CBA/1.0\r\n"
            request_file = file_data

            request_data = (request_head + request_server + "\r\n").encode("utf-8") + request_file

            new_socket.send(request_data)
        finally:
            new_socket.close()

    def start(self):
        while True:
            # 循环创建客户端的套接字,并且为新的套接字创建多线程执行,设置守护线程
            new_socket, ip_port = self.server_socket.accept()
            print("客户端的IP的端口是:", ip_port)
            sub_thread = threading.Thread(target=self.handler_client_request, args=(new_socket, ip_port))
            sub_thread.setDaemon(True)
            sub_thread.start()


if __name__ == "__main__":
    # 获取命令行参数
    print(sys.argv)
    # 判断命令行参数长度不是2个就说明没有输入端口实参
    if len(sys.argv) != 2:
        print("命令输入格式错误:python3 XXX.py 8080")
        exit()
    # 判断实参是否为整数
    if not sys.argv[1].isdigit():
        print("命令输入格式错误:python3 XXX.py 8080")
        exit()
    # 将实参字符串转换为整数
    port = int(sys.argv[1])
    # 给对象传参
    wp = WebServer(port)
    # 启动对象
    wp.start()

5. html + css基础

5.1 html

		HTML5草案的前身名为 Web Applications 1.0,于2004年被WHATWG(网页超文本应用技 术工作小组)提出,于2007年被W3C接纳,并成立了新的 HTML 工作团队。 HTML 5 的第一份正式草案已于2008年1月22日公布。HTML5 仍处于完善之中。然而,大部 分现代浏览器已经具备了某些 HTML5 支持。 2012年12月17日,万维网联盟(W3C)正式宣布凝结了大量网络工作者心血的HTML5规范 已经正式定稿。根据W3C的发言稿称:“开放的HTML5是Web网络平台的奠基石。” 2013年5月6日, HTML 5.1正式草案公布。该规范定义了第五次重大版本,第一次要修订万 维网的核心语言:超文本标记语言(HTML)。在这个版本中,新功能不断推出,以帮助Web 应用程序的作者,努力提高新元素交互操作性。 本次草案的发布,从2012年12月27日至今,进行了多达近百项的修改,包括HTML和XHTML 的标签,相关的API、Canvas等,同时HTML5的图像img标签及svg也进行了改进,性能得到 进一步提升。

html的定义:
	HTML的全称为:HyperText Mark-up Language,指的是超文本标记语言
所谓超文本,有两层含义:
	1.因为网页中还可以图片,视频,音频等内容(超越文本限制)
	2.它还可以在网页中国跳转到另一个网页,与世界各地主机的网页链接(超链接文本)

小结:
	html是开发网页的语言
	html中的标签大多数都是成对出现的,格式
html的基本结构
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>网页标题</title>
  </head>
  <boby>
  		网页显示内容
  </boby>
</html>

1. 第一行<!DOCTYPE html> 是文档声明,用来指定页面所使用的html的版本,这里声明的是一个html5的文档
2.<html>...</html> 这个标签是开发人员在告诉浏览器,整个页面是从<html>这里开始的,到</html>结束,也就是html文档的开始和结束标签
3.<head> ... </head>标签用于定义文档的头部,是负责对网页进行设置标题,编码格式以及引入css和js文件的
4.<body>...</body> 标签是编写网页上显示的内容
 
5.1.1 初始常用的html标签
--------------------常用的 html标签--------------------
<!-- 1、成对出现的标签:-->

<h1>h1标题</h1>
<div>这是一个div标签</div>
<p>这个一个段落标签</p>

<!-- 2、单个出现的标签: -->
<br>
<img src="images/pic.jpg" alt="图片">
<hr>

<!-- 3、带属性的标签,如src、alt 和 href等都是属性 -->
<img src="images/pic.jpg" alt="图片">
<a href="http://www.baidu.com">百度网</a>

<!-- 4、标签的嵌套 -->
<div>
    <img src="images/pic.jpg" alt="图片">
    <a href="http://www.baidu.com">百度网</a>
</div>

提示:
1. 标签不区分大小写,但是推荐使用小写
2. 根据标签的书写形式,标签分为双标签(闭合标签)和单标签(空标签)
	2.1 双标签是指由开始标签和结束标签组合成的一对标签,这种标签允许嵌套和承载内容,比如:div标签
	2.2 单标签是一个标签组成,没有标签内容,比如:img标签

小结:
	学习 html 语言就是学习标签的用法,常用的标签有20多个
	编写html标签建议使用小写
	根据书写形式,html标签分为双标签和单标签
	单标签没有标签内容,双标签可以嵌套其他标签和承载文本内容
5.1.2 资源路径

1). 相对路径和绝对路径是html标签使用资源的两种方式,一般使用相对路径
2). 相对路径是从当前操作的html文档所在目录算起的路径
3). 绝对路径是从根路径算起的路径

5.1.3 列表标签

列表标签的种类:
无序列表标签(ul 标签) - 一般是列表对顺序无要求时使用
有序列表标签(ol 标签) - 一般是列表对顺序有要求时使用

无序列表
<!-- ul标签定义无序列表 -->
<ul>
  <!-- li标签定义列表项目 -->
  <li>列表标题一</li>
  <li>列表标题二</li>
</ul>

有序列表
<!-- ol标签定义有序列表 -->
<ol>
  <!-- li标签定义列表项目-->
  <li><a href="#">列表标题一</a></li>
  <li><a href="#">列表标题二</a></li>
</ol>
5.1.4 表格标签

表格的结构:表格是由行和列组成,好比一个excel文件

表格标签:
<table>标签:表示一个表格
	<tr>标签:表示表格中的一行
		<td> 标签:表示表格中的列
		<th> 标签:表示表格中的表头
  
border-collapse 设置表格的边线合并,如:border-collapse:collapse
5.1.5 表单标签

表格的介绍:表单用于搜集不同类型的用户输入(用户输入的数据),然后可以把用户数据提交到web服务器

表单相关标签的使用
1. <form>...</form> 标签表示表单标签,定义整体的表单区域
2.<label>...</label>表示单元素的文字标注标签,定义文字标注
3.<input>标签表示表单元素的用户输入标签,定义不同类型的用户输入数据方式
	type属性:
		type="text" 定义单行文本输入框
		type="password" 定义密码输入框
		type="radio" 定义单选框
		type="checkbox" 定义复选框
		type="file" 定义上传文件
		type="submit" 定义提交按钮
		type="reset" 定义重置按钮
		type="button"定义一个普通按钮
4.<textarea>...</textarea>表示表单元素的多行文本输入框标签 定义多行文本输入框
5.<select>...</select> 标签表示表单元素的下拉列表标签定义下拉列表
	<option>标签与<select>标签配合,定义下拉列表中的选项
5.1.6 表单提交
1. 表单属性设置
<form> 标签 表示表单标签,定义整体的表单区域
		标签中的属性有 action 属性设置表单数据提交地址
		method属性,设置表单提交方式,一般有"GET"方式和"POST"方式,不区分大小写
		
2. 表单元素属性设置
	name 属性,设置表单元素的名称,该名称是提交数据时的参数名
	value 属性,设置表单元素的值,该值是提交数据时参数名对应的值

示例代码:

<!-- form 表单标签 -->
<!-- 表单元素标签: label input textarea select -->
<!DOCTYPE html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
        <form style="height:500px;width:500px;margin: auto;">
            <p>
                <label>姓名:</label>
                <input type="text">
            </p>
            
            <p>
                <label>密码:</label>
                <input type="text">
            </p>
            
            <p>
                <label>性别:</label>
                <input type="radio" name="redio" value="1">男
                <input type="radio" name="redio" value="0">女
            </p>
    
            <p>
                <label>爱好:</label>
                <input type="checkbox"> 学习
                <input type="checkbox"> 唱歌
                <input type="checkbox"> 跳舞
            </p>
    
            <p>
                <label>图片:</label>
                <input type="file">
            </p>
            
            <p>
                <label>简介:</label>
                <textarea></textarea>
            </p>
            <p>
                <label>籍贯:</label>
                <select>
                    <option>北京</option>
                    <option>上海</option>
                    <option>深圳</option>
                </select>
            </p>
    
            <p>
                <input type="submit" value="提交">
                <input type="submit" value="复位">
                <input type="submit" value="普通按钮">
            </p>
        </form> 
      
</body>
</html>

5.2 CSS

css的作用:名称是层叠样式表,美化页面的一种语言

**css 的基本语法**
选择器{
		样式规则
}
样式规则:
		属性名:属性值;
		...
		
选择器:是用来选择标签的,选出来以后给标签加样式规则
说明:css是由两个主要部分构成,选择器和一条或多条样式规则,样式规则要写在大括号中
5.2.1 css的引入方式
***包括:行内式 内嵌式 外链式***
**行内式(几乎不用)**:直接在标签的 style 属性中添加 css 样式
		优点:方便,直观     			 缺点是缺乏可重用性

**内嵌式(学习阶段)**:在<head> 标签内加入<style>标签,在<style>标签中编写css代码
		优点:在同一个页面内部便于复用和维护  		缺点:在多个页面之间的可重用性不够高

**外链式(开发阶段)**:将 css 代码写在一个单独的.css文件中,再在<head>标签中使用<link>标签直接引入该文件到页面中
		优点:使得css样式与html 页面分离,便于整个页面系统的规划和维护,可重用性高
		缺点:css代码离了单独的css文件,容易出现css代码过于集中,若维护不当则极容易混乱
5.2.2 css选择器

选择器的定义:css 选择器是用来选择标签的,选出来以后给标签加样式
css 选择器的种类:
1.标签选择器 2.类选择器 3.层级选择器 4.id选择器 5.组选择器 6.伪类选择器

1. 标签选择器:根据标签来选择,以标签开头,此类选择器影响范围大,一般用来做一些通用设置
示例:
<style type="text/css">
p{
  color: red
}
</style>

<div>hello</div>
<p>hello</p>

=========================================================================

2.类选择器:根据类名来选择标签,以.开头,一个类选择器可应用于多个标签上,一个标签上也可以使用多个类选择器,多个类选择器需要使用空格分割。应用灵活,可复用,是css中应用最多的一种选择器
<style type="text/css">
.blue{color:blue}
.big{font-size:20px}
.box{width:100px;height:100px;background:gold}
</style>

<div class="blue">这是一个div</div>
<h3 class="bule big box">这是一个标题</h3>
<p class="bule box">这是一个段落</p>

=========================================================================

3.层级选择器(后代选择器):根据层级关系选择后代标签,以选择器1 选择器2开头,主要应用在标签嵌套的结构中,减少命名。
<style type="text/css">
div p{
  color:red
}
.con{width:300px;height:80px;background:green}
.con span{color:red}
.con .pink{color:pink}
.con.gold{color:gold}
</style>

<div>
	<p>hello</p>
</div>

<div class="con">
	<span>hhhhh</span>
	<a href="#" class="pink">百度</a>
	<a href="#" class="gold">谷歌</a>
</div>
<span>你好</span>
<a href="#" class="pink">新浪</a>

=========================================================================

4. id选择器:根据id选择标签,以#开头,元素id名称不能重复,所以id选择器只能对应于页面上一个元素,不能复用,id名一般给程序使用,所以不推荐使用id作为选择器
<style type="text/css">
			#box{color:red}
</style>

<p id="box">这是一个段落标签</p>    #对应以上一条样式,其他元素不允许应用此样式
<p>这是第二个段落标签</p> 		#无法应用以上样式,每个标签只能有唯一的ID名
<p>这是第三个段落标签</p> 		#无法应用以上样式,每个标签只能有唯一id

=========================================================================

5. 组选择器:根据组合的选择器选择不同的标签,以,分割开,如果有公共的样式设置,可以使用组选择器
<style type="text/css">
    .box1,.box2,.box3{width:100px;height:100px}
    .box1{background:red}
    .box2{background:pink}
    .box2{background:gold}
</style>

<div class="box1">这是第一个div</div>
<div class="box2">这是第二个div</div>
<div class="box3">这是第三个div</div>

=========================================================================

6. 伪类选择器:用于向选择器添加特殊的效果,以;分割,当用户和网址交互的时候改变显示效果可以使用伪类选择器

<!-- /* 伪类选择器 : 给标签添加特效 */
/* hover: 鼠标进入,修改背景 */ -->

<style type="text/css">
    .box1{width:100px;height:100px;background:gold;}
    .box1:hover{width:300px;}
</style>

<div class="box1">这是第一个div</div>
5.2.3 css属性
1. 布局常用样式属性
width 设置元素(标签)的宽度,如:width:100px;
height 设置元素(标签)的高度,如:height:200px;
background 设置元素背景色或者背景图片,如:background:gold; 设置元素的背景色, background: url(images/logo.png); 设置元素的背景图片。
border 设置元素四周的边框,如:border:1px solid black; 设置元素四周边框是1像素宽的黑色实线
以上也可以拆分成四个边的写法,分别设置四个边的:
border-top 设置顶边边框,如:border-top:10px solid red;
border-left 设置左边边框,如:border-left:10px solid blue;
border-right 设置右边边框,如:border-right:10px solid green;
border-bottom 设置底边边框,如:border-bottom:10px solid pink;

2. 文本常用样式属性
color 设置文字的颜色,如: color:red;
font-size 设置文字的大小,如:font-size:12px;
font-family 设置文字的字体,如:font-family:'微软雅黑';为了避免中文字不兼容,一般写成:font-family:'Microsoft Yahei';
font-weight 设置文字是否加粗,如:font-weight:bold; 设置加粗 font-weight:normal 设置不加粗
line-height 设置文字的行高,如:line-height:24px; 表示文字高度加上文字上下的间距是24px,也就是每一行占有的高度是24px
text-decoration 设置文字的下划线,如:text-decoration:none; 将文字下划线去掉
text-align 设置文字水平对齐方式,如text-align:center 设置文字水平居中
text-indent 设置文字首行缩进,如:text-indent:24px; 设置文字首行缩进24px

5.3 html+css+Javascript部分总结

# 1.HTML是什么
		html是超文本标记语言,负责网页编辑的标记语言
  -------------------------'html 的基本结构'-------------------------------
        <!DOCTYPE html>  					# 文档声明
        <html lang="en">					# 网页语言, en是中文 英文会自动翻译成中文
        <head> 										# 文档头部,负责嵌套定义标题,编码格式以及引入css js
           <meta charset="UTF-8">
           <meta name="viewport" content="width=device-width, initial-scale=1.0">
           <title>Document</title>
        </head>
        <body>										# 网页主体内容,嵌套编辑
        ....
        </body>
        </html>

# 2.HTML的标签
		标签形式有三种:
  			双标签:可以嵌套,可以承载内容,也叫做闭合标签
    		单标签: 不能嵌套,没有标签内容,也叫做空标签
		
    标签的种类:
    h1-h6 双标签,标题标签
    p 双标签,段落标签
    div 双标签,块标签,在使用容器时嵌套使用其他标签
    a 双标签,创建一个超链接,href 地址
   
    br 单标签,产生一个空行
    hr 单标签,产生一个分割线
    img 单标签,图片标签, src图片路径 alt图片加载失败的文字
  
# 3.能够知道相对路径和绝对路径的区别
		绝对路径:从盘符或者/起始的路径
  	相对路径:是相对于本地执行文件的路径
		并且推荐使用相对路径。因为改变了执行文件的位置后程序不会报错
# 4.能够知道列表标签的种类
		列表标分为有序列表(ol)和无序列表(ul),当对列表中的内容顺序有要求是选择使用
		
    表格的元素标签:
    table 标签,是一个双标签,在这个标签中嵌套定义表单的格式等信息
    tr 表格中的行
    td 表格中的列
    th 表格中的表头,也就是表格中的第一行内容
# 5.能够知道表单中常用的表单元素标签
		表单元素的标签:
  	form 标签,表示表单标签,定义整体的表单区域,表单的其他属性嵌套在这个标签中定义
    label 标签,表示表单元素的文字标注,定义文字标注
    input 标签,表示表单的接收用户输入,定义不同类型的输入数据方式
    		type 定义用户输入数据的属性,嵌套在input中使用
   	select 标签,表示表单元素的下拉列表标签,定义下拉列表
    textarea 标签,表示表单元素的多行文本输入框标签,定义多行文本
# 6.能够知道表单的提交方式
		通过GET请求和POST请求
  	GET 是通过域名后面的查询参数传给指定的地址
    POST 是通过请求体传送到服务器
		在form中嵌套定义,method="GET"
# 7.能够知道表单中action属性的作用
	设置表单数据的提交地址,在form中嵌套定义 action=网址 

# 8.能够知道css的作用
	美化页面的作用,做页面的排版
# 9.能够知道css 的引入三种方式
	内嵌式 在<head>标签内加入<style>标签,在<style>内编写css代码
  外链式 将css样式定义在一个文件中,link 链接引用即可
	行内式 在需要定义的行内直接定义 <div style="">
  
# 10.能够知道JavaScript的作用
	负责网页行为,提供于用户交互的作用,比如提交表单数据
  
# 11.能够知道JavaScript的使用方式
	JavaScript的使用方式:行内式,内嵌式,外链式
	行内式:<input type="button" value="按钮" onclick="js代码"
  内嵌式:<script> js代码 </script>
  外链式:新建js代码文件夹和js文件,调用<script src="文件路径" </script>
  
# 12.能够说出常用的数据类型
	变量赋值格式:var 变量名 = 值
  值/数据类型:
  		number 数字类型
    	string	字符串类型
      boolean	布尔类型
      undefined	变量声明未初始化的值
      null	空对象
      object 符合类型,也就是既有数字类型也有字符类型或者其他

# 13.能够写出函数的定义和调用方式
	<script type="text.javascript">
      // 定义函数:
      funtion 函数名(){
        函数体
      }
</script>

函数调用
函数名();

6. JavaScript

​ JavaScript 的定义:JavaScript是运行在浏览器的脚本语言,是由浏览器解释执行的,简称js;它能够让网页和用户有交互功能,增加良好的用户体验效果

前端开发三大块:

**1.HTML:负责网页结构 **

2.CSS:负责网页样式

3.JavaScript:负责网页行为

6.1 JavaScript 的使用方式
JavaScript的使用方式
1. 行内式(主要用于事件)
<!-- 1.行内式: 把js代码写在标签内属性中. 一般用于事件处理 -->
<!-- alert('OK!'); js代码 -->
<!-- onclick= 这个是按钮点击事件 -->
<!--<input type="button" value="普遍按钮" onclick="js代码;">-->

<input type="button" name="" onlick="alert('ok!')">
  
  
 2. 内嵌式
<script type="text/javascript">
   alert('ok');
</script>

3.外链式
创建一个js文件夹,及js结尾的文件,src引用
<script type="text/javascript" src="js/index.js"></script>


  
6.2 变量和数据类型
1. 定义变量
	Javascript是一种若类型语言,也就是说不需要指定变量的类型,javascript的变量类型由它的值来决定,定义变量需要用关键字'var',一条Javascript语句应该以";"结尾
  定义变量的语法格式:var 变量名=值
var iNum = 123;
var sTr = 'abc';
同时定义多个变量可以用","隔开,共用一个'var'关键字
var iNum = 2,sTr='abc',sCount='60';

2.Javascript的注释分为单行注释和多行注释
单行注释://注释内容
多行注释:/* 注释内容 */

3.数据类型
js有六种数据类型,包括5种基本数据类型和一种复杂数据类型(object)
number	数字
string	字符串
boolean 布尔型
undefined 变量声明未初始化
null	空对象,如果定义的变量将来准备保存对象,可以将变量初始化为null
object 复合类型,后面学习的javascript对象属于复合类型

4.变量命名规范
1)区分大小写
2)第一个字符必须是字母,下划线,或者美元符号$
3)其他字符可以是字母,下划线,美元符或数字

5. 匈牙利命名风格
对象o Object 比如:oDiv
数组a Array 比如:aItems
字符串s String 比如:sUserName
整数i Integer 比如:iItemCount
布尔值b Boolean 比如:bIsComplete
浮点数f Float 比如:fPrice
函数fn Function 比如:fnHandler

6.3 函数定义和调用
1. 函数定义
函数就是可以重复使用的代码块,使用关键字 function 定义函数
<script type="text/javascript">
  function fnAlert(){
  	alert('hello')
}
</script>

2. 函数调用
函数名();

3. 定义有参有返回值函数
<script>
        function iFoo(){
            alert("helllllll")
        }
        iFoo()

        function iNoe(num1,num2){
            var num3 = num1 + num2;
            return num3;			 # return后的代码不会执行,所以不会打印'here'
            alert('here');
        }
        var rf = iNoe(1,2)
        alert(rf);
    </script>

6.4 变量作用域

变量的作用域就是变量的使用范围,变量分为: 局部变量和全局变量

局部变量:局部变量就是在函数内使用,只能在函数内部使用

全局变量:定义在函数外,可以在不同函数内使用

6.5 条件语句
条件语句的语法:
1. if 语句 -- 只能当指定条件为 True 时,使用该语句来执行代码
2. if(条件){代码}else{代码} 语句 -- 当条件为True时执行代码,当条件为flase时执行其他代码
3. if...else if...else 语句 -- 使用该语句;来判断多条件,执行条件成立的语句(中间的else if 相当于elif用法)

比较运算符:大部分与python相同
不同的有以下两个:
== 等于 ,两个等于号的等于相当于元素相等,只要元素值相等就为True,例如 8 == "8" 为True
=== 全等(值和类型),三个等于号是等号两边的值相等的同时,值的类型也相等

逻辑运算符:
或 && and		
与 || or			
非 !not


6.6 获取标签元素

​ 可以使用内置对象 document 上的getElementByld方法来获取页面上设置了id属性的标签元素,获取到的是一个html对象,然后将它赋值给一个变量,比如:

<script>
        // 2. 把获取标签元素的代码的执行时间 延后,放在页面加载完成后在执行,这样才能正常获取到标签元素
        
        function foo(){
            var oDiv = document.getElementById("div1");
            alert(oDiv);  
        }
        
        
        // onload: onload是页面所有元素加载完成的事件,给onload设置函数时,当事件触发就会执行设置的函数。
        window.onload = foo;
     
       
        // 2. 简写:匿名函数(没有名字的函数,无法调用,只会在页面加载完成时执行一次)
        window.onload = function (){
            var oDiv = document.getElementById("div1");
            alert(oDiv);  
         }

    </script>

</head>
<body>
    
    <div id="div1">这是一个div标签</div>

    <!-- 1.把获取标签元素的代码放在 标签声明的后面,这样才能正常获取到 -->
    <script>
        var oDiv = document.getElementById("div1");

        alert(oDiv);
    </script>

说明:如果<script>中定义的获取变迁元素的属性写在要获取的标签id前面会导致获取不生效,因为标签元素id还没有被加载,如果要忽略这种加载顺序的影响,可以使用window.onload 方法,表示在执行函数之前加载页面的所有元素
6.7 操作标签元素属性
1. 属性的操作
首先获取页面的标签元素,然后对页面标签元素的属性进行操作,属性的操作包括:
		属性的读取,属性的设置
		
		标签属性的获取和设置:
		1. var 标签对象=document.getElementByld(`id名称`); -> 获取标签对象
		2. var 变量名=标签对象.属性名 -> 读取属性
		3. 标签对象.属性名 = 新属性值 -> 设置属性
		
   <script>
        window.onload = function(){
            var oDiv = document.getElementById("div1");
            //1. 获取标签中承载的内容 
            alert(oDiv.innerHTML);

            //2. 设置标签中承载的内容
            oDiv.innerHTML = "<p> 这是一个div中嵌套的段落 </p>"
        }
    
    </script>
<body>    
    <div id="div1">这是一个div标签</div>
</body>
6.8 数组及操作方法
1. 数组的介绍
数组就是一组数据的集合,javascript 中,数组里面的数据可以是不同类型的数据,相当于python中的列表

2.数组的定义
var aList = [1,3,2,'abc'];
var aList = new Array(1,2,3);   实例化对象的方式创建
多维数组:多维数组指的是数组的成员也是数组
var aList = [[1,2,3],['a','b','c']]; 

3.数组的操作
1).获取数组的长度
var aList=[1,2,3,4];
alert(aList.lenght);
2).根据下标取值
var aList=[1,2,3,4];
alert(aList[0]);
3)从数组最后添加和删除数据
aList.push(5);			// 在数组最后插入数据5
aList.pop();				// 删除数组中的最后一个数字

4.根据下标添加和删除元素
# 两种用途:可删除元素 和 添加元素
语法:arr.aplice(start,num,element1,...,elementN)
参数解析:
		start:必须项,开始删除的索引,起始位置
    num:可选,删除数组元素的个数
    elementN:可选,在start索引位置要插入的新元素
此方法会删除从start索引开始的num个元素,并将elementN参数插入到start索引位置。
>>> var iNum = [1,2,3,4,5,6]
>>> alert(iNum.splice(0,1))   # 从下标为0开始,删除1个元素
>>>alert(iNum.splice(1,1,0))#从下标为1的元素开始删除1个元素并添加元素0
>>> alert(iNum)
>>> 1,2,0,4,5,6		#替换效果
6.9 循环语句

循环语句就是让一部分代码重复执行,javascript中常用的循环语句有:
for while do-while

1. for循环
var array = [1,2,3,4];
for(var index=0; index < array.length; index++){
  var result = array[index];
  alert(result);
}

2. while 循环		# 先做条件判段,在执行代码块中内容
var array = [1,2,3,4];
var index = 0;
while (index < array.length) {
  var result = array[index];
  alert(result);
  index++;
}

2. do-while 循环		# 先执行代码块中的内容,在做条件判断
var array = [1,2,3,4];
var index = 0;
do {
  var result = array[index];		# 无论是否满足条件都会执行一次
  alert(result);
  index ++;
}while (index < array.length);

6.10 字符串拼接

字符串的拼接直接使用+运算符,但需要注意的是,字符串拼接的元素不管是不是字符串类型,都会自动转换成字符串进行拼接;数字和字符进行拼接时会自动进行隐式类型转换,把数字转换成字符进行拼接

6.11 定时器

定时器的介绍:定时器的作用就是在一段时间后执行某程序代码

定时器的使用

js 定时器有两种创建方式:
1. setTimeout(func[,delay,param1,param2,...]) :以指定的时间间隔(以毫秒为单位) 调用一次 函数的定时器
2. setinterval(func[,delay,param1,param2,...]) :以指定的时间间隔(以毫秒为单位) 重复 调用一个函数的定时器


// setTimeout 函数的参数说明:
(func[,delay,param1,param2,...])
  第一个参数 func ,表示定时器要执行的函数名
  第二个参数 delay ,表示时间间隔,默认是0,单位是毫秒 3000 就是 3 秒
  第三个参数 param1 ,表示定时器执行函数的第一个参数,依次类推传多个执行函数对应的参数
          <script>
            function hello(){
              alert('hello');
          }
          setTimeout(hello, 3000);		# 隔3s后调用一次函数hello
          </script>



// setInterval 函数的参数说明:
(func[,delay,param1,param2,...])
	第一个参数 func,表示定时器要执行的函数名
  第二个参数 delay,表示时间间隔,默认是0,单位是毫秒
  第三个参数 param1,表示定时器执行函数的第一个参数,以此类推传入多个执行函数对应的参数
  <script>
  	function hello(){
    alert('hello');
  }  
		setInterval(hello, 3000);			# 每隔3秒重复执行一次函数hello
	</script>


# 清除定时器:让重复执行或者有定时器的函数停止
clearTime(timeoutID)	清除只执行一次的定时器(指的是 setTimeout函数)
clearInterval(timeoutID) 清除反复执行的定时器(指的是setInterval函数)
	参数说明:timeoutID 为调用该定时器时的返回值

<script>
  function hello(){
    alert('hello');
    clearTime(t1)		#清除定时器 
  }
 t1 = setTimeout(hello, 3000);		
 </script>

<script>
   function hello(){
   	alert('hello');
 }
var t2 = setInterval(hello, 3000)   

	 function stop(){
     clearInterval(t2);
   }
</sctipt>
<input type="button" value="停止" onclick="stop();">
  
  

7. jQuery

7.1 JQuery 的介绍

  • jQuery的定义:jQuery是对JavaScript的封装,是免费开源的JavaScript函数库,JQuery 极大地简化了JavaScript 编程
  • jQuery的作用:jQuery和JavaScript它们的作用一样,都是负责网页行为操作,增加网页和用户的交互效果的,只不过jQuery简化了JavaScript编程,jQuery实现交互效果更简单
  • jQuery的优点:
    • jQuery兼容了现在主流的浏览器,增加了程序猿的开发xiaol
    • jQuery简化了 JavaScript 编程,代码编写更加简单

7.2 jQuery的用法

  • jQuery的引入

    <script src="js/jquery-1.12.4.min.js"></script>
    
  • jQuery的入口函数:知道使用js获取标签元素,需要页面加载完成以后再获取,我们通过给onload事件属性设置了一个函数来获取标签元素,而jquery提供了ready函数来解决这个问题,保证获取标签没有问题,它的速度比原生的 window.onload 更快

  • 入口函数示例代码

    <script src="js/jquery-1.12.4.min.js"></script>
    <script>
        window.onload = function(){
            var oDiv = document.getElementById('div01');
            alert('原生就是获取的div:' + oDiv);
        };
        $(document).ready(function(){
            var $div = $('#div01');
            alert('jquery获取的div:' + $div);
        });
    </script>
    
    <div id="div01">这是一个div</div>
    
     // 入口函数的简写格式:
    <script src="js/jquery-1.12.4.min.js"></script>
    <script>
        window.onload = function(){
            var oDiv = document.getElementById('div01');
            alert('原生就是获取的div:' + oDiv);
        };
    
        /*
        $(document).ready(function(){
            var $div = $('#div01');
            alert('jquery获取的div:' + $div);
        });
        */
    
        // 上面ready的写法可以简写成下面的形式:
        $(function(){
            var $div = $('#div01');
            alert('jquery获取的div:' + $div);
        }); 
    </script>
    
    <div id="div01">这是一个div</div>
    

7.3 jQuery 选择器

  • jQuery选择器介绍:选择器可以快速选择标签元素,获取标签的,选择规则和css样式一样

  • jQuery选择器的种类:

    1. 标签选择器

    2. 类选择器

    3. id选择器

    4. 层级选择器

    5. 属性选择器

      示例:

      $('#myId') //选择id为myId的标签
      $('.myClass') // 选择class为myClass的标签
      $('li') //选择所有的li标签
      $('#ul1 li span') //选择id为ul1标签下的所有li标签下的span标签
      $('input[name=first]') // 选择name属性等于first的input标签
      

      说明:可以使用length属性来判断标签是否选择成功,如果length大于0表示选择成功,否则选择失败

      $(function(){
          result = $("div").length;
          alert(result);
      });
      

7.4 选择集过滤

  • 选择集过滤的介绍:选择集过滤就是在选择标签的集合里面过滤自己需要的标签

  • 选择集过滤的操作

    • has(选择器名称)方法,表示选取包含指定选择器的标签

    • eq(索引)方法,表示选取指定索引的标签,id索引是从0开始

      // has方法的示例代码:
      <script>
          $(function(){
              //  has方法的使用
              var $div = $("div").has("#mytext");
              //  设置样式
              $div.css({"background":"red"});
          });
      </script>
      
      <div>
          这是第一个div
          <input type="text" id="mytext">
      </div>
      
      <div>
          这是第二个div
          <input type="text">
          <input type="button">
      </div>
      
      // eq方法的示例:
      <script>
          $(function(){
              //  has方法的使用
              var $div = $("div").has("#mytext");
              //  设置样式
              $div.css({"background":"red"});
      
              //  eq方法的使用
              var $div = $("div").eq(1);
              //  设置样式
              $div.css({"background":"yellow"});
          });
      </script>
      
      <div>
          这是第一个div
          <input type="text" id="mytext">
      </div>
      
      <div>
          这是第二个div
          <input type="text">
          <input type="button">
      </div>
      
      

7.5 选择集转移

  • 选择集转移介绍:选择集转移就是以选择的标签为参照,然后获取转移后的标签

  • 选择集转移操作:

    • $('#box').prev(); 表示选择id是box元素的上一个的同级元素

    • $('#box').prevAll(); 表示选择id是box元素的上面所有的同级元素

    • $('#box').next(); 表示选择id是box元素的下一个的同级元素

    • $('#box').nextAll(); 表示选择id是box元素的下面所有的同级元素

    • $('#box').parent(); 表示选择id是box元素的父元素

    • $('#box').children(); 表示选择id是box元素的所有子元素

    • $('#box').siblings(); 表示选择id是box元素的其他同级元素

    • $('#box').find('.myClass'); 表示选择id是box元素的class等于myClass的元素

      <script>
          $(function(){
              var $div = $('#div01');
      
              $div.prev().css({'color':'red'});
              $div.prevAll().css({'text-indent':50});
              $div.next().css({'color':'blue'});
              $div.nextAll().css({'text-indent':80});
              $div.siblings().css({'text-decoration':'underline'})
              $div.parent().css({'background':'gray'});
              $div.children().css({'color':'red'});
              $div.find('.sp02').css({'font-size':30});
          });  
      </script>
      
      <div>
          <h2>这是第一个h2标签</h2>
          <p>这是第一个段落</p>
          <div id="div01">这是一个<span>div</span><span class="sp02">第二个span</span></div>
          <h2>这是第二个h2标签</h2>
          <p>这是第二个段落</p>
      </div>
      

7.6 获取和设置元素内容

  • 给指定标签追加html内容使用 append 方法

    <script>
        $(function(){
    
            var $div = $("#div1");
            //  获取标签的html内容
            var result = $div.html();
            alert(result);
            //  设置标签的html内容,之前的内容会清除
            $div.html("<span style='color:red'>你好</span>");
            //  追加html内容
            $div.append("<span style='color:red'>你好</span>");
    
        });
    </script>
    
    <div id="div1">
        <p>hello</p>
    </div>
    

7.7 获取和设置元素属性

  • prop 方法的使用:之前使用css方法可以给标签设置样式属性,那么设置标签的其他属性可以使用prop方法

    // 示例代码
    <style>
        .a01{
            color:red;
        }
    </style>
    
    <script>
        $(function(){
            var $a = $("#link01");
            var $input = $('#input01')
    
            // 获取元素属性
            var sId = $a.prop("id");
            alert(sId);
    
            // 设置元素属性
            $a.prop({"href":"http://www.baidu.com","title":'这是去到百度的链接',"class":"a01"});
    
            //  获取value属性
            // var sValue = $input.prop("value");
            // alert(sValue);
    
            // 获取value属性使用val()方法的简写方式
            var sValue = $input.val();
            alert(sValue);
            // 设置value值
            $input.val("222222");
        })
    </script>
    
    <a id="link01">这是一个链接</a>
    <input type="text" id="input01" value="111111">
    
  • 获取和设置元素属性的操作可以通过prop方法来完成

  • 获取和设置元素的value属性可以通过val方法来完成,更加简单和方便

7.8 jQuery事件

  • 常用事件

    • click() 鼠标单击
    • blur() 元素失去焦点
    • focus() 元素获得焦点
    • mouseover() 鼠标进入(进入子元素也触发)
    • mouseout() 鼠标离开(离开子元素也触发)
    • ready() DOM加载完成

    示例代码:

    <script>
        $(function(){
            var $li = $('.list li');
            var $button = $('#button1')
            var $text = $("#text1");
            var $div = $("#div1")
    
            //  鼠标点击
            $li.click(function(){             
                // this指的是当前发生事件的对象,但是它是一个原生js对象
                // this.style.background = 'red';
    
                // $(this) 指的是当前发生事件的jquery对象
                $(this).css({'background':'gold'});
                // 获取jquery对象的索引值,通过index() 方法
                alert($(this).index());
            });
    
            //  一般和按钮配合使用
            $button.click(function(){
                alert($text.val());
            });
    
            //  获取焦点
            $text.focus(function(){
                $(this).css({'background':'red'});
    
            });
    
            //  失去焦点
            $text.blur(function(){
                $(this).css({'background':'white'});
    
            });
    
            //  鼠标进入
            $div.mouseover(function(){
                $(this).css({'background':'gold'});
    
            });
    
            //  鼠标离开
            $div.mouseout(function() {
                $(this).css({'background':'white'});
            });
        });
    </script>
    
    <div id="div1">
        <ul class="list">
            <li>列表文字</li>
            <li>列表文字</li>
            <li>列表文字</li>
        </ul>
    
        <input type="text" id="text1">
        <input type="button" id="button1" value="点击">
    </div>
    
  • this 指的是当前发生事件的对象,但是它是一个原生js对象

  • $(this) 指的是当前发生事件的jquery对象

7.9 事件代理

  • 事件代理介绍:事件代理就是利用事件冒泡的原理(事件冒泡就是事件会向它的父级一级一级传递),把事件加到父级上,通过判断事件来源,执行相应的子元素的操作,事件代理首先可以极大减少事件绑定次数,提高性能;其次可以让新加入的子元素也可以拥有相同的操作

    // 事件冒泡代码:当点击子元素div,它的点击事件会向它父元素传递,也会触发父元素的点击事件,这就是事件冒泡
    
     <script>
        $(function(){
    
            var $div1 = $('#div1');
            var $div2 = $('#div2');
    
            $div1.click(function(){
                alert($(this).html());
            }); 
    
            $div2.click(function(){
                alert($(this).html());
            }); 
        });
    </script>
    
     <div id="div1" style="width:200px; height:200px; background: red;">
        <div id="div2" style="width:100px; height:100px;background: yellow;">
            哈哈
        </div>
    </div>
    
  • 事件代理的使用

    // 一般绑定事件的写法:
    $(function(){
        $ali = $('#list li');
        $ali.click(function() {
            $(this).css({background:'red'});
        });
    })
    
    <ul id="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
    
    
    
    //事件代理的写法
    $(function(){
        $list = $('#list');
        // 父元素ul 来代理 子元素li的点击事件
        $list.delegate('li', 'click', function() {
            // $(this)表示当前点击的子元素对象
            $(this).css({background:'red'});
        });
    })
    
    <ul id="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
    
    
    // delegate 方法参数说明:
    delegate(childSelector,event,function)
    childSelector : 子元素的选择器
    event:事件名称,比如:'click'
    function: 当事件触发执行的函数
    

7.10 Javascript对象

  • JavaScript 对象的介绍:JavaScript 中的所有事物都是对象:字符串,数值,数组,函数等都可以认为是对象,此外,JavaScript 允许自定义对象,对象可以拥有属性和方法

  • Javascript创建对象操作

    • 创建自定义JavaScript对象有两种方式:

      • 通过顶级Object类型来实例化一个对象

      • 通过对象字面量创建一个对象

        // object类创建对象的示例代码:
        <script>
            var person = new Object();
        
            // 添加属性:
            person.name = 'tom';
            person.age = '25';
        
            // 添加方法:
            person.sayName = function(){
                alert(this.name);
            }
        
            // 调用属性和方法:
            alert(person.age);
            person.sayName();
        </script>
        
        
        // 对象字面量创建对象的示例代码:
        <script>
            var person2 = {
                name:'Rose',
                age: 18,
                sayName:function(){
                    alert('My name is' + this.name);
                }
            }
        
            // 调用属性和方法:
            alert(person2.age);
            person2.sayName();
        </script>
        
      • 调用属性和方法的操作都是通过点语法的方式来完成,对象的创建推荐使用字面量方式会更加简单

7.11 json

  • json的介绍:json是 JavaScript Object Notation 的首字母缩写,翻译过来就是javascript对象表示法,这里说的json就是类似于javascript对象的字符串,它同时是一种数据格式,目前这种数据格式比较流行,逐渐替换掉了传统的xml数据格式。

  • json的格式有两种:

    • 对象格式:对象格式的json数据,使用一对大括号({}),大括号里面放入key:value形式的键值对,多个键值对使用逗号分隔。
    • 数组格式:数组格式的json数据,使用一对中括号([]),中括号里面的数据使用逗号分隔
    • 格式说明:json中的冒号两边,(key)属性名称和字符串值需要用双引号引起来,用单引号或者不用引号会导致读取数据错误。
  • 实际开发中的示例:

    {
        "name":"jack",
        "age":29,
        "hobby":["reading","travel","photography"]
        "school":{
            "name":"Merrimack College",
            "location":"North Andover, MA"
        }
    }
    
  • json数据转换成JavaScript 对象

    // json本质上是字符串,如果在js中操作json数据,可以将json字符串转换为Javascript对象
    
    var sJson = '{"name":"tom","age":18}';
    var oPerson = JSON.parse(sJson);
    
    // 操作属性
    alert(oPerson.name);
    alert(oPerson.age);
    
    

7.12 ajax

ajax的介绍:ajax时Asynchronous JavaScript and XML的简写,意思是异步的js和xml格式的通信请求,ajax一个前后台配合的技术,它可让Javascript发送异步的http请求,与后台通信进行数据的获取,ajax最大的优点是实现局部刷新,ajax可以发送http请求,当获取到后台数据的时候更新页面显示数据实现局部刷新。在html页面使用ajax需要在web服务器环境下运行,一般向自己的web服务器发送ajax请求*

ajax的使用

jquery将ajax封装成一个方法 $.ajax() ,我们可以直接用这个方法来执行ajax请求
<script>
  $.ajax({
  // URL 请求服务器的后台地址
  url:'',
  // type 请求方式,默认是‘GET’,常用‘POST’
  type:'GET',
  // datatype 设置返回的数据格式,常用'json'格式
  dataType:'JSON',
  // data 设置发送给服务器的数据,没有参数不需要设置
  
  // success 设置请求成功后的回调函数,成功后执行的代码
  success:function(response){
  		console.log(response);
},
  // error 设置请求失败后的回调函数,失败后执行的代码
  error:function(){
    alert("请求失败,请稍后再试!");
  }
  // async 设置是否异步,默认值是'true',表示异步,一般不用写
	async:true
});
</script>

ajax方法的参数说明:
1.url 请求地址
2.type 请求方式,默认是'GET',常用的还有'POST'
3.dataType 设置返回的数据格式,常用的是'json'格式
4.data 设置发送给服务器的数据,没有参数不需要设置
5.success 设置请求成功后的回调函数
6.error 设置请求失败后的回调函数
7.async 设置是否异步,默认值是'true',表示异步,一般不用写
8.同步和异步说明
    同步是一个ajax请求完成另外一个才可以请求,需要等待上一个ajax请求完成,好比线程同步。
    异步是多个ajax同时请求,不需要等待其它ajax请求完成, 好比线程异步。

    
  • ajax的简写方式: $.ajax 按照请求方式可以简写成 $.get或者$.post方式

    // ajax简写方式的示例代码
    <script>
        $(function(){
            /*
             1. url 请求地址
             2. data 设置发送给服务器的数据, 没有参数不需要设置
             3. success 设置请求成功后的回调函数
             4. dataType 设置返回的数据格式,常用的是'json'格式, 默认智能判断数据格式
            */ 
            $.get("http://t.weather.sojson.com/api/weather/city/101010100", function(dat,status){
                console.log(dat);
                console.log(status);
                alert(dat);
            }).error(function(){
                alert("网络异常");
            });
    
            /*
             1. url 请求地址
             2. data 设置发送给服务器的数据, 没有参数不需要设置
             3. success 设置请求成功后的回调函数
             4. dataType 设置返回的数据格式,常用的是'json'格式, 默认智能判断数据格式
            */ 
            $.post("test.php", {"func": "getNameAndTime"}, function(data){
                alert(data.name); 
                console.log(data.time); 
            }, "json").error(function(){
                alert("网络异常");
            }); 
        });
    
    
    </script>
    
    $.get和$.post方法的参数说明:
    
    $.get(url,data,success(data, status, xhr),dataType).error(func)
    $.post(url,data,success(data, status, xhr),dataType).error(func)
    
    1. url 请求地址
    2. data 设置发送给服务器的数据,没有参数不需要设置
    3. success 设置请求成功后的回调函数
    		data 请求的结果数据
    		status 请求的状态信息, 比如: "success"
    		xhr 底层发送http请求XMLHttpRequest对象
    4. dataType 设置返回的数据格式
    		"xml"
    		"html"
    		"text"
    		"json"
    5. error 表示错误异常处理
    		func 错误异常回调函数
    

8. MySQL数据库的基本使用

8.1 数据库的介绍

# 1.关系型数据库中核心元素:
数据行(也叫做记录),数据列(也叫做字段),数据表,数据库(数据表的集合)

# 2.数据库的作用:存储管理数据

# 3.数据路的特点:持久化存储,读写速度极高,保证数据的有效性

# 4.分类:
关系型数据库:二维表模型
非关系型数据库:key-value 模型

# 5.关系型数据库管理系统的介绍
数据库管理系统(Relational Database Management System,简称RDBMS)是为管理关系型数据库而设计的软件系统。

# 6.SQL语句主要分为:
DOL:数据查询语句,用于对数据进行查询,如select
DML:数据操作语言,对数据进行增加,修改,删除,如insert,update,delete
TPL:事务处理语言,对事物进行处理,包括begin,transaction,commit,rollback
DCL:数据控制语言,进行授权与权限回收,如grant,revoke
DDL:数据定义语言,进行数据库,表的管理等,如create,drop

8.2 数据类型和约束

8.2.1数据类型

​ 数据类型是指在创建表的时候为表中字段指定数据类型,只是数据符合类型要求才能存储起来,使用数据类型的原则是够用就行,尽量使用取值范围小的,而不用大的,这样可以更多的节省存储空间

​ **创建表或者库时,需要定义: 字段名 字段数据类型 可选的字段约束条件 **

常用数据类型和说明:

整数类型:int , bit
字符串类型:char , varchar
小数类型:decimal
日期类型:date,time,datetime
枚举类型:enum

说明:
'decimal 表示符点数',如decimal(5,2) 表示共存有5位数,小数占其中2位
'char 表示固定长度的字符串',如char(10),如果填充的字符长度没有10则会用空格填充到10位,其中的10表示整个字符的总长度
'varchar 表示可变长度的字符串',如varchar(10),如果填充的字符数小于10也不进行填充,还是原样保存,10表示总长度。
对于图片,音频,视频等文件,不存储在数据中,而是上传到某个服务器上,然后表中只存储给图片的保存路径。
字符串 text 表示存储大文本,当字符大于 4000时推荐使用,比如技术博客

数据约束:

在创建新表时,需要指定约束,约束是指数据在数据类型限定的基础上额外增加的要求

常见约束如下:

  • 主键约束 primary key:物理上存储的顺序,MySQL 建议所有表的主键子段都叫id,类型为 int unsigned

  • 非空约束 not null:此字段不允许填写空值

  • 唯一约束 unique :此字段的值不允许重复

  • 默认约束 default:当不填写字段对应的值时会使用默认值,如填写有值则填写该值

  • 外建约束 foreign key:对关系字段进行约束,当关系字段填写值时,会到关联的表中查询此值是否存在,如果存在则填写成功,如果不存在则填写失败并抛出异常

    ========================================================

在创建数据库建数据表,添加数据时,要定义编码和字符类型和字符类型的数据范围有符号范围和无符号的区别是没有负数

8.2.1.1.整数类型
类型 字节大小 有符号范围(Signed) 无符号范围(Unsigned)
TINYINT 1 -128 ~ 127 0 ~ 255
SMALLINT 2 -32768 ~ 32767 0 ~ 65535
MEDIUMINT 3 -8388608 ~ 8388607 0 ~ 16777215
INT/INTEGER 4 -2147483648 ~2147483647 0 ~ 4294967295
BIGINT 8 -9223372036854775808 ~ 9223372036854775807 0 ~ 18446744073709551615
8.2.1.2. 字符串
类型 说明 使用场景
CHAR 固定长度,小型数据 身份证号、手机号、电话、密码
VARCHAR 可变长度,小型数据 姓名、地址、品牌、型号
TEXT 可变长度,字符个数大于 4000 存储小型文章或者新闻
LONGTEXT 可变长度, 极大型文本数据 存储极大型文本数据
8.2.1.3. 时间类型
类型 字节大小 示例
DATE 4 '2020-01-01'
TIME 3 '12:29:59'
DATETIME 8 '2020-01-01 12:29:59'
YEAR 1 '2017'
TIMESTAMP 4 '1970-01-01 00:00:01' UTC ~ '2038-01-01 00:00:01' UTC

总结:

常用的数据类型:
    整数:int,bit
    小数:decimal
    字符串:varchar,char
    日期时间: date, time, datetime
    枚举类型(enum)

常见的约束:
    主键约束 primary key
    非空约束 not null
    惟一约束 unique
    默认约束 default
    外键约束 foreign key
    数据类型和约束保证了表中数据的准确性和完整性

=============================================================

8.2.1.4.命令行操作数据库

#-------------------- 数据 库 结构 操作 SQL语句 -------------------
1.创建数据库:
	create database 数据库名 charset=utf8;
	create table 表名(字段名,字段数据类型,可选定义的字段约束); 
2.删除数据库:
	drop database 数据库名;
3.使用数据库:
	use 数据库名;
4.查询库名:
	show databases;
5.查询当前使用的数据库:
	select database();
	
	
#--------------------数据 表 结构 操作 SQL语句 --------------------
1.查看 
		--- 显示数据表 show tables;
		--- 查看创表SQL语句 show create table 表名;
		--- 查看创库SQL语句 show create databases 数据库名;
2.创建数据表
	create table 表名(字段名称,字段类型,可选的字段约束)
        例:
        create table students(
           # 定义列表名id为无符号类型的整数并且是主键,自动递增 不能为空
         id int unsigned primary key auto_increment not null,
         # 定义字段 name 是可变长度的字符串类型,并且不能为空
         name varchar(20) not null,
         # age字段 无符号整数类型,默认值为0
         age tinyint unsigned default 0,
         # height字段 为小数类型,5位数中有2位小数
         height decimal(5,2),
         #gender字段 为枚举类型,只能保存一个值但能够处理65535个预定义值
         gender enum('男','女','人妖','保密')
        );
 3.修改表
 add: 对数据表添加新字段
 		alter table 表名 add 列名 数据类型 约束;
 modify:只能修改字段类型或者约束,不能修改字段名
 		alter table 表名 modify 列名 类型 约束; 		
 change:既能对字段重命名又能修改字段类型和约束
 		alter table 表名 change 原名 新名 类型及约束;
 		
 4.删除表中的字段
 	alter table 表名 drop 列名;
 	
 5.删除表
 	drop table 表名;

#---------------------- 表中 数据 操做 SQL语句 --------------------
1.查询表中数据
	查看表中所有数据:				  select * from 表名;
	查看表中某一列的数据:			select 列名... from 表名;
	
2.在表中添加数据
说明:主键列如果是自动增长,在全列插入数据是还需要使用0或者null或者default进行展位输入;全列插入时,如果字段列有默认值可以使用可以用default来占位,插入的数据就是之前设置的默认值

	2.1 全列插入:值的顺序与表结构字段的顺序完全一一对应
			insert into 表名 values(...)
例:insert into students values(0, 'xx', default, default, '男');
	
	2.2 部分列插入数据:值的顺序与给出的列顺序对应,对某些列插入值
			insert into 表名(列1,列2...) values(值1,值2,...)
例:insert into students(name, age) values('王二小', 15);

	2.3 全列多行插入(一次写入多行)
			insert into 表名 values(...)(...)
例:insert into students values(0, '张飞', 55, 1.75, '男'),(0, '关羽', 58, 1.85, '男');
	
	2.4 部分列多行插入
			insert into 表名(列1,列2...) values(...)(...)
例:insert into students(name, height) values('刘备', 1.75),('曹操', 1.6);

3.在数据表中删除数据
		delete from 表名 where 条件(列名=值)
例: delete from students where id=1;

4.修改数据表中的数据
		update 表名 set 列1=值1,列2=值2...  where 条件;
例: update students set age = 18, gender = '女' where id = 6;

				
# 总结
登录数据库: mysql -uroot -p
退出数据库: quit 或者 exit 或者 ctr + d
创建数据库: create database 数据库名 charset=utf8;
使用数据库: use 数据库名;
删除数据库: drop database 数据库名;
创建表: create table 表名(字段名 字段类型 约束, ...);
修改表-添加字段: alter table 表名 add 字段名 字段类型 约束
修改表-修改字段类型: alter table 表名 modify 字段名 字段类型 约束
修改表-修改字段名和字段类型: alter table 表名 change 原字段名 新字段名 字段类型 约束
修改表-删除字段: alter table 表名 drop 字段名;
删除表: drop table 表名;
查询数据: select * from 表名; 或者 select 列1,列2,... from 表名;
插入数据: insert into 表名 values (...) 或者 insert into 表名 (列1,...) values(值1,...)
修改数据: update 表名 set 列1=值1,列2=值2... where 条件
物理 删除数据: delete from 表名 where 条件
查看表结构: desc 表名;
查看表的部分列: select Host,User,Password from user;
逻辑删除:	alter table students add isdelete bit default 0;
					update students set isdelete=1 where id = 1;

8.3 as和distinct关键字

1. as关键字  -- 字段别名
在使用SQL语句显示结果的时候,往往在屏幕显示的字段名并不具备良好的可读性,此时可以使用as给字段起一个别名
select s.name,s.age from students as s;

2. distinct 关键字 -- 去重
distinct 可以去除重复数据行
select distinct gender from students;

8.4 where条件查询

1. where 语句支持的运算符:
比较运算符 逻辑运算符 模糊查询 范围查询 空判断

2. where 条件语句查询语法格式如下:
	select * from 表名 where 条件;	

3. 常见的比较运算符有 >,<,>=,<=,!=
例:select * from students where id > 1;

4. 逻辑运算符
		and 表示多个条件同时成立则为真
		or 表示多个条件有一个成立则为真
		not 表示对条件取反

5. 模糊查询 - like
		like 是模糊查询关键字
		% 表示任意多个人一字符
		_ 下划线表示任意一个字符

6. 查询范围
between-and 限制连续性范围 
例:mysql> select * from students where id between 2 and 9;
in 限制非连续性范围
例:mysql> select * from students where id in (1,3,5,6);

7. 非空判断
判断为空使用: is null
判断非空使用: is not null

8.5 排序

排序查询语法
select * from 表名 order by 列1 asc(升序)|desc(降序) [,列2 asc|desc,...]

语法说明:
1.先按照列1进行排序,如果列1的值相同时,则会按照列2排序,以此类推
2.asc 从小到大排序,即升序
3.desc 从大到小排序,即降序
4.默认按照列值从小到大排序,即 asc

例:
select * from students where gender=1 and is_delete=0 order by id desc;
# 年龄按降序排列,当年龄相同时,根据身高的降序排列
select * from students  order by age desc,height desc;

8.6 分页查询

1. 数据量特别大时会用到分页查询
2. 分页查询语法
select * from 表名 limit start,count;

limit 是分页关键字
start 表示开始索引的行,默认是0
count 表示每页查询的条数

例:mysql> select * from areas where num limit 1,5;

查询学生表,获取第n页数据的SQL语句:
select * from students limit (n-1)*m,m;

9. MySOL 数据库的条件查询

9.1 聚合函数

​ 聚合函数又叫做组函数,通常是对表中的数据进行统计和计算,一般结合分组(group by)来使用,用于统计和计算分组数据

常用的聚合函数:

  1. count(col) : 表示求指定列的总行数
  2. max(col) : 表示求指定列的最大值
  3. min(col) : 表示求指定列的最小值
  4. sum(col) :表示求指定列的和
  5. avg(col) :表示求指定列的平均值
  6. round(avg(col),2):计算平均数并且保留两位小数
-- 聚合函数示例
	-- 总数
	-- count
	-- 计算班级学生的总数
	select count(*) from students;


	-- 查询男性有多少人
	select count(*) from students where gender='男';

	-- 女性有多少人
	select count(*) from students where gender='女';


	-- 最大值
	-- max
	-- 查询最大的年龄
	select max(age) from students;

	-- 查询女性的最高 身高
	select max(height) from students;

	-- 最小值
	-- min
	select min(age) from students;
	
	-- 求和
	-- sum
	-- 计算所有人的年龄总和
	select sum(age) from students;
	
	-- 平均值
	-- avg
	-- 计算平均年龄
	select avg(age) from students;

	-- 计算平均年龄 sum(age)/count(*)
	select sum(age)/count(*) from students;

	-- 四舍五入 round(123.23 , 1) 保留1位小数
	-- 计算所有人的平均年龄,保留2位小数
	select round(avg(age), 2) from students;

聚合函数的特点

​ 聚合函数默认忽略字段为null的记录,想要列值为null的记录页参于计算,必须使用函数 ifnull 函数对null值做替换

-- ifnull 判断空用0替换,示例:判断身高为空的用0替换
select avg(ifnull(height, 0)) from students where gender='男';

9.2 分组查询

​ 分组查询就是将查询结果按照指定字段进行分组,字段中数据相等的分为一组。

分组查询基本的语句格式如下:

GROUP BY 列名 [HAVING 条件表达式][WITH ROLLUP]

说明:
列名:是指按照指定字段的值进行分组
HAVING 条件表达式:用来过滤分组后的数据
WITH ROLLUP :在所有记录的最后加上一条记录,显示select查询是聚合函数的统计和计算结果
1. group by 指定字段,...    用于分组的使用:
# group by可用于单个字段分组也可以用于多个字段分组
mysql> select gender from students group by gender;
mysql> select id,name,gender from students group by id,name,gender;


2. group by + group_concat() ;分组+分组中指定字段内容链接为一个字符串的使用
# group_concat(字段名): 统计每个分组指定字段的信息集合,每个信息之间使用逗号进行分割
# 根据性别gender分组并显示组中的id和name值
# 注:下列分组时以gender分组,select后就必须写gender字段做查询条件,除非分组的是id号,否则显示会出错
mysql> select gender,group_concat(id,name) from students group by gender;
+--------+--------------------------------------------------------+
| gender | group_concat(id,name)                                  |
+--------+--------------------------------------------------------+
| 男     | 16司马二狗,3彭于晏,4刘德华,9程坤,8周杰伦,13郭靖              |
| 女     | 1小明,14周杰,15凌小小,12静香,10刘亦菲,7王祖贤,5黄蓉,2小月月   |
| 中性   | 11金星                                                  |
| 保密   | 6凤姐,17zhang,18b                                       |
+--------+--------------------------------------------------------+

3. group by 指定字段 + 聚合函数 ;用于分组的使用
-- 计算每个年龄中的人数
	select age,count(*) from students group by age;
-- 查询 男、女性别中年龄的最大值
	select gender,max(age) from students group by gender having gender in ('男','女');



4. group by + having ; 分组+过滤的使用
# having作用和where类似都是过滤数据的,但having是过滤分组数据的,只能用于group by
# 根据gender字段进行分组,并统计每组中元素的个数
mysql> select gender,count(*) from students group by gender ;
# 对分组后的数据做having过滤,个数大于2的进行显示
mysql> select gender,count(*) from students group by gender having count(*)>2;




5. group by + with rollup 分组+小计的使用;最后一行做汇总
# with rollup的作用是:在最后记录后面新增一行,显示select查询时聚合函数的统计和计算结果
# 根据gender字段进行分组,并且汇总符合条件的总人数
# 注意:如果语句中使用了having过滤就不能使用 with rollup
mysql> select gender,count(*) from students group by gender with rollup;
+--------+----------+
| gender | count(*) |
+--------+----------+
| 男     |        6 |
| 女     |        8 |
| 中性   |        1 |
| 保密   |        3 |
| NULL   |       18 |
+--------+----------+
# 根据gender字段进行分组,汇总符合字段的所有人的年龄
mysql> select gender,group_concat(age) from students group by gender with rollup;
+--------+---------------------------------------------------------+
| gender | group_concat(age)                                       |
+--------+---------------------------------------------------------+
| 男     | 28,29,59,27,36,12                                       |
| 女     | 18,34,28,12,25,18,38,18                                 |
| 中性   | 33                                                      |
| 保密   | 28,201,200                                              |
| NULL   | 28,29,59,27,36,12,18,34,28,12,25,18,38,18,33,28,201,200 |
+--------+---------------------------------------------------------+

9.3 连接查询-内连接

连接查询:可以实现多个表的查询,当查询的字段数据来自不同的表就可以使用连接查询来完成。

内连接根据连接查询条件取出两个表的“交集”

内连接语法格式:
select 字段 from 表1 inner join 表2 on 表1.字段1 = 表2.字段2
说明:
inner join 就是内连接查询关键字
on 就是连接查询条件,连接 条件

例:
mysql> select * from students inner join classes on students.cls_id=classes.id ;
+----+--------------+------+--------+--------+--------+-----------+----+--------------+
| id | name         | age  | height | gender | cls_id | is_delete | id | name         |
+----+--------------+------+--------+--------+--------+-----------+----+--------------+
|  1 | 小明         |   18 | 180.00 | 女     |      1 |           |  1 | python_01期  |
|  2 | 小月月       |   18 | 180.00 | 女     |      2 |          |  2 | python_02期  |
|  3 | 彭于晏       |   29 | 185.00 | 男     |      1 |           |  1 | python_01期  |
|  4 | 刘德华       |   59 | 190.00 | 男     |      2 |          |  2 | python_02期  |

内连接的另一种写法

mysql> select * from students,classes  where students.cls_id=classes.id ;
+----+--------------+------+--------+--------+--------+-----------+----+--------------+
| id | name         | age  | height | gender | cls_id | is_delete | id | name         |
+----+--------------+------+--------+--------+--------+-----------+----+--------------+
|  1 | 小明         |   18 | 180.00 | 女     |      1 |           |  1 | python_01期  |
|  2 | 小月月       |   18 | 180.00 | 女     |      2 |          |  2 | python_02期  |
|  3 | 彭于晏       |   29 | 185.00 | 男     |      1 |           |  1 | python_01期  |
|  4 | 刘德华       |   59 | 190.00 | 男     |      2 |          |  2 | python_02期  |

9.4 连接查询-左连接

以左表为主根据条件查询右表数据,如果根据条件查询右表数据不存在使用null值填充

根据左表条件查询显示全部,右表匹配内容,右表不存在的显示null

# 左连接的语法格式:
select 字段 from 表1 left join 表2 on 表1.字段1 = 表2.字段2

# 说明:
left join 就是左连接查询关键字
on 就是连接查询条件
表1 是左表
表2 是右表


9.5 连接查询-右连接

以右表为主根据条件查询左表数据,如果根据条件查询左表数据不存在使用null值填充

# 右连接查询语法格式
select 字段 from 表1 right join 表2 on 表1.字段1 = 表2.字段2
# 说明
right join 就是右连接查询关键字
on 就是连接查询条件
表 1 是左表
表 2 时右表


9.6 连接查询-自连接

左表和右表是同一个表,根据连接查询条件查询两个表中的数据,但对同一个表需要 as 分别取个别名

# 自连接查询的用法:
select c.id, c.title, c.pid, p.title from areas as c inner join areas as p on c.pid = p.id where p.title = '山西省';

# 说明:
自连接查询必须对表起别名

# 小结
自连接查询就是把一张表模拟成左右两张表,然后进行连表查询。
自连接就是一种特殊的连接方式,连接的表还是本身这张表

# 例:
mysql> select c.id, c.title, c.pid, p.title from areas as c inner join areas as p on c.pid = p.id where p.title = '山西省';
+--------+-----------+--------+-----------+
| id     | title     | pid    | title     |
+--------+-----------+--------+-----------+
| 140100 | 太原市    | 140000 | 山西省    |
| 140200 | 大同市    | 140000 | 山西省    |
| 140300 | 阳泉市    | 140000 | 山西省    |
| 140400 | 长治市    | 140000 | 山西省    |
| 140500 | 晋城市    | 140000 | 山西省    |
| 140600 | 朔州市    | 140000 | 山西省    |
| 140700 | 晋中市    | 140000 | 山西省    |
| 140800 | 运城市    | 140000 | 山西省    |
| 140900 | 忻州市    | 140000 | 山西省    |
| 141000 | 临汾市    | 140000 | 山西省    |
| 141100 | 吕梁市    | 140000 | 山西省    |
+--------+-----------+--------+-----------+
11 rows in set (0.00 sec)

9.7 子查询

在一个 select 语句中,嵌入了另一个 select 语句,那么被嵌入的 select 语句称之为子查询语句,外部那个 select 语句则称为主查询

主查询和子查询的关系:子查询是一个完整的SQL语句,子查询被嵌入到一对小括号里面

  1. 子查询是嵌入到主查询中
  2. 子查询是辅助主查询的,要么充当条件,要么充当数据源
  3. 子查询是可以独立存在的语句,是一条完整的 select 语句
# 查询大于平均年龄的学生:
mysql> select * from students where age > (select avg(age) from students);
+----+-----------+------+--------+--------+--------+-----------+
| id | name      | age  | height | gender | cls_id | is_delete |
+----+-----------+------+--------+--------+--------+-----------+
|  4 | 刘德华     |   59 | 175.00 | 男     |      2 |          |
| 17 | zhang     |  201 |  10.00 | 保密   |      1 |           |
| 18 | b         |  200 |  11.00 | 保密   |      1 |          |
+----+-----------+------+--------+--------+--------+-----------+
3 rows in set (0.00 sec)

9.8 数据库设计之三范式

数据库设计之三范式的介绍:
范式: 对设计数据库提出的一些规范,目前有迹可寻的共有8种范式,一般遵守3范式即可。

第一范式(1NF): 强调的是列的原子性,即列不能够再分成其他几列。
第二范式(2NF): 满足 1NF,另外包含两部分内容,一是表必须有一个主键;二是非主键字段 必须完全依赖于主键,而不能只依赖于主键的一部分。
第三范式(3NF): 满足 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。

E-R 模型的介绍:E-R模型即实体-关系模型,E-R模型就是描述数据库存储数据的结构模型

表间关系:

​ 一对一:关系信息存储任何一张表中都行

​ 一对多:关系信息存储在多的那张表中

​ 多对多:保存多对多的关系,需要建立中间表来保存另外两个表的关系

E -R模型的使用场景:

  1. 对于大型公司开发项目,我们需要根据产品经理的设计,先使用建模工具,如:power designer,db desinger等这些软件来画出实体-关系模型(E-R模型)
  2. 然后根据三范式设计数据库表结构

9.9 外键约束SQL语句的编写 - 约束=关联表查询

外键的概念:存储另一张表主键的字段就是外键字段

外键约束概念和作用:外键约束对外键字段的值进行更新和插入时会和引用表中字段的数据进行验证,数据如果不合法则更新和插入会失败,保证数据的有效性

添加外键约束: alter table 从表 add foreign key(外键字段) references 主表(主键字段);
删除外键约束: alter table 表名 drop foreign key 外键名;

# 对以存在的表添加外键约束:
alter table students add foreign key(cls_id) references classes(id);

# 创建表时就定义外键约束:
create table teacher(
		id int unsigned primary key auto_increment not null,
		name varchar(10),
		s_id int unsigned not null,
		foreign key(s_id) references school(id)
	);
	
# 在删除外键约束时,需要先查询外键约束名称
show create tables teacher;
删除外键:alter table teacher drop foreihn key teacher_ibfk_1;

10. MySQL数据库的高级使用-分表操作4个高级操作

表中插入数据时插入另一个表的查询数据: insert into ... select ...
修改表中的数据时两个表嵌套连接语句进行修改:update 连接语句 set 修改字段
创建表时直接在表中插入数据(注意别名):create table ... select
修改表结构时同时修改多个字段的名称或属性及约束:alter table 表名 change .... , change ...

10.1 将查询结果插入到其他表中

-- 将查询结果插入到一个新表的语法:
insert into ... select ... 表示把查询结果插入到指定表中,也就是表复制。

例:
insert into new_list(name) select cate_name from list group by cate_name;

10.2 使用连接更新表中某个字段数据

-- 语法格式:
连接更新表中数据使用: update .. join .. 语句
mysql> update goods g inner join good_cates gc on g.cate_name=gc.name set g.cate_name=gc.id;

10.3 创建表时给某个字段添加数据

-- 语法格式
创建表并给字段插入数据使用 create table ... select 语句
mysql> create table good_brands(id int unsigned primary key auto_increment not null, name varchar(20) not null) select brand_name  as name from goods group by brand_name;

10.4 修改goods表结构

-- 语法格式
修改表结构可以使用:alter table 语句,多个修改字段之间使用逗号分隔
mysql> alter table goods change cate_name cate_id int not_null,change brand_name brand_id int not null;

10.5 事物 begin-commit-rollback

事物的介绍:事物就是用户定义的一系列执行SQL语句的操作,这些操作要么完全执行,7要么都不执行,它是一个不可分割的工作执行单元

事务的作用:能够保证数据的完整性和一致性,让用户的操作更加安全

事物的四大特性:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(isolation)
  • 持久性(Durability)

原子性:一个事务必须被视为一个不可分割的最小工作单位,整个事务中的所有操作要么全部提交成功,要么全部失败,不允许有中间状态,对于一个事物来说,不可能只执行其中的一部分操作,这就是事物的原子性

一致性:数据库总是从一个一致性状态转换到另一个一致性的状态

隔离性:多个事务并行执行,彼此对数据的修改是否可见,这种数据的可见性问题

持久性:一旦事务提交,则其所做的修改会永久保存到数据库

事务的使用:事务必须在支持存储引擎是 InnoDB 类型的关系型数据库上才能执行,mysql的数据库的存储引擎默认是InnoDB类型

表的存储引擎说明:表的存储引擎就是提供存储数据的一种机制,不同表的存储引擎提供不同的存储机制

说明:
常用的表的存储引擎是InnoDB 和MyISAM
InnoDB 是支持事务和外键的
MyISAM 不支持事务和外键,优势是访问速度快,对事务没有要求或者以select,insert为主的都可以使用该存储引擎创建表

-- 查看MySQL数据库支持的表的存储引擎
show engines;
-- 开启事务
begin; 或者 start transaction;

说明:
1. 开启事务后执行修改命令,变更数据会保存到MySQL服务端的缓存文件中,而不维护到物理表中
2. MySQL数据库默认采用自动提交(autocommit)模式,如果没有显示的开启一个事务,那么每条sql语句都会被当作一个事务执行提交的操作
3. 当设置autocommit=0就是取消了自动提交事务模式,直到显示的执行commit和rollback表示该事务结束。
		set autocommit = 0 表示取消自动提交事务模式,需要手动执行commit完成事务的提交
		
-- 提交事务
将本地缓存文件中的数据提交到物理表中,完成数据的更新
commit;

-- 回滚事务
放弃本地缓存文件中的缓存数据,表示回到开始事务前的状态
rollback;

-- 开启运行时间监控
set profiling=1;
-- 查看执行的时间
show profiles;

10.6 索引

索引的介绍:索引在MySQL中也叫做‘键’,它是一个特殊的文件,它保存着数据表里所有记录的位置信息,更通俗的来说,数据库索引好比是一本书的目录,能加快数据库的查询速度。

应用场景:当数据库中数据量很大时,查找表的数据会很慢,我们就可以通过索引来提高数据库的查询效率。

10.6.1 索引的使用
-- 查看表中已有的索引:
show index from 表名;

说明:主键列会自动创建索引,外键也会自动创建索引

-- 创建索引的语法格式
alter table 表名 add index 索引名[可选] (列名,...)
# 索引名不指定默认使用字段名
例:给name字段添加索引
alter table classes add index my_name(name);

-- 删除索引
alter table 表名 drop index 索引名

MySQL中索引的优缺点:

# 优点: 加快数据的查询速度
# 缺点:创建索引会耗费时间和占用磁盘空间,并且随着数据量的增加所耗费的时间也会增加

# 使用原则:
1. 通过优缺点对比,不是索引越多越好,而是需要自己合理的使用。
2. 对经常更新的表就避免对其进行过多索引的创建,对经常用于查询的字段应该创建索引,
3. 数据量小的表最好不要使用索引,因为由于数据较少,可能查询全部数据花费的时间比遍历索引的时间还要短,索引就可能不会产生优化效果。
4. 在一字段上相同值比较多不要建立索引,比如在学生表的"性别"字段上只有男,女两个不同值。相反的,在一个字段上不同值较多可是建立索引。
10.6.2 联合索引

​ 联合索引又叫做复合索引,即一个索引覆盖表中两个或者多个字段,一般用在多个字段一起查询的时候。

-- 创建teacher表
create table teacher
(
    id int not null primary key auto_increment,
    name varchar(10),
    age int
);

-- 创建联合索引
alter table teacher add index (name,age);

联合索引的好处:减少磁盘空间开销,因为每创建一个索引,就会创建一个索引文件,就会增加磁盘空间开销

联合索引的最左原则:在使用联合索引的时候,遵循一个最左原则,即index(name,age)支持name,name和age同时组合查询,而不支持单独 age 查询,因为没有用到创建的联合索引。

示例:

-- 下面的查询使用到了联合索引
select * from stu where name='张三' -- 这里使用了联合索引的name部分
select * from stu where name='李四' and age=10 -- 这里完整的使用联合索引,包括 name 和 age 部分 
-- 下面的查询没有使用到联合索引
select * from stu where age=10 -- 因为联合索引里面没有这个组合,只有 name | name age 这两种组合

说明:在使用联合索引的查询数据时候一定要保证联合索引的最左侧字段出现在查询条件里面,否则联合索引失效

10.7 PyMySQL 的使用

  • PyMySQL的操作步骤:
    • 导入模块 -- import pymysql
    • 创建连接对象 -- con = pymysql.connect(...)
    • 创建游标对象 -- con.cursor()
    • 使用游标对象执行SQL -- con.execute('selecet * from goods;')
    • 提交 -- con.commit()
-- 导入模块
import pymysql
-- 建立连接对象
conn = pymysql.connect(host='127.0.0.1',port=3306,user=root,password='', database='goods', charset='utf8')

-- 创建游标对象
cs = conn.cursor()

-- 使用游标对象执行SQL语句
n = cs.execute("select * from goods;")

-- 获取执行结果
row = cs.fetchone()  #读取一行
print(row)

for rows in cs.fetchall() #读取多行
	print(rows)

-- 关闭游标对象
cs.close()
-- 关闭连接对象
conn.close()

SQL 防注入

  • SQL注入

    • 用户提交带有恶意的数据与SQL语句进行字符串方式的拼接,从而影响了SQL语句的语义,最终产生数据泄漏的现象
  • SQL防注入

    • SQL语句参数化:SQL语言中的参数使用%s来占位,此处不是python中的字符串格式化操作
    • 将SQL语句中%s占位所需要的参数存放在一个列表中,把参数列表传递给 execute 方法中的第二个参数
  • sql 中需要变化的地方,可以用占位符 %s %d

 sql="select * from goods where name=%s order by in desc"
 注意:SQL可以出现多个占位符,后续列表中元素的个数要与之对应
  • 防注入写法,把用户出入的参数封装到列表中
good_name = input('请输入需要查询的商品名:')
ret_list = [good_name]
sql = "select from goods where name=%s ;"
# 把列表传递给 execute(sql, 列表)
# execute方法会自动过滤or或者其他的语法,起到筛选的作用
cs.execute(sql,ret_list)
  
* SQL注入示例:
  # 1.导入模块
  import pymysql
  
  if __name__ == '__main__':
      # 2.建立链接,获取链接对象
      conn = pymysql.connect(host='127.0.0.1', port=3306, user='root',
                             password='',database='jing_dong', charset='utf8')
  
      # 3.获取游标
      cousor = conn.cursor()
      # 3.1 接收用户的输入
      a = input('请输入要查询的商品名:')
      # 4.通过游标执行sql语句
    # sql注入:用户提交的恶意输入和sql语句使用字符串拼接后,改变原来的sql语句的语义,造成数据泄露。
  
   		# id数字时输入 1 or 1=1  会获得所有数据
      sql = "select * from goods where id=%s ;" % a
      # name字符时输入 ' or 1=1 or ' 会获得所有数据
      sql = "select * from goods where name='%s' ;" % a   
      
  str = cousor.execute(sql)
      print("查询到的数据行数:", str)
      # 5.提交 或者 回滚
      conn.commit()
      # 6.如果执行的是查询语句获取查询结果
      # fetchone: ()
      # fetchall: ((),(),(),(),...)
      # 7.显示查询的结果
      for rows in  cousor.fetchall():
          print(rows)
      # 8.关闭游标
      cousor.close()
      # 9.关闭链接
      conn.close()
  -------------------------------------------------------------------------
   # 正常情况:
   # 如果用户输入:苹果笔记本15.1
   # 经过字符串拼接后相当于: select * from goods where name='苹果笔记本15.1';
  
   # 恶意输入:
   # 用户输入: ' or 1=1 or '
   # 经过字符串拼接后相当于: select * from goods where name='' or 1=1 or '';
   # 1=1 表示永远是真
   # 整个sql语句的意义发生改变,变成: select * from goods;
   # sql_str = "select * from goods where name='%s';" % good_name
   ---------------------------------------------------------------------  
      
  

11. 闭包和装饰器

11.1 闭包

  • 闭包的作用:保存函数中的局部变量,不会随着函数调用后而销毁

  • 闭包构成的条件:

    • 函数嵌套的前提下
    • 内部函数使用外部函数的变量
    • 外部函数返回内部函数名
  • 那么我们叫这个访问外部函数变量的内部函数为闭包

  • 基本使用:

    def func_out(num1):
    		def func_inner(num2):
    				result = num1 + num2
    				print(result)
    		return func_inner
    		
    f = func_out(1)			# 闭包对象
    

    示例:

    def config_name(name):  # name='张三'(0x1)     name='里斯'(0x2)
        
        def say_info(info):     #say_info(0x11)    say_info(0x22)
            print(name + ":", info) 
    
        return say_info
    
    
    zs = config_name('张三')  # zs = sqy_info(0x1)  闭包对象
    zs("是吗")            # say_info('是吗')
    
    ls = config_name("里斯")      #ls=say_info(0x11)  闭包对象
    ls('在啊')
    ls('说没事没事没事')
    
    zs("来阿里")
    

11.2 修改闭包内使用的外部变量

  • 闭包中修改外部变量

    • 内层定义和外层同名的变量:重新在内层函数中定义了新的变量

    • 解决办法:nonlocal (在内层声明要修改的变量)

      • 如果要修改的变量是函数外的变量就要用 global 声明全局变量
      """
      1.存在函数嵌套定义
      2.内层函数使用外层函数的变量
      3.外层函数返回内层函数
      """
      # 这里不是修改外层函数的变量,而是重新定义了一个新的局部变量num1=100
      # num1 = 100
      
      # 声明使用全局变量
      # global num1
      
      # 声明使用外部函数变量num1
      # nonlocal num1
      # 修改外层函数变量num1
      
      def func_out(num1):
          def func_inner(num2):
              # num1 = 100
              # nonlocal num1
              num1 = 100
              result = num1 + num2
              print("内部函数:", result)
      
          print(num1)
          func_inner(1)
          print(num1)
          return func_inner
      
      f = func_out(1)
      f(1)
      
      

11.3 装饰器--就是一个特殊的闭包

就是给已有的函数增加额外功能的函数,它本质上就是一个闭包函数

装饰器的功能特点:
1.不修改已有函数的源代码
2.不修改已有函数的调用方式
3.给已有函数增加额外的功能

装饰器示例:
# 装饰器: 就是一个闭包函数,但是外层函数的参数有且只能有一个,必须是函数类型。

def func_out(fn):   # 外层函数有且只有一个参数

    def inner():
        print("请先登录...")
        fn()  # 调回原函数,前后都可以执行新的语句
        print("记录用户日志。。。。")

    return inner

def comment(): # 这就是原函数
    print("发表评论...")


# 使用装饰器装饰原函数
comment = func_out(comment)

if __name__ == '__main__':
    # 使用装饰器装饰原函数
    comment()
    
    
# 代码说明:
闭包函数有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器
写代码要遵循开发封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展
  • 装饰器的语法糖写法:
    • 如果有多个函数都需要添加登录的功能,每次都需要编写func=check(func)这样代码对已有函数进行装饰,这种做法还是比较麻烦
    • Python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是:@装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰

# 装饰器: 就是一个闭包函数,但是外层函数的参数有且只能有一个,必须是函数类型。
# 使用语法糖方式实现装饰

def check(fn):
    def inner():
        print("请先登录。。")
        fn()
    return inner

@check # 当前模块被加载时,@check 自动等价与执行 comment=check(comment)
def comment():
    print("发表评论...")

if __name__ == '__main__':
    comment()

# 执行顺序:
@check --> comment=check(comment) --> '因为' check(fn) --> '所以' check(comment)--'所以' fn = comment --> '因为' return inner --> '所以' comment=inner -->执行inner函数 print("请先登录...") ——> '因为'fn()=comment() -->'所以' print("发表评论...") --> 执行完毕

# 总结
装饰器装饰了函数后:原函数指向=装饰器中的内嵌函数来执行代码,内嵌函数中代码执行完成之后又执行原函数
  • 总结:

    • 装饰器本质就是一个闭包函数,它可以对已有函数进行额外的功能扩展

    • 装饰器的语法格式:

      # 装饰器
      def decorator(fn):
      		def inner():
      		'''执行函数之前'''
      		fn() 							#执行被装饰得原函数
      		'''执行函数之后'''
      		return inner
        
      注:执行函数的前后都可以添加任何代码,来装饰原函数
      
    • 装饰器的语法糖用法:@装饰器名称,同样可以完成对已有函数的装饰操作

11.4 装饰器的使用 -- 就是特殊的闭包

"""目的利用装饰器来计算原函数的执行时间"""
import time
# 使用装饰器统计函数的执行时间
def check(fn):
    def innner():
        start = time.time()  # 开始执行原函数的时间
        fn()  # 执行原函数 fn()=loop()
        end = time.time()  # 记录原函数执行结束的时间
        print("函数执行花费时间", end - start)  # 时间差值

    return innner

@check  # loop=check(loop)
def loop():
    for i in range(100000):
        print(i)

if __name__ == '__main__':
    """
    调用函数时顺序:内层函数-》原函数-》内层函数
    """
    loop()

11.5 通用装饰器的使用

  • 装饰带有参数的函数

    # 内层函数的结构应该和待装饰函数的结构一样
    
    def decorator(fn):
        def inner(n1, n2):
            print("扩展内容")
            fn(n1, n2)
        return inner
    
    @decorator  
    def foo(a, b):
        print(a + b)
    
    if __name__ == '__main__':
        foo(1, 2)
    
    
  • 装饰带有返回值的函数

    # 内层函数的结构应该和待装饰函数的结构一样
    def decorator(fn):
        def inner(n1, n2):    # 原函数有形参时,内嵌函数也要有对应形参
            print("--正在计算中-----")
            result = fn(n1, n2)
            return result			# 原函数有返回值时内嵌函数也要有对应的返回值
        return inner
    
    @decorator		# 使用装饰器装饰函数
    def foo(a, b):
        result = a + b
        return result
    
    if __name__ == '__main__':
        r = foo(1, 2)
        print(r)
    
  • 装饰带有不定长参数的函数

    def decorator(fn):
        def inner(*args, **kwargs):
            print("--扩展功能-----")
            # *args 元组类型,**kwargs 字典类型
            fn(*args, **kwargs)		# 解包,必须带有星号,表示解包
            print(args, kwargs)     #打印结果 (1, 2) {'a': 10}
        return inner
    
    @decorator
    def foo(*args, **kwargs):
        result = 0
        for value in args:
            result += value
    
        for value in kwargs.values():		#kwargs是一个字典类型,values表示取字典的values值,否则取得是 key 值
            result += value
    
        print(result)
    
    
    foo(1, 2, a=10)
    
  • 装饰带有返回值的不定长参数的函数

    # 有返回值
    def decorator(fn):
        def inner(*args, **kwargs):
            print("--正在计算中-----")
            result = fn(*args, **kwargs)		# 解包后(1,2,1=10)返回给foo函数
            return result
    
        return inner
    
    @decorator
    def foo(*args, **kwargs):
        result = 0
        for value in args:
            result += value
    
        for value in kwargs.values():
            result += value
        print(result)
    
    foo(1, 2, a=10)
    
  • 通用装饰器的语法格式--可以装饰任意函数

    # 通用装饰器
    def logging(fn):
      def inner(*args, **kwargs):
        print("插入扩展功能1...")
        result = fn(*agrs, **kwargs)				# 解包后return回传到原函数
        print("插入扩展功能2...")
        return result
      return inner
    

11.6 多个装饰器的使用


def make_div(fn):  # fn=make_p.inner
    print("调用装饰器make_div外层")
    def inner2():
        print("调用装饰器make_div内层")
        return "<div>" + fn() + "</div>"

    return inner2


def make_p(fn):  # fn=content
    print("调用装饰器make_p外层")
    def inner1():
        print("调用装饰器make_p内层")
        return "<p>" + fn() + "</p>"

    return inner1


# 多个装饰器装饰一个函数:装饰顺序是先装饰离函数近的装饰器
@make_div  # 2. content=make_div(concent) --> content=make_div.inner
@make_p  # 1. content=make_p(concent) --> content=make_p.inner
def content():
    return "人生苦短"


if __name__ == '__main__':
    result = content()
    print(result)  # 先调p再div


输出结果: # 装饰顺序和调用顺序完全相反
调用装饰器make_p外层			---》concent=inner1
调用装饰器make_div外层		---》concent=inner2
调用装饰器make_div内层
调用装饰器make_p内层
<div><p>人生苦短</p></div>

# 总结多个装饰器装饰一个函数时,先装饰后执行代码内容

11.7 带有参数的装饰器

# 装饰器外部嵌套一个函数实现闭包,使闭包接收到参数
# 因为一个装饰器的外层必须只有一个参数,并且该参数是一个函数,所以如果直接调用装饰器并且传参时就会出现错误,为了解决这个问题就可以在装饰器外嵌套一个函数,用来接收参数,并把装饰器做成一个闭包函数;

def logging(flag):				# 用来形成闭包函数,接收参数 flag="+" flag="-"
    def decorator(fn):		# 这是一个装饰器,也是一个闭包函数
        def inner(a, b):
            print("扩展功能模块")
            if flag == "+":
                print("正在做加法")
            elif flag == "-":
                print("正在做减法")

            result = fn(a, b)
            return result

        return inner

    return decorator

# 执行顺序 logging("+") -> @decorator -> add=decorator(add) -> add=inner
@logging("+")  
def add(n1, n2):
    result = n1 + n2
    return result

# 执行顺序 logging("-") -> @decorator -> sub=decorator(sub) -> sub=inner
@logging("-")
def sub(n1, n2):
    result = n1 - n2
    return result

if __name__ == '__main__':
    aa = add(1, 2)
    bb = sub(2, 1)
    print(aa, bb)

11.8 类装饰器的使用

class FuncOut(object):
    def __init__(self, fn):					# fn=comment
        self.__fn = fn							# self.__fn=comment

    def __call__(self, *args, **kwargs):
        print("对象被调用了")					# 扩展功能
        res = self.__fn(*args, **kwargs)
        print("调用完毕")
        return res

# comment=FuncOut(comment) 	comment是FuncOut类的对象,如果没有装饰器直接调用函数是会报错的
@FuncOut  
def comment():
    print("原函数:发表评论")

if __name__ == '__main__':
    comment()				 #对象直接被调用时会执行__call__魔法方法;comment.__call__()

12. 正则表达式

12.1 property属性
"property属性可以把方法当作属性使用,例如True和Flase 关键字其实就是一个函数方法,但在使用时都是当作属性来使用的"
访问属性一样访问方法,
p.age()
p.age				# property属性起到省略一个括号的作用

1. property属性的介绍:property属性就是负责把一个方法当作属性进行使用,这样做可以简化代码使用。

  • 定义property属性有两种方式
    • 装饰器方式
    • 类属性方式
  • 装饰器方式:
    • @property 修饰获取值的方法
    • @方法名.setter 修饰设置值的方法
  • 类属性方式:
    • 类属性=property(获取值方法,设置值方法)

装饰器方式定义 property 属性:

class Person(object):

    def __init__(self):
        self.__age = 0 #这是一个私有属性,外部不能直接调用,只能内部用函数调用,再以函数为接口进行访问

    # 装饰器方式的property, 把age方法当做属性使用, 表示当获取属性时会执行下面修饰的方法
    @property
    def age(self):         # 私有属性的访问接口
        return self.__age

    # 把age方法当做属性使用, 表示当设置属性时会执行下面修饰的方法
    @age.setter
    def age(self, new_age):   #私有属性的设置方法,修改私有属性
        if new_age >= 150:
            print("成精了")
        else:
            self.__age = new_age

# 创建person
p = Person()
print(p.age)
p.age = 100
print(p.age)
p.age = 1000

代码说明:
1. @property 表示把方法当作属性使用,表示当获取属性时会执行下面修饰的方法
2. @方法名.setter 表示把方法当作属性使用,表示当设置属性时会执行下面修饰的方法
3. 装饰器方式的 property 属性修饰的方法名一定要一样

类属性方式定义property属性:

#   类属性方式
class Person(object):
    def __init__(self):
        self.__age = 0								#__age是私有属性,不能被外部调用,但可以被内部函数调用
    def get_age(self):								# 内部函数调用私有属性
        print("调用读方法")
        return self.__age
    def set_age(self, new_age):				# 定义修改内部属性的方法
        print("调用写方法")
        self.__age = new_age
    age = property(get_age, set_age)		# 利用property方法设置类属性

if __name__ == '__main__':
    p1 = Person()
    # print(p1.age())

    print(p1.age)
    p1.age = 100

    print(p1.age)
    
  代码说明:
  property 的参数说明:
  	第一个参数是获取属性时要执行的方法
    第二个参数是设置属性时要执行的方法
12.2 with语句和上下文管理器

​ 总结: Python提供了 with 语句用于简化资源释放的操作,使用 with 语句操作建立在上下文管理器(实现 _enter_ 和_exit_) 的基础上

12.2.1 with语句的使用
  • with语句在打开文件中的使用,会简化代码,with 语句执行完成以后会自动调用关闭文件操作,即使出现异常也会自动调用关闭文件操作

  • with 语句示例代码

    1. 以读写的方式打开文件
    with open("test.txt", "rw") as f:
    		2. 写内容到文件中
    		f.write("hello world")
        
     """
     上述 with 代码解读:
     1. open("1.txt","w"):返回一个上下文管理器对象
     2. with 对象 :自动调用上下文方法,获取到文件内的资源
     3. as f  :设置获取的资源名为 f
     4. f.write() : 文件写操作
     5. with 语句结束:自动调用下文方法,释放资源执行close关闭文件
     """
    
  • 不使用with语句的用法(代码冗长):

    try: 
      	f = open("test.txt", "r")					# 只读方式打开文件
        f.write("xxxxxxxx")								# 在只读模式下写入内容
        
    except IOError as e:									# 捕获异常后执行的代码
      	print("文件操作出错", e)
        
    finally:															# 无论异常是否捕获都执行的代码
      	f.close()
    
12.2.2上下文管理器
  • 一个类只要实现了 _enter_() 和 _exit_() 这个两个方法(魔法方法),通过该类创建的对象我们就称之为上下文管理器。

  • 上下文管理器可以使用 with 语句,with语句之所以这么强大,背后是由上下文管理器做支撑的,也就是说刚才使用 open 函数创建的文件对象就是一个上下文管理器对象。

自定义上下文管理器类,模拟实现with的文件操作:

# with: with语句需要和上下文管理器配合使用
#   1. with开始时,上文函数__enter__会被自动调用
#   2. with结束时,下文函数__exit__会被自动调用

# 上下文管理器: 一个类中实现了__enter__和__exit__ 方法,这个类的对象是上下文管理器

class File(object):

    def __init__(self, filename, filemode):
        self.__filename = filename
        self.__filemode = filemode

    # 上文函数
    def __enter__(self):
        print("进入上文函数")
        self.__file = open(self.__filename, self.__filemode)
        return self.__file

    # 下文函数, 下面的with语句结束就会自动执行下文函数
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("进入下文函数")
        self.__file.close()

    # exc_type, exc_val, exc_tb 表示异常信息,执行错误时可以print打印这三个参数,就能显示异常信息,return True 就可以处理异常,也就是不打印异常出来
if __name__ == '__main__':
    # open: 能够使用with实现自动关闭文件资源, open返回的对象是上下文管理器
    # with open("1.txt", "w") as file:
    #     file.write("hello world")

    # F1 = File("1.txt", "r")
    #
    # with F1 as file:
    #     data = file.read()
    #     print(data)
    #
    with File("1.txt", "r") as file:	# with语句后的File函数调用上文函数,它的返回值赋给 file变
        data = file.read()		# 量,然后执行 print 后自动调用__exit__下文函数
        print(data)
# 执行结果是:
进入上文函数
hello world
进入下文函数


"代码说明:"
__enter__ 表示上文方法,需要返回一个操作文件对象
__exit__ 表示下文方法,with语句执行完成会自动执行,即使出现异常也会执行该方法
12.3 生成器的创建方式

python的三大器,迭代器(for 循环遍历的列表,元组,字典等容器都是迭代的效果),生成器,装饰器

  • 生成器的介绍:根据程序猿指定的规则循环生成数据,当条件不成立时则生成数据结束。数据不是一次性全部生成出来,而是使用一个,再生成一个,可以节约大量的内存

  • 创建生成器的方式

    • 生成器推导式
    • yield 关键字

    生成器推导式:与列表推导式类似,只不过生成器推导使用小括号

    # 列表推导式:一次性生成所有数据(消耗内存)
    my_list = [x*2 for x in range(5)]
    print my_list     # [0, 2, 4, 6, 8] 列表推导式会一次性打印所有结果
    
    =======================================================================
    
    # 生成器与列表推导式不同,是会将生成的数据存入生成器对象中,需要逐个取出
    # 生成器:按照程序的规则生成数据,使用一个数据,生成一个数据,节约内存空间
    # 创建生成器
    my_generator = (i * 2 for i in range(5))
    print(my_generator)  # 打印出生成器对象 <generator object <genexpr> at 0x1139347b0>
    
    # next获取生成器下一个值
    value = next(my_generator)
    print(value)    #调用一次只会打印生成器中的一个元素;打印 2
    value = next(my_generator)
    print(value)    #调用一次只会打印生成器中的一个元素;打印 4
    
    
    # 遍历生成器
    for value in my_generator:		# 从内存空间中一个一个取出数据并打印,依次打印所有元素
        print(value)							# 打印 0,2,4,6,8
        
        
    代码说明:
    next 函数获取生成器中的下一个值
    for 循环遍历生成器中的每一个值
    

    yield 关键字:

    • 只要在def函数里面看到有 yield 关键字那么就是生成器

      def mygenerater(n):				# 只要函数里面有yield就是一个生成器
          for i in range(n):
              print('开始生成...')
              yield i
              print('完成一次...')
      
      
      if __name__ == '__main__':
      
          g = mygenerater(2)
          # 获取生成器中下一个值。next执行一次就会停在 函数的yield上,不再执行,直到下次启动
          # result = next(g)
          # print(result)
      
          # while True:
          #     try:
          #         result = next(g)
          #         print(result)
          #     except StopIteration as e:
          #         break
      
          # # for遍历生成器, for 循环内部自动处理了停止迭代异常,使用起来更加方便
          for i in g:
              print(i)
              
              
        代码说明:
        1.代码执行到 yield 会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
        2.生成器如果把数据生成完成,再次获取生成器中的下一个数据会抛出一个Stoplteration 异常,表示停止迭代异常
        3.while 循环内部没有处理异常操作,需要手动添加处理异常操作
        4.for 循环内部自动处理了停止迭代异常,使用起来更加方便,推荐使用
      
    • 生成器的使用--斐波拉契数列

      使用生成器生成斐波那契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可以由前两个数相加得到:0,1,1,2,3,5,8,13,21....

      现在使用生成器来实现斐波那契数列,每次取值都通过算法来生成下一个数据,生成器每次调用只生成一个数,可以节约大量的内存

      def fibonacci(num):
          a = 0
          b = 1
      
          # 记录生成fibonacci数字的下标
          current_index = 0
      
          while current_index < num:
              result = a
              a, b = b, a + b						# 先组包再拆包
              current_index += 1
              # 代码执行到yield会暂停,然后把结果返回出去,下次启动生成器会在暂停的位置继续往下执行
              yield result
      
      
      fib = fibonacci(5)
      # 遍历生成的数据
      for value in fib:
          print(value)
      
12.4 深拷贝和浅拷贝

​ ======================= 重点 ========================

  • 浅拷贝使用copy.copy函数
    • 不可变类型的浅拷贝:是引用,不会拷贝
    • 可变类型的浅拷贝:看第一层,最多也就拷贝一层
  • 深拷贝使用copy.deepcopy函数

    • 不可变类型的深拷贝:拷贝到有可变类型的那一层,没有则是引用,内存就地址相同

    • 可变类型的深拷贝:拷贝到有可变类型的那一层

      ​ ======================================================

  • 不管是给对象进行深拷贝还是浅拷贝,只要拷贝成功就会开辟新的内存空间存储拷贝的对象。

  • 浅拷贝和深拷贝的区别是:

    • 浅拷贝最多拷贝对象的一层,深拷贝可能拷贝对象的多层。
    • 深拷贝时如果列表中有可变类型,则拷贝到可变类型那一层,如果可变类型中有不可变类则不会拷贝
  • 浅拷贝:copy函数是浅拷贝,只对可变类型的第一层对象进行拷贝,对拷贝的对象开辟新的内存空间进行存储,不会拷贝对象内部的子对象。

  • 补充:

    • 可变类型:列表,字典,集合
      特性:在地址不变的情况下,可以修改内容 #(修改内容,内存指向不变)
    1. 不可变类型:数字类型(int, bool, float), 字符串,元组
      特性:在地址不变的情况下,不可修改内容; #(修改内容,内存地址指向改变)

    不可变类型的浅拷贝示例:

    import copy  # 使用浅拷贝需要导入copy模块
    
    # 不可变类型有: 数字、字符串、元组
    
    a1 = 123123
    b1 = copy.copy(a1)  # 使用copy模块里的copy()函数就是浅拷贝了
    # 查看内存地址
    print(id(a1))
    print(id(b1))
    
    print("-" * 10)
    a2 = "abc"
    b2 = copy.copy(a2)
    # 查看内存地址
    print(id(a2))
    print(id(b2))
    
    print("-" * 10)
    a3 = (1, 2, ["hello", "world"])
    b3 = copy.copy(a3)
    # 查看内存地址
    print(id(a3))
    print(id(b3))
    
    # 执行结果
    140459558944048
    140459558944048
    ----------
    140459558648776
    140459558648776
    ----------
    140459558073328
    140459558073328
    
    
    # 不可变类型的浅拷贝说明:
    通过上面的执行结果可以得知,不可变类型进行浅拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用。
    
    

    可变类型的浅拷贝示例代码:

    import copy # 使用浅拷贝需要导入copy模块
    
    # 可变类型有: 列表、字典、集合
    
    a1 = [1, 2]
    b1 = copy.copy(a1) # 使用copy模块里的copy()函数就是浅拷贝了
    # 查看内存地址
    print(id(a1))
    print(id(b1))
    print("-" * 10)
    a2 = {"name": "张三", "age": 20}
    b2 = copy.copy(a2)
    # 查看内存地址
    print(id(a2))
    print(id(b2))
    print("-" * 10)
    a3 = {1, 2, "王五"}
    b3 = copy.copy(a3)
    # 查看内存地址
    print(id(a3))
    print(id(b3))
    
    print("-" * 10)
    a4 = [1, 2, [4, 5]]
    # 注意:浅拷贝只会拷贝父对象,不会对子对象进行拷贝
    b4 = copy.copy(a4) # 使用copy模块里的copy()函数就是浅拷贝了
    # 查看内存地址
    print(id(a4))
    print(id(b4))
    print("-" * 10)
    # 查看内存地址
    print(id(a4[2]))
    print(id(b4[2]))
    
    # 修改数据
    a4[2][0] = 6
    
    # 子对象的数据会受影响
    print(a4)
    print(b4)
    
    # 执行结果
    139882899585608
    139882899585800
    ----------
    139882919626432
    139882919626504
    ----------
    139882919321672
    139882899616264
    ----------
    139882899587016
    139882899586952
    ----------
    139882899693640
    139882899693640
    [1, 2, [6, 5]]
    [1, 2, [6, 5]]
    
    # 可变类型的浅拷贝说明:
    通过上面的执行结果可以得知,可变类型进行浅拷贝只对可变类型的第一层对象进行拷贝,对拷贝的对象会开辟新的内存空间进行存储,子对象不进行拷贝。
    
    
    • 深拷贝: deepcopy函数是深拷贝, 只要发现对象有可变类型就会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。

      不可变类型的深拷贝示例:

      import copy  # 使用深拷贝需要导入copy模块
      
      # 不可变类型有: 数字、字符串、元组
      
      a1 = 1
      b1 = copy.deepcopy(a1)  # 使用copy模块里的deepcopy()函数就是深拷贝了
      # 查看内存地址
      print(id(a1))
      print(id(b1))
      print("-" * 10)
      a2 = "张三"
      b2 = copy.deepcopy(a2)
      # 查看内存地址
      print(id(a2))
      print(id(b2))
      print("-" * 10)
      a3 = (1, 2)
      b3 = copy.deepcopy(a3)
      # 查看内存地址
      print(id(a3))
      print(id(b3))
      print("-" * 10)
      
      # 注意: 元组里面要是有可变类型对象,发现对象有可变类型就会该对象到最后一个可变类型的每一层对象进行拷贝
      a4 = (1, ["李四"])
      b4 = copy.deepcopy(a4)
      # 查看内存地址
      print(id(a4))
      print(id(b4))
      # 元组里面的可变类型子对象也会进行拷贝
      print(id(a4[1]))
      print(id(b4[1]))
      
      运行结果:
      9289120
      9289120
      ----------
      140115621848320
      140115621848320
      ----------
      140115621859592
      140115621859592
      ----------
      140115602480584
      140115621834568
      140115602328136
      140115602436168
      

不可变类型的深拷贝说明:

  • 通过上面的执行结果可以得知:不可变类型进行深拷贝如果子对象没有可变类型则不会进行拷贝,而只是拷贝了这个对象的引用,否则会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储

可变类型的深拷贝说明:

  • 通过上面的执行结果可以得知, 可变类型进行深拷贝会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储。
12.5 正则表达式的概述
  • 正则表达式是匹配符合某些规则的字符串数据

    re 模块源码解释说明:
    The special characters are:
        "."      Matches any character except a newline.
        "^"      Matches the start of the string.
        "$"      Matches the end of the string or just before the newline at
                 the end of the string.
        "*"      Matches 0 or more (greedy) repetitions of the preceding RE.
                 Greedy means that it will match as many repetitions as possible.
        "+"      Matches 1 or more (greedy) repetitions of the preceding RE.
        "?"      Matches 0 or 1 (greedy) of the preceding RE.
        *?,+?,?? Non-greedy versions of the previous three special characters.
        {m,n}    Matches from m to n repetitions of the preceding RE.
        {m,n}?   Non-greedy version of the above.
        "\\"     Either escapes special characters or signals a special sequence.
        []       Indicates a set of characters.
                 A "^" as the first character indicates a complementing set.
        "|"      A|B, creates an RE that will match either A or B.
        (...)    Matches the RE inside the parentheses.
                 The contents can be retrieved or matched later in the string.
        (?aiLmsux) The letters set the corresponding flags defined below.
        (?:...)  Non-grouping version of regular parentheses.
        (?P<name>...) The substring matched by the group is accessible by name.
        (?P=name)     Matches the text matched earlier by the group named name.
        (?#...)  A comment; ignored.
        (?=...)  Matches if ... matches next, but doesn't consume the string.
        (?!...)  Matches if ... doesn't match next.
        (?<=...) Matches if preceded by ... (must be fixed length).
        (?<!...) Matches if not preceded by ... (must be fixed length).
        (?(id/name)yes|no) Matches yes pattern if the group with id/name matched,
                           the (optional) no pattern otherwise.
    The special sequences consist of "\\" and a character from the list
    below.  If the ordinary character is not on the list, then the
    resulting RE will match the second character.
        \number  Matches the contents of the group of the same number.
        \A       Matches only at the start of the string.
        \Z       Matches only at the end of the string.
        \b       Matches the empty string, but only at the start or end of a word.
        \B       Matches the empty string, but not at the start or end of a word.
        \d       Matches any decimal digit; equivalent to the set [0-9] in
                 bytes patterns or string patterns with the ASCII flag.
                 In string patterns without the ASCII flag, it will match the whole
                 range of Unicode digits.
        \D       Matches any non-digit character; equivalent to [^\d].
        \s       Matches any whitespace character; equivalent to [ \t\n\r\f\v] in
                 bytes patterns or string patterns with the ASCII flag.
                 In string patterns without the ASCII flag, it will match the whole
                 range of Unicode whitespace characters.
        \S       Matches any non-whitespace character; equivalent to [^\s].
        \w       Matches any alphanumeric character; equivalent to [a-zA-Z0-9_]
                 in bytes patterns or string patterns with the ASCII flag.
                 In string patterns without the ASCII flag, it will match the
                 range of Unicode alphanumeric characters (letters plus digits
                 plus underscore).
                 With LOCALE, it will match the set [0-9_] plus characters defined
                 as letters for the current locale.
        \W       Matches the complement of \w.
        \\       Matches a literal backslash.
    
    This module exports the following functions:
        match     Match a regular expression pattern to the beginning of a string.
        fullmatch Match a regular expression pattern to all of a string.
        search    Search a string for the presence of a pattern.
        sub       Substitute occurrences of a pattern found in a string.
        subn      Same as sub, but also return the number of substitutions made.
        split     Split a string by the occurrences of a pattern.
        findall   Find all occurrences of a pattern in a string.
        finditer  Return an iterator yielding a Match object for each match.
        compile   Compile a pattern into a Pattern object.
        purge     Clear the regular expression cache.
        escape    Backslash all non-alphanumerics in a string.
    
    Each function other than purge and escape can take an optional 'flags' argument
    consisting of one or more of the following module constants, joined by "|".
    A, L, and U are mutually exclusive.
        A  ASCII       For string patterns, make \w, \W, \b, \B, \d, \D
                       match the corresponding ASCII character categories
                       (rather than the whole Unicode categories, which is the
                       default).
                       For bytes patterns, this flag is the only available
                       behaviour and needn't be specified.
        I  IGNORECASE  Perform case-insensitive matching.
        L  LOCALE      Make \w, \W, \b, \B, dependent on the current locale.
        M  MULTILINE   "^" matches the beginning of lines (after a newline)
                       as well as the string.
                       "$" matches the end of lines (before a newline) as well
                       as the end of the string.
        S  DOTALL      "." matches any character at all, including the newline.
        X  VERBOSE     Ignore whitespace and comments for nicer looking RE's.
        U  UNICODE     For compatibility only. Ignored for string patterns (it
                       is the default), and forbidden for bytes patterns.
    
    This module also defines an exception 'error'.
    
    
12.6 re的模块介绍
  • re.match() 根据正则表达式从头开始匹配字符串数据
# 导入re模块
import re

# 使用match方法进行匹配操作
result = re.match(正则表达式,要匹配的字符串)

# 如果上一步匹配到数据的话,可以使用group方法来提取数据
result.group()


示例:
import re

result = re.match(".*t*", "itcast")
info = result.group()						# group返回匹配成功的字符串
print(info)

返回结果:
itcast

12.7 匹配单个字符
代码 功能
. 匹配任意1个字符(除了\n)
[ ] 匹配[ ]中列举的字符
\d 匹配数字,即0-9
\D 匹配非数字,即不是数字
\s 匹配空白,即 空格,tab键,换行
\S 匹配非空白
\w 匹配非特殊字符,即a-z、A-Z、0-9、_、汉字
\W 匹配特殊字符,即非字母、非数字、非汉字
12.8 匹配多个字符
代码 功能
* 匹配前一个字符出现0次或者无限次,即可有可无
+ 匹配前一个字符出现1次或者无限次,即至少有1次
? 匹配前一个字符出现1次或者0次,即要么有1次,要么没有
匹配前一个字符出现m次
匹配前一个字符出现从m到n次

贪婪匹配:.* 满足的字符越多越好,re.match 方法默认使用贪婪匹配

非贪婪匹配:.*? 满足的字符越少越好,匹配到第一个就停止

12.9 匹配开头和结尾
代码 功能
^ 匹配字符串开头
$ 匹配字符串结尾
12.10 匹配分组
代码 功能
| 匹配左右任意一个表达式
(ab) 将括号中字符作为一个分组
\num 引用分组num匹配到的字符串
(?P<name>) 分组起别名
(?P=name) 引用别名为name分组匹配到的字符串
result = re.match("(qq):(\d{4,10})", "qq:123456")
    if result:
        print("匹配成功", result.group())
        print("匹配成功", result.group(1))
        print("匹配成功", result.group(2))

    else:
        print("匹配失败")

        
    # 需求5:匹配出<html><h1>www.itcast.cn</h1></html>                                       # \\ : 表示字符\,第一个\转义第二个\, 把第二个\的特殊意义去掉;                                 # \\1: 表示字符\1, 表示引用分组1匹配的字符串                                                 # \\2: 表示字符\2, 表示引用分组2匹配的字符串                              
    result = re.match("<([a-zA-Z1-6]+)><([a-zA-Z0-9]+)>.*</\\2></\\1>", "<html><h1>www.itcast.cn</h1></html>")                               
    if result:                                                                               	print("匹配成功", result.group())                                                   	else:                                                                                      print("匹配失败")                                                                                                                        
# (?P<name>)分组起别名                                                   
# (?P=name)	引用别名为name分组匹配到的字符串                                          
# 需求6:匹配出<html>h1>www.itcast.cn</h1></html>                            
result = re.match("<(?P<name1>[a-zA-Z1-6]+)><(?P<name2>[a-zA-Z0-9]+)>.*</(?P=name2)></(?P=name1)>", "<html><h1>www.itcast.cn</h1></html>"
                  if result:                              
                  print("匹配成功", result.group())                                                    else:                                                                                     print("匹配失败")                                                

13. mini-Web框架

  • 静态资源:可以提前准备的,不会发生变化的资源(png,jpg,css,js)
  • 动态资源:不可以提前准备,经常发生变化的资源(.html)
  • Web框架:为web服务器提供动态请求服务的程序
  • WSGI协议:(Web Server Gateway Interface )web服务器和web框架的通信约定

13.1 web框架概述

  • 前面已经学习过web服务器, 我们知道web服务器主要是接收用户的http请求,根据用户的请求返回不同的资源数据,但是之前我们开发的是静态web服务器,返回的都是静态资源数据,假如我们想要web服务器返回动态资源那么该如何进行处理呢?

  • 这里我们给大家介绍一个web框架,使用web框架专门负责处理用户的动态资源请求,这个web框架其实就是一个为web服务器提供服务的应用程序,简称web框架。

  • 静态资源:不需要经常变化的资源,这种资源web服务器可以提前准备好,比如: png/jpg/css/js等文件。

  • 动态资源:和静态资源相反, 这种资源会经常变化,比如: 我们在京东浏览商品时经常会根据条件进行筛选,选择不同条件, 浏览的商品就不同,这种资源web服务器无法提前准备好,需要web框架来帮web服务器进行准备,在这里web服务器可以把.html的资源请求认为是动态资源请求交由web框架进行处理。

  • WSGI协议:它是web服务器和web框架之间进行协同工作的一个规则,WSGI协议规定web服务器把动态资源的请求信息传给web框架处理,web框架把处理好的结果返回给web服务器。

    ![image-20201211112830824](/Users/luojie/Library/Application Support/typora-user-images/image-20201211112830824.png)

13.2 框架程序开发

  • 框架职责介绍:接收web服务器的动态资源请求,给web服务器提供处理动态资源请求的服务
  • 动态资源判断:
    • 根据请求资源路径的后缀名进行判断
      • 如果请求资源路径的后缀名是.html 则是动态资源请求,让web框架程序进行处理
      • 否则是静态资源请求,让web服务器程序进行处理

13.3 模块替换功能开发

  • 思路:在前端 index.html 模版中定义一个变量用于读取时替换,在后端函数中with open 打开index.html 文件对变量进行替换,代码实现如下:

    def center():
        # 1. 状态码 200 OK
        status = "200 OK"
    
        # 2. 响应头部信息  Server: PWS/2.0
        headers = [("Server", "PWS/2.0")]
    
        # 3. 响应体数据
        #   ctime: 返回当前时间的字符串; 模拟数据查询到的数据
        data = time.ctime()
    
        # 1.打开模板文件
        with open("template/center.html", "r") as file:
            file_data = file.read()
    
        # 2.替换模板中的变量为数据
        # replace: 字符串替换函数
        data = file_data.replace("{%content%}", data)
    
        return status, headers, data
    
    

13.4 路由列表功能开发

  • 在没有路由表时。前端发来的请求通过服务器截取出请求路径,再到后端处理函数中进行判断,如果路径名等于某个存在的路径名就执行某个对应的处理函数,达到响应请求的效果

  • 有路由表时,单独定义一个列表,其中存储的内容是,请求路径和处理函数的对应映射关系,在路由列表的基础上加上循环,就可以起到动态处理效果,也可以起到解耦,分离的效果,代码示例如下:

    # 定义路由列表
    route_list = [
        ("/index.html", index),
        ("/center.html", center)
    ]
    
    # 处理动态资源请求
    def handle_request(env):
        # 获取动态请求资源路径
        request_path = env["request_path"]
        print("接收到的动态资源请求:", request_path)
        # 遍历路由列表,选择执行的函数
        for path, func in route_list:
            if request_path == path:
                result = func()
                return result
        else:
            # 没有找到动态资源
            result = not_found()
            return result
    

13.5 装饰器方式的添加路由(路由的另一种定义方式)

  • 将上述的路由列表添加封装到装饰器中,并且可以接收参数,在使用装饰器时需要传递路径参数,给处理函数添加路由装饰器达到响应数据效果,代码示例如下:

    """WAGI 后端框架app 处理完整代码"""
    import time
    import pymysql
    import logging
    
    route_list = []
    
    
    # 闭包函数接收装饰器参数
    def route(path):
        def decorator(fn):
            route_list.append((path, fn))
    
            def inner():
                fn()
    
            return inner
    
        return decorator
    
    
    @route("/index.html")
    def index():
        status = "200 OK"
        headers = [("server", "NBA/1.0")]
        data = time.ctime()
    
        with open("template/index.html", "r") as f:
            file_data = f.read()
    
        conn = pymysql.connect(host="127.0.0.1",
                               port=3306,
                               user="root",
                               password="",
                               database="stock_db",
                               charset="utf8")
        cs = conn.cursor()
        sql = "select * from info;"
        row = cs.execute(sql)
        # print("查询到的数据行数是:", row)
        logging.info("浏览器查询到的数据行数:" + str(row))
        conn.commit()
    
        result = cs.fetchall()
        print(result)
        cs.close()
        conn.close()
    
        data = ""
    
        # 将查询到的数据替换到index.html模版中
        for item in result:
            data += """<tr>
                    <td>%s</td>
                    <td>%s</td>
                    <td>%s</td>
                    <td>%s</td>
                    <td>%s</td>
                    <td>%s</td>
                    <td>%s</td>
                    <td>%s</td>
                    <td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007"></td>
                </tr>""" % item
    
        data = file_data.replace("{%content%}", data)
        return status, headers, data
    
    """
    @route("/center.html")
    def center():
        status = "200 OK"
        headers = [("server", "NBA/1.0")]
        data = time.ctime()
    
        with open("template/center-base.html", "r") as f:
            file_data = f.read()
    
        data = file_data.replace("{%content%}", data)
    
        return status, headers, data
    
    """
    # 个人数据接口(前后端不分离):浏览器访问路径为 /center_data.html 时返回该函数
    @route("/center.html")
    def center_data():
        status = "200 OK"
        headers = [("Server", "CBA/1.0"),
                   ("Content-Type", "text/html;charset=utf-8")]
    
        with open("template/center.html", "r", encoding="utf-8") as file:
            file_data = file.read()
    
        conn = pymysql.connect(host="127.0.0.1",
                               port=3306,
                               user="root",
                               password="",
                               database="stock_db",
                               charset="utf8")
        cs = conn.cursor()
        sql = "select i.code,i.short,i.chg,i.turnover," \
              "i.price," \
              "i.highs," \
              "f.note_info from info i inner join focus f on i.id=f.info_id;"
        cs.execute(sql)
        conn.commit()
        result = cs.fetchall()
        print(result)
        cs.close()
        conn.close()
    
        data = ""
    
        for item in result:
            data += """<tr>
                       <td>%s</td>
                       <td>%s</td>
                       <td>%s</td>
                       <td>%s</td>
                       <td>%s</td>
                       <td>%s</td>
                       <td>%s</td>
                       <td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007"></td>
                   </tr>""" % item
    
        data = file_data.replace("{%content%}", data)
    
        return status, headers, data
    
        # 把数据库中取出的数据发给前端js代码进行处理
    
    @route("/add/000007.html")
    def add():
        status = "200 OK"
        headers = [("server", "NBA/1.0")]
        data = time.ctime()
    
        conn = pymysql.connect(host="127.0.0.1",
                               port=3306,
                               user="root",
                               password="",
                               database="stock_db",
                               charset="utf8")
        cs = conn.cursor()
        sql = "insert into focus(info_id) select id from info where id=20;"
        row = cs.execute(sql)
        # print("查询到的数据行数是:", row)
        logging.info("添加数据到个人中心的行数:" + str(row))
        conn.commit()
    
        return status, headers, data
    
    def not_found():
        status = "404 NOT FOUND"
        headers = [("server", "NBA/1.0")]
        data = "404 NOT"
    
        return status, headers, data
    
    
    def handle_request(env):
        # env字典中取出客户端请求路径
        request_path = env["request_path"]
        print("客户端动态资源请求路径:", request_path)
    
        for path, fn in route_list:
            if request_path == path:
                result = fn()
                return result
    
        else:
            result = not_found()
            return result
    
    
    

13.6 显示股票信息页面的开发

  • 使用pymysql 连接数据库,将查询结果跟前端页面中变量进行替换,在上述完整代码中有示例

13.7 个人数据接口的开发

  • 【重点】将字典转换成json字符的语法格式

    # 示例:
    json_str = json.dumps(center_data_list, ensure_ascii=False)
    
    # 参数说明:
    json.dumps 函数把字典转成json字符串
    1.函数的第一个参数表示要把指定对象转换成json字符串
    2.函数的第二个参数ensure_ascii=False表示不使用ascii编码,可以在控制台显示中文
    
    

13.8 ajax请求数据渲染个人中心页面

  • 根据用户请求返回个人中心空模版文件数据
  • 在个人中心模板文件添加ajax请求获取个人中心数据
  • 将个人中心数据在页面完成展示

13.9 logging日志

  • 介绍:记录程序运行状况

  • 作用:1.程序运行情况 2. 记录用户喜好

  • logging 日志级别介绍

    日志等级分为5个,从低到高分别是:

    	1. DEBUG:程序调试时使用
    	2. INFO:程序正常运行时使用
    	3. WARNING:程序未按照预期运行时使用,但并不是错误,如:用户登录密码错误
      4. ERROR:程序出错误时使用,如:IO操作失败
      5. CRITICAL:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘满了,一般很少使用
    

    ​ 默认的是WARNING等级,当在WARNING或WARNING之上等级的才记录日志信息

    日志等级从低到高的顺序是:DEBUG < INFO < WARNING < ERROR < CRITICAL

    日志信息保存到日志文件的示例代码

    
    import logging
    
    # 日志输出格式的配置设置:
    logging.basicConfig(level=logging.DEBUG,
                        format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
                        filename="logg.txt",
                        filemode="w")
    
    # 日志打印
    logging.debug("这是一个debug程序调试日志")
    logging.info("这是一个info 程序正常运行日志")
    logging.warning("这是一个warning 非程序错误的日志")
    logging.error("这是一个error 程序错误日志")
    logging.critical("这是一个严重问题日志,会导致程序不能运行")
    
    
    # 日志输出格式代码说明:
    level 表示设置的日志等级
    format 表示日志的输出格式, 参数说明:
    		%(levelname)s: 打印日志级别名称
    		%(filename)s: 打印当前执行程序名
    		%(lineno)d: 打印日志的当前行号
    		%(asctime)s: 打印日志的时间
    		%(message)s: 打印日志信息
          
    说明:logging日志配置信息在程序入口模块设置一次,整个程序都可以生效。
    		logging.basicConfig 表示 logging 日志配置操作
    
    # 日志打印在代码中的示例:
    # 处理动态资源请求
     def handle_request(env):
         # 获取动态请求资源路径
         request_path = env["request_path"]
         print("接收到的动态资源请求:", request_path)
         # 遍历路由列表,选择执行的函数
         for path, func in route_list:
             if request_path == path:
                 result = func()
                 return result
         else:
             logging.error("没有设置相应的路由:" + request_path)
             # 没有找到动态资源
             result = not_found()
             return result
    

15. 错误总结:

1. scp命令格式是 scp -r 远程用户名@远程IP:文件路径 本地存放路径
2. chmod ugo+x a.txt	可以给所有用户对文件加执行权限
3. select * from student where id in (1,2,5); in 选择的是不连续的范围,between and 选择的是连续范围 where id between 3 and 5 表示(3,4,5)
4. select * from students group by id desc limit 1; 语句可以查询最新的一条记录
5.json 格式要求是[]中括号内部不能直接出现键值对,并且属性名必须使用引号包起来

6. $("#box img").eq(1) 用于获取id是"box"的元素中的子元素集,并从中取出编号为1的元素,由于编号从0开始,所以获取的第二个元素
7. $("#box").children()用于获取id是“box的元素中的所有子元素
8. $(".img1").prev()用于获取class属性包含"img1"的元素的同级上一个元素
9. $("#box").find(".img2") 表示先获取id是"box"的元素,然后在该元素的子元素中查找class属性是"img2"的元素
posted @ 2025-07-10 10:27  星河霓虹  阅读(21)  评论(0)    收藏  举报