0%

Python-logging模块

logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等等。

logging的优点:

  • 可以通过设置不同的日志等级,在release版本中只输出重要信息,而不必显示大量的调试信息;
  • print将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging则可以由开发者决定将信息输出到什么地方,以及怎么输出

未完待续

#Python中的pass语句

Python中的pass语句是空语句,是为了保持程序结构的完整性。

pass不做任何事情,一般用做占位语句。以为像定义一个空函数和空的if判断会把报错,所以pass语句还是很有用的。

Python函数装饰器

在函数中定义函数

Python中可以在函数中定义函数,需要注意的是:无论什么时候调用最外面的函数的时候,里面的函数都会被调用,但是里面的函数在外面函数之外是不能访问的

从函数中返回函数

返回一个函数时并没有函数名后面的小括号,这是为什么?

因为如果在函数名后面加上一对小括号,这个函数就会执行

将函数作为参数传递给另外一个函数

装饰器

装饰器让你在一个函数前后去执行代码

你的第一个装饰器

装饰器蓝本规范

image-20200729222240810



基础

字符串和编码

字符编码

8个bit(比特)作为一个byte(字节),一个字节能表示的最大整数就是255(二进制11111111=255)

可变长编码的UTF-8

总结: 在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-编码。

用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件

浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器

ord()函数: 用于获取字符的整数表示,chr()把编码转换为对应的字符

Python中的字符类型是str,在内存中以Unicode表示,一个字符对应若干个字节。在网络上传输或者保存到磁盘上,就需要将str变为以字节为单位的bytes。

Python对bytes类型的数据用带b前缀的单引号或双引号表示。

以Unicode表示的str通过encode()方法可以编码为指定的bytes。

纯英文的str可以用ascii编码为bytes;含有中文的str可以用utf-8编码为bytes;含有中文的str无法用ascii编码。

要把bytes变为str,可以用到decode()方法。

len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节流书。

1个中文3个字节,一个英文字符只占用1个字节

当Python解释器读取源代码时,为了让它按UTF-8编码读取,通常在文件开头写上这两行

1
2
#!/usr/bin/env python3
#-*- coding: utf-8 -*-

%x: 十六进制整数

使用%%来表示一个%

使用list和tuple

list

插入: insert(index, content)

删除:

  • pop —- 删除末尾的元素
  • pop(i)删除指定元素

元素替换: 直接用索引表示

list里面的元素的数据类型也可以不同

list元素也可以是另一个list

tuple: 元组

元组中除开appened(),insert()这样的方法,其他获取元素的方法和list是一样的。

元组不可变所以更安全,如果可能,尽量使用tuple代替list

特别注意:

1
2
3
# 像下面这样定义的不是元组而是一个整数
t = (1)
# 所以tuple中只有一个元素时,必须加上一个逗号, 用来消除歧义

特殊的tuple

image-20200723000207152

使用dict和set

dict

要避免key不存在的错误,有两种方法,一种是通过in判断key是否存在,一种是通过dict提供的get方法,如果key不存在,可以返回None,或者自己指定的value。

注意:返回None的时候Python的交互命令行不显示结果

删除一个key,使用pop(key)

dicet的缺点:需要占用大量的内存,内存浪费多

dict是用空间来换取时间的方法。

关于dict,需要牢记的一条就是dict的key必须是不可变对象

通过key计算位置的算法称为哈希算法

Python中:整数、字符串都是不可变的。而list是可变的

set

set也是一组key的集合,但是不存储value。set中,由于key不能重复,所以没有重复的key

创建一个set,需要提供一个list作为输入集合:

1
2
3
>>> s = set([1, 2, 3, 4, 5])
>>> s
{1, 2, 3, 4, 5}

想set中添加元素使用add(key),可以重复添加,但是不会有效果。

使用remove(key)方法可以删除元素

注意: set也需要放入不可变的对象

再议不可变对象

image-20200723002519612

高级特性

切片

L[start: end] : 索引包括start但是不包括end

倒数的第一个元素是-1

高级玩法:

L[start:end :space ]

L[:]用于复制一个原样list

tuple仍然可以使用切片,但是因为tuple是不可变的,所以使用切片后得到的还是tuple

字符串也可以使用切片,使用之后得到的仍然是字符串

迭代

给定一个list或者tuple,使用for循环来遍历这个list或tuple,这种遍历我们称之为遍历(lteration)

Python中,迭代是通过for … in 来完成的,相比java,python的for循环抽象程度要更高,因为Python中的for循环不仅可以在list或tuple上,还可以作用在其他可迭代对象上。

只要是可迭代对象,不管它有没有下标,都可以使用迭代:

例如dict就可以迭代,但是dict的存储不是按照list的方式顺序排列,所以,迭代出的结果的顺序很可能不一样。默认情况下,dict迭代的是key,如果要迭代value,可以用for value in d.values(),如果同时迭代key和value,可以使用for k, v in d.items():

如何判断一个对象是可迭代对象呢? 可通过collections模块的lterable类型判断:

1
2
from collection.abc import Iterable 
isinstance("123", Iterable)

如何让list实现类似java那样的下标循环?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

1
2
for i, value in enumerate(["A", 'B','C' ]):
print(i, value)

小结: 任何可迭代对象都可以作用于for循环,包括我们自定义的数据类型

列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

生成器

在Python中,这种一边循环一边计算大的机制,称为生成器:generator

创建一个generator有很多种方法,第一种方法就是把一个列表生成式的[]改成()。

使用next()可以获得geneartor的下一个返回值,当没有更多元素时,抛出StopIteration的错误。

Generator 也是可迭代对象

定义generator的另一种方法:

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

