Python-logging模块
logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等等。
logging的优点:
- 可以通过设置不同的日志等级,在release版本中只输出重要信息,而不必显示大量的调试信息;
- print将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging则可以由开发者决定将信息输出到什么地方,以及怎么输出
#Python中的pass语句
Python中的pass语句是空语句,是为了保持程序结构的完整性。
pass不做任何事情,一般用做占位语句。以为像定义一个空函数和空的if判断会把报错,所以pass语句还是很有用的。
和
*kwargsargs和
kwargs是Python中的可变参数:args表示任意多个无名参数,返回一个tuple;kwargs表示关键字参数,返回一个dict
同时使用args和kwargs时,args参数必须在kwargs之前
参考:
https://blog.csdn.net/yjk13703623757/article/details/76521420
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 | #!/usr/bin/env python3 |
%x: 十六进制整数
使用%%来表示一个%
插入: insert(index, content)
删除:
元素替换: 直接用索引表示
list里面的元素的数据类型也可以不同
list元素也可以是另一个list
元组中除开appened(),insert()这样的方法,其他获取元素的方法和list是一样的。
元组不可变所以更安全,如果可能,尽量使用tuple代替list
特别注意:
1 | # 像下面这样定义的不是元组而是一个整数 |
特殊的tuple
要避免key不存在的错误,有两种方法,一种是通过in判断key是否存在,一种是通过dict提供的get方法,如果key不存在,可以返回None,或者自己指定的value。
注意:返回None的时候Python的交互命令行不显示结果
删除一个key,使用pop(key)
dicet的缺点:需要占用大量的内存,内存浪费多
dict是用空间来换取时间的方法。
关于dict,需要牢记的一条就是dict的key必须是不可变对象
通过key计算位置的算法称为哈希算法
Python中:整数、字符串都是不可变的。而list是可变的
set也是一组key的集合,但是不存储value。set中,由于key不能重复,所以没有重复的key
创建一个set,需要提供一个list作为输入集合:
1 | >>> s = set([1, 2, 3, 4, 5]) |
想set中添加元素使用add(key),可以重复添加,但是不会有效果。
使用remove(key)方法可以删除元素
注意: set也需要放入不可变的对象
再议不可变对象
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 | from collection.abc import Iterable |
如何让list实现类似java那样的下标循环?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
1 | for i, value in enumerate(["A", 'B','C' ]): |
小结: 任何可迭代对象都可以作用于for循环,包括我们自定义的数据类型
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
在Python中,这种一边循环一边计算大的机制,称为生成器:generator
创建一个generator有很多种方法,第一种方法就是把一个列表生成式的[]改成()。
使用next()可以获得geneartor的下一个返回值,当没有更多元素时,抛出StopIteration的错误。
Generator 也是可迭代对象
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
1 | # generator生成的另一种方法 |
较难理解的就是generator和函数的执行流程不一样。generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
使用for循环调用generator时,发现拿不到generator的return语句中的返回值。如果想要拿到返回值,必须捕获到StopIteration错误,返回值包含在StopIteration的value中
1 | # Exercise 写出一个关于杨辉三角形的generator |
可以直接作用于for循环的对象统称为可迭代对象: Iterable
可以被next()函数调用并不断返回下一个值的对象称为迭代器: Iterator
可以使用isinstance()判断一个对象是不是Iterator对象
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
Python的Iterator对象是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
可以把数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。
通过把大段代码拆成函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元
理解计算机和计算的概念
在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最接近计算机的语言。
而计算则指的是数学意义上的计算,越是抽象的计算,离计算机硬件越远
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python不是纯函数式编程语言。
高阶函数英文名: High-order function
abs(-10)是函数调用,abs是函数本身
1 | f = abs |
函数本身也可以赋值给变量,即:变量可以指向函数
一个函数可以接受另一个函数作为参数,这种函数就称之为高阶函数
一个最简单的高阶函数:
1 | def add(x, y, f): |
高阶函数中作为参数的函数只需要函数名,不需要后面的括号
map()函数接收两个参数,一个是函数,一个是Iterable,map函数将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
map()作为高阶函数,事实上它把运算规则抽象了。
下面将一个list中的所有数字转为字符串:
1 | list(map(str, [1, 2, 3, 4, 5, 6, 7, 9])) |
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,ruduce把结果继续和序列的下一个元素做累积计算。
1 | # 关于reduce的用法 |
Python内建的filter()函数用于过滤序列
和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的时,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用到list()函数获得所有结果并返回一个list
用filter求素数:但是没有怎么看懂
1 | # 用filter求素数 -- 没有怎么看懂 |
Python中对匿名函数提供了有限支持
1 | lambda x : x * x |
匿名函数有一个限制,就是只能有一个表达式,不用谢retrun,返回值就是该表达式的结果
匿名函数没有名字,所以不用担心函数名冲突。匿名函数也是一个对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数;
也可以把匿名函数作为一个返回值返回。
排序的核心是比较两个元素的大小
对于字符串或者两个dict如何比较呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
Python内置的sorted()函数就可以对list进行排序
sorted()是一个高阶函数,它可以接受一个key函数来实现自定义的排序:
key函数使用方法: key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
默认情况下,对字符串进行排序,是按照ASCII的大小进行比较的。
如何实现排序时忽略大小写,按照字母序排序?实现这个算法,只需要将所有字符串都改成大写或者小写
如果要进行反向排序,不必改动key函数,只需要传入第三个参数: reverse=True
下面的练习题虽然我做出来了,但是我还是很蒙,有时间记得复习复习:
1 | # 请用sorted()对上述列表分别按名字排序 |
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
对于一个求和函数,如果不需要立刻求和,而是在后面的代码中,根据需要再计算。这时就可以不返回求和的结果,而是返回求和的函数
1 | ''' |
注意 :返回一个函数时,不需要加上函数名后面的括号
在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使用入相同的参数:
1 | f1 = lazy_sum(1, 3, 5, 7, 9) |
f1()
和f2()
的调用结果互不影响。
需要注意的是:返回的函数并没有立刻执行,而是直到它被调用之后才执行
返回闭包时牢记的一点就是: 返回函数不要引用任何循环变量,或者后续会发生变化的变量
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论循环变量后续如何更改,已绑定到函数参数的值不变:
1 | # 在闭包中使用循环变量 |
函数对象有一个name属性,可以拿到函数的名字:
在代码运行期间动态增加功能的方式,称之为”装饰器”(Decorator)
本质上,decoator就是一个返回函数的高阶函数。
总感觉装饰器decorator有点难
在OPP的设计模式中,decorator被称为装饰模式。OPP的装饰模式需要通过继承和组合来实现,而Python除了能支持OPP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
*这一小节的练习题答案参见大神做法: *
https://blog.csdn.net/GBA_Eagle/article/details/80764749
对于函数,通过设定参数的默认值,可以降低函数调用的难度。偏函数也可以做到这点
int()函数可以把字符串转换为整数,默认为十进制转换
int()函数中有个base参数,默认为10,修改base可以让int()函数按照你希望的进制进行转换
转换大量二进制字符串的时候,每次都传入int(x, base=2)非常麻烦,这时候,可以自定义一个int2()函数,默认把base=2传进去,像下面这样:
1 | def int2(x, base=2): |
functools.partial就是帮助我们创建一个偏函数的,不需要自定义int2(),可以直接使用下面的代码创建一个新的函数int2():
1 | import functools |
functools.partial的作用就是,把一个函数的某些参数固定住,返回一个新的函数
当函数的参数个数太多,需要简化时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
任何模块的第一个字符串,都被视为模块的文档注释
使用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变量中
如何添加自己的搜索路径,有两种方法:
直接修改sys.path,在其中添加要搜索的目录
1 | import sys |
这种方法是在运行时修改,运行结束后失效
第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模版,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同
可以自由地给一个实例变量绑定属性:
1 | # 给实例bart绑定一个name属性: |
由于类可以起到模版的作用,因此可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去,通过定义一个特殊的init方法,可以在创建实例的时候,就把name,score等属性绑定上去
下面的需要重点理解一下:
1 | # 可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,可以在创建实例的时候, |
self不需要传,Python解释器自己会把实例变量传进去:
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
我们可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”封装起来了。这些封装数据的函数是和Student类本身关联起来的,我们称之为类的方法
外部代码还是可以自由地修改一个实例的属性。
如果想让内部属性不被外部访问,可以把属性的名字前加上两个下划线,在Python中,实例的变量名如果以双下划线开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
以双下划线开头且以双下划线结尾的是特殊变量,特殊变量可以直接访问,不是private变量。
对于前面只有一个下划线的变量,是可以在外部访问到的。但是,按照约定俗成的规定,当你看到这样的变量时,意思是说,“虽然我可以被访问,但是请把我当作私有变量,不要随意访问我”
双下划线开头的实例变量其实也是可以访问到的,因为Python其实并没有严格的private变量。之所以不能访问以双下划线开头的私有变量,是因为Python解释器把
1 | # __name变量变成了_Student__name |
总的来说就是: Python本身并没有任何机制阻止你干坏事,全凭自觉
当子类和父类有相同的方法的时候,子类的方法会覆盖父类的方法。当运行子类的实例的时候,总是会调用子类的方法
创建一个class的时候实际上是创建了一种数据类型。
判断某个变量是否是某种类型,可以使用isinstance()判断
在继承关系中,如果一个实例的数据类型是某个子类,那么它的数据类型也可以被看做是父类。但是反过来就不行
多态的真正威力: 调用方法只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()
方法,返回其内容。但是,许多对象,只要有read()
方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()
方法的对象。
对于class的继承关系,使用isinstance()函数
isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上
能用type()判断的基本类型也可以用isinstance()判断
还可以用isinstance判断一个变量是否是某些类型中的一种
1 | # 使用isinstance()判断一个变量是否是某些类型中的一个 |
dir()用于获取一个对象的所有属性和方法。它返回一个包含字符串的list
1 | # 获得一个str对象的所有属性和方法 |
1 | # __len__和len()是等价的,使用后者,它会自动去调用该对象的__len__() |
1 | # 使用getattr(),setattr(),hasattr(),我们可以直接操作一个对象的状态 |
如果获取不存在的属性,会抛出AttributeError(属性)的错误:
使用getattr()时,可以传入一个default参数,如果属性不存在,就返回默认值
1 | # 使用getattr()时,可以传入一个default参数,如果属性不存在,就返回默认值 |
如果知道一个对象的属性和方法,就直接使用,不要使用getattr(),setattr()等等
由于Python是动态语言,根据类创建的实例可以任意绑定属性
给实例绑定属性的方法是通过实例变量,或者通过self
变量
类属性:类本身需要绑定的属性,可以直接在class中定义,这种属性是类属性,归该class所有
1 | # 实例属性和类属性 |
在编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
给实例绑定一个方法:
1 | # 定义一个函数作为实例方法 |
但是给一个实例绑定的方法,对于另一个实例是不起作用的
想要给所有实例都绑定方法,可以给class绑定方法:
1 | def set_score(self, score): |
如何才能限制实例的属性,比如,只允许对Student实例添加name和age属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,用来限制该class实例能添加的属性
*使用slots时要注意: *
slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的;除非,在子类中也定义一个slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。
类似下面这样就实现了多重继承
1 | class Bat(Mammal, Flyable): |
为了让一个类实现更多的类,从而让它再多继承一个类,这种设计通常被称为Mixln
Mixln的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixln的功能,而不是设计多层次的复杂的继承关系
只允许单一继承的语言(如Java)不能使用Mixln的设计
Python中有很多特殊的变量或函数都有着特殊的用途,可以帮助我们定制类
1 | # __str__()和__repr__()用于返回有关类和实例的字符串,其中前者返回用户看到的字符串,后者返回程序开发者看到的字符串,后者是为调试服务的 |
如果一个类想被用于for…in循环,类似list或tuple那样,就必须实现个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,知道遇到StopIteration错误时间退出循环
1 | # iter |
想要FIb实例可以实现按索引取值必须实现getitem方法:
1 | # getitem |
1 | # 让Fib实现切片 |
经过上面的操作之后,Fib还没有实现list的所有功能,例如对step参数还没有处理,也没有对含有负数的slice进行处理。要正确实现一个getitem()还是有很多工作要做的
如果想把对象看成一个dict,getitem()的参数也可能是一个可以作key的object,例如str。
与getitem类似的还有setitem以及delitem()方法
使用getattr()方法可以动态返回一个属性: 即当调用一个不存在的属性时,比如score,Python解释器会试图调用getattr(self, ‘score’)来尝试获得属性
1 | # __getattr__() |
###call
一个对象实例有自己的属性和实例方法,调用实例方法时,用instance.method()来调用。
在Python中,可以直接在实例本身上调用实例方法
1 | # call |
Python的class允许定义许多定制方法,可以让我们非常方便地生成特定的类。
Python官方文档地址:https://docs.python.org/3/reference/datamodel.html#special-method-names
定义常量时,更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:
1 | # 使用枚举类 |
运行一个程序总会遇到各种各样的错误:
程序编写造成的问题叫做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 | import logging |
错误是class,捕获一个错误就是捕获到该class的一个实例。
错误并不是凭空产生的,而是有意创建并抛出的。Python内置的函数或抛出很多类型的错误,我们自己编写的函数也可以抛出错误。
抛出错误,首先根据需要,可以定义你一个错误的class,选择好继承关系,然后用raise语句抛出一个错误的实例:
1 | class FootError(ValueError): |
只有在有需要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型,尽量使用Python内置的错误类型。
raise语句如果不带参数,就会把当前错误原样抛出,在except中raise一个Error,还可以把一种类型的错误转化为另一种类型
程序也可以主动抛出错误,让调用者来处理相应的错误。但是,应该在文档中写清楚可能会抛出哪些错误,以及错误产生的原因。
程序能一次写完并且正常运行的概率很小,基本不超过1%
我们需要知道,出措时,哪些变量的值是正确的,哪些变量的值是错误的。
凡是用print()来辅助查看的的地方,都可以用断言(assert)来替代:
1 | # 断言(assert) |
把print()换成logging是第三种方法,和assert相比,logging不会抛出任何错误,而且可以输出到文本
1 | # logging |
1 | 这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。(备注: 使用Debug的时候,logging.info以及logging.debug都是会运行的) |
pdb是Python的调试器,它可以让程序以单步方式运行。
使用方法:
这个方法也是使用pdb,但是不需要单步执行,我们只需要import pdb,然后在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点。
运行代码时,程序会自动在pdb,set_trace()暂停并进入pdb调试环境,可以使用命令p查看变量,或者用命令c继续运行
1 | import pdb |
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作,详见:
https://www.nli.cn/read/liaoxuefeng-python30/7cfd1dfdf10276d3.md
可以在单元测试中编写两个特殊的setUp()和setDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。
doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用…
表示中间一大段烦人的输出。
什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把**getattr**()
方法注释掉,再运行就会报错
当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。
doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest
在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 | try: |
上述操作更好的方法是使用Python的with语句来自动帮助我们调用close()方法:
1 | with open('/Users/bowenkei/Desktop/Test.txt', 'r') as file_object: |
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 | with open('/Users/bowenkei/Desktop/GBK.txt', encoding='gbk') as file_object: |
遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError
,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()
函数还接收一个errors
参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
1 | with open('/Users/bowenkei/Desktop/GBK.txt', encoding='gbk', errors='ignore') as file_object: |
‘a’模式可以实现往一个文件中添加内容而不清空原有内容
下面是各种模式:
要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:
1 | >>> f = StringIO() |
1 | >>> from io import BytesIO |
Python内置的os可以直接调用操作系统提供的借口函数
1 | # 导入os模块之后,使用os.name可以获得操作系统类型 |
在操作系统中定义的环境变量,全部在os.environ这个变量中,可以直接查看:
1 | # 查看变量 |
操作文件和目录的函数一部分放在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写入文件
在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,例如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 |
将class对象序列化为JSON的{} + 将JSON反序列化为class对象:
1 | import pickle |
Unix/linux操作系统提供了一个fork()系统调用。普通的函数调用,返回一次,fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()
就可以拿到父进程的ID。
Python的os
模块封装了常见的系统调用,其中就包括fork
,可以在Python程序中轻松创建子进程
1 | import os |
####multiprocessing
multiprocessing模块提供了一个Process类来代表一个进程对象
1 | from multiprocessing import Process |
####Pool
用进程池的方式批量创建大量子进程:
1 | from multiprocessing import Pool |
####子进程
很多时候,子进程并不是自身,而是一个外部进程。创建子进程后,还需要控制子进程的输入和输出
subprocess模块用于启动一个子进程,然后控制其输入和输出
1 | import subprocess |
子进程需要输入,可以使用communicate()方法输入(下面代码没有看懂,希望有大神指点指点):
1 | import subprocess |
####进程间通信
Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据,以实现Process之间的通信:
1 | import os, time, random |
多任务可以由多进程完成,也可以由一个进程内的多进程完成。
进程是由若干个线程组成的,一个进程至少有一个线程
线程是操作系统直接支持的执行单元。
Python的线程是真正的Posix Thread,而不是模拟出来的线程。
1 | # Python中 _thread和threading用于多线程。其中_thread是低级模块,threading是高级模块,它对_thread进行了封装。 |
任何进程默认会启动一个线程,这个线程称为主线程(MainThread),Python的threading模块下面的current_thread()函数用于返回当前线程的实例。子线程的名字在创建时指定。名字仅仅在打印时用来显示,完全没有其他任何意义。默认情况下,Python自动给线程命名为Thread-1, Thread-2……
线程的调度是由操作系统决定的
1 | # 没有使用Lock之前: |
两个线程同时一存一取,就可能导致余额不对,你肯定不希望你的银行存款莫名其妙地变成了负数,所以,我们必须确保一个线程在修改balance
的时候,别的线程一定不能改。
如果我们要确保balance
计算正确,就要给change_it()
上一把锁,当某个线程开始执行change_it()
时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it()
,只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()
来实现:
1 | import threading, time |
使用Lock的优缺点:
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。
Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
用一个全局dict
存放所有的Student
对象,然后以thread
自身作为key
获得线程对应的Student
对象
1 | # 实现 |
用Threadlocal替代上面自己创建的dict
1 | import threading |
全局变量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。
正则表达式是一种用来匹配字符串的有力的武器
在正则表达式中,直接给出字符,就是精确匹配。
更精确地匹配,使用[]
1 |
|
1 | import re |
常见的判断方法:
1 | import re |
用正则表达式切分字符串比用固定的字符更灵活。
1 | # 普通的切分代码: |
正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。
1 | # 分组 |
正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
使用正则表达式时,re模块内部会做下面两件事:
如果一个正则表达式要重复使用上千次,出于效率的考虑,可以预编译该正则表达式并保存下来,接下来重复使用的时候就可以跳过编译这个步骤,直接匹配了:
1 | re_name = re.compile(r'bowen[a-zA-Z]{3}') |
1 | ''' |
1 | # 获取当前时间 |
下面截图是Python官方关于时间、日期转换的介绍:
1 | """ |
Collections 是Python内建的一个集合模块,提供了很多有用的集合类
1 | # namedtuple |
1 | # namedtuple |
1 | # defaultdict |
1 | # OrderedDict |
1 | # Counter: 一个简单的计数器 |
Base是一种用64个字符来表示任意二进制数据的方法。
要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法。
Base64 是一种最常见的二进制编码方法
Python内置的base64可以直接进行base64的解编码:
1 | # base64 |
由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数。
一种叫做“Url safe”的base64编码,其实就是把字符+和/分别编程-和_:
1 | # 使用url-safe的base64编码 |
1 |
|
用于将序列中的元素以指定的字符连接生成一个新的字符
用法:
Str.join(sequence)
-capitalize()方法返回字符串的一个副本,只有它的第一个字母大写。对于8位的字符串,这个方法与语言环境相关。
用法如下:
1 | str.capitalize() |
index(content):用于判断给定字符的下标
用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
*注意: *该方法只能删除开头或是结尾的字符,不能删除中间部分的字符
描述:
bytes函数返回一个新的bytes对象,该对象是一个0<=x<256区间内的整数不可变序列。它是bytearray的不可变版本。
语法如下:
1 | class bytes([source[, encoding[, errors]]]) |
参数解释:
Python提供了一个struct模块来解决bytes和其他二进制数据的转换
struct的pack函数把任意数据类型编程bytes:
1 | # struct |
1 | import struct |
Python的hashlib提供了常见的摘要算法: 如MD5, SHA1等等
摘要算法: 即哈希算法、散列算法。把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
摘要算法就是通过摘要函数对任意长度的数据data计算出固定长度的digest,目的是为了发现原始数据是否被篡改过。
以MD5为例,计算出一个字符串的MD5值:
1 | # hashlib |
任何允许用户登录的网站都会存储用户登录的用户名和口令。
正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要
当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。
1 | import hashlib |
常用口令的MD5很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”
1 | def calc_mad(password): |
这样只要salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推口令。
1 | import hashlib |
*注意: *
摘要算法不是加密算法,不能用于加密,只能用于防篡改,但是它的单向计算性决定了可以在不存储明文口令的情况下验证用户口令
Python的内建模块itertools提供了非常有用的用于操作迭代对象的函数
“无限”迭代器
1 | # count()会创建一个无限迭代器。 |
itertools提供的几个迭代器操作函数:
1 | # chain() |
操作XML有两种方法:DOM和SAX。正常情况下,优先考虑SAX,因为DOM实在太占内存
解析XML时,我们关心三件事情: start_element, end_element和char_data。
1 |
1 | #!/usr/bin/env python3 |
对于切片,[:]中:前后两个数字必须满足从小到大的顺序,同时,包括前者,但并不包括后者
Python中,可以使用if判断key是否在dict中,但是不能直接使用value判断其是否在dict中
标准JSON中不能有多余的——逗号(,)
1 | import pickle |
参见:
https://blog.csdn.net/songyunli1111/article/details/76095971
Join()函数并不会改变原有的序列
String类型是Java的一个数据类型,但并不是原始数据类型
像上面这样,同时使用重定向输入和输出会清空文件中的数据,从而引发java.util.NoSuchElementException: No line found 异常
经验启发: 如何确定一个正方形(包括位置和大小):
只需要确定正方形的中心位置,以及中心位置具体一条边的距离即可
StdDraw.fillRectangle(x, y, rw, rh)中x,y是长方形左下角的坐标。以屏幕左下角为(0,0)作为标准
数据抽象: 主要思想是鼓励程序员定义自己的数据类型(一系列值和对这些值的操作),而不仅仅是那些操作预定义的数据类型的静态方法。
抽象数据类型(ADT)是一种能够对使用者以藏数据表示的数据类型。
抽象数据类型的主要不同之处在于它将数据和函数的实现关联,并将数据的表示方式隐藏起来。在使用抽象数据类型时,我们的注意力集中在API描述的操作上而不会去关心数据的表示;在实现抽象数据类型时,我们的注意力集中在数据本身并将实现对该数据的各种操作.
对象是能够承载数据类型的值的实体。所有对象都有三大重要特性:状态、标识和行为。对象的状态即数据类型中的值。对象的标识能够将一个对象区别于另一个对象。可以认为对象的标识就是它在内存中的位置。对象的行为就是数据类型的操作。
*要点提示: *递归是一种针对使用简单的循环难以编程实现的问题,提供优雅解决方案的技术
有关H-树的知识:
使用递归就是使用递归方法编程,递归方法就是直接或间接调用自身的方法。递归是一个很有用的程序技术。
一个递归调用可以导致更多的递归调用,因为这个方法继续把每个子问题分解成新的子问题。要终止一个递归方法,问题最后必须达到一个终止条件。
如果递归不能使问题简化并最终收敛到基础情况,就有可能出现无限递归。
所有的递归方法都具有下面的特点:
要点提示 :有时候可以通过针对要解决的初始问题的类似问题定义一个递归方法,来找到初始问题的解决方法。这个新的方法称为递归辅助方法,初始问题可以通过调用递归辅助方法来得到解决。
下面是实例:
划重点:
在递归程序设计中定义第二个方法来接收附加的参数是一个常用的设计,这样的方法被称为递归辅助方法。
##第1章 何为Web发布
Web是:
超文本的含义: 不像看书那样以线性方式阅读,而是可以轻松从一个地方跳到另一个地方: 可以获取更详细的信息,再回过去阅读、跳到其他主题或者根据兴趣阅读。
术语超文本,可以理解为链接。链接也可称为超链接。
跨平台意味着不管使用什么计算机硬件,也不管运行的是什么操作系统,装备的是什么显示器,都可以一样地访问Web信息。
下面是书中提供的一个很有意思的观点:
跨平台是一种理想:
随着众多特殊功能、技术和媒体类型的推出,Web的跨平台性特征遭到了损害,Web发布者可以使用很多非标准特性,如Flash,但这样做将缩小其网站的受众范围,如果考虑到越来越多的人转而使用智能手机和移动设备来访问Web,Web发布者还必须做出如下选择: 专门创建用于移动设备的应用程序,还是打造跨平台兼容性更强的Web应用程序。该不该为特定平台提供更强大的功能而降低跨平台的灵活性呢?
Web为什么能成功地提供海量信息呢?因为这些信息分布在全球的数百万个网站中,而每个网站都分别存储其发布的信息。每个网站驻留在一台或者多台被称为Web服务器的计算机上。Web服务器也是计算机,只是负责侦听并相映Web浏览器的请求。我们使用Web浏览器时,请求服务器提供资源以便查看它。我们只是将浏览器指向网站,其他什么都没有做。
网站是Web上一个发布信息的位置。您浏览网页时,浏览器连接到网络以获取该网页。每个网站、网站的每个 网页乃至每项信息都有独一无二的地址,这个地址被称为 :统一资源定位符(URL).
重启浏览器可以获取Web上更新的信息
当前,甚至都无须重新加载网页就能看到最新的信息。通过使用JavaScript可以实时地更新网页的内容。
Web的交互性是通过单击链接跳到其他网页实现的。除此之外,Web还可以让用户同发布者和其他用户交流。
注意:仅当您确定只有使用特定浏览器的用户访问您的网站时,选择针对该浏览器进行开发才是合适的。
Web浏览器的核心作用是连接到We服务器并请求文档,再妥善地设置这些文档的格式并显示它们。
Web浏览器还可显示本地计算机中的文件、下载并非用于显示的文件,甚至让用户能够发送和接受电子邮件。
所有网页都是使用超文本标记语言HTML编写的,这种语言指定网页包含的文本、描述网页的结构、指定到其他文档和图像等多媒体。
即使是同一个文件,不同浏览器设置其格式和显示的方式也可能不同,这取决于系统的功能以及浏览器的配置。
Google Chrome
和Apple Safari使用的是一个HTML引擎——开源引擎WebKit.
注意:
如果要检查跨平台兼容性问题,请从IE 和 Firefox开始,再将Chrome也囊括进来。
Firfox
Firfox得以流行是因为它不存在困扰IE的安全问题。
要将网页发布到Web上,需要一个Web服务器。
Web服务器是在计算机上运行的程序,负责响应Web浏览器的请求——向它提供URL指定的内容。运行服务器程序的计算机也被称为服务器。
您使用浏览器请求网页时,浏览器使用HTTP建立一条到服务器的Web连接。服务器接受连接,发送请求的文件,再关闭连接。接下来,浏览器对从服务器获取的信息设置格式。
在服务器端,可能有很多不同的浏览器连接到同一个服务器,该服务器负责处理所有这些请求。
Web服务器还负责管理表单输入以及将表单和浏览器关联到运行在服务器上的数据库等程序。
URL为查找并访问信息提供了统一而一致的方法。
还可以使用URL在文档中创建到另一个文档中的超文本链接。
*URL包含有关如下方面的信息: *
如何获取信息(使用哪种协议: FTP,HTTP还是file);
该信息所在计算机的Internet主机名(www.ncsa.uiuc.edu)等
该信息位于网站的哪个目录或位置
还可以使用特殊的URL来完成发送邮件和运行JavaScript代码等任务
一般而言,网页都是根据别人创建的模版生成的,通常可以设置内容的格式——使用图形编辑器或让您能够避免使用HTML的简化标记。
如果,发布的内容看起来不妥,要修复问题就必须懂HTML。
需要区分由您控制的页面部分和您使用发布应用程序生成的部分。
问: Web由谁负责运营?这些协议都是由谁控制的?这一切都是由谁管理的?
万维网并非由某个组织拥有或控制。有两类组织给Web的外观和发展方向带来了重大影响:
万维网联盟(W3C): 负责制定万维网标准和实施相关的规则,网址为:www.w3.org
浏览器开发商 :一些关心Web未来的个人和公司成立了一个名为WHATWG——超文本应用技术工作组。HTML5规范就是WHATWG和W3C一起制定的。
从现在开谁,WHATWG放弃了给HTML规范制定版本号,相反,HTML是一个“流动的标准”,将涵盖实验性功能和得到广泛支持的功能,旨在确保该规范紧跟发展步伐,涵盖浏览器开发商一致同意在其浏览器中添加的功能。
网站都由Web服务器托管。
网页有时也被称为Web文档。
网页由HTML文档和其他部分组成。
学习如何从空白开始创建网站,就需要配置计算机,以便能够在本地创建和查看网页
尝试Web发布,只有两款工具是必不可少的,就是文本编辑器和Web浏览器。
HTML属于纯文本文件,应使用处理纯文本的工具对其进行编辑。
下载Google Chrome
###2.3 使用Google Chrome 开发者工具
打开开发者工具的快捷键 :
开发者工具是查看源代码这种概念的扩展,功能更加强大。
开发者工具中包含很多选项卡,默认显示的是Element,其中包含的内容类似于网页的源代码。浏览器下载网页的时候,将其进行转换,让设置HTML格式并显示它的引擎能够明白。
Element选项卡显示的是浏览器看到的HTML,而“查看源代码”显示的是浏览器下载的HTML。这两者有几个不同的地方。
将鼠标指向Element选项卡中的元素,相应的网页部分将呈高亮显示,由此可以知道网页各部分与HTML源代码的对应关系。
对于要发布到Web上的东西,我在本书中称之为内容。
网络规划的下一步是确定如何在网页之间分配内容,并制定在网页之间导航的方案。
线框图是网站完成后的大致轮廓,指出了内容在网页之间的分配情况以及内容是如何彼此关联起来的。
对于复杂的大型网站,线框图可节省大量的时间,避免众多的弯路。
线框图,一般而言,是成组的文档或图像,每组表示网站中一种特定类型的网页。文档包含网页的粗略示意图指出了网页各各部分处于什么位置、占据多大空间以及将发挥的作用
如何在网页之间分配主题
最佳的做法是,让每个页面都包含一个主题的信息。如果页面有好几屏长,也许该奖相应的主题按逻辑分成多个子主题。
需要链接。这些是文档中的主链接,让访客能够实现您确定的目标。向前、向后、向上的链接以及到主页的链接都属于主要的导航方式。
除了简单的导航链接外,还可以包含与主要Web内容平行的额外信息,如术语表
按字母顺序排列的概念索引、版权信息或职员页面。
主页上的内容应该有足够的吸引力,让目标受众留下来
可以通过设计和导航实现这个目标。
注意聚焦于你的目标
迟早需要将本地计算机上创建的网站放到Internet上,最简单的方法,是获取一个这样的Web托管账户,即让您能够将HTML文件、图像、样式和其他Web内容上传到一台Web服务器。
使用应用程序将内容发布到Web上:
上述两种方式,通常只需填写表单并为网站选择URL和主题,然后就可以通过表单输入内容。
注册托管账户以便将网页发布到Web上的步骤如下:
HTML指的是超文本标记语言
是一种描述文档结构而非实际呈现效果的语言。
HTML规范对页面的外观都未置一言。HTML标签只是指出元素是标题或列表,没有说明该如何设置标题或列表的格式。
注意:级联样式表(CSS)能够将复杂的格式设置应用于HTML标签。
应用于标签的视觉样式的发展历程:
链接应该带有下划线并显示为蓝色,访问过的链接为紫色,要突出的文本为斜体……
设计原则:
HTML是一种标记语言。
HTML包含一组可供使用的预处理标签,不能编造标签来创建新样式或功能。
不同的浏览器支持不同的标签。
HTM文件包含的以下内容:
HTML中在结束标签中出现的是”/“而不是反斜杠
有些标签只有起始标签或者结束标签;
有些标签是荣国旗,在<>内包含额外的信息和文本;
有些标签内部有额外的文本,提供了有关签名的额外信息。这些文本被称为属性,通常以name=value的形式定义的,位于标签名后面,并用一个空格与标签名分隔开。
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。下面是规则:
访问http://caniuse.com/ ,查看HTML5的各项功能、支持该功能的浏览器版本以及其浏览器支持该功能的用户所占的百分比。
可以对字符串设置一些格式。大多数设置文本格式的标签都已被CSS取代,但浏览器依然支持较旧的文本格式设置元素。
HTML定义了三个用于定义页面总体结构以及提供简单标题信息的标签——,
,DOCTYPE标识符:
它不是网页结构的标签,但是XHTML和HTML5都要求网页必须包含它。
网页的所有文本和HTML元素都必须放在起始和结束标签之间。
如果省略它,浏览器将会替你添加。
是一个容器,包含所有提供有关网页的信息的标签,但不包含将显示在网页中的信息。
结束HTML标签时,务必确保结束的时最近的未结束标签。
每个HTML页面都需要一个名称,它指出页面描述的是什么。用户浏览网页时,这个名称出现在浏览器的标题栏中。这个名称被浏览器用来创建书签;还被搜索引擎用来建立页面索引。
使用
将
HTML定义了6级标题
在HTML文档中,缩进可以更好地展示层次结构。但是在展示的页面中,不会缩进。浏览器对缩进置若罔闻。
*警告: *不要为了将文本显示为粗体或突出网页的某些部分而使用标题,这样做虽然会获得想要的效果,但标记表示的将不再是网页的结构。这样会影响搜索引擎、便利性和有些浏览器。
提示:从视觉效果来看,4-6级标题不是很有用,但是从文档结构的角度看,很有意义。另外,使用4-6级标题可以使用样式来获得想要的效果。
分析HTML文件时,会忽略注释中的文本。但是,在浏览器中,用户可以使用“查看源代码”功能来查看注释,因此,对于不希望用户看到的东西,请不要将其放在注释中。
格式:
1 | <!-- This is comment --> |
除了
是用于添加网页内容的通用标签吗?
不是,标签
专用于在网页中添加文本段落。包括
里面。
可以在注释中包含HTML标签,但是浏览器不会显示它们。使用注释来隐藏页面的某部分是一种常见的做法,通常这种做法叫做“注释掉”。
知识导航:
列表分为:
所有的列表标签都有如下特征 :
带编号的列表是
显示有序列表时,列览器会缩进列表并编号。
仅当列表项的顺序很重要时,才应使用带编号的列表。
定制的方法有两种:
修改列表的编号样式(1.使用CSS属性list-style-type;2.使用属性[attribute]type——在html5中已经被舍弃)
修改编号本身。
要指定列表使用的起始编号或字母,可使用属性start.不管指定的编号形式是什么,属性start的值必须时十进制数字。
在任何列表项都可以添加一个value值以重新开始从这个列表项开始的序号。
无序列表通常称为项目符号列表,其中每个列表项都使用相同的项目符号,而不是编号。
对于无序列表,各个浏览器标记每个列表项时默认使用的项目符号相同,但文本浏览器通常使用星号。
项目符号样式如下:
可以使用一幅画来自定义项目符号样式,例如:
1 | <ul style="list-style-image: url(/bullet.gif)"> |
使用如下方式实现后续行与项目符号或列表符号对齐:
1 | <ul style="list-style-position: inside"></ul> |
同时修改多个与列表相关的属性,可以像下面这样,同时指定三个值: 列表样式类型、列表样式位置和用作项目符号图像的url:
1 | <ul style="list-style: circle inside url("")"></ul> |
定义列表包含两部分:
定义列表的每部分都有其标签: 术语标签为
,定义标签整个定义列表是使用标签
在浏览器中显示定义列表时,通常术语和定义是分开的并缩进定义。
在一个列表中放置另一个列表。
列表远不是简单的项目符号列表。很多常见的Web设计元素的结构都类似于列表。
用于控制列表缩进程度的属性是 :margin-left和padding-left,有些浏览器使用前者,有些浏览器使用后者。
为了让文本按照特定的方式对齐,可以将边距(margin)设置为负值。
使用URL创建链接,需要:
在链接标签中,只有文本是在网页上可见的;单击链接时,浏览器将加载相关联的URL。
也被称为锚标签,因为它也可用于创建锚。链接标签最常用的用途是,用于创建到其他网页的链接。
在链接标签中,最常见的属性是herf,它表示超文本引用,可以使用它来指定链接指向的文件名或URL。
*注意: *在HTML5中,没有任何属性的也是合法的,这种链接称为占位符链接,可将其与CSS和脚本结合起来使用。
关于具体如何使用链接:
在链接中,文件名是区分大小写的,但是url不区分。
有关标签嵌套的提醒:
*注意: *文件夹和目录说的是一码事,具体使用哪个取决于的是操作系统。
要在链接中指定相对路径名,必须使用UNIX式路径,而不管使用的是什么操作系统。即,
使用斜杠(/)来分隔目录名,并使用两点(..)来表示当前目录的父目录。
相对路径名通过指定相对于当前页面的位置来指定要链接到的页面,而绝对路径名,从顶级目录出发,要经过哪些目录才能到达这个文件。
绝对路径总是以斜杠打头,以便与当前目录区分开来。斜杠后面从顶级目录出发,前往链接到的文件需要经过的所有目录。
*注意: *
合理混合使用这两种链接是最佳的选择。经验规则是:
如果链接的页面属于同一个集合,就使用相对路径,否则,使用绝对路径。
要链接到Internet上的其他页面(远程页面),也可以使用链接标签。
使用可以将字体设置为斜体。
要链接到另一个页面的特定位置,只需在链接的URL中指定要链接的元素的ID。
也可以使用链接跳到网页中的特定元素。
ID属性可用于页面的任何元素,唯一的要求是,每个ID在当前页面中是独一无二的。
Id还可用于设置页面的样式。
创建一个锚的例子:
1 | <h1 id="part4">Part Four: Grapefruit from Heaven </h1> |
在链接中指向一个锚:
1 | <a href="mybgdoc.html#part">Go to part 4</a> |
其中#后面是id
使用标签创建锚时,不是使用属性herf来链接到特定的页面,而使用属性name来指出这是一个可链接的锚。
1 | <h1> |
建议不要不用属性name来创建锚链接,而是使用ID。ID属性适用于任何HTML元素,但是name只支持标签。
在当前页面链接是,只需要6.4.1的锚的示例中#号后面的部分,包含#号在内。
URL:统一资源定位符
包含三部分: 协议,主机名,目录或文件名
端口: URL的主机部分可能包含端口号。端口号让浏览器使用合适的协议链接到相应的网络端口。仅当相应请求的服务器被显式地设置为侦听特定端口时,才需要在URL中包含端口号。服务器默认端口是80,此时可以省略。
当存在端口号时:
1 | http://my-public-access-unix.com:155/pub/file |
服务器的根url的路径为/,如http://www.example.com/
很多url后面还有查询,它通过问号与URL的其他部分分开。查询由名称-值对组成,名称-值对之间用&分隔,而名称和值之间用等号=分隔。例如:
1 | https://www.google.com/search?q=china&oq=china&aqs=chrome..69i57j69i61j69i60.6294j0j1&sourceid=chrome&ie=UTF-8 |
其中
oq:上次搜索关键字
ie:关键字编码格式
URL中的特殊字符是指除了大小写字母、数字0-9和下述符号外的字符: 美元$,连字符(-),下划线(_)句点(.)。
使用特殊字符,要经过转义编码以防止它们被视为URL的一部分。而编码是由百分符号和两位十六进制数组成(0-9,A-F)的
例如:
空格: %20
问号:%3e
斜杠: %2f
rel是的另一个属性,用于描述当前文档与链接到的文档之间的关系。
这个属性有一组特定的可能取值,其中最著名的是nofollow,它让搜索引擎给链接到的文档排名时不要考虑这个链接,是一种防范搜索引擎作弊的手段。使用如下:
1 | <a href="http://www.example.com/" ref="nofollow">Link to example site</a> |
关于rel更多的属性,请查看网站:
1 | http://microformats.org/wiki/existing-values |
统一资源定位符规范定义了很多种URL。
是最长的URL类型。
浏览器使用FTP来获取文件时,如果获取的是HTML文件,浏览器就将显示它,否则就将它保存在磁盘中。
ps: 我在想那些打开链接就直接下载的URL应该就属于FTP URL。
总结就是: 尽量不使用,必须使用时使用专用的FTP客户端而不是浏览器。
文件URL指向本地磁盘中的文件,即指向浏览器所在系统中的文件。(它们包含三个而不是两个斜杠)
1 | file:///dirl/dir2/file |
文件URL的另一个用途是,为浏览器指定一个本地启动页面,其中包含到经常访问的网站的链接。
ps: 有点绕
可以。具体见截图。
字符级元素指的是影响其他HTML标签中单词或字符的标签,它们修改这些文本的外观,使其不同于周围的文本,例如将其设置为粗体或带下划线。
可以在块级元素中嵌套字符级元素,也可以在字符级元素中嵌套块级元素。
要修改文本中一系列字符的外观,可以使用:
语义标签描述了它包含的文本的定义,而不是这些文本在浏览器中的外观。例如 :HTML语义标签可能指出它包含的内容是一个定义、代码片段或者要突出的单词。
每个字符样式标签都有起始标签和结束标签,并影响包含在这两个标签之间的文本。下面是HTML一些语义标签:
: 这个标签指出它包含的文本是示例代码,应使用Courier等等宽字体显示。
一样使用等宽字体显示。
物理样式: physical style.
书上提示不要使用它们,而应该使用CSS或等价的语义标签。
大多数标签都以某种方式影响其包含的文本的外观,但有一个标签对其包含的文本没有任何影戏,这就是标签,它存在的目的就是要与样式表相关联。使用时,只需要用它来包含文本即可。
单独使用时没有任何效果,与属性结合起来,它可取代前面的所有标签,而且效果好很多。
text-decoration属性用于指定要对标签内的文本应用哪种装饰。这个属性的可取值包括: underline,overline,line-through和blink。
修改文本的外观时,可使用的一系列其他主要属性时字体属性。使用字体属性时,几乎可以修改浏览器渲染文本时使用的任何字体的任何方面。
属性font-style可用于将文本设置为斜体,它有三种可能取值:normal,italic(像标签那样渲染文本),oblique(标准字体的倾斜版本)。
将文本设置为oblique 或italic时,浏览器将选择这两者中可用的那个。如果这两者都没有安装,浏览器通常会生成字体的倾斜版本。
使用CSS来创建粗体文本。在HTML中,只有两种选择:要么为粗体,要么不为粗体。而使用CSS时,可以有更多的选择。实际上,文本要么为粗体,要么正常。要将文本设置为粗体,可以属性font-weight。这个值的属性的可能取值包括normal,bold,bolder,lighter以及100-900(以100为单位)
提示:
还可以使用属性font-family来设置文本使用的字体系列;还可以指定文本使用特定的字体。
指定字体系列时,具体使用哪种字体将取决于用户系统的首选项。
指定字体系列时,可使用属性font-family,其值可能为serif, sans-serif,cursive,fantasy,monospase.
使用CSS可以实现HTML标签无法实现的一种功能:font-variant.它可以渲染文本,即将小写字母替换为小型大写字母(small capital)字母,这个 属性有两个可能的取值: normal,small-caps.
PS: 结果类似于下面这种,应该一看就知道是什么意思了:
HTML代码中包含的多余空白都会被浏览器删除,但是使用预定义格式文本标签
时会有例外。对于放在标签和之前的文本,其中的所有空白都会保留在最终输出中,这可以用于在显示的页面中保留HTML代码中文本的距离。预定义格式的文本通常是使用Courier等等宽字体显示的。
在包含在标签
内的文本中,可食用链接标签和字体样式,但不能使用标题和段落等元素。应使用硬回车进行换行,并尽可能让每行的长度不超过60个字符。
在预定义格式文本中慎用制表符,因为字表符在不同浏览器中代表不同字符。
pre还非常适合用于快速而轻松地将纯文本格式文件(电子邮件)转换为HTML。
标签pre还可用于在网页中创建ASCII艺术品。像下面这样:
7.4 水平分割线(分隔)
标签
在网页中创建了一条水平线,它没有结束标签,也不包含任何文本。在HTML5中,这个标签被赋予了语义:主题分隔。这个标签一直都显示为一条水平线,现在的语义是:表示主题变了。结束空元素:
为了符合XHTML的要求,可以使用
作为结束标签。即使这个标签包含属性,斜杠也应位于标签名后面。7.4.1
的属性
PS: 这里说一下我看到这里时的困惑,size和width有什么不同呢?具体看下面的截图就能知道了。
如果指定的宽度比浏览器的宽度小,还可使用属性align指定水平线的对齐方式:
默认情况下,水平居中
使用已摒弃的属性noshade让浏览器将水平线绘制为没有三维投影的普通线条。
在其所在的地方换行。浏览器遇到标签
时间,将重起一行显示它后面的文本,且缩进程度与当前元素的左边距相同。
br仅仅是重起一行显示后续文本,它不会增加新行的行前距和行后距,也不会改变当前实体的字体和样式。
上图中,第一个选择器选择first下的second;第二个选择器选择first下的third
CSS选择器中的大于是什么意思?
可以把>理解为是直接的小孩(direct descendant child),而平常比较容易看到的空格(space)是所有小孩的意思。
区别在于,>要求被选择的元素不是嵌套元素,也就是说不是在其他元素内部的。
space则没有上述要求,指的是class下所有的此类元素
-