1
2
3
4
5
6
7
8
9
# generator生成的另一种方法
def fib1(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n += 1

return "done"

较难理解的就是generator和函数的执行流程不一样。generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

使用for循环调用generator时,发现拿不到generator的return语句中的返回值。如果想要拿到返回值,必须捕获到StopIteration错误,返回值包含在StopIteration的value中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Exercise 写出一个关于杨辉三角形的generator
def yH(max):
list9 = []
list8 = []
index = 1
while index < max:
if index == 1:
list8 = [1]
yield list8
index = index + 1
elif index == 2:
list8 = [1, 1]
yield list8
index = index + 1
else:
list9 = [None] * index
for I in range(1, index - 1):
list9[I] = list8[I - 1] + list8[I]
list9[0] = 1
list9[index - 1] = 1
yield list9
index += 1
list8 = list9


g1 = yH(11)
for n in g1:
print(n)

迭代器

可以直接作用于for循环的对象统称为可迭代对象: Iterable

可以被next()函数调用并不断返回下一个值的对象称为迭代器: Iterator

可以使用isinstance()判断一个对象是不是Iterator对象

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

Python的Iterator对象是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。

可以把数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。

image-20200724141020012

函数式编程 - Functional Programming

通过把大段代码拆成函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元

理解计算机和计算的概念

在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最接近计算机的语言。

而计算则指的是数学意义上的计算,越是抽象的计算,离计算机硬件越远

对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

Python不是纯函数式编程语言。

高阶函数

高阶函数英文名: High-order function

abs(-10)是函数调用,abs是函数本身

1
2
3
4
5
6
7
f = abs
print(f(-10))

"""
输出:
10
"""

函数本身也可以赋值给变量,即:变量可以指向函数

传入函数

一个函数可以接受另一个函数作为参数,这种函数就称之为高阶函数

一个最简单的高阶函数:

1
2
def add(x, y, f):
return f(x) + f(y)

高阶函数中作为参数的函数只需要函数名,不需要后面的括号

map/reduce

map

map()函数接收两个参数,一个是函数,一个是Iterable,map函数将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

map()作为高阶函数,事实上它把运算规则抽象了。

下面将一个list中的所有数字转为字符串:

1
list(map(str, [1, 2, 3, 4, 5, 6, 7, 9]))

reduce

reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,ruduce把结果继续和序列的下一个元素做累积计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 关于reduce的用法
# 实现累加
def add(x, y):
return x + y

redce_detail = reduce(add, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(redce_detail)

# 实现将一个序列中的所有数字变成一个整数
def changeToInteger(x, y):
return x * 10 + y
list_for_str = [1, 2, 3, 4, 5, 6, 7]
integer = reduce(changeToInteger, list_for_str)
print(integer)

# 使用reduce和map()函数,写出将str转换为int的函数:
def fn(x, y):
return x * 10 + y

def char2num(s):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
# 上一行末尾的[s]是什么意思?(大概意思我是了解了)
str2int = reduce(fn, map(char2num, "13579"))
print(str2int)

filter

Python内建的filter()函数用于过滤序列

map()类似,filter()也接收一个函数和一个序列。和map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用到list()函数获得所有结果并返回一个list

用filter求素数:但是没有怎么看懂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 用filter求素数 -- 没有怎么看懂
# 首先定义一个从3开始的奇数序列:
def _odd_iter():
n = 1
while True:
n = n + 2
yield n

# 然后定义一个筛选函数
def _not_divisible(n):
return lambda x: x % n > 0
# 定义一个生成器,不断返回下一个素数
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it)
yield n
it = filter(_not_divisible(n),it) # 构造新序列

# 输出结果:
print("1000以内的所有素数: ")
for n in primes():
if n< 1000:
print(n)
else:
break

匿名函数

Python中对匿名函数提供了有限支持

1
2
lambda x : x * x
# 上述匿名函数中,第一个x表示函数参数

匿名函数有一个限制,就是只能有一个表达式,不用谢retrun,返回值就是该表达式的结果

匿名函数没有名字,所以不用担心函数名冲突。匿名函数也是一个对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数;

也可以把匿名函数作为一个返回值返回。

sorted – 排序算法

排序的核心是比较两个元素的大小

对于字符串或者两个dict如何比较呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。

list排序

Python内置的sorted()函数就可以对list进行排序

sorted()是一个高阶函数,它可以接受一个key函数来实现自定义的排序:

key函数使用方法: key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。

字符串排序

默认情况下,对字符串进行排序,是按照ASCII的大小进行比较的。

如何实现排序时忽略大小写,按照字母序排序?实现这个算法,只需要将所有字符串都改成大写或者小写

如果要进行反向排序,不必改动key函数,只需要传入第三个参数: reverse=True

下面的练习题虽然我做出来了,但是我还是很蒙,有时间记得复习复习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 请用sorted()对上述列表分别按名字排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

def by_name(t):

return t[0]

L2 = sorted(L, key=by_name)
print(L2)
# 请用sorted()对上述列表分别按成绩高低排序
def by_score(t):
return t[1]

L3 = sorted(L, key=by_score)
print(L3)

返回函数

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回

对于一个求和函数,如果不需要立刻求和,而是在后面的代码中,根据需要再计算。这时就可以不返回求和的结果,而是返回求和的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'''
对于一个求和函数,如果不需要立刻求和,而是在后面的代码中,根据需要再计算。这时就可以不返回求和的结果,而是返回求和的函数
'''
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum

# 我们调用lazy_sum()时,返回的并不是求和结果而是求和函数
f = lazy_sum(1, 3, 5, 7, 9)
print(type(f))
print(f())

注意 :返回一个函数时,不需要加上函数名后面的括号

何为闭包?

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使用入相同的参数:

1
2
3
4
5
6
f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
print(f1 == f2)
'''输出结果
False
'''

f1()f2()的调用结果互不影响。

闭包–Closure

需要注意的是:返回的函数并没有立刻执行,而是直到它被调用之后才执行

返回闭包时牢记的一点就是: 返回函数不要引用任何循环变量,或者后续会发生变化的变量

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论循环变量后续如何更改,已绑定到函数参数的值不变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在闭包中使用循环变量
def count():
def f(j):
def g():
return j * j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs


f1, f2, f3 = count()
print(f1(), '', f2(), '', f3()

装饰器

函数对象有一个name属性,可以拿到函数的名字:

什么是装饰器

在代码运行期间动态增加功能的方式,称之为”装饰器”(Decorator)

本质上,decoator就是一个返回函数的高阶函数。

总感觉装饰器decorator有点难

小结

在OPP的设计模式中,decorator被称为装饰模式。OPP的装饰模式需要通过继承和组合来实现,而Python除了能支持OPP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

*这一小节的练习题答案参见大神做法: *

https://blog.csdn.net/GBA_Eagle/article/details/80764749

偏函数– Partial function

对于函数,通过设定参数的默认值,可以降低函数调用的难度。偏函数也可以做到这点

int()函数

int()函数可以把字符串转换为整数,默认为十进制转换

int()函数中有个base参数,默认为10,修改base可以让int()函数按照你希望的进制进行转换

转换大量二进制字符串的时候,每次都传入int(x, base=2)非常麻烦,这时候,可以自定义一个int2()函数,默认把base=2传进去,像下面这样:

1
2
def int2(x, base=2):
return int(x, base)

functools.partial就是帮助我们创建一个偏函数的,不需要自定义int2(),可以直接使用下面的代码创建一个新的函数int2():

1
2
3
import functools
int2 = functools.partial(int, base=2)
int2('1000000')

functools.partial的作用就是,把一个函数的某些参数固定住,返回一个新的函数

小结:

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

模块

sys模块

任何模块的第一个字符串,都被视为模块的文档注释

使用author变量可以添加作者姓名

1
__author__ = "YourName"

sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,第一个元素永远是该.py文件的文件名

在一个模块中,我们可能会定义很多函数和变量。对于其中一些函数和变量,我们是想用来给别人使用的,对于另外一些函数和变量,我们只想在模块内部使用。实现上述目标是通过_前缀来实现的。

正常的函数和变量名是公开的(public),可以直接被使用

下面的一段话没有读太懂:
类似**xxx**这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的**author****name**就是特殊变量,hello模块定义的文档注释也可以用特殊变量**doc**访问,我们自己的变量一般不要用这种变量名;

类似_abc这样的函数或变量就是非公开的(private),不应该被直接使用。

需要注意的是: private函数和变量是不应该被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数和变量,但是,从编程习惯上不应该引用private函数或变量

小结

外部不需要引用的函数全部定义为private,只有外部需要引用的函数才定义为public

安装第三方模块

安装使用pip或者pip3

模块搜索路径

加载一个模块的时候,Python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错。

默认情况下,搜索路径会存放在sys模块的path变量中

如何添加自己的搜索路径,有两种方法:

  1. 直接修改sys.path,在其中添加要搜索的目录

    1
    2
    import sys 
    sys.path.append('') # 在引号中添加要搜索的目录

    这种方法是在运行时修改,运行结束后失效

  2. 第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响

类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模版,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同

可以自由地给一个实例变量绑定属性:

1
2
3
# 给实例bart绑定一个name属性:
bart.name = 'Bart Simpson'
print(bart.name)

由于类可以起到模版的作用,因此可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去,通过定义一个特殊的init方法,可以在创建实例的时候,就把name,score等属性绑定上去

下面的需要重点理解一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,可以在创建实例的时候,
# 就把name,score等属性绑上去
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score


class Student1(object):
def __init__(self):
self.name = 'bowenkei'
self.score = 100

no1 = Student("chenqiaochu", 100)
no2 = Student1()
print("--------------------------")
print(str(no1.score) + " " + no1.name )
print(str(no2.score) + " " + no2.name )
# 注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身

self不需要传,Python解释器自己会把实例变量传进去:

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

数据封装

我们可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”封装起来了。这些封装数据的函数是和Student类本身关联起来的,我们称之为类的方法

访问限制

外部代码还是可以自由地修改一个实例的属性。

如果想让内部属性不被外部访问,可以把属性的名字前加上两个下划线,在Python中,实例的变量名如果以双下划线开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

以双下划线开头且以双下划线结尾的是特殊变量,特殊变量可以直接访问,不是private变量。

对于前面只有一个下划线的变量,是可以在外部访问到的。但是,按照约定俗成的规定,当你看到这样的变量时,意思是说,“虽然我可以被访问,但是请把我当作私有变量,不要随意访问我”

双下划线开头的实例变量其实也是可以访问到的,因为Python其实并没有严格的private变量。之所以不能访问以双下划线开头的私有变量,是因为Python解释器把

1
# __name变量变成了_Student__name

总的来说就是: Python本身并没有任何机制阻止你干坏事,全凭自觉

继承和多态

继承

当子类和父类有相同的方法的时候,子类的方法会覆盖父类的方法。当运行子类的实例的时候,总是会调用子类的方法

多态

创建一个class的时候实际上是创建了一种数据类型。

判断某个变量是否是某种类型,可以使用isinstance()判断

在继承关系中,如果一个实例的数据类型是某个子类,那么它的数据类型也可以被看做是父类。但是反过来就不行

多态的真正威力: 调用方法只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

  • 对扩展开放: 允许新增Animal子类;
  • 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数

静态语言 VS 动态语言

image-20200731184112361

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

获取对象信息

使用type()

使用isinstance

对于class的继承关系,使用isinstance()函数

isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上

能用type()判断的基本类型也可以用isinstance()判断

还可以用isinstance判断一个变量是否是某些类型中的一种

1
2
3
4
5
# 使用isinstance()判断一个变量是否是某些类型中的一个
# 判断某个变量是否是list或者tuple:
print(isinstance([1, 2, 3, 4, 5, 6], (list, tuple)))
print(isinstance((1, 2, 3, 4, 5, 6), (list, tuple)))
print(isinstance('hello', (list, tuple)))

使用dir()

dir()用于获取一个对象的所有属性和方法。它返回一个包含字符串的list

1
2
# 获得一个str对象的所有属性和方法
print(dir('str'))
1
2
3
# __len__和len()是等价的,使用后者,它会自动去调用该对象的__len__()
print(len('abc'))
print('abc'.__len__())

使用getattr(), setattr(),hasattr()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 使用getattr(),setattr(),hasattr(),我们可以直接操作一个对象的状态
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x


obj = MyObject()
# 测试上面新创建的对象的属性
print(hasattr(obj, 'x'))
print(obj.x)
print(hasattr(obj, 'y'))
setattr(obj, 'y', 19)
print(hasattr(obj, 'y'))
print(getattr(obj, 'y'))
print(obj.y)

如果获取不存在的属性,会抛出AttributeError(属性)的错误:

使用getattr()时,可以传入一个default参数,如果属性不存在,就返回默认值

1
2
# 使用getattr()时,可以传入一个default参数,如果属性不存在,就返回默认值
print(getattr(obj, 'z', '404: No such a attribute'))

小结

如果知道一个对象的属性和方法,就直接使用,不要使用getattr(),setattr()等等

实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性

给实例绑定属性的方法是通过实例变量,或者通过self变量

类属性:类本身需要绑定的属性,可以直接在class中定义,这种属性是类属性,归该class所有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 实例属性和类属性
# 给实例绑定属性
class Student(object):
def __init__(self, name):
self.name = name

s = Student('Bob')
s.score = 19
# 给类添加类属性
class Student1(object):
name = 'Student'
# 上面的name就是类属性。类属性虽然归类所有,但是该类的所有实例都可以访问到
s = Student1()
print(s.name)
print(Student1.name)
s.name = "Michael"
# 因为实例属性的优先级比类属性高,所以他会屏蔽掉类的name属性
print(s.name)
# s有了实例属性之后,Student的name类属性还在且不会消失
# 在删除掉s的实例属性之后,name类属性就会显示出来
del s.name
print(s.name)

注意:

在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

面向对象高级编程

使用slots

给实例绑定一个方法:

1
2
3
4
5
6
7
# 定义一个函数作为实例方法
def set_age(self, age):
self.age = age
# 给实例绑定一个方法
s.set_age = types.MethodType(set_age, s)
s.set_age(20)
print(s.age)

但是给一个实例绑定的方法,对于另一个实例是不起作用的

想要给所有实例都绑定方法,可以给class绑定方法:

1
2
3
4
5
6
7
8
9
10
def set_score(self, score):
self.score = score


Student1.set_score = types.MethodType(set_score, Student1)
s.set_score(100)
print(s.score)
s2 = Student1()
s2.set_score(10000)
print(s2.score)

使用slots

如何才能限制实例的属性,比如,只允许对Student实例添加name和age属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,用来限制该class实例能添加的属性

*使用slots时要注意: *

slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的;除非,在子类中也定义一个slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。

多重继承

类似下面这样就实现了多重继承

1
2
3
class Bat(Mammal, Flyable):
pass
# Bat类继承了Mammal和Flyable类

Mixln

为了让一个类实现更多的类,从而让它再多继承一个类,这种设计通常被称为Mixln

Mixln的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixln的功能,而不是设计多层次的复杂的继承关系

只允许单一继承的语言(如Java)不能使用Mixln的设计

定制类

Python中有很多特殊的变量或函数都有着特殊的用途,可以帮助我们定制类

str()和repr()

1
2
3
4
5
6
# __str__()和__repr__()用于返回有关类和实例的字符串,其中前者返回用户看到的字符串,后者返回程序开发者看到的字符串,后者是为调试服务的
# 使用下面的方法可以让str()和repr()统一
__str__() = __repr__()
# 直接使用变量时调用的是__repr__(),使用print调用的是__str__
# 如何才能调用__str__方法?使用:
print(Class("Attribute"))

iter

如果一个类想被用于for…in循环,类似list或tuple那样,就必须实现个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,知道遇到StopIteration错误时间退出循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# iter
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a, b

def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 推出循环
raise StopIteration()
return self.a

# Test
for n in Fib():
print(n)

getitem

想要FIb实例可以实现按索引取值必须实现getitem方法:

1
2
3
4
5
6
7
8
9
10
# getitem
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
Fib.__getitem__ = types.MethodType(__getitem__, Fib)
# 现在可以按下标访问数列的任意一项了
f = Fib()
print(f[0])

让Fib实现切片操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 让Fib实现切片
# 删除原来的__getitem__方法
del Fib.__getitem__
def __getitem__(self, n):
if isinstance(n, int):
a, b = 1, 1
for x in n:
a, b = b, a + b
return a
if isinstance(n, slice):
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
# 把新的__getitem__添加到Fib class中去
Fib.__getitem__ = types.MethodType(__getitem__, Fib)
# 现在可以尝试Fib的切片了
f = Fib()
print(f[0:5])

经过上面的操作之后,Fib还没有实现list的所有功能,例如对step参数还没有处理,也没有对含有负数的slice进行处理。要正确实现一个getitem()还是有很多工作要做的

如果想把对象看成一个dict,getitem()的参数也可能是一个可以作key的object,例如str。

与getitem类似的还有setitem以及delitem()方法

getattr

使用getattr()方法可以动态返回一个属性: 即当调用一个不存在的属性时,比如score,Python解释器会试图调用getattr(self, ‘score’)来尝试获得属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# __getattr__()
class my_getattr(object):
def __init__(self, name):
self.name = name

def __getattr__(self, item):
if item == 'score':
return 99

one_getattr = my_getattr('Bowenkei')
print(one_getattr.name)
print(one_getattr.score)
# 使用__getattr__()返回函数也是可以的
del my_getattr.__getattr__
def __getattr__(self, attr):
if attr == 'money':
return lambda: '这是我的存款:∞'
my_getattr.__getattr__ = types.MethodType(__getattr__, my_getattr)

two_getattr = my_getattr("bowenkei")
print(two_getattr.money)
print(two_getattr.money())
# 注意,只有在没有找到属性的情况下,才调用__getattr__(),对于已经存在的属性,不会在__getattr__中查找
print(two_getattr.today) # None
# 如上述任意调用都会返回None,因为__getattr__()的默认返回值就是None,但是如果类中没有__getattr__()
# 方法的时候,调用class中没有的熟悉则都会抛出AttributeError。要让class只响应特定的几个属性,我们就要按照约定,
# 抛出AttributeError的错误
del my_getattr.__getattr__
def __getattr__(self, attr):
if attr == 'age':
return lambda :20
raise AttributeError("\'my_getattr\'object has no attribute \'%s\'" % attr)

my_getattr.__getattr__ = types.MethodType(__getattr__, my_getattr)
three_my_getattr = my_getattr("bowenkei")
print(three_my_getattr.age())
# 由此可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
# 这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况做调用
# 如果要写SDK,给每个URL对应的API都写一个方法,那太麻烦,而且API一旦改动,SDK也要改
# 利用完全动态的__getattr__(),可以写出一个链式调用
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path

__repr__ = __str__

# Demo
print(Chain('bowenkei').status.user.timeline.list)
print(Chain('bowenkei').status)
print(Chain('bowenkei').status.user)
print(Chain('bowenkei').status.user.timeline)
print(Chain('bowenkei').status.user.timeline.list)
"""output:
bowenkei/status/user/timeline/list
bowenkei/status
bowenkei/status/user
bowenkei/status/user/timeline
bowenkei/status/user/timeline/list
"""

###call

一个对象实例有自己的属性和实例方法,调用实例方法时,用instance.method()来调用。

在Python中,可以直接在实例本身上调用实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# call
# 在Python中可以直接在实例本身上调用实例方法
# 任何类,只要实现一个call()方法就可以直接对实例进行调用
class Call_student(object):
def __init__(self, name):
self.name = name

def __call__(self):
print('My name is %s.' % self.name)

call_s = Call_student('bowenkei')
call_s()
"""
call()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。

如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
"""
# 如何判断一个变量是对象还是函数?
# 能被调用的对象就是一个Callable对象
# 函数和带有call()的类实例都是Callable对象
# 使用callable()
print(callable(Call_student))

小结

Python的class允许定义许多定制方法,可以让我们非常方便地生成特定的类。

Python官方文档地址:https://docs.python.org/3/reference/datamodel.html#special-method-names

使用枚举类

使用枚举类

定义常量时,更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 使用枚举类
from enum import Enum, unique
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 输出枚举成员
print(Month.Jan.value)
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
# value属性是自动赋给成员的int常量,默认从1开始计数
# 从Enum中派生出自定义类
@unique
class Weekday(Enum):
Sun = 0 #Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

# @unique是一个装饰器,可以帮助我们检查有没有重复值
# 访问枚举类型可以有若干种方法
print(Weekday.Mon)
print(Weekday['Sat'])
print(Weekday(1))
# 即既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量

错误、调试和测试

错误、调试和测试

运行一个程序总会遇到各种各样的错误:

程序编写造成的问题叫做bug,bug是必须修复的

用户输入造成的错误可以通过检查用户的输入来做相应的处理

另一类错误是完全无法在程序运行过程中预测的。这种错误也称为异常,例如:写入文件的时候,磁盘满了,写不进去了。

Python内置有一套异常处理机制。

我们也需要跟踪程序的执行,查看变量的值是否正确,这个过程称为调试。Python的pdb可以让我们以单步方式执行代码。

错误处理

在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样,就知道是否有错。

但是用错误码来表示是否出错十分不便,更好的方法是使用try-except-finally的错误处理机制

当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。

注意:如果有finally语句,则finally语句则一定会被执行,但是可以没有finally语句

还可以在except语句后面加一个else,当没有错误发生时,会自动执行else语句

Python的错误也是class,所有的错误都继承自BaseExceptioon

常见的错误类型和继承关系:

https://docs.python.org/3/library/exceptions.html#exception-hierarchy

Try-except-finally可以跨越多层调用,所以在使用时,只需要在合适的层次去捕获错误就可以了。

###调用堆栈

如果错误没有被捕获,它就会一直往上抛,直到最后被Python解释器捕获,打印一个错误信息,然后程序退出。

###记录错误

Python内置的logging模块可以非常容易地记录错误信息:

1
2
3
4
5
6
7
import logging
try:
do something
except Exception as e:
logging.exception(e)

do someting

抛出错误

错误是class,捕获一个错误就是捕获到该class的一个实例。

错误并不是凭空产生的,而是有意创建并抛出的。Python内置的函数或抛出很多类型的错误,我们自己编写的函数也可以抛出错误。

抛出错误,首先根据需要,可以定义你一个错误的class,选择好继承关系,然后用raise语句抛出一个错误的实例:

1
2
3
4
5
6
7
8
9
10
11
class FootError(ValueError):
pass

def foo(s):
n = int(s)
if n == 0:
raise FootError("invalid value: %s " %s)

return 10 / n

foo('0')

只有在有需要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型,尽量使用Python内置的错误类型。

raise语句如果不带参数,就会把当前错误原样抛出,在except中raise一个Error,还可以把一种类型的错误转化为另一种类型

程序也可以主动抛出错误,让调用者来处理相应的错误。但是,应该在文档中写清楚可能会抛出哪些错误,以及错误产生的原因。

调试

调试

程序能一次写完并且正常运行的概率很小,基本不超过1%

我们需要知道,出措时,哪些变量的值是正确的,哪些变量的值是错误的。

第一种方法: 使用print()把可能有问题的变量打印出来

第二种方法: 断言

凡是用print()来辅助查看的的地方,都可以用断言(assert)来替代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 断言(assert)
def foo(s):
n = int(s)
assert n != 0, '0 is zero!'
return 10 / n

def main():
foo('0')

main()

# assert的意思是,表达式n!=0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。如果断言失败,
# assert语句本身就会抛出AssertionError
# 启动python解释器时可以用 -0 参数(python3 -0 python_file_name)来关闭assert,关闭后,所有的assert语句可以当成pass

第三种方法: logging

把print()换成logging是第三种方法,和assert相比,logging不会抛出任何错误,而且可以输出到文本

1
2
3
4
5
6
7
8
9
10
11
12
# logging
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info("n = %d" % n)
print(10 / n)
'''
# info是通告,信息的意思
# logging的好处就是,它允许你使用不同的参数来区分记录信息的级别,可选的参数有debug, info, waring, error。
# 当我们选定其中info的时候哦,logging.debug就不起作用了,这样就可以输出不同级别的信息,也不用删除。
# 使用logging的另外一个好处是,通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件

*注意: *

1
这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。(备注: 使用Debug的时候,logging.info以及logging.debug都是会运行的)

第四种方法: pdb

pdb是Python的调试器,它可以让程序以单步方式运行。

使用方法:

  1. 以参数 -m pdb启动后,pdb自动定位到下一步将要执行的代码
  2. 使用命令参数 l来查看代码
  3. 使用命令参数 n 可以单步执行代码
  4. 在任何时候都可以输入命令参数 p 变量名来查看变量
  5. 输入命令参数 q结束调试

第五种方法:pdb.set_trace()

这个方法也是使用pdb,但是不需要单步执行,我们只需要import pdb,然后在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点。

运行代码时,程序会自动在pdb,set_trace()暂停并进入pdb调试环境,可以使用命令p查看变量,或者用命令c继续运行

1
2
3
4
5
import pdb
for i in range(10):
pdb.set_trace() # 设置断点
print(i)
pdb.set_trace() # 设置断点

最后一种方法: 使用IDE

单元测试

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作,详见:

https://www.nli.cn/read/liaoxuefeng-python30/7cfd1dfdf10276d3.md

setUp与setDown

可以在单元测试中编写两个特殊的setUp()和setDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。

文档测试

Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。

doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用表示中间一大段烦人的输出。

什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把**getattr**()方法注释掉,再运行就会报错

当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。

doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest

IO编程

异步IO和同步IO

在IO编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,怎么办呢?有两种办法:

第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;

另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。

使用异步IO来编写程序性能会远远高于同步IO,但是异步IO的缺点是编程模型复杂。想想看,你得知道什么时候通知你“汉堡做好了”,而通知你的方法也各不相同。如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步IO的复杂度远远高于同步IO

操作IO的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也不例外。我们后面会详细讨论Python的IO编程接口。

操作IO的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也不例外。

文件读写

文件对象: 通常被称为文件描述

读文件

使用Python内置的open()函数,传入文件名和标识符:

1
>>> f = open('/User/Test/test.txt', 'r')

如果文件不存在,open()函数就会抛出一个IOErro,并且给出错误码和详细的信息告诉你文件不存在。

如果文件打开成功,调用read()方法可以一次读取文件的全部内容,Python把内容读取到内容中,用一个str对象表示

最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的

由于文件读写都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以为了保证无论是否出错都能够正确地关闭文件,我们可以使用try…finally来实现:

1
2
3
4
5
6
7
try: 
file_object = open('/Users/bowenkei/Desktop/Test.txt', 'r')
print(file_object.read())

finally:
if file_object:
file_object.close()

上述操作更好的方法是使用Python的with语句来自动帮助我们调用close()方法:

1
2
with open('/Users/bowenkei/Desktop/Test.txt', 'r') as file_object:
print(file_object.read())

read():会一次性读取文件的所有内容

read(size):每次最多读取size个字节的内容,可以反复调用

readline():每次读取一行内容

readlines():一次读取所有内容并按行返回list。

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便

####file_like object

像open()函数返回的这种有个read()方法的对象,在Python中统称为file-like object。除了file外,还可以是内存中的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。

StringIO就是在内存中创建的file-like object,常用作临时缓冲。

二进制文件

要读取二进制文件,比如图片、视频,用’rb’模式打开即可

字符编码

要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如GBK编码的文件如下读取:

1
2
with open('/Users/bowenkei/Desktop/GBK.txt', encoding='gbk') as file_object:
print(file_object.read())

遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:

1
2
with open('/Users/bowenkei/Desktop/GBK.txt', encoding='gbk', errors='ignore') as file_object:
print(file_object.read())

写入文件时,只需要使用相应的’w’和’wb’即可

‘a’模式可以实现往一个文件中添加内容而不清空原有内容

下面是各种模式:

image-20200809223545474

StringIO和BytesIO

StringIO: 在内存中读写str

要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:

1
2
3
4
5
6
7
8
9
10
11
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('')
0
>>> f.write('world!')
6
>>> print(f.getvalue()) # 用于获得写入后的str
hello world!

BytesIO: 实现了在内存中读写bytes:

1
2
3
4
5
6
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

操作文件和目录

Python内置的os可以直接调用操作系统提供的借口函数

1
2
3
4
5
6
7
# 导入os模块之后,使用os.name可以获得操作系统类型
# 如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。
# 要获取详细的系统信息,可以调用uname()函数:
# 注意uname()函数在Windows上不提供,也就是说,os模块的某些函数是跟操作系统相关的。
import os
print(os.name)
print(os.uname())

环境变量

在操作系统中定义的环境变量,全部在os.environ这个变量中,可以直接查看:

1
2
3
4
# 查看变量
os.environ
# 获取某个环境变量的值
os.emviron.get('key')

操作文件和目录

操作文件和目录的函数一部分放在os模块中,一部分放在o s.path模块中。

查看、创建和删除目录 :

  • 把新目录的完整路径表示出来: os.path.join(‘’, ‘’)

  • 创建一个目录: os.mkdir(‘’)

  • 删除一个目录: os.rmdir(‘’)

  • 拆分路径: os.path.split()

  • 拆分路径之分离文件名和后缀: os.path.splitext()

  • 重命名: os.rename(‘’)

  • 删掉文件: os.remove(‘’)

  • 列出当前目录下的所有文件: os.listdir()

  • 判断是否是目录: os.path.isdir()

把两个路径合为一个时,通过os.path.join()可以正确处理不同操作系统的路径分隔符

拆分路径时,不要直接去拆字符串,要通过os.path.split(),这样就可以baggie路径分成两部分,后一部分总是最后级别的目录或文件名

合并、拆分路径的函数并不是要求目录和文件要真实存在,它们只对字符串尽心操作。

复制文件的函数在os中不存在,因为复制文件并非由操作系统提供的系统调用

shutil模块是os的补充,其中提供了包括copy file()在内的多个函数

序列化

我们把变量从内存中变成可存储或可传输的过程称之为序列化,在Python中称为pickling,在Java中称为serialization。

序列化之后,我们就可以把序列化后的内容写入磁盘或者通过网络传输到别的机器上

把变量内容从序列化的对象重新读取到内存里称之为反序列化,即unpickling.

Python提供了pickle模块来实现序列化。

pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object

由反序列化得到的变量和原来的变量是完全不相干的对象,它们只是内容相同而已

Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的数据

*小总结: *

pickle.dumps() and pickle.loads()都是将一个byte写入文件

pickle.dump() and pickle.load()都是将一个file-like-Object写入文件

JSON

在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,例如XML,但是JOSN更好。因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。

JSON和Python内置的数据类型对应如下:

JSON Python
{} dict
[] list
“string” str
1234.56 int或float
true/false True/False
null None

Python内置了json模块提供了非常完善的Python对象到JSON格式的转换。

1
2


JSON进阶

将class对象序列化为JSON的{} + 将JSON反序列化为class对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import pickle
import json
# 将class对象序列为JSON的{} 和 反序列化class对象
class SuperMan(object):
def __init__(self, name='bowenkei', age=20, sex='man'):
self.name = name
self.age = age
self.sex = sex

def get_name(self):
return self.name


s = SuperMan()


def superman2dict(super):
return {
'name': super.name,
'age': super.age,
'sex': super.sex
}


def dict2super(dict):
return SuperMan(dict['name'], dict['age'], dict['sex'])


print(json.dumps(s, default=superman2dict))
print(json.dumps(s, default=lambda obj: obj.__dict__))

json_str = '{"name": "lei bo wen", "age": 20, "sex": "man"}'
print(json.loads(json_str, object_hook=dict2super))
new_superman = json.loads(json_str, object_hook=dict2super)
print(new_superman.name)

进程和线程

多进程

Unix/linux操作系统提供了一个fork()系统调用。普通的函数调用,返回一次,fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
print("Process (%s) start ... " % os.getpid())
pid = os.fork()
if pid == 0:
print("I am child process (%s) and my parent is %s." % (os.getpid(), os.getppid()))
else:
print("I (%s) just created a child process (%s)." % (os.getpid(), pid))


"""
os.getpid(): 获取当前进程id
os.getppid(): 获取父进程id
"""

####multiprocessing

multiprocessing模块提供了一个Process类来代表一个进程对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from multiprocessing import Process
import os
def run_proc(name):
print("Run child process %s (%s) ..." % (name, os.getpid()))

if __name__ == '__main__':
print("Parent process %s ." % os.getpid())
p = Process(target=run_proc, args=('test',))
print("Child process will start.")
p.start()
p.join()
print("Child process end.")

"""
创建子进程时,使用Process传入一个执行函数和函数的参数以创建一个实例。用start()方法启动;join()方法可以等待子进程结束后再继续往下运行。
"""

####Pool

用进程池的方式批量创建大量子进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print("Run task %s (%s)" % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print("Task %s runs %0.2f" % (name, (end - start)))

if __name__ == '__main__':
print("Parent process %s." % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
p.close()
p.join()
print("All subprocesses done.")

"""
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。

请注意输出的结果,task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,
因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:p = Pool(5)就可以同时跑5个进程。
Pool的默认大小是CPU的核心数。
"""

####子进程

很多时候,子进程并不是自身,而是一个外部进程。创建子进程后,还需要控制子进程的输入和输出

subprocess模块用于启动一个子进程,然后控制其输入和输出

1
2
3
4
import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)

子进程需要输入,可以使用communicate()方法输入(下面代码没有看懂,希望有大神指点指点):

1
2
3
4
5
6
import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)

####进程间通信

Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据,以实现Process之间的通信:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import os, time, random
# 以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据
# 写数据进程执行的代码
def write(q):
print("Process to write: %s" % os.getpid())
for value in ['A', 'B', 'C']:
q.put(value)
time.sleep(random.random())


# 读数据进程执行的代码
def read(q):
print("Process to read:%s " % os.getpid())
while True:
value = q.get(True)
print("Get %s from queue" % value)


if __name__ == '__main__':
# 父进程创建Queue,并传给各个子进程
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))

# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束
pw.join()
# pr进程是死循环,只能强行终止:
pr.terminate()

多线程

多任务可以由多进程完成,也可以由一个进程内的多进程完成。

进程是由若干个线程组成的,一个进程至少有一个线程

线程是操作系统直接支持的执行单元。

Python的线程是真正的Posix Thread,而不是模拟出来的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Python中 _thread和threading用于多线程。其中_thread是低级模块,threading是高级模块,它对_thread进行了封装。
# 启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行
import time, threading

def loop():
print("thread %s is running ... " % threading.current_thread().name)
n = 0
while n < 5:
n = n + 1
print("thread %s >>> %s " % (threading.current_thread().name, n))
time.sleep(1)
print("thread %s ended" % threading.current_thread().name)


print('thread %s is running... ' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread') # 这里指定了子线程的名字
t.start()
t.join()
print("thread %s ended." % threading.current_thread().name)

任何进程默认会启动一个线程,这个线程称为主线程(MainThread),Python的threading模块下面的current_thread()函数用于返回当前线程的实例。子线程的名字在创建时指定。名字仅仅在打印时用来显示,完全没有其他任何意义。默认情况下,Python自动给线程命名为Thread-1, Thread-2……

Lock

线程的调度是由操作系统决定的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 没有使用Lock之前:
import threading, time
#
balance = 0
def change_it(n):
#
global balance
balance = balance + n
balance = balance - n


def run_thread(n):
for i in range(10000):
change_it(n)

t1 = threading.Thread(target=run_thread, args=(5, ))
t2 = threading.Thread(target=run_thread, args=(8, ))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

两个线程同时一存一取,就可能导致余额不对,你肯定不希望你的银行存款莫名其妙地变成了负数,所以,我们必须确保一个线程在修改balance的时候,别的线程一定不能改。

如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import threading, time
#
balance = 0
lock = threading.Lock()
def change_it(n):
#
global balance
balance = balance + n
balance = balance - n


def run_thread(n):
for i in range(10000):
# 获取锁
lock.acquire()
try:
change_it(n)
finally:
# 释放锁
lock.release()

t1 = threading.Thread(target=run_thread, args=(5, ))
t2 = threading.Thread(target=run_thread, args=(8, ))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

使用Lock的优缺点:

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

Threadlocal

用一个全局dict存放所有的Student对象,然后以thread自身作为key获得线程对应的Student对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 实现
from ReviewPython3 import Student
import threading
global_dict = {}

def std_thread(name):
std = Student(name)
# 把std放到全局变量global_dict中:
global_dict[threading.current_thread()] = std
do_task1()
do_task2()

def do_task1():
# 不传入std,而是根据当前的线程查找
std = global_dict[threading.current_thread()]
def do_task2():
std = global_dict[threading.current_thread()]

用Threadlocal替代上面自己创建的dict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading
local_school = threading.local()

def process_student():
# 获取当前线程关联的student
std = local_school.student
print("Hello, %s (in %s)" % (std, threading.current_thread().name))


def process_thread(name):
# 绑定ThreadLocal的student:
local_school.student = name
process_student()


t1 = threading.Thread(target=process_thread, args=('Alice', ), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('Bob', ), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。

可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。

ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

###进程 VS 线程

Master-Worker模式

Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。

如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。

如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。

多线程和多进程的优劣

正则表达式

正则表达式是一种用来匹配字符串的有力的武器

在正则表达式中,直接给出字符,就是精确匹配。

  • \d:one number
  • \w: one letter or one number
  • .: everything
  • *:任意个字符(包括0个)
  • +: 至少一个字符
  • ?: 0个或1个字符
  • {n}:n个字符
  • {n, m}: n-m个字符
  • \s: 可以匹配一个空格(也包括Tab等空白符)

进阶

更精确地匹配,使用[]

1
2
3
4
5
6
7
8
9

- [0-9a-zA-Z_]: 匹配一个数字,字母,或者下划线

- [0-9a-zA-Z_]+:匹配至少由一个数字、字母或者下划线组成的字符串

- [a-zA-Z_][0-9a-zA-Z_]{0, 19}: 更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
- A|B: 可以匹配A或B
- ^: 表示行的开头(^\d:以数字开头)
_ $: 表示行的结束(\d$:以数字结束)

re模块

1
2
3
4
5
import re
# 使用match方法判断正则表达式是否匹配
is_true = re.match(r'bowen[a-zA-Z]{3}', 'bowenkei')
print(is_true)
# 如果匹配,会返回一个Match对象,否则返回None。

常见的判断方法:

1
2
3
4
5
6
7
import re
yourname = input("Enter your name: ")

if re.match(r'bowen[a-zA-Z]{3}$', yourname):
print("Your name is true.")
else:
print("Sorry, your name is not right.")

切分字符串

用正则表达式切分字符串比用固定的字符更灵活。

1
2
3
4
5
6
7
8
# 普通的切分代码:
print('a b c'.split(' ')) # ['a', '', 'b', '', '', '', '', 'c']

# 使用正则表达式
print('a b c'.split(' ')) # ['a', '', 'b', '', '', '', '', 'c']
print(re.split(r'\s+', 'a b c')) # ['a', 'b', 'c']
print(re.split(r'[\s,]+', 'a,b,c, d')) # ['a', 'b', 'c', 'd']
print(re.split(r'[\s,;]+', 'a,b;c, d')) # ['a', 'b', 'c', 'd']

分组

正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 分组
m = re.match(r'^([0-9]{3})-(\d{3,8})$', '012-456789')
print(m)
print(m.group(0))
print(m.group(1))
print(m.group(2))
'''
012-456789
012
456789
'''
# 注意到group(0)始终是原始字符串。
# 匹配时间: 19:05:30
time = '19:05:30'

m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', time)

print(m.groups())

贪婪匹配

正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。

编译

使用正则表达式时,re模块内部会做下面两件事:

  • 编译正则表达式
  • 用编译后的正则表达式去匹配字符串

如果一个正则表达式要重复使用上千次,出于效率的考虑,可以预编译该正则表达式并保存下来,接下来重复使用的时候就可以跳过编译这个步骤,直接匹配了:

1
2
3
4
5
6
7
re_name = re.compile(r'bowen[a-zA-Z]{3}')

yourname = 'bowenkei'
yourname1 = 'bowenlei'

print(re_name.match(yourname))
print(re_name.match(yourname1))

小节练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'''
1.1 请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email
1.2 版本二可以提取出带名字的Email地址
'''
import re
# Version 1
# someone@gmail.com
# bill.gates@microsoft.com
email_1 = re.compile(r'^[\w\.]+@[\w]+\.com$')
print(email_1.match('someone@gmail.com'))
print(email_1.match('bill.gates@microsoft.com'))
print(email_1.match('nianchucqc@gmail.com'))

# Version 2
# <Tom Paris> tom@voyager.org

email_2 = re.compile(r'^(<)([\w\.\s]+)(>)(\s[\w\.\s]+@[\w\.\s]+)$')
print(email_2.match('<Tom Paris> tom@voyager.org').group(2))

常用内建模块

datetime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 获取当前时间
from datetime import datetime
n = datetime.now()
print(n)

print(type(n)) # 获得datetime类型
# 注意,使用的是datetime包下的datetime类

# 获取指定日期和时间
dt = datetime(2008, 8, 8, 20, 8, 8)
print(dt)

# datetime转换为timestamp
'''
在计算机中,时间实际上使用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0。
1970年以前的时间timestamp为负数。
当前时间就是相对于epoch time的秒数,称为timestamp
可以认为:
timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00
对应的北京时间就是:
timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00

可见timestamp的值与时区毫无关系,因为timestamp一旦确定,其UTC时间就确定了,转换到任意时区的时间也是完全确定的,
这就是为什么计算机存储的当前时间是以timestamp表示的,因为全球各地的计算机在任意时刻的timestamp都是完全相同的(假定时间已校准)。
'''
tt = dt.timestamp()
print(tt)

# timestamp是一个浮点数,后面的小数表示毫秒
# Java和JavaScript的timestamp使用整数表示毫秒数,只需要将其timestamp除以1000就得到Python的浮点数表示方法

# timestamp转换为datetime
dt_1 = datetime.fromtimestamp(tt)
print(dt_1)

'''
timestamp没有时区,但是datetime有时区。
fromtimestamp默认转换为本地时间,也就是操作系统设置的时间。
要想转换为UTC标准时间可以将其替换为utcformtimestamp
'''
## 获得08年奥运会举办时标准UTC时间
dt_2 = datetime.utcfromtimestamp(tt)
print(dt_2)
# 可见北京时间时间比UTC标准时间多了8个小时
print("-------------------------------")
# str转换为datetime(转换后的时间没有时区信息) —— 使用strptime()
copy = datetime.strptime('200888200808', '%Y%m%d%H%M%S')
print(copy)
"""
注意:
year: Y
month: m
day: d
hour: H
minute: M
second: S
"""
print("-------------------------------")
# datetime转换为str —— 使用strftime()
print(n.strftime('%A, %B, %d %H:%M:%S'))
print("-------------------------------")
# datetime加减
"""
对时间和日期进行加减实际上就是把datetime往后或者往前计算,得到新的datetime。在导入timedelta这个类之后,加减可以直接使用+,-运算符

"""
from datetime import timedelta
# 获取当前时间
now = datetime.now()
print(now)
# 时间加
print(now + timedelta(hours=8))
# 时期减
print(now - timedelta(days=3))
# 时间加减
print(now + timedelta(days=3, hours=10))

print("-------------------------------")
# 本地时间转换为UTC时间
"""
一个datetime类型有一个时区属性tzinfo,但是默认为None,所以无法区分这个datetime到底是哪个时区,除非强行给datetime设置一个时区
"""
from datetime import timezone
now = datetime.now()
print(now)
# 创建时区UTC+8
timezone_utc_8 = timezone(timedelta(hours=8))

datetime = now.replace(tzinfo=timezone_utc_8)
print(datetime)

print("-------------------------------")
# 时区转换
# 拿到utc时间,并设置时区为utc+0.00
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
print('UTC时间:',utc_dt)

# astimezone()将时区转换为北京时间
beijing_date = datetime.astimezone(timezone(timedelta(hours=8)))
print('北京时间:',beijing_date)
# astimezone()转换为东京时间
dongjing_date = datetime.astimezone(timezone(timedelta(hours=9)))
print('东京时间:', dongjing_date)

"""
时区转换的关键在于,拿到一个datetime时,要获知其正确的时区,然后强制设置时区,作为基准时间。

利用带时区的datetime,通过astimezone()方法,可以转换到任意时区。

注:不是必须从UTC+0:00时区转换到其他时区,任何带时区的datetime都可以正确转换,例如上述bj_dt到tokyo_dt的转换。
"""

下面截图是Python官方关于时间、日期转换的介绍:

image-20200814121200406

小节练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""
假设你获取了用户输入的日期和时间如2015-1-21 9:01:30,以及一个时区信息如UTC+5:00,均是str,请编写一个函数将其转换为timestamp:
"""
# answer
import re
from datetime import timezone, datetime, timedelta
def to_timestamp(dt_str, tz_str):
dt_com = re.compile(r'UTC([+|-][\d]{1,2}):00')
tz = dt_com.match(tz_str).group(1)
int_tz = int(tz)
set_timezone = timezone(timedelta(hours=int_tz))
str2dt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
str2dt_new = str2dt.replace(tzinfo=set_timezone) # 使用replace之后必须复制给另外一个变量,否则tzinfo无法修改

return str2dt_new.timestamp()




# 测试:
t1 = to_timestamp('2015-6-1 08:10:30', 'UTC+7:00')
assert t1 == 1433121030.0, t1
t2 = to_timestamp('2015-5-31 16:10:30', 'UTC-09:00')
assert t2 == 1433121030.0, t2
print('Pass')

collections

Collections 是Python内建的一个集合模块,提供了很多有用的集合类

namedtuple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# namedtuple
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)

print(p.x)
print(p.y)

"""
namedtuple是一个函数,用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素
下面验证Point的类型:
"""
print(isinstance(p, Point))
print(isinstance(Point, tuple))
print(isinstance(p, tuple))

# 下面使用namedtuple定义一个圆
Circle = namedtuple('Circle', ['x', 'y', 'radius'])
# 创建一个圆形
circle = Circle(0, 0, 5)
print("The center of the circle is (%d, %d), and its radius is %s " % (circle.x, circle.y, circle.radius))

deque

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# namedtuple
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)

print(p.x)
print(p.y)

"""
namedtuple是一个函数,用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素
下面验证Point的类型:
"""
print(isinstance(p, Point))
print(isinstance(Point, tuple))
print(isinstance(p, tuple))

# 下面使用namedtuple定义一个圆
Circle = namedtuple('Circle', ['x', 'y', 'radius'])
# 创建一个圆形
circle = Circle(0, 0, 5)
print("The center of the circle is (%d, %d), and its radius is %s " % (circle.x, circle.y, circle.radius))

defaultdict

1
2
3
4
5
6
7
8
9
10
11
12
# defaultdict
'''
使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict
默认值是调用函数返回的,函数在创建defaultdict对象时传入
除了在Key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的
'''
from collections import defaultdict

dd = defaultdict(lambda: 'The default value')
dd['key1'] = ' The value of key1'
print(dd['key1']) # The value of key1
print(dd['key2']) # The default value

orderedDict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# OrderedDict
'''
dict是无序的,如果要保持Key的顺序,可以使用OrderedDict;
OrderedDict的Key会按照插入的顺序排列,不是Key本身排序;
'''
from collections import OrderedDict
d = dict(a=1, c = 3, b = 2) # {'a': 1, 'b': 2, 'c': 3} ——--- Python3.6以后dict也是有序的了。
print(d)
# 实现Orderedict
d1 = OrderedDict(d)
print(d1)
# OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key:
# 下面这个实现没有怎么看懂
class LastUpdatedOrderedDict(OrderedDict):
def __init__(self, capacity):
super(LastUpdatedOrderedDict, self).__init__()
self.__capacity = capacity

def __setitem__(self, key, value):
containsKey = 1 if key in self else 0
if len(self) - containsKey >= self.__capacity:
last = self.popitem(last=False)
print('remove', last)

if containsKey:
del self[key]
print('set', (key, value))

else:
print('add', (key, value))
OrderedDict.__setitem__(self, key, value)

Counter

1
2
3
4
5
6
7
8
9
10
# Counter: 一个简单的计数器
# 使用Counter统一字符串中字符出现的次数
# Counter就是一个dict
from collections import Counter
c = Counter()
s = 'today is a good day, it\'s a good time to learn something'
for ch in s:
c[ch] = c[ch] + 1

print(c)

base64

Base是一种用64个字符来表示任意二进制数据的方法。

要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法。

Base64 是一种最常见的二进制编码方法

Python内置的base64可以直接进行base64的解编码:

1
2
3
4
5
6
7
8
9
# base64
# 解编码
import base64
# 编码
bs1 = base64.b64encode(b'binary string')
print(bs1)
# 解码
bs2 = base64.b64decode(bs1)
print(bs2)

由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数。

一种叫做“Url safe”的base64编码,其实就是把字符+和/分别编程-和_:

1
2
3
4
5
6
7
8
# 使用url-safe的base64编码
'''
由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+和/分别变成-和_
'''
bs3 = base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(bs3) # b'abcd++//' ---- 字符+和/没有改变
bs4 = base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(bs4) # 字符+和/分别变成了-和_
1
2
3
4
5
6
7
8
9

base64是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行
Base适用于小段内容的编码,比如数字证书签名、Cookie的内容等。
由于=字符也可能出现在Base64编码中,但=用在URL、Cookie里面会造成歧义,所以,很多Base64编码后会把=去掉:
# 标准Base64:
'abcd' -> 'YWJjZA=='
# 自动去掉=:
'abcd' -> 'YWJjZA'
去掉=后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以正常解码了。

常见函数

.join()

用于将序列中的元素以指定的字符连接生成一个新的字符

用法:

Str.join(sequence)

  • Sequence – 要连接的元素序列

capitalize ()

-capitalize()方法返回字符串的一个副本,只有它的第一个字母大写。对于8位的字符串,这个方法与语言环境相关。

用法如下:

1
2
str.capitalize()
# 上述方法并不会改变str中的内容,只是会返回一个首字母大写的副本

index()

index(content):用于判断给定字符的下标

strip()

用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列

*注意: *该方法只能删除开头或是结尾的字符,不能删除中间部分的字符

bytes()

描述:

bytes函数返回一个新的bytes对象,该对象是一个0<=x<256区间内的整数不可变序列。它是bytearray的不可变版本。

语法如下:

1
class bytes([source[, encoding[, errors]]])

参数解释:

  • 如果source为整数,则返回一个长度为source的初始化数组;
  • 如果source为字符串,则按照指定的encoding将字符串转换为字节序列;
  • 如果source为可迭代类型,则元素必须为[0.256]中的整数
  • 如果source为与buffer接口一致的对象,则此对象也可以被用于初始化bytearray;
  • 如果没有输入任何参数,默认就是初始化数组为0个元素

struct

Python提供了一个struct模块来解决bytes和其他二进制数据的转换

struct的pack函数把任意数据类型编程bytes:

1
2
3
4
5
6
7
8
9
10
11
12
# struct
# struct的pack函数把任意数据类型变成bytes
import struct
s = struct.pack('>I', 10240099)
print(s) # b'\x00\x9c@c'
# 上面>表示: 字节顺序是big-endian,也就是网络序;I表示4字节无符号整数
# 后面的参数个数要和处理指令一致
# unpack把bytes变成相应的数据类型
four_bytes_int, two_bytes_int = struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
print(four_bytes_int) # 4042322160
print(two_bytes_int) # 32896
# H表示2字节无符号整数

小节练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import struct
# 请编写一个bmpinfo.py,可以检查任意文件是否是位图文件,如果是,打印出图片大小和颜色数。
s = b'\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'

def bmpinfo(s):
if not isinstance(s, bytes):
s = bytes(s, encoding='utf-8')
if s[0:2] == (b'BM' or b'BA'):
result = struct.unpack('<ccIIIIIIHH', s)
photo_size = result[-4:-2]
print("此位图的大小是:%d x %d" % (photo_size[0], photo_size[1]))
print("此位图的颜色数是:%d" % result[len(result) - 1])

else:
print("抱歉, 这不是一个位图。")

bmpinfo(s)
s1 = 'fdsfsdfsdfdsf'
bmpinfo(s1)

"""
output:
b'BM'
此位图的大小是:640 x 360
此位图的颜色数是:24
b'fd'
抱歉, 这不是一个位图。
"""

hashlib

Python的hashlib提供了常见的摘要算法: 如MD5, SHA1等等

摘要算法: 即哈希算法、散列算法。把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。

摘要算法就是通过摘要函数对任意长度的数据data计算出固定长度的digest,目的是为了发现原始数据是否被篡改过。

以MD5为例,计算出一个字符串的MD5值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# hashlib
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
'''
MD5是常见的摘要算法,速度很快,生成结果是固定的128bit字节,通常用一个32位的16进制字符串表示。
'''

"""
另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似:
"""
sh1 = hashlib.sha1()
sh1.update('how to use sha1 in '.encode('utf-8'))
sh1.update('python hashlib?'.encode('utf-8'))
print(sh1.hexdigest())
""""
SHA1的结果是160bit字节,通常用一个40位的16进制字符串表示
"""

摘要算法应用

任何允许用户登录的网站都会存储用户登录的用户名和口令。

正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要

当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。

小节练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import hashlib


# 1 - 根据用户输入的口令,计算出存储在数据库中的MD5口令:
def calc_md5(password):
s = str(password)
one_md5 = hashlib.md5()
one_md5.update(s.encode('utf-8'))

return one_md5.hexdigest()


print(calc_md5("todayisaniceday."))

# 2 - 设计一个验证用户登录的函数,根据用户输入的口令是否正确,返回True或False:
db = {
'michael': 'e10adc3949ba59abbe56e057f20f883e',
'bob': '878ef96e86145580c38c87f0410ad153',
'alice': '99b1c2188db85afee403b1536010c2c9'
}

def login(user, password):
s = str(password)
one_md5 = hashlib.md5()
one_md5.update(s.encode('utf-8'))
return one_md5.hexdigest() == db[str(user)]


print(login('michael', 123456))
print(login('bob', '888888'))
print(login('alice', password=123456))

常用口令的MD5很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”

1
2
def calc_mad(password):
return get_md5(password + 'the-Salt')

这样只要salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推口令。

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import hashlib

# 根据用户输入的登录名和口令模拟用户注册,计算更安全的MD5:
salt_value = 'the-Salt'
db = {}
def register(username, password):
db[username] = get_md5(password + username + salt_value)



def get_md5(password):
s = str(password)
one_md5 = hashlib.md5()
one_md5.update(s.encode('utf-8'))
return one_md5.hexdigest()



# 测试
register('bob', 'hello world')
print(db) # {'bob': '7fa0c1935fed7e7730be403321f3b04c'}
register('bowenkei', 'today is a good day')
print(db)

# 根据修改后的MD5算法实现用户登录的验证:
def login(user, password):
return db[str(user)] == get_md5(str(password) + str(user) + salt_value)


# Test
print(login('bob', 'hello world'))
print(login('bowenkei', 'today is a good day'))
print(login('bowenkei', 123456))

*注意: *

摘要算法不是加密算法,不能用于加密,只能用于防篡改,但是它的单向计算性决定了可以在不存储明文口令的情况下验证用户口令

itertools

Python的内建模块itertools提供了非常有用的用于操作迭代对象的函数

“无限”迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# count()会创建一个无限迭代器。
import itertools
naturals = itertools.count(100) # count()括号里面的数字指定了从哪一个数字开始数
print(naturals.__next__())
print(naturals.__next__())

# cycle() —— 会把传入的一个序列无限重复下去
abc = itertools.cycle(['a', 'b', 'c'])
for i in range(3 * 2): # 这里指定了次数,避免出现死循环
print(abc.__next__())

# repeat()负责将一个元素无限重复下去,不过如果提供第二个参数就可以限定重复次数:
ns = itertools.repeat('A', 3)
for n in ns:
print(n) # 迭代了三次

# 无限序列虽然可以无限迭代下去,但是通常会通过takenwhile()等函数根据条件判断来截取一个有限的序列:
naturals = itertools.count(1)
ns = itertools.takewhile(lambda x:x <= 10, naturals)
print(ns)
print(list(ns))

itertools提供的几个迭代器操作函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
# chain()
# chain()可以把一组迭代对象串联起来,形成一个更大的迭代器
new_iter = itertools.chain('ABC', 'XYZ')
print(list(new_iter)) # ['A', 'B', 'C', 'X', 'Y', 'Z']

# proupby()
# groupby把迭代器中相邻的重复元素挑出来放在一起:
for key, group in itertools.groupby('AAABBBBBBDDDDCCCCAAA'):
print(key, list(group))

# 向groupby中传入一个函数可以实现忽略大小写:
for key, group in itertools.groupby('AAABBBBbcdaabccBBDDDDCCCCAAA', lambda c: c.upper()):
print(key, list(group))

XML(解析)

操作XML有两种方法:DOM和SAX。正常情况下,优先考虑SAX,因为DOM实在太占内存

解析XML时,我们关心三件事情: start_element, end_element和char_data。

1
2


其他知识

Python- 变量前加或者*

➕*

image-20200729113837756

➕ **

image-20200729113908275

Python标准注释

1
2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

关于切片

对于切片,[:]中:前后两个数字必须满足从小到大的顺序,同时,包括前者,但并不包括后者

关于Dict

Python中,可以使用if判断key是否在dict中,但是不能直接使用value判断其是否在dict中

关于JSON

标准JSON中不能有多余的——逗号(,)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pickle
import json
# python to json
python_str = {"age":20, "score":88, "name":"Bob"}
json_str = json.dumps(python_str)
print(json_str)
print(type(json_str))
# json to python
json_str = '{"age":20, "score":88, "name":"Bob"}'
python_str = json.loads(json_str)
print(python_str)
print(type(python_str))
"""
{"age": 20, "score": 88, "name": "Bob"}
<class 'str'>
{'age': 20, 'score': 88, 'name': 'Bob'}
<class 'dict'>
"""

关于全局变量

参见:

https://blog.csdn.net/songyunli1111/article/details/76095971

关于join()

Join()函数并不会改变原有的序列

1.1.8 字符串

String类型是Java的一个数据类型,但并不是原始数据类型

1.1.8.1 字符串拼接

1.1.8.2 类型转换

1.1.8.3 自动转换

1.1.8.4 命令行参数

1.1.9.5 重定向与管道

image-20200712112430202

像上面这样,同时使用重定向输入和输出会清空文件中的数据,从而引发java.util.NoSuchElementException: No line found 异常

1.1.9.7 标准绘图库(基本方法)

经验启发: 如何确定一个正方形(包括位置和大小):

只需要确定正方形的中心位置,以及中心位置具体一条边的距离即可

1.1.9.8 标准会图库(控制方法)

StdDraw.fillRectangle(x, y, rw, rh)中x,y是长方形左下角的坐标。以屏幕左下角为(0,0)作为标准

1.1.11 展望

数据抽象: 主要思想是鼓励程序员定义自己的数据类型(一系列值和对这些值的操作),而不仅仅是那些操作预定义的数据类型的静态方法。

1.2 数据抽象

抽象数据类型(ADT)是一种能够对使用者以藏数据表示的数据类型。

抽象数据类型的主要不同之处在于它将数据和函数的实现关联,并将数据的表示方式隐藏起来。在使用抽象数据类型时,我们的注意力集中在API描述的操作上而不会去关心数据的表示;在实现抽象数据类型时,我们的注意力集中在数据本身并将实现对该数据的各种操作.

1.2.1.4 对象

对象是能够承载数据类型的值的实体。所有对象都有三大重要特性:状态、标识和行为。对象的状态即数据类型中的值。对象的标识能够将一个对象区别于另一个对象。可以认为对象的标识就是它在内存中的位置。对象的行为就是数据类型的操作。

4.4 最短路径

4.4.1 最短路径的性质

  • 路径是有向的
  • 权重不一定等于距离
  • 并不是所有顶点都是可到达的。如果t并不是从s可达的,那么就不存在任何路径,也就不存在最短路径
  • 负权重会使问题更复杂
  • 最短路径一般都是简单的。找到的最短路径都不会含有环
  • 最短路径不一定是唯一的
  • 可能存在平行边和字环:平行边的权重最小者才会被选上,最短路径也不可能包含自环(除非自环的权重为零)

有意思的问答

image-20200808104316694

image-20200808105104209

image-20200808105641873

image-20200808110013526

final关键字

基本用法

修饰类

image-20200612152537147

修饰方法

image-20200612152704529

修饰变量

image-20200612152758241

第18章 递归

18.1 引言

*要点提示: *递归是一种针对使用简单的循环难以编程实现的问题,提供优雅解决方案的技术

有关H-树的知识:

image-20200603222118178

使用递归就是使用递归方法编程,递归方法就是直接或间接调用自身的方法。递归是一个很有用的程序技术。

18.2 示例学习: 计算阶乘

一个递归调用可以导致更多的递归调用,因为这个方法继续把每个子问题分解成新的子问题。要终止一个递归方法,问题最后必须达到一个终止条件。

image-20200603225556923

如果递归不能使问题简化并最终收敛到基础情况,就有可能出现无限递归。

18.3 示例学习: 计算斐波那契数

18.4 使用递归解决问题

所有的递归方法都具有下面的特点:

  • 这些方法使用if-else或switch语句来引导不同的情况
  • 一个或多个基础情况(最简单的情况)用来停止递归
  • 每个递归调用都会简化原始问题,让它不断地接近基础情况,直到它变成这种基础境况为止。

18.5 递归辅助方法

要点提示 :有时候可以通过针对要解决的初始问题的类似问题定义一个递归方法,来找到初始问题的解决方法。这个新的方法称为递归辅助方法,初始问题可以通过调用递归辅助方法来得到解决。

下面是实例:

image-20200606101938995

划重点:

在递归程序设计中定义第二个方法来接收附加的参数是一个常用的设计,这样的方法被称为递归辅助方法。

18.5.1 递归选择排序

起步

##第1章 何为Web发布

1.1 像Web发布者一样思考

Web是:

  • 一个超文本信息系统
  • 跨平台的
  • 分布式的
  • 交互式的

1.1.1 Web是一个超文本信息系统

超文本的含义: 不像看书那样以线性方式阅读,而是可以轻松从一个地方跳到另一个地方: 可以获取更详细的信息,再回过去阅读、跳到其他主题或者根据兴趣阅读。

术语超文本,可以理解为链接。链接也可称为超链接。

1.1.2 Web是跨平台的

跨平台意味着不管使用什么计算机硬件,也不管运行的是什么操作系统,装备的是什么显示器,都可以一样地访问Web信息。

下面是书中提供的一个很有意思的观点:

跨平台是一种理想:

随着众多特殊功能、技术和媒体类型的推出,Web的跨平台性特征遭到了损害,Web发布者可以使用很多非标准特性,如Flash,但这样做将缩小其网站的受众范围,如果考虑到越来越多的人转而使用智能手机和移动设备来访问Web,Web发布者还必须做出如下选择: 专门创建用于移动设备的应用程序,还是打造跨平台兼容性更强的Web应用程序。该不该为特定平台提供更强大的功能而降低跨平台的灵活性呢?

1.1.3 Web是分布式的

Web为什么能成功地提供海量信息呢?因为这些信息分布在全球的数百万个网站中,而每个网站都分别存储其发布的信息。每个网站驻留在一台或者多台被称为Web服务器的计算机上。Web服务器也是计算机,只是负责侦听并相映Web浏览器的请求。我们使用Web浏览器时,请求服务器提供资源以便查看它。我们只是将浏览器指向网站,其他什么都没有做。

网站是Web上一个发布信息的位置。您浏览网页时,浏览器连接到网络以获取该网页。每个网站、网站的每个 网页乃至每项信息都有独一无二的地址,这个地址被称为 :统一资源定位符(URL).

1.1.4 Web是动态的

重启浏览器可以获取Web上更新的信息

当前,甚至都无须重新加载网页就能看到最新的信息。通过使用JavaScript可以实时地更新网页的内容。

1.1.5 Web是交互式的

Web的交互性是通过单击链接跳到其他网页实现的。除此之外,Web还可以让用户同发布者和其他用户交流。

1.2 Web浏览器

注意:仅当您确定只有使用特定浏览器的用户访问您的网站时,选择针对该浏览器进行开发才是合适的。

1.2.1 Web浏览器的作用

Web浏览器的核心作用是连接到We服务器并请求文档,再妥善地设置这些文档的格式并显示它们。

Web浏览器还可显示本地计算机中的文件、下载并非用于显示的文件,甚至让用户能够发送和接受电子邮件。

所有网页都是使用超文本标记语言HTML编写的,这种语言指定网页包含的文本、描述网页的结构、指定到其他文档和图像等多媒体。

即使是同一个文件,不同浏览器设置其格式和显示的方式也可能不同,这取决于系统的功能以及浏览器的配置。

1.2.2 概述一些流行的Web浏览器

Google Chrome

和Apple Safari使用的是一个HTML引擎——开源引擎WebKit.

注意:

如果要检查跨平台兼容性问题,请从IE 和 Firefox开始,再将Chrome也囊括进来。

Firfox

Firfox得以流行是因为它不存在困扰IE的安全问题。

1.3 Web服务器

要将网页发布到Web上,需要一个Web服务器。

Web服务器是在计算机上运行的程序,负责响应Web浏览器的请求——向它提供URL指定的内容。运行服务器程序的计算机也被称为服务器。

您使用浏览器请求网页时,浏览器使用HTTP建立一条到服务器的Web连接。服务器接受连接,发送请求的文件,再关闭连接。接下来,浏览器对从服务器获取的信息设置格式。

在服务器端,可能有很多不同的浏览器连接到同一个服务器,该服务器负责处理所有这些请求。

Web服务器还负责管理表单输入以及将表单和浏览器关联到运行在服务器上的数据库等程序。

1.4 统一资源定位符

URL为查找并访问信息提供了统一而一致的方法。

还可以使用URL在文档中创建到另一个文档中的超文本链接。

*URL包含有关如下方面的信息: *

  • 如何获取信息(使用哪种协议: FTP,HTTP还是file);

  • 该信息所在计算机的Internet主机名(www.ncsa.uiuc.edu)等

  • 该信息位于网站的哪个目录或位置

还可以使用特殊的URL来完成发送邮件和运行JavaScript代码等任务

1.5 广义的Web发布

一般而言,网页都是根据别人创建的模版生成的,通常可以设置内容的格式——使用图形编辑器或让您能够避免使用HTML的简化标记。

如果,发布的内容看起来不妥,要修复问题就必须懂HTML。

需要区分由您控制的页面部分和您使用发布应用程序生成的部分。

1.7 作业

1.7.1 问与答

问: Web由谁负责运营?这些协议都是由谁控制的?这一切都是由谁管理的?

万维网并非由某个组织拥有或控制。有两类组织给Web的外观和发展方向带来了重大影响:

  1. 万维网联盟(W3C): 负责制定万维网标准和实施相关的规则,网址为:www.w3.org

  2. 浏览器开发商 :一些关心Web未来的个人和公司成立了一个名为WHATWG——超文本应用技术工作组。HTML5规范就是WHATWG和W3C一起制定的。

    从现在开谁,WHATWG放弃了给HTML规范制定版本号,相反,HTML是一个“流动的标准”,将涵盖实验性功能和得到广泛支持的功能,旨在确保该规范紧跟发展步伐,涵盖浏览器开发商一致同意在其浏览器中添加的功能。

    第2章 准备好工具

    2.1 网站剖析

    • 网站: 一个或多个网页,它们以有意义的方式组织起来,一起描述了一项信息或者营造出一种效果。
    • Web服务器: Internet或内联网中的计算机,在收到浏览器的请求时提供网页或其他文件(内联网是Internet协议但不对公众开放的网络)
    • 网页 :网站上的入口页面,可链接到当前网站或者其他网站的页面。
    • 主页: 网站的入口页面,可链接到当前网站或者其他网站的页面。

网站都由Web服务器托管。

网页有时也被称为Web文档。

网页由HTML文档和其他部分组成。

2.2 为发布到Web准备好计算机

学习如何从空白开始创建网站,就需要配置计算机,以便能够在本地创建和查看网页

尝试Web发布,只有两款工具是必不可少的,就是文本编辑器和Web浏览器。

2.2.1 文本编辑器

HTML属于纯文本文件,应使用处理纯文本的工具对其进行编辑。

2.2.2 Web浏览器

下载Google Chrome

###2.3 使用Google Chrome 开发者工具

打开开发者工具的快捷键 :

  • Windows中为ctrl + Shift + I
  • Mac OS 中: Command + Option + I

开发者工具是查看源代码这种概念的扩展,功能更加强大。

开发者工具中包含很多选项卡,默认显示的是Element,其中包含的内容类似于网页的源代码。浏览器下载网页的时候,将其进行转换,让设置HTML格式并显示它的引擎能够明白。

Element选项卡显示的是浏览器看到的HTML,而“查看源代码”显示的是浏览器下载的HTML。这两者有几个不同的地方。

将鼠标指向Element选项卡中的元素,相应的网页部分将呈高亮显示,由此可以知道网页各部分与HTML源代码的对应关系。

2.4 您要在Web上做什么

对于要发布到Web上的东西,我在本书中称之为内容。

2.5 绘制网站线框图

网络规划的下一步是确定如何在网页之间分配内容,并制定在网页之间导航的方案。

2.5.1 线框图是什么,为何需要绘制线框图

线框图是网站完成后的大致轮廓,指出了内容在网页之间的分配情况以及内容是如何彼此关联起来的。

对于复杂的大型网站,线框图可节省大量的时间,避免众多的弯路。

线框图,一般而言,是成组的文档或图像,每组表示网站中一种特定类型的网页。文档包含网页的粗略示意图指出了网页各各部分处于什么位置、占据多大空间以及将发挥的作用

2.5.2 线框图绘制小贴士

  • 如何在网页之间分配主题

    最佳的做法是,让每个页面都包含一个主题的信息。如果页面有好几屏长,也许该奖相应的主题按逻辑分成多个子主题。

    • 在网页之间导航的主要方式是什么?

    需要链接。这些是文档中的主链接,让访客能够实现您确定的目标。向前、向后、向上的链接以及到主页的链接都属于主要的导航方式。

    • 要提供其他哪些导航方式

    除了简单的导航链接外,还可以包含与主要Web内容平行的额外信息,如术语表

    按字母顺序排列的概念索引、版权信息或职员页面。

    • 要在主页上放置哪些内容?

    主页上的内容应该有足够的吸引力,让目标受众留下来

    • 如何让访问内部网页的用户知道他身处何方?

    可以通过设计和导航实现这个目标。

    • 您的目标是什么?

    注意聚焦于你的目标

    2.6 Web托管

    迟早需要将本地计算机上创建的网站放到Internet上,最简单的方法,是获取一个这样的Web托管账户,即让您能够将HTML文件、图像、样式和其他Web内容上传到一台Web服务器。

    2.6.1 使用内容管理应用程序

    使用应用程序将内容发布到Web上:

    • 博客网站
    • 一些在线工具可以建立格式比博客更开放、更专业的网站

上述两种方式,通常只需填写表单并为网站选择URL和主题,然后就可以通过表单输入内容。

2.6.2 设置Web托管

注册托管账户以便将网页发布到Web上的步骤如下:

  1. 注册一个域名。
  2. 选择一家Web托管公司并注册账户。
  3. 将注册的域名同新创建的网站关联起来。
  4. 开始上传内容。

第3章 HTML 和 CSS简介

3.1 HTML是什么

HTML指的是超文本标记语言

3.1.1 HTML定义了网页的结构

是一种描述文档结构而非实际呈现效果的语言。

3.1.2 HTML未定义网页的布局

HTML规范对页面的外观都未置一言。HTML标签只是指出元素是标题或列表,没有说明该如何设置标题或列表的格式。

注意:级联样式表(CSS)能够将复杂的格式设置应用于HTML标签。

应用于标签的视觉样式的发展历程

链接应该带有下划线并显示为蓝色,访问过的链接为紫色,要突出的文本为斜体……

3.1.3 HTML为何以这样的方式工作

设计原则:

image-20200602081006343

3.1.4 标记的工作原理

HTML是一种标记语言。

HTML包含一组可供使用的预处理标签,不能编造标签来创建新样式或功能。

不同的浏览器支持不同的标签。

3.2 HTML文件怎么样

HTM文件包含的以下内容:

  • 页面本身的内容
  • HTML标签

HTML中在结束标签中出现的是”/“而不是反斜杠

有些标签只有起始标签或者结束标签;

有些标签是荣国旗,在<>内包含额外的信息和文本;

有些标签内部有额外的文本,提供了有关签名的额外信息。这些文本被称为属性,通常以name=value的形式定义的,位于标签名后面,并用一个空格与标签名分隔开。

HTML标签不区分大小写。

3.2.1 文本格式设置和HTML

分析HTML页面时,浏览器会忽略您为设置格式而添加的所有空白字符,包括多余的空格、制表符、换行符等

在HTML中设置格式的唯一途径就是使用HTML标签。(此规则有两个例外,它们是标签

和CSS属性pre)

3.3 HTML 属性

可使用属性来修改HTML元素。属性是在元素的起始标签中指定的。

很多元素有其特有的属性,也有适用于所有元素的全局元素。例如: ID属性指定一个标识符,用于唯一地标识页面元素。

这些标识符主要供JavaScript和级联样式表使用。

class是一个全局属性,可用于建立元素编组。可给多个元素指定相同的class属性,以便在CSS或JavaScript中将这些元素作为一个编组来饮用它们。

第三个全局属性是style

3.4 使用属性style

级联样式表告诉浏览器如何渲染HTML元素。

CSS的优点在于,可以各种方式使用它。

3.4.1 在标签中指定样式

3.5 HTML标准简史

3.5.1 XHTML

XHTML文档必须是有效的XML。下面是规则:

  • 所有标签小写
  • 没有结束标签的标签,必须在其标签后面加上反斜杠,例如
    ->
  • 所有属性都必须有值。如果某些属性没有与之相关联的值,XHTML要求将其值设置为属性名,即采用格式attribute = "attribute"

3.6 不断发展的最新标准HTML5

访问http://caniuse.com/ ,查看HTML5的各项功能、支持该功能的浏览器版本以及其浏览器支持该功能的用户所占的百分比。

3.8 问与答

3.8.1 可以使用HTML设置文本格式吗?

可以对字符串设置一些格式。大多数设置文本格式的标签都已被CSS取代,但浏览器依然支持较旧的文本格式设置元素。

第4章 HTML基础知识

4.1 定义页面的总体结构

HTML定义了三个用于定义页面总体结构以及提供简单标题信息的标签——,,

DOCTYPE标识符

它不是网页结构的标签,但是XHTML和HTML5都要求网页必须包含它。

4.1.1 标签

网页的所有文本和HTML元素都必须放在起始和结束标签之间。

如果省略它,浏览器将会替你添加。

4.1.2 标签

是一个容器,包含所有提供有关网页的信息的标签,但不包含将显示在网页中的信息。

4.1.3 标签

结束HTML标签时,务必确保结束的时最近的未结束标签。

4.2 名称

每个HTML页面都需要一个名称,它指出页面描述的是什么。用户浏览网页时,这个名称出现在浏览器的标题栏中。这个名称被浏览器用来创建书签;还被搜索引擎用来建立页面索引。

使用标签指定名称</strong></p> <p><strong>将<title>标签放在<head>中</strong></p> <p><strong><title>不能包含其他标签</strong></p> <h3 id="4-3-标题"><a href="#4-3-标题" class="headerlink" title="4.3 标题"></a>4.3 标题</h3><p>HTML定义了6级标题</p> <p>在HTML文档中,缩进可以更好地展示层次结构。但是在展示的页面中,不会缩进。浏览器对缩进置若罔闻。</p> <p>*<em>警告: *</em>不要为了将文本显示为粗体或突出网页的某些部分而使用标题,这样做虽然会获得想要的效果,但标记表示的将不再是网页的结构。这样会影响搜索引擎、便利性和有些浏览器。</p> <p><strong>提示:</strong>从视觉效果来看,4-6级标题不是很有用,但是从文档结构的角度看,很有意义。另外,使用4-6级标题可以使用样式来获得想要的效果。</p> <h3 id="4-4-段落"><a href="#4-4-段落" class="headerlink" title="4.4 段落"></a>4.4 段落</h3></p>虽然并非 必不可少,但是对CSS确定段落的内容至关重要。 <h3 id="4-5-注释"><a href="#4-5-注释" class="headerlink" title="4.5 注释"></a>4.5 注释</h3><p>分析HTML文件时,会忽略注释中的文本。但是,在浏览器中,用户可以使用“查看源代码”功能来查看注释,因此,对于不希望用户看到的东西,请不要将其放在注释中。</p> <p>格式: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- This is comment --></span></span><br></pre></td></tr></table></figure> <h3 id="4-7-问与答"><a href="#4-7-问与答" class="headerlink" title="4.7 问与答"></a>4.7 问与答</h3><h4 id="4-7-1-有些网页没有使用结构页面标签-、、-。那真的是必须包含它们吗?"><a href="#4-7-1-有些网页没有使用结构页面标签-、、-。那真的是必须包含它们吗?" class="headerlink" title="4.7.1 有些网页没有使用结构页面标签(、、)。那真的是必须包含它们吗?"></a>4.7.1 有些网页没有使用结构页面标签(<html>、<head>、<body>)。那真的是必须包含它们吗?</h4><p>除了<title>以外可以不包括,但是最好养成使用结构标签的习惯。</p> <h4 id="4-7-2-标签是用于添加网页内容的通用标签吗?"><a href="#4-7-2-标签是用于添加网页内容的通用标签吗?" class="headerlink" title="4.7.2 标签是用于添加网页内容的通用标签吗?"></a>4.7.2 标签<p>是用于添加网页内容的通用标签吗?</h4><p>不是,标签<div>才是用于添加网页内容的通用标签。标签<p>专用于在网页中添加文本段落。包括<div>在内的很多标签都不能放在标签<p>里面。</p> <h4 id="4-7-3-注释可以包含HTML标签"><a href="#4-7-3-注释可以包含HTML标签" class="headerlink" title="4.7.3 注释可以包含HTML标签"></a>4.7.3 注释可以包含HTML标签</h4><p>可以在注释中包含HTML标签,但是浏览器不会显示它们。使用注释来隐藏页面的某部分是一种常见的做法,通常这种做法叫做“注释掉”。</p> <h2 id="第5章-使用列表组织信息"><a href="#第5章-使用列表组织信息" class="headerlink" title="第5章 使用列表组织信息"></a>第5章 使用列表组织信息</h2><p><strong>知识导航:</strong></p> <ul> <li>如何创建编号列表</li> <li>如何创建项目列表</li> <li>如何创建定义列表(definition list)</li> <li>与列表相关的级联样式表(CSS)属性</li> </ul> <h3 id="5-1-列表概述"><a href="#5-1-列表概述" class="headerlink" title="5.1 列表概述"></a>5.1 列表概述</h3><p>列表分为: </p> <ul> <li>有序列表</li> <li>无序列表</li> <li>定义列表</li> </ul> <p>所有的列表标签都有如下特征 :</p> <ul> <li>列表有指定类型的外部元素。<ul></ul>表示无序列表,<ol></ol>表示有序列表,<dl></dl>表示定义列表。</li> <li>每个列表项都有自己的标签:在词汇列表中为<dt>和<dd>.在其他列表中为<li></li> </ul> <h3 id="5-2-带编号的列表"><a href="#5-2-带编号的列表" class="headerlink" title="5.2 带编号的列表"></a>5.2 带编号的列表</h3><p>带编号的列表是<ol></ol>定义的,而其中的列表行是使用标签<li>和</li>定义的。</p> <p>显示有序列表时,列览器会缩进列表并编号。</p> <p><strong>仅当列表项的顺序很重要时,才应使用带编号的列表。</strong></p> <h4 id="5-2-1-定制有序列表"><a href="#5-2-1-定制有序列表" class="headerlink" title="5.2.1 定制有序列表"></a>5.2.1 定制有序列表</h4><p>定制的方法有两种: </p> <ul> <li><p>修改列表的编号样式(1.使用CSS属性list-style-type;2.使用属性[attribute]type——在html5中已经被舍弃)</p> </li> <li><p>修改编号本身。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfeu2l0967j31by0gugx1.jpg" alt="image-20200603091556411"></p> </li> </ul> <p>要指定列表使用的起始编号或字母,可使用属性start.不管指定的编号形式是什么,属性start的值必须时十进制数字。</p> <p>在任何列表项都可以添加一个value值以重新开始从这个列表项开始的序号。</p> <h3 id="5-3-无序列表"><a href="#5-3-无序列表" class="headerlink" title="5.3 无序列表"></a>5.3 无序列表</h3><p>无序列表通常称为项目符号列表,其中每个列表项都使用相同的项目符号,而不是编号。</p> <p>对于无序列表,各个浏览器标记每个列表项时默认使用的项目符号相同,但文本浏览器通常使用星号。</p> <h4 id="5-3-1-定制无序列表"><a href="#5-3-1-定制无序列表" class="headerlink" title="5.3.1 定制无序列表"></a>5.3.1 定制无序列表</h4><p>项目符号样式如下: </p> <ul> <li>disc: 圆盘,这是默认样式</li> <li>square:实心正方形</li> <li>circle:生成空心圆</li> </ul> <p>可以使用一幅画来自定义项目符号样式,例如: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">ul</span> <span class="attr">style</span>=<span class="string">"list-style-image: url(/bullet.gif)"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">li</span>></span>Example<span class="tag"></<span class="name">li</span>></span></span><br><span class="line"><span class="tag"></<span class="name">ul</span>></span></span><br></pre></td></tr></table></figure> <p>使用如下方式实现后续行与项目符号或列表符号对齐: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">ul</span> <span class="attr">style</span>=<span class="string">"list-style-position: inside"</span>></span><span class="tag"></<span class="name">ul</span>></span></span><br></pre></td></tr></table></figure> <p>同时修改多个与列表相关的属性,可以像下面这样,同时指定三个值: 列表样式类型、列表样式位置和用作项目符号图像的url: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">ul</span> <span class="attr">style</span>=<span class="string">"list-style: circle inside url("</span>")"></span><span class="tag"></<span class="name">ul</span>></span></span><br></pre></td></tr></table></figure> <h3 id="5-4-定义列表"><a href="#5-4-定义列表" class="headerlink" title="5.4 定义列表"></a>5.4 定义列表</h3><p>定义列表包含两部分: </p> <ul> <li>术语</li> <li>术语的定义</li> </ul> <p>定义列表的每部分都有其标签: 术语标签为<dt></dt>,定义标签<dd><dd>。它们往往成对存在。</p> <p>整个定义列表是使用标签<dl></dl>定义的。</p> <p>在浏览器中显示定义列表时,通常术语和定义是分开的并缩进定义。</p> <h3 id="5-5-嵌套列表"><a href="#5-5-嵌套列表" class="headerlink" title="5.5 嵌套列表"></a>5.5 嵌套列表</h3><p>在一个列表中放置另一个列表。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gffis1mo3gj31ey0esaos.jpg" alt="image-20200603233039568"></p> <h3 id="5-6-列表的其他用途"><a href="#5-6-列表的其他用途" class="headerlink" title="5.6 列表的其他用途"></a>5.6 列表的其他用途</h3><p>列表远不是简单的项目符号列表。很多常见的Web设计元素的结构都类似于列表。</p> <h3 id="5-8-作业"><a href="#5-8-作业" class="headerlink" title="5.8 作业"></a>5.8 作业</h3><h4 id="5-8-1-可修改列表项的缩进程度或让它们不缩进吗?"><a href="#5-8-1-可修改列表项的缩进程度或让它们不缩进吗?" class="headerlink" title="5.8.1 可修改列表项的缩进程度或让它们不缩进吗?"></a>5.8.1 可修改列表项的缩进程度或让它们不缩进吗?</h4><p>用于控制列表缩进程度的属性是 :margin-left和padding-left,有些浏览器使用前者,有些浏览器使用后者。</p> <p>为了让文本按照特定的方式对齐,可以将边距(margin)设置为负值。</p> <h2 id="第6章-使用链接"><a href="#第6章-使用链接" class="headerlink" title="第6章 使用链接"></a>第6章 使用链接</h2><h3 id="6-1-创建链接"><a href="#6-1-创建链接" class="headerlink" title="6.1 创建链接"></a>6.1 创建链接</h3><p>使用URL创建链接,需要: </p> <ul> <li>要链接到的文件名(或者URL)</li> <li>用作可单击链接的文本</li> </ul> <p>在链接标签中,只有文本是在网页上可见的;单击链接时,浏览器将加载相关联的URL。</p> <h4 id="6-1-1-链接标签"><a href="#6-1-1-链接标签" class="headerlink" title="6.1.1 链接标签"></a>6.1.1 链接标签<a></h4><p><a></a>也被称为锚标签,因为它也可用于创建锚。链接标签最常用的用途是,用于创建到其他网页的链接。</p> <p>标签<a>必须包含一些属性。</p> <p>在链接标签中,最常见的属性是herf,它表示超文本引用,可以使用它来指定链接指向的文件名或URL。</p> <p>*<em>注意: *</em>在HTML5中,没有任何属性的<a>也是合法的,这种链接称为占位符链接,可将其与CSS和脚本结合起来使用。</p> <p>关于具体如何使用链接: </p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gffz85iunhj31di0g6grd.jpg" alt="image-20200604085951473"></p> <p>在链接中,文件名是区分大小写的,但是url不区分。</p> <p>有关标签嵌套的提醒: </p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gffzmcfesnj31au0bob0c.jpg" alt="image-20200604091329375"></p> <h3 id="6-2-使用相对和绝对路径链接到本地页面"><a href="#6-2-使用相对和绝对路径链接到本地页面" class="headerlink" title="6.2 使用相对和绝对路径链接到本地页面"></a>6.2 使用相对和绝对路径链接到本地页面</h3><p>*<em>注意: *</em>文件夹和目录说的是一码事,具体使用哪个取决于的是操作系统。</p> <p>要在链接中指定相对路径名,必须使用UNIX式路径,而不管使用的是什么操作系统。即,</p> <p>使用斜杠(/)来分隔目录名,并使用两点(..)来表示当前目录的父目录。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gffzrke0vmj31bk0ic7ip.jpg" alt="image-20200604091831046"></p> <h4 id="6-2-1-绝对路径名"><a href="#6-2-1-绝对路径名" class="headerlink" title="6.2.1 绝对路径名"></a>6.2.1 绝对路径名</h4><p>相对路径名通过指定相对于当前页面的位置来指定要链接到的页面,而绝对路径名,从顶级目录出发,要经过哪些目录才能到达这个文件。</p> <p>绝对路径总是以斜杠打头,以便与当前目录区分开来。斜杠后面从顶级目录出发,前往链接到的文件需要经过的所有目录。</p> <p>*<em>注意: *</em></p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gffzv91xspj31ey0o0qnz.jpg" alt="image-20200604092202966"></p> <h4 id="6-2-2-该使用相对路径还是使用绝对路径"><a href="#6-2-2-该使用相对路径还是使用绝对路径" class="headerlink" title="6.2.2 该使用相对路径还是使用绝对路径"></a>6.2.2 该使用相对路径还是使用绝对路径</h4><p>合理混合使用这两种链接是最佳的选择。经验规则是: </p> <p>如果链接的页面属于同一个集合,就使用相对路径,否则,使用绝对路径。</p> <h3 id="6-3-链接到Web上的其他文档"><a href="#6-3-链接到Web上的其他文档" class="headerlink" title="6.3 链接到Web上的其他文档"></a>6.3 链接到Web上的其他文档</h3><p>要链接到Internet上的其他页面(远程页面),也可以使用链接标签。</p> <p>使用<em></em>可以将字体设置为斜体。</p> <h3 id="6-4-链接到文档的特定位置"><a href="#6-4-链接到文档的特定位置" class="headerlink" title="6.4 链接到文档的特定位置"></a>6.4 链接到文档的特定位置</h3><p>要链接到另一个页面的特定位置,只需在链接的URL中指定要链接的元素的ID。</p> <p>也可以使用链接跳到网页中的特定元素。</p> <p>ID属性可用于页面的任何元素,唯一的要求是,每个ID在当前页面中是独一无二的。</p> <p>Id还可用于设置页面的样式。</p> <h4 id="6-4-1-创建链接和锚"><a href="#6-4-1-创建链接和锚" class="headerlink" title="6.4.1 创建链接和锚"></a>6.4.1 创建链接和锚</h4><p>创建一个锚的例子: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">h1</span> <span class="attr">id</span>=<span class="string">"part4"</span>></span>Part Four: Grapefruit from Heaven <span class="tag"></<span class="name">h1</span>></span></span><br></pre></td></tr></table></figure> <p>在链接中指向一个锚: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"mybgdoc.html#part"</span>></span>Go to part 4<span class="tag"></<span class="name">a</span>></span></span><br></pre></td></tr></table></figure> <p>其中#后面是id</p> <h4 id="6-4-2-标签的name属性"><a href="#6-4-2-标签的name属性" class="headerlink" title="6.4.2 标签的name属性"></a>6.4.2 标签<a>的name属性</h4><p>使用标签<a>创建锚时,不是使用属性herf来链接到特定的页面,而使用属性name来指出这是一个可链接的锚。</p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span>></span> name="part4"<span class="tag"></<span class="name">a</span>></span>Part Four : Grapefruit from Heaven </span><br><span class="line"><span class="tag"></<span class="name">h1</span>></span></span><br></pre></td></tr></table></figure> <p>建议不要不用属性name来创建锚链接,而是使用ID。ID属性适用于任何HTML元素,但是name只支持<a>标签。</p> <h4 id="6-4-3-链接到当前文档的其他元素"><a href="#6-4-3-链接到当前文档的其他元素" class="headerlink" title="6.4.3 链接到当前文档的其他元素"></a>6.4.3 链接到当前文档的其他元素</h4><p>在当前页面链接是,只需要6.4.1的锚的示例中#号后面的部分,包含#号在内。</p> <h3 id="6-5剖析URL"><a href="#6-5剖析URL" class="headerlink" title="6.5剖析URL"></a>6.5剖析URL</h3><p>URL:统一资源定位符</p> <h4 id="6-5-1-URL的组成成分"><a href="#6-5-1-URL的组成成分" class="headerlink" title="6.5.1 URL的组成成分"></a>6.5.1 URL的组成成分</h4><p>包含三部分: 协议,主机名,目录或文件名</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfh4l9mzr4j319o0e4dmq.jpg" alt="image-20200605085059262"></p> <p><strong>端口:</strong> URL的主机部分可能包含端口号。端口号让浏览器使用合适的协议链接到相应的网络端口。仅当相应请求的服务器被显式地设置为侦听特定端口时,才需要在URL中包含端口号。服务器默认端口是80,此时可以省略。</p> <p>当存在端口号时: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://my-public-access-unix.com:155/pub/file</span><br></pre></td></tr></table></figure> <p>服务器的根url的路径为/,如<a href="http://www.example.com/" target="_blank" rel="noopener">http://www.example.com/</a></p> <p>很多url后面还有查询,它通过问号与URL的其他部分分开。查询由名称-值对组成,名称-值对之间用&分隔,而名称和值之间用等号=分隔。例如: </p> <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://www.google.com/search?q=china&oq=china&aqs=chrome..69i57j69i61j69i60.6294j0j1&sourceid=chrome&ie=UTF-8</span><br></pre></td></tr></table></figure> <p>其中</p> <p>oq:上次搜索关键字</p> <p>ie:关键字编码格式</p> <h4 id="6-5-2-URL中的特殊字符"><a href="#6-5-2-URL中的特殊字符" class="headerlink" title="6.5.2 URL中的特殊字符"></a>6.5.2 URL中的特殊字符</h4><p>URL中的特殊字符是指除了大小写字母、数字0-9和下述符号外的字符: 美元$,连字符(-),下划线(_)句点(.)。</p> <p>使用特殊字符,要经过转义编码以防止它们被视为URL的一部分。而编码是由百分符号和两位十六进制数组成(0-9,A-F)的</p> <p>例如: </p> <p>空格: %20</p> <p>问号:%3e</p> <p>斜杠: %2f</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfh81cd0ymj31cc0iuauw.jpg" alt="image-20200605105015529"></p> <h4 id="6-5-3-属性rel"><a href="#6-5-3-属性rel" class="headerlink" title="6.5.3 属性rel"></a>6.5.3 属性rel</h4><p>rel是<a>的另一个属性,用于描述当前文档与链接到的文档之间的关系。</p> <p>这个属性有一组特定的可能取值,其中最著名的是nofollow,它让搜索引擎给链接到的文档排名时不要考虑这个链接,是一种防范搜索引擎作弊的手段。使用如下: </p> <figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"http://www.example.com/"</span> <span class="attr">ref</span>=<span class="string">"nofollow"</span>></span>Link to example site<span class="tag"></<span class="name">a</span>></span></span><br></pre></td></tr></table></figure> <p>关于rel更多的属性,请查看网站: </p> <figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">http://microformats.org/wiki/existing-values</span></span><br></pre></td></tr></table></figure> <h3 id="6-6-URL分类"><a href="#6-6-URL分类" class="headerlink" title="6.6 URL分类"></a>6.6 URL分类</h3><p>统一资源定位符规范定义了很多种URL。</p> <h4 id="6-6-1-HTTP-URL"><a href="#6-6-1-HTTP-URL" class="headerlink" title="6.6.1 HTTP URL"></a>6.6.1 HTTP URL</h4><p>是最长的URL类型。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfh8dt83qoj31cg0t8gt5.jpg" alt="image-20200605110207485"></p> <h4 id="6-6-2-匿名FTP-URL"><a href="#6-6-2-匿名FTP-URL" class="headerlink" title="6.6.2 匿名FTP URL"></a>6.6.2 匿名FTP URL</h4><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfh8fp5qx8j31b40ti4qp.jpg" alt="image-20200605110403122"></p> <p>浏览器使用FTP来获取文件时,如果获取的是HTML文件,浏览器就将显示它,否则就将它保存在磁盘中。</p> <p>ps: 我在想那些打开链接就直接下载的URL应该就属于FTP URL。</p> <h4 id="6-6-3-非匿名-FTP-URL"><a href="#6-6-3-非匿名-FTP-URL" class="headerlink" title="6.6.3 非匿名 FTP URL"></a>6.6.3 非匿名 FTP URL</h4><p>总结就是: 尽量不使用,必须使用时使用专用的FTP客户端而不是浏览器。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfh8mpt5b0j31es0l0txx.jpg" alt="image-20200605111047982"></p> <h4 id="6-6-4-mailto-URL"><a href="#6-6-4-mailto-URL" class="headerlink" title="6.6.4 mailto URL"></a>6.6.4 mailto URL</h4><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfh8r7fqaxj30u00w4kjl.jpg" alt="image-20200605111505682"></p> <h4 id="6-6-5-文件URL"><a href="#6-6-5-文件URL" class="headerlink" title="6.6.5 文件URL"></a>6.6.5 文件URL</h4><p>文件URL指向本地磁盘中的文件,即指向浏览器所在系统中的文件。(它们包含三个而不是两个斜杠)</p> <figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">file:///dirl/dir2/file</span></span><br></pre></td></tr></table></figure> <p>文件URL的另一个用途是,为浏览器指定一个本地启动页面,其中包含到经常访问的网站的链接。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfh8xh9xodj31bs0eqwxf.jpg" alt="image-20200605112109294"></p> <p>ps: 有点绕</p> <h3 id="6-8-问答"><a href="#6-8-问答" class="headerlink" title="6.8 问答"></a>6.8 问答</h3><h4 id="问:有办法在malto-URL中指定主题吗?"><a href="#问:有办法在malto-URL中指定主题吗?" class="headerlink" title="问:有办法在malto URL中指定主题吗?"></a>问:有办法在malto URL中指定主题吗?</h4><p>可以。具体见截图。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfhmw2jc0bj31bg08gdni.jpg" alt="image-20200605192408660"></p> <h2 id="第7章-使用HTML和CSS设置文本的格式"><a href="#第7章-使用HTML和CSS设置文本的格式" class="headerlink" title="第7章 使用HTML和CSS设置文本的格式"></a>第7章 使用HTML和CSS设置文本的格式</h2><h3 id="7-1-字符级元素"><a href="#7-1-字符级元素" class="headerlink" title="7.1 字符级元素"></a>7.1 字符级元素</h3><p>字符级元素指的是影响其他HTML标签中单词或字符的标签,它们修改这些文本的外观,使其不同于周围的文本,例如将其设置为粗体或带下划线。</p> <p><a>是字符级元素。</p> <p>可以在块级元素中嵌套字符级元素,也可以在字符级元素中嵌套块级元素。</p> <p>要修改文本中一系列字符的外观,可以使用: </p> <ul> <li>HTML语义标签或级联样式表(CSS)</li> </ul> <h4 id="7-1-1-HTML语义标签"><a href="#7-1-1-HTML语义标签" class="headerlink" title="7.1.1 HTML语义标签"></a>7.1.1 HTML语义标签</h4><p>语义标签描述了它包含的文本的定义,而不是这些文本在浏览器中的外观。例如 :HTML语义标签可能指出它包含的内容是一个定义、代码片段或者要突出的单词。</p> <p>每个字符样式标签都有起始标签和结束标签,并影响包含在这两个标签之间的文本。下面是HTML一些语义标签: </p> <ul> <li><em>: 这个标签指出要以某种方式突出字符。大多数浏览器都将<em>显示为斜体。</li> <li><strong>使用这个标签时,字符比使用<em>时更加突出,通常显示为粗体</li> <li><code>: 这个标签指出它包含的文本是示例代码,应使用Courier等等宽字体显示。</li> <li><samp>:这个标签指出它包含的是示例文本,通常像<code>一样使用等宽字体显示。</li> <li><kbd>:这个标签标示需要用户输入的文本,也用等宽字体显示</li> <li><var>: 这个标签标示需要用实际值替换的变量或其他实体的名称,通常显示为斜体或下划线.</li> <li><dfn>: 这个标签标示定义。用于突出要定义或刚定义的单词(通常使用斜体表示)</li> <li><cite>: 这个标签标示引用的作品名,通常显示为斜体</li> <li><abbr>: 这个标签标示缩略语</li> </ul> <h4 id="7-1-2-HTML5-物理样式标签的变化"><a href="#7-1-2-HTML5-物理样式标签的变化" class="headerlink" title="7.1.2 HTML5 物理样式标签的变化"></a>7.1.2 HTML5 物理样式标签的变化</h4><p>物理样式: physical style.</p> <p>书上提示不要使用它们,而应该使用CSS或等价的语义标签。</p> <ul> <li><b>:通常显示为粗体的文本</li> <li><i>:通常显示为斜体的文本</li> <li><u>:通常显示为带下划线的文本</li> <li><samll>:使用小字号显示的文本</li> <li><sub>:下标</li> <li><sup>:上标</li> </ul> <h3 id="7-2-使用CSS设置字符的格式"><a href="#7-2-使用CSS设置字符的格式" class="headerlink" title="7.2 使用CSS设置字符的格式"></a>7.2 使用CSS设置字符的格式</h3><p>大多数标签都以某种方式影响其包含的文本的外观,但有一个标签对其包含的文本没有任何影戏,这就是标签<span>,它存在的目的就是要与样式表相关联。使用时,只需要用它来包含文本即可。</p> <p>单独使用<span>时没有任何效果,与属性结合起来,它可取代前面的所有标签,而且效果好很多。</p> <h4 id="7-2-1-text-decoration-属性"><a href="#7-2-1-text-decoration-属性" class="headerlink" title="7.2.1 text-decoration 属性"></a>7.2.1 text-decoration 属性</h4><p>text-decoration属性用于指定要对标签内的文本应用哪种装饰。这个属性的可取值包括: underline,overline,line-through和blink。</p> <h4 id="7-2-2-字体属性"><a href="#7-2-2-字体属性" class="headerlink" title="7.2.2 字体属性"></a>7.2.2 字体属性</h4><p>修改文本的外观时,可使用的一系列其他主要属性时字体属性。使用字体属性时,几乎可以修改浏览器渲染文本时使用的任何字体的任何方面。</p> <p>属性font-style可用于将文本设置为斜体,它有三种可能取值:normal,italic(像标签<i>那样渲染文本),oblique(标准字体的倾斜版本)。</p> <p>将文本设置为oblique 或italic时,浏览器将选择这两者中可用的那个。如果这两者都没有安装,浏览器通常会生成字体的倾斜版本。</p> <p>使用CSS来创建粗体文本。在HTML中,只有两种选择:要么为粗体,要么不为粗体。而使用CSS时,可以有更多的选择。实际上,文本要么为粗体,要么正常。要将文本设置为粗体,可以属性<strong>font-weight</strong>。这个值的属性的可能取值包括normal,bold,bolder,lighter以及100-900(以100为单位)</p> <p>提示: </p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfhqdxo4jpj31dc0ae7fi.jpg" alt="image-20200605212508661"></p> <p>还可以使用属性<strong>font-family</strong>来设置文本使用的字体系列;还可以指定文本使用特定的字体。</p> <p>指定字体系列时,具体使用哪种字体将取决于用户系统的首选项。</p> <p>指定字体系列时,可使用属性<strong>font-family</strong>,其值可能为serif, sans-serif,cursive,fantasy,monospase.</p> <p>使用CSS可以实现HTML标签无法实现的一种功能:<strong>font-variant</strong>.它可以渲染文本,即将小写字母替换为小型大写字母(small capital)字母,这个 属性有两个可能的取值: normal,small-caps.</p> <p>PS: 结果类似于下面这种,应该一看就知道是什么意思了:</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfhqodvajhj30m2024t8p.jpg" alt="image-20200605213512350"></p> <h3 id="7-3-预定格式的文本"><a href="#7-3-预定格式的文本" class="headerlink" title="7.3 预定格式的文本"></a>7.3 预定格式的文本</h3><p>HTML代码中包含的多余空白都会被浏览器删除,但是使用预定义格式文本标签<pre>时会有例外。对于放在标签<pre>和</pre>之前的文本,其中的所有空白都会保留在最终输出中,这可以用于在显示的页面中保留HTML代码中文本的距离。</p> <p>预定义格式的文本通常是使用Courier等等宽字体显示的。</p> <p>在包含在标签<pre>内的文本中,可食用链接标签和字体样式,但不能使用标题和段落等元素。</p> <p>应使用硬回车进行换行,并尽可能让每行的长度不超过60个字符。</p> <p>在预定义格式文本中慎用制表符,因为字表符在不同浏览器中代表不同字符。</p> <p>pre还非常适合用于快速而轻松地将纯文本格式文件(电子邮件)转换为HTML。</p> <p>标签pre还可用于在网页中创建ASCII艺术品。像下面这样: <img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfi8bo5kqnj30ii09k75c.jpg" alt="image-20200606074546132"></p> <h3 id="7-4-水平分割线-分隔"><a href="#7-4-水平分割线-分隔" class="headerlink" title="7.4 水平分割线(分隔)"></a>7.4 水平分割线(分隔)</h3><p>标签<hr>在网页中创建了一条水平线,它没有结束标签,也不包含任何文本。在HTML5中,这个标签被赋予了语义:主题分隔。这个标签一直都显示为一条水平线,现在的语义是:表示主题变了。</p> <p><strong>结束空元素</strong>:</p> <p>为了符合XHTML的要求,可以使用<hr/>作为结束标签。即使这个标签包含属性,斜杠也应位于标签名后面。 </p> <h4 id="7-4-1-的属性"><a href="#7-4-1-的属性" class="headerlink" title="7.4.1 的属性"></a>7.4.1 <hr>的属性</h4><ul> <li>size:指定水平线的粗细,单位为像素,默认为2像素,也是最细的水平线</li> <li>Width:指定水平线的水平宽度。可以将宽度指定为特定的像素级,也可以指定为屏幕宽度的百分比。</li> </ul> <p>PS: 这里说一下我看到这里时的困惑,size和width有什么不同呢?具体看下面的截图就能知道了。</p> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfjp50csj8j322m0caq4l.jpg" alt="image-20200607141306694"></p> <p>如果指定的宽度比浏览器的宽度小,还可使用属性align指定水平线的对齐方式:</p> <ul> <li>左对齐:align="left"</li> <li>右对齐:align="right"</li> <li>居中:align="center"</li> </ul> <p>默认情况下,水平居中</p> <p>使用已摒弃的属性noshade让浏览器将水平线绘制为没有三维投影的普通线条。</p> <h3 id="7-5-换行"><a href="#7-5-换行" class="headerlink" title="7.5 换行"></a>7.5 换行<br></h3><p><br>在其所在的地方换行。浏览器遇到标签<br>时间,将重起一行显示它后面的文本,且缩进程度与当前元素的左边距相同。</p> <p>br仅仅是重起一行显示后续文本,它不会增加新行的行前距和行后距,也不会改变当前实体的字体和样式。</p> <h1 id="11"><a href="#11" class="headerlink" title="11"></a>11</h1><h2 id="问题一"><a href="#问题一" class="headerlink" title="问题一"></a>问题一</h2><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1ghey4u9winj31p20s27ad.jpg" alt=""></p> <h2 id="问题二"><a href="#问题二" class="headerlink" title="问题二"></a>问题二</h2><p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1ghezpvuj2pj30z00m8480.jpg" alt="image-20200804191201098"></p> <p>上图中,第一个选择器选择first下的second;第二个选择器选择first下的third</p> <h2 id="问题三"><a href="#问题三" class="headerlink" title="问题三"></a>问题三</h2><p><strong>CSS选择器中的大于是什么意思?</strong></p> <p>可以把>理解为是直接的小孩(direct descendant child),而平常比较容易看到的空格(space)是所有小孩的意思。</p> <p>区别在于,>要求被选择的元素不是嵌套元素,也就是说不是在其他元素内部的。</p> <p>space则没有上述要求,指的是class下所有的此类元素</p> <h1 id="遗忘知识"><a href="#遗忘知识" class="headerlink" title="遗忘知识"></a>遗忘知识</h1><h2 id="标签"><a href="#标签" class="headerlink" title="标签"></a>标签</h2><ul> <li><caption> : 表格的标题</li> <li><b>:加粗</li> </ul> <h1 id="自己没有解决但是上网找到解决方法的问题"><a href="#自己没有解决但是上网找到解决方法的问题" class="headerlink" title="自己没有解决但是上网找到解决方法的问题"></a>自己没有解决但是上网找到解决方法的问题</h1><ul> <li>HTML中嵌套表格时,最里面的表格按照常规方法是不能居中显示的,解决办法如下</li> </ul> <p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1ghb3jngmj7j31h803swit.jpg" alt="image-20200801102208275"></p> <p>- </p> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-CN"> <link itemprop="mainEntityOfPage" href="http://www.nianchu.space/2020/06/01/HTML/HTML/"> <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person"> <meta itemprop="image" content="/images/avatar.gif"> <meta itemprop="name" content="nianchu"> <meta itemprop="description" content=""> </span> <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"> <meta itemprop="name" content="nianchu-Blog"> </span> <header class="post-header"> <h1 class="post-title" itemprop="name headline"> <a href="/2020/06/01/HTML/HTML/" class="post-title-link" itemprop="url">HTML/HTML</a> </h1> <div class="post-meta"> <span class="post-meta-item"> <span class="post-meta-item-icon"> <i class="fa fa-calendar-o"></i> </span> <span class="post-meta-item-text">发表于</span> <time title="创建时间:2020-06-01 10:22:20 / 修改时间:14:27:50" itemprop="dateCreated datePublished" datetime="2020-06-01T10:22:20+08:00">2020-06-01</time> </span> </div> </header> <div class="post-body" itemprop="articleBody"> <h1 id="HTML"><a href="#HTML" class="headerlink" title="HTML"></a>HTML</h1><p>全称: 超文本标记语言(HyperText Markup Language)</p> <p>##HTML文件的后缀: </p> <ul> <li>.html</li> <li>htm</li> </ul> <p>以上两种没有任何区别</p> </div> <footer class="post-footer"> <div class="post-eof"></div> </footer> </article> </div> <nav class="pagination"> <a class="extend prev" rel="prev" href="/"><i class="fa fa-angle-left" aria-label="上一页"></i></a><a class="page-number" href="/">1</a><span class="page-number current">2</span><a class="page-number" href="/page/3/">3</a><span class="space">…</span><a class="page-number" href="/page/14/">14</a><a class="extend next" rel="next" href="/page/3/"><i class="fa fa-angle-right" aria-label="下一页"></i></a> </nav> </div> </div> <div class="toggle sidebar-toggle"> <span class="toggle-line toggle-line-first"></span> <span class="toggle-line toggle-line-middle"></span> <span class="toggle-line toggle-line-last"></span> </div> <aside class="sidebar"> <div class="sidebar-inner"> <ul class="sidebar-nav motion-element"> <li class="sidebar-nav-toc"> 文章目录 </li> <li class="sidebar-nav-overview"> 站点概览 </li> </ul> <!--noindex--> <div class="post-toc-wrap sidebar-panel"> </div> <!--/noindex--> <div class="site-overview-wrap sidebar-panel"> <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person"> <p class="site-author-name" itemprop="name">nianchu</p> <div class="site-description" itemprop="description"></div> </div> <div class="site-state-wrap motion-element"> <nav class="site-state"> <div class="site-state-item site-state-posts"> <a href="/archives/"> <span class="site-state-item-count">131</span> <span class="site-state-item-name">日志</span> </a> </div> <div class="site-state-item site-state-categories"> <a href="/categories/"> <span class="site-state-item-count">9</span> <span class="site-state-item-name">分类</span></a> </div> <div class="site-state-item site-state-tags"> <a href="/tags/"> <span class="site-state-item-count">4</span> <span class="site-state-item-name">标签</span></a> </div> </nav> </div> <div class="feed-link motion-element"> <a href="/atom.xml" rel="alternate"> <i class="fa fa-rss"></i>RSS </a> </div> <div class="links-of-author motion-element"> <span class="links-of-author-item"> <a href="https://github.com/nianchu99?tab=projects" title="GitHub → https://github.com/nianchu99?tab=projects" rel="noopener" target="_blank"><i class="fa fa-fw fa-github"></i></a> </span> <span class="links-of-author-item"> <a href="/mailto:l17623029272@163.com" title="E-Mail → mailto:l17623029272@163.com" rel="noopener" target="_blank"><i class="fa fa-fw fa-envelope"></i></a> </span> <span class="links-of-author-item"> <a href="https://weibo.com/2795043945/profile?topnav=1&wvr=6" title="Weibo → https://weibo.com/2795043945/profile?topnav=1&wvr=6" rel="noopener" target="_blank"><i class="fa fa-fw fa-weibo"></i></a> </span> </div> <div class="cc-license motion-element" itemprop="license"> <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/null" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a> </div> </div> </div> </aside> <div id="sidebar-dimmer"></div> </div> </main> <footer class="footer"> <div class="footer-inner"> <div class="copyright"> © 2019 – <span itemprop="copyrightYear">2021</span> <span class="with-love"> <i class="fa fa-nianchu"></i> </span> <span class="author" itemprop="copyrightHolder">by nianchu</span> </div> <!-- <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> 强力驱动 v4.0.0 </div> <span class="post-meta-divider">|</span> <div class="theme-info">主题 – <a href="https://mist.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Mist</a> v7.5.0 </div> --> <div class="theme-info"> <div class="powered-by"></div> <span class="post-count">博客全站共105.7k字</span> </div> <div class="busuanzi-count"> <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script> <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;"> <span class="post-meta-item-icon"> <i class="fa fa-user"></i> </span> <span class="site-uv" title="总访客量"> <span id="busuanzi_value_site_uv"></span> </span> </span> <span class="post-meta-divider">|</span> <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;"> <span class="post-meta-item-icon"> <i class="fa fa-eye"></i> </span> <span class="site-pv" title="总访问量"> <span id="busuanzi_value_site_pv"></span> </span> </span> </div> </div> </footer> </div> <script color='0,0,255' opacity='0.5' zIndex='-1' count='99' src="/lib/canvas-nest/canvas-nest.min.js"></script> <script size="300" alpha="0.6" zIndex="-1" src="/lib/canvas-ribbon/canvas-ribbon.js"></script> <script src="/lib/anime.min.js"></script> <script src="/js/utils.js"></script> <script src="/js/schemes/muse.js"></script> <script src="/js/next-boot.js"></script> <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; bp.src = (curProtocol === 'https') ? 'https://zz.bdstatic.com/linksubmit/push.js' : 'http://push.zhanzhang.baidu.com/push.js'; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> <script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script> <script src="/js/local-search.js"></script> <script type="text/javascript" color="0,0,255" opacity='0.7' zIndex="-2" count="99" src="//cdn.bootcss.com/canvas-nest.js/1.0.0/canvas-nest.min.js"></script> <script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"log":false,"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script></body> </html>