什么(what)?如何做(how)?说区别/谈优势(difference)以及实践操作(practice)。

What?

  1. 什么是Python?

Python是一种编程语言,它有对象、模块、线程、异常处理和自动内存管理。可以加入与其他语言的对比。下面是回答这一问题的几个关键点:

  1. Python是一种解释型语言,python代码在运行之前不需要编译。
  2. Python是动态类型语言,在声明变量时,不需要说明变量的类型。
  3. Python适合面向对象的编程,因为它支持通过组合与继承的方式定义类。
  4. 在Python语言中,函数是第一类对象。
  5. Python代码编写快,但是运行速度比编译语言通常要慢。
  6. Python用途广泛,常被用作“胶水语言”,可帮助其他语言和组件改善运行状况。
  7. 使用Python,程序员可以专注于算法和数据结构的设计,而不用处理底层的细节。
  8. 什么是Python自省?

python自省是python具有的一种能力,使程序员面向对象的语言所写的程序在运行时,能够获得对象的类python型。Python是一种解释型语言。为程序员提供了极大的灵活性和控制力。

  1. 什么是PEP 8?

PEP8是一种编程规范,内容是一些关于如何让你的程序更具可读性的建议。

  1. 什么是pickling和unpickling?

Pickle模块读入任何Python对象,将它们转换成字符串,然后使用dump函数将其转储到一个文件中——这个过程叫做pickling。反之从存储的字符串文件中提取原始Python对象的过程,叫做unpickling。

  1. 什么是Python装饰器?

Python装饰器是Python中的特有变动,可以使修改函数变得更容易。

  1. 什么是Python的命名空间?

在Python中,所有的名字都存在于一个空间中,它们在该空间中存在和被操作——这就是命名空间。它就好像一个盒子,每一个变量名字都对应装着一个对象。当查询变量的时候,会从该盒子里面寻找相应的对象。

  1. 什么是字典推导式和列表推导式?

它们是可以轻松创建字典和列表的语法结构。

  1. Lambda函数是什么?

这是一个常被用于代码中的单个表达式的匿名函数。

  1. args,*kwargs?参数是什么?

如果我们不确定要往函数中传入多少个参数,或者我们想往函数中以列表和元组的形式传参数时,那就使要用args;如果我们不知道要往函数中传入多少个关键词参数,或者想传入字典的值作为关键词参数时,那就要使用*kwargs。

  1. 什么是Pass语句?

Pass是一个在Python中不会被执行的语句。在复杂语句中,如果一个地方需要暂时被留白,它常常被用于占位符。

  1. unittest是什么?

在Python中,unittest是Python中的单元测试框架。它拥有支持共享搭建、自动测试、在测试中暂停代码、将不同测试迭代成一组,等等的功能。

  1. 构造器是什么?

构造器是实现迭代器的一种机制。它功能的实现依赖于yield表达式,除此之外它跟普通的函数没有两样。

  1. doc string是什么?

Python中文档字符串被称为docstring,它在Python中的作用是为函数、模块和类注释生成文档。

  1. 负索引是什么?

Python中的序列索引可以是正也可以是负。如果是正索引,0是序列中的第一个索引,1是第二个索引。如果是负索引,(-1)是最后一个索引而(-2)是倒数第二个索引。

  1. 模块和包是什么?

在Python中,模块是搭建程序的一种方式。每一个Python代码文件都是一个模块,并可以引用其他的模块,比如对象和属性。

一个包含许多Python代码的文件夹是一个包。一个包可以包含模块和子文件夹。

  1. 垃圾回收是什么?

在Python中,为了解决内存泄露问题,采用了对象引用计数,并基于引用计数实现自动垃圾回收。

  1. CSRF是什么?

CSRF是伪造客户端请求的一种攻击,CSRF的英文全称是Cross Site Request Forgery,字面上的意思是跨站点伪造请求。

How?

  1. 如何让你的程序更具可读性?

适当地加入非前导空格,适当的空行以及一致的命名。

  1. Python是如何被解释的?

Python是一种解释性语言,它的源代码可以直接运行。Python解释器会将源代码转换成中间语言,之后再翻译成机器码再执行。

  1. 如何在Python中拷贝一个对象?

如果要在Python中拷贝一个对象,大多时候你可以用copy.copy()或者copy.deepcopy()。但并不是所有的对象都可以被拷贝。

  1. 如何用Python删除一个文件?

使用函数os.remove("file")

  1. 如何将一个数字转换成一个字符串?

你可以使用自带函数str()将一个数字转换为字符串。如果你想要八进制或者十六进制数,可以用oct()或hex()。

  1. Python是如何进行内存管理的?

Python的内存管理是由私有heap空间管理的。所有的Python对象和数据结构都在一个私有heap中。程序员没有访问该heap的权限,只有解释器才能对它进行操作。为Python的heap空间分配内存是由Python的内存管理模块进行的,其核心API会提供一些访问该模块的方法供程序员使用。Python有自带的垃圾回收系统,它回收并释放没有被使用的内存,让它们能够被其他程序使用。

  1. 如何实现tuple和list的转换?

以list作为参数将tuple类初始化,将返回tuple类型

以tuple作为参数将list类初始化,将返回list类型

  1. Python里面如何生成随机数?

在python中用于生成随机数的模块是random,在使用前需要import. 如下例子可以酌情列举:

random.random():生成一个0-1之间的随机浮点数

random.uniform(a, b):生成[a,b]之间的浮点数

random.randint(a, b):生成[a,b]之间的整数

random.randrange(a, b, step):在指定的集合[a,b)中,以step为基数随机取一个数

random.choice(sequence):从特定序列中随机取一个元素,这里的序列可以是字符串,列表,元组等

  1. 如何在一个function里面设置一个全局的变量

如果要给全局变量在一个函数里赋值,必须使用global语句。global VarName的表达式会告诉Python, VarName是一个全局变量,这样Python就不会在局部命名空间里寻找这个变量了

  1. Python如何实现单例模式?其他23种设计模式python如何实现?

单例模式主要有四种方法:__new__、共享属性、装饰器、import。

其他23种设计模式可基本分为创建型、结构型和行为型模式。

创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。

结构化模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。

行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易,更灵活的通信方法。

各模式的实现可根据其特点编写代码(限于篇幅,此处不做示例)

  1. 如何判断单向链表中是否有环

首先遍历链表,寻找是否有相同地址,借此判断链表中是否有环。如果程序进入死循环,则需要一块空间来存储指针,遍历新指针时将其和储存的旧指针比对,若有相同指针,则该链表有环,否则将这个新指针存下来后继续往下读取,直到遇见NULL,这说明这个链表无环。

  1. 如何遍历一个内部未知的文件夹?

常用的有以下这几种办法:os.path.walk(),os.walk(),listdir

  1. mysql数据库如何分区、分表?

分表可以通过三种方式:mysql集群、自定义规则和merge存储引擎。

分区有四类:

RANGE 分区:基于属于一个给定连续区间的列值,把多行分配给分区。

LIST 分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。

HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。

KEY 分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL 服务器提供其自身的哈希函数。必须有一列或多列包含整数值。

  1. 如何对查询命令进行优化?
  2. 应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索。
  3. 应尽量避免在 where 子句中对字段进行 null 值判断,避免使用!=或<>操作符,避免使用 or 连接条件,或在where子句中使用参数、对字段进行表达式或函数操作,否则会导致权标扫描
  4. 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
  5. 使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用。
  6. 很多时候可考虑用 exists 代替 in
  7. 尽量使用数字型字段
  8. 尽可能的使用 varchar/nvarchar 代替 char/nchar
  9. 任何地方都不要使用 select from t ,用具体的字段列表代替“”,不要返回用不到的任何字段。
  10. 尽量使用表变量来代替临时表。
  11. 避免频繁创建和删除临时表,以减少系统表资源的消耗。
  12. 尽量避免使用游标,因为游标的效率较差。
  13. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF
  14. 尽量避免大事务操作,提高系统并发能力。
  15. 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
  16. 如何理解开源?

开源,即开放源代码。开源诞生于软件行业,它不仅仅代表软件源代码的开放,本身即意味着自由、共享和充分利用资源。开源是一种精神,是一种文化,如今已经成为软件业发展的大势所趋。

  1. 如何理解MVC/MTV框架?

MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的、松耦合的方式连接在一起。MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同。

  1. MSSQL的死锁是如何产生的?

如下是死锁产生的四个必要条件:

互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

  1. Sql注入是如何产生的,如何防止?

程序开发过程中不注意规范书写sql语句和对特殊字符进行过滤,导致客户端可以通过全局变量POST和GET提交一些sql语句正常执行。产生Sql注入。下面是防止办法:

  1. 过滤掉一些常见的数据库操作关键字,或者通过系统函数来进行过滤。
  2. 在PHP配置文件中将Register_globals=off;设置为关闭状态
  3. SQL语句书写的时候尽量不要省略小引号(tab键上面那个)和单引号
  4. 提高数据库命名技巧,对于一些重要的字段根据程序的特点命名,取不易被猜到的
  5. 对于常用的方法加以封装,避免直接暴漏SQL语句
  6. 开启PHP安全模式:Safe_mode=on;
  7. 打开magic_quotes_gpc来防止SQL注入
  8. 控制错误信息:关闭错误提示信息,将错误信息写到系统日志。
  9. 使用mysqli或pdo预处理。
  10. xxs如何预防?

XSS漏洞难以检测,但是为了WEB安全仍需要尽力避免:

针对反射型和存储型XSS,需要服务端和前端共同预防,针对用户输入的数据做解析和转义,对于前端开发而言,则是善于使用escape,针对data URI内容做正则判断,禁止用户输入非显示信息。

对于DOM XSS,由于造成XSS的原因在于用户的输入,因此在前端,需要特别注意用户输入源,并对可能造成的XSS的操作需要进行字串转义。

  1. 如何生成共享秘钥? 如何防范中间人攻击?

密钥的生成是通过使用全局配置命令完成的:对于不可输出密钥是<crypto key generate rsa label {label string},而对于可输出密钥则是<crypto key generate rsa exportable label {label string}>。标记(label)是可选择的;如果没有指定标记,那么密钥名称将是hostname.domain-name。

对于中间人的攻击,可以采用如下防范手段:

  1. 通过采用动态ARP检测、DHCP Snooping等控制操作来加强网络基础设施
  2. 采用传输加密
  3. 使用CASBs(云访问安全代理)
  4. 创建RASP(实时应用程序自我保护)
  5. 阻止自签名证书
  6. 强制使用SSL pinning
  7. 安装DAM(数据库活动监控)
  8. 如何管理不同版本的代码?

进行版本管理。可举例告知如何使用Git(或是其他工具)进行追踪。

Difference

  1. 数组和元组之间的区别?

数组在python中叫作列表。列表可以修改,而元组不可以修改,如果元组中仅有一个元素,则要在元素后加上逗号。元组和列表的查询方式一样。元组只可读不可修改,如果程序中的数据不允许修改可用元组。

  1. _new_和_init_的区别?

__init__是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。

__new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法。

也就是,__new__在__init__之前被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数,然后__init__给这个实例设置一些参数。

  1. Python中单下划线和双下划綫的区别?

"单下划线" 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;

"双下划线" 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。

  1. 浅拷贝与深拷贝的区别是?

在python中,对象赋值实际上是对象的引用。浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变,而深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变。

  1. 使用装饰器的单例和使用其他方法的单例,在后续使用中,有何区别?

Import方法改变了类本身,new方法,但是只是把所有实例对象共享属性,每次产生一个新对象。算作伪单例,共享属性方法实例化了许多个相同属性。所以,装饰器方法最为实用。

  1. 多进程与多线程的区别?
  2. 简而言之,一个程序至少有一个进程,一个进程至少有一个线程。
  3. 线程的划分尺度小于进程,使得多线程程序的并发性高。
  4. 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  5. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  6. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
  7. select和epoll的区别?
  8. select实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。
  9. select每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
  10. TCP和UDP的区别?边缘触发和水平触发的区别?
  11. 基本区别:

基于连接与无连接

TCP要求系统资源较多,UDP较少;

UDP程序结构较简单

流模式(TCP)与数据报模式(UDP);

TCP保证数据正确性,UDP可能丢包

TCP保证数据顺序,UDP不保证

  1. 编程中的区别

socket()的参数不同

UDP Server不需要调用listen和accept

UDP收发数据用sendto/recvfrom函数

TCP:地址信息在connect/accept时确定

UDP:在sendto/recvfrom函数中每次均 需指定地址信息

UDP:shutdown函数无效

  1. HTTP连接:get和post的区别?

GET请求,请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。URL的编码格式采用的是ASCII编码,而不是uniclde,即是说所有的非ASCII字符都要编码之后再传输。

POST请求:POST请求会把请求的数据放置在HTTP请求包的包体中。上面的item=bandsaw就是实际的传输数据。

因此,GET请求的数据会暴露在地址栏中,而POST请求则不会。

  1. varchar与char的区别?

char 长度是固定的,不管你存储的数据是多少他都会都固定的长度。而varchar则处可变长度但他要在总长度上加1字符,这个用来存储位置。所以在处理速度上char要比varchar快速很多,但是对费存储空间,所以对存储不大,但在速度上有要求的可以使用char类型,反之可以用varchar类型。

  1. BTree索引和hash索引的区别?

Hash 索引因其结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引。但也有如下明显的缺点:

  1. Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询。
  2. Hash 索引无法被用来避免数据的排序操作。
  3. Hash 索引不能利用部分索引键查询。
  4. Hash 索引在任何时候都不能避免表扫描。
  5. Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
  6. primary key和unique的区别?
  7. 作为Primary Key的域/域组不能为null,而Unique Key可以。
  8. 在一个表中只能有一个Primary Key,而多个Unique Key可以同时存在。

C. 逻辑设计上讲,Primary Key一般在逻辑设计中用作记录标识,这也是设置Primary Key的本来用意,而Unique Key只是为了保证域/域组的唯一性。

  1. ecb和cbc模式有什么区别?

ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。

CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或操作后再加密,这样做的目的是增强破解难度。ECB和CBC的加密结果是不一样的,两者的模式不同,而且CBC会在第一个密码块运算时加入一个初始化向量。

  1. 对称加密与非对称加密的区别?

对称加密,需要对加密和解密使用相同密钥的加密算法。由于其速度快,对称性加密通常在消息发送方需要加密大量数据时使用。所以,对称性加密也称为密钥加密。

而非对称加密算法需要两个密钥:公开密钥和私有密钥。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。

  1. Xrange和range的区别?

range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列。xrange 用法与 range 完全相同,所不同的是生成的不是一个list对象,而是一个生成器。要生成很大的数字序列的时候,用xrange会比range性能优很多,因为不需要一上来就开辟一块很大的内存空间。range会直接生成一个list对象,而xrange则不会直接生成一个list,而是每次调用返回其中的一个值。

  1. os与sys模块的区别?

前者提供了一种方便的使用操作系统函数的方法。后者提供访问由解释器使用或维护的变量和与解释器进行交互的函数。

  1. NoSQL和关系数据库的区别?
  2. SQL数据存在特定结构的表中;而NoSQL则更加灵活和可扩展,存储方式可以省是JSON文档、哈希表或者其他方式。
  3. 在SQL中,必须定义好表和字段结构后才能添加数据,例如定义表的主键(primary key),索引(index),触发器(trigger),存储过程(stored procedure)等。表结构可以在被定义之后更新,但是如果有比较大的结构变更的话就会变得比较复杂。在NoSQL中,数据可以在任何时候任何地方添加,不需要先定义表。
  4. SQL中如果需要增加外部关联数据的话,规范化做法是在原表中增加一个外键,关联外部数据表。而在NoSQL中除了这种规范化的外部数据表做法以外,我们还能用如下的非规范化方式把外部数据直接放到原数据集中,以提高查询效率。缺点也比较明显,更新审核人数据的时候将会比较麻烦。
  5. SQL中可以使用JOIN表链接方式将多个关系数据表中的数据用一条简单的查询语句查询出来。NoSQL暂未提供类似JOIN的查询方式对多个数据集中的数据做查询。所以大部分NoSQL使用非规范化的数据存储方式存储数据。
  6. SQL中不允许删除已经被使用的外部数据,而NoSQL中则没有这种强耦合的概念,可以随时删除任何数据。
  7. SQL中如果多张表数据需要同批次被更新,即如果其中一张表更新失败的话其他表也不能更新成功。这种场景可以通过事务来控制,可以在所有命令完成后再统一提交事务。而NoSQL中没有事务这个概念,每一个数据集的操作都是原子级的。
  8. 在相同水平的系统设计的前提下,因为NoSQL中省略了JOIN查询的消耗,故理论上性能上是优于SQL的。

Practice

这种实践操作类题目比较丰富多样,如下几类比较常见:

  1. 补充缺失的代码

例如:

def print_directory_contents(sPath):
import os
for sChild in os.listdir(sPath):
sChildPath = os.path.join(sPath,sChild)
if os.path.isdir(sChildPath):
print_directory_contents(sChildPath)
else:
print sChildPath

  1. 下面这段代码的输出结果是什么?请解释。

新的默认列表只在函数被定义的那一刻创建一次。当extendList被没有指定特定参数list调用时,这组list的值随后将被使用。这是因为带有默认参数的表达式在函数被定义的时候被计算,不是在调用的时候被计算。

  1. 下面的代码能够运行么?请解释?

能够运行。当key缺失时,执行DefaultDict类,字典的实例将自动实例化这个数列。

  1. 将函数按照执行效率高低排序,并证明自己的答案是正确的。

例如:

按执行效率从高到低排列:f2、f1和f3。要证明这个答案是正确的,你应该知道如何分析自己代码的性能。Python中有一个很好的程序分析包,可以满足这个需求。

nginx内置变量
内置变量存放在 ngx_http_core_module 模块中,变量的命名方式和apache 服务器变量是一致的。总而言之,这些变量代表着客户端请求头的内容,例如$http_user_agent, $http_cookie, 等等。下面是nginx支持的所有内置变量:

$arg_name
请求中的的参数名,即“?”后面的arg_name=arg_value形式的arg_name

$args
请求中的参数值

$binary_remote_addr
客户端地址的二进制形式, 固定长度为4个字节

$body_bytes_sent
传输给客户端的字节数,响应头不计算在内;这个变量和Apache的mod_log_config模块中的“%B”参数保持兼容

$bytes_sent
传输给客户端的字节数 (1.3.8, 1.2.5)

$connection
TCP连接的序列号 (1.3.8, 1.2.5)

$connection_requests
TCP连接当前的请求数量 (1.3.8, 1.2.5)

$content_length
“Content-Length” 请求头字段

$content_type
“Content-Type” 请求头字段

$cookie_name
cookie名称

$document_root
当前请求的文档根目录或别名

$document_uri
同 $uri

$host
优先级如下:HTTP请求行的主机名>”HOST”请求头字段>符合请求的服务器名

$hostname
主机名

$http_name
匹配任意请求头字段; 变量名中的后半部分“name”可以替换成任意请求头字段,如在配置文件中需要获取http请求头:“Accept-Language”,那么将“-”替换为下划线,大写字母替换为小写,形如:$http_accept_language即可。

$https
如果开启了SSL安全模式,值为“on”,否则为空字符串。

$is_args
如果请求中有参数,值为“?”,否则为空字符串。

$limit_rate
用于设置响应的速度限制,详见 limit_rate。

$msec
当前的Unix时间戳 (1.3.9, 1.2.6)

$nginx_version
nginx版本

$pid
工作进程的PID

$pipe
如果请求来自管道通信,值为“p”,否则为“.” (1.3.12, 1.2.7)

$proxy_protocol_addr
获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串。(1.5.12)

$query_string
同 $args

$realpath_root
当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径。

$remote_addr
客户端地址

$remote_port
客户端端口

$remote_user
用于HTTP基础认证服务的用户名

$request
代表客户端的请求地址

$request_body
客户端的请求主体
此变量可在location中使用,将请求主体通过proxy_pass, fastcgi_pass, uwsgi_pass, 和 scgi_pass传递给下一级的代理服务器。

$request_body_file
将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off 。

$request_completion
如果请求成功,值为”OK”,如果请求未完成或者请求不是一个范围请求的最后一部分,则为空。

$request_filename
当前连接请求的文件路径,由root或alias指令与URI请求生成。

$request_length
请求的长度 (包括请求的地址, http请求头和请求主体) (1.3.12, 1.2.7)

$request_method
HTTP请求方法,通常为“GET”或“POST”

$request_time
处理客户端请求使用的时间 (1.3.9, 1.2.6); 从读取客户端的第一个字节开始计时。

$request_uri
这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI,不包含主机名,例如:”/cnphp/test.php?arg=freemouse”。

$scheme
请求使用的Web协议, “http” 或 “https”

$sent_http_name
可以设置任意http响应头字段; 变量名中的后半部分“name”可以替换成任意响应头字段,如需要设置响应头Content-length,那么将“-”替换为下划线,大写字母替换为小写,形如:$sent_http_content_length 4096即可。

$server_addr
服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中。

$server_name
服务器名,www.cnphp.info

$server_port
服务器端口

$server_protocol
服务器的HTTP版本, 通常为 “HTTP/1.0” 或 “HTTP/1.1”

$status
HTTP响应代码 (1.3.2, 1.2.2)

$tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space
客户端TCP连接的具体信息

$time_iso8601
服务器时间的ISO 8610格式 (1.3.12, 1.2.7)

$time_local
服务器时间(LOG Format 格式) (1.3.12, 1.2.7)

$uri
请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如”/foo/bar.html”。

Nginx作为一个成熟、久经考验的负载均衡软件,与其提供丰富、完整的内置变量是分不开的,它极大增加了对Nginx网络行为的控制细度。这些变量大部分都是在请求进入时解析的,并把他们缓存到请求cycle中,方便下一次获取使用。首先来看看Nginx对都开放了那些API。
参看下表:

名称 说明
$arg_name 请求中的name参数
$args 请求中的参数
$binary_remote_addr 远程地址的二进制表示
$body_bytes_sent 已发送的消息体字节数
$content_length HTTP请求信息里的"Content-Length"
$content_type 请求信息里的"Content-Type"
$document_root 针对当前请求的根路径设置值
$document_uri 与$uri相同; 比如 /test2/test.php
$host 请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名
$hostname 机器名使用 gethostname系统调用的值
$http_cookie cookie 信息
$http_referer 引用地址
$http_user_agent 客户端代理信息
$http_via 最后一个访问服务器的Ip地址。
$http_x_forwarded_for 相当于网络访问路径
$is_args 如果请求行带有参数,返回“?”,否则返回空字符串
$limit_rate 对连接速率的限制
$nginx_version 当前运行的nginx版本号
$pid worker进程的PID
$query_string 与$args相同
$realpath_root 按root指令或alias指令算出的当前请求的绝对路径。其中的符号链接都会解析成真是文件路径
$remote_addr 客户端IP地址
$remote_port 客户端端口号
$remote_user 客户端用户名,认证用
$request 用户请求
$request_body 这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义
$request_body_file 客户端请求主体信息的临时文件名
$request_completion 如果请求成功,设为"OK";如果请求未完成或者不是一系列请求中最后一部分则设为空
$request_filename 当前请求的文件路径名,比如/opt/nginx/www/test.php
$request_method 请求的方法,比如"GET"、"POST"等
$request_uri 请求的URI,带参数
$scheme 所用的协议,比如http或者是https
$server_addr 服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费)
$server_name 请求到达的服务器名
$server_port 请求到达的服务器端口号
$server_protocol 请求的协议版本,"HTTP/1.0"或"HTTP/1.1"
$uri 请求的URI,可能和最初的值有不同,比如经过重定向之类的

据说,每个做 Python 开发的都被字符编码的问题搞晕过,最常见的错误就是 UnicodeEncodeError、UnicodeDecodeError,你好像知道怎么解决,遗憾的是,错误又出现在其它地方,问题总是重蹈覆辙,str 到 unicode 之间的转换用 decode 还是 encode 方法还特不好记,老是混淆,问题究竟出在哪里?

为了弄清楚这个问题,我决定从 python 字符串的构成以及字符编码的细节上进行深入浅出的分析

字节与字符

计算机存储的一切数据,文本字符、图片、视频、音频、软件都是由一串01的字节序列构成的,一个字节等于8个比特位。

而字符就是一个符号,比如一个汉字、一个英文字母、一个数字、一个标点都可以称为一个字符。

字节方便存储和网络传输,而字符用于显示,方便阅读。例如字符 "p" 存储到硬盘是一串二进制数据 01110000,占用一个字节的长度

编码与解码

我们用编辑器打开的文本,看到的一个个字符,最终保存在磁盘的时候都是以二进制字节序列形式存起来的。那么从字符到字节的转换过程就叫做编码(encode),反过来叫做解码(decode),两者是一个可逆的过程。编码是为了存储传输,解码是为了方便显示阅读。

例如字符 "p" 经过编码处理保存到硬盘是一串二进制字节序列 01110000 ,占用一个字节的长度。字符 "禅" 有可能是以 "11100111 10100110 10000101" 占用3个字节的长度存储,为什么说是有可能呢?这个放到后面再说。

Python 的编码为什么那么蛋疼?当然,这不能怪开发者。

这是因为 Python2 使用 ASCII 字符编码作为默认编码方式,而 ASCII 不能处理中文,那么为什么不用 UTf-8 呢?因为 Guido 老爹为 Python 编写第一行代码是在1989年的冬天,1991年2月正式开源发布了第一个版本,而 Unicode 是1991年10月发布的,也就是说 Python 这门语言创立的时候 UTF-8 还没诞生,这是其一。

Python 把字符串的类型还搞成两种,unicode 和 str ,以至于把开发者都弄糊涂了,这是其二。python3 彻底把 字符串重新改造了,只保留一种类型,这是后话,以后再说。

str与unicode

Python2 把字符串分为 unicode 和 str 两种类型。本质上 str 是一串二进制字节序列,下面的示例代码可以看出 str 类型的 "禅" 打印出来是十六进制的 xecxf8 ,对应的二进制字节序列就是 '11101100 11111000'。

s = '禅'
s
'xecxf8'
type(s)

<type 'str'>
而 unicode 类型的 u"禅" 对应的 unicode 符号是 u'u7985'

u = u"禅"
u

u'u7985'

type(u)

<type 'unicode'>
我们要把 unicode 符号保存到文件或者传输到网络就需要经过编码处理转换成 str 类型,于是 python 提供了 encode 方法,从 unicode 转换到 str,反之亦然。

python2-str

encode

u = u"禅"
u

u'u7985'

u.encode("utf-8")

'xe7xa6x85'
decode

s = "禅"
s.decode("utf-8")

u'u7985'

不少初学者怎么也记不住 str 与 unicode 之间的转换用 encode 还是 decode,如果你记住了 str 本质上其实是一串二进制数据,而 unicode 是字符(符号),编码(encode)就是把字符(符号)转换为 二进制数据的过程,因此 unicode 到 str 的转换要用 encode 方法,反过来就是用 decode 方法。

encoding always takes a Unicode string and returns a bytes sequence, and decoding always takes a bytes sequence and returns a Unicode string".
清楚了 str 与 unicode 之间的转换关系之后,我们来看看什么时候会出现 UnicodeEncodeError、UnicodeDecodeError 错误。

UnicodeEncodeError

UnicodeEncodeError 发生在 unicode 字符串转换成 str 字节序列的时候,来看一个例子,把一串 unicode 字符串保存到文件

-- coding:utf-8 --

def main():

name = u'Python之禅'
f = open("output.txt", "w")
f.write(name)

错误日志

UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-7: ordinal not in range(128)
为什么会出现 UnicodeEncodeError?

因为调用 write 方法时,Python 会先判断字符串是什么类型,如果是 str,就直接写入文件,不需要编码,因为 str 类型的字符串本身就是一串二进制的字节序列了。

如果字符串是 unicode 类型,那么它会先调用 encode 方法把 unicode 字符串转换成二进制形式的 str 类型,才保存到文件,而 encode 方法会使用 python 默认的 ascii 码来编码

相当于:

u"Python之禅".encode("ascii")
但是,我们知道 ASCII 字符集中只包含了128个拉丁字母,不包括中文字符,因此 出现了 'ascii' codec can't encode characters 的错误。要正确地使用 encode ,就必须指定一个包含了中文字符的字符集,比如:UTF-8、GBK。

u"Python之禅".encode("utf-8")

'Pythonxe4xb9x8bxe7xa6x85'

u"Python之禅".encode("gbk")

'Pythonxd6xaexecxf8'
所以要把 unicode 字符串正确地写入文件,就应该预先把字符串进行 UTF-8 或 GBK 编码转换。

def main():

name = u'Python之禅'
name = name.encode('utf-8')
with open("output.txt", "w") as f:
    f.write(name)

当然,把 unicode 字符串正确地写入文件不止一种方式,但原理是一样的,这里不再介绍,把字符串写入数据库,传输到网络都是同样的原理

UnicodeDecodeError

UnicodeDecodeError 发生在 str 类型的字节序列解码成 unicode 类型的字符串时

a = u"禅"
a
u'u7985'
b = a.encode("utf-8")
b

'xe7xa6x85'

b.decode("gbk")

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'gbk' codec can't decode byte 0x85 in position 2: incomplete multibyte sequence
把一个经过 UTF-8 编码后生成的字节序列 'xe7xa6x85' 再用 GBK 解码转换成 unicode 字符串时,出现 UnicodeDecodeError,因为 (对于中文字符)GBK 编码只占用两个字节,而 UTF-8 占用3个字节,用 GBK 转换时,还多出一个字节,因此它没法解析。避免 UnicodeDecodeError 的关键是保持 编码和解码时用的编码类型一致。

这也回答了文章开头说的字符 "禅",保存到文件中有可能占3个字节,有可能占2个字节,具体处决于 encode 的时候指定的编码格式是什么。

再举一个 UnicodeDecodeError 的例子

x = u"Python"
y = "之禅"
x + y

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

str 与 unicode 字符串 执行 + 操作时,Python 会把 str 类型的字节序列隐式地转换成(解码)成 和 x 一样的 unicode 类型,但Python是使用默认的 ascii 编码来转换的,而 ASCII字符集中不包含有中文,所以报错了。相当于:

y.decode('ascii')

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
正确地方式应该是找到一种包含有中文字符的字符编码,比如 UTF-8或者 GBK 显示地把 y 进行解码转换成 unicode 类型

x = u"Python"
y = "之禅"
y = y.decode("utf-8")
x + y

u'Pythonu4e4bu7985'
以上内容都是基于 Python2 来讲的,关于 Python3 的字符和编码将会另开一篇文章来写,保持关注。

promise 介绍

promise是异步编程的一种解决方法,比传统的回调函数和事件更合理更强大。

他由社区最早提出和实现,ES6将其写进语言标准,统一了用法,原生提供了promise对象。

所谓promise,简单说是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从语法上说,promise是一个对象,从它可以获取异步操作的消息,promise提供了统一的API,各种异步操作都可以用同样的方法进行处理。

promise 用法

promise对象是一个构造函数,用来生成promise实例。

创建一个promise对象实例

var promise = new Promise(function( resolve, reject) {
       //some code 
      if(//异步操作成功){
        resolve(value);
      }else{
        reject(error);
      }
});

Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject,他们是两个函数,由Javascript引擎提供,不用自己部署。

resolve函数的作用是,将promise对象的状态从“pending”变为‘’resolved‘’,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数

then 用法

promise.then(
    function(value){
   //success
   },
    function(error){
   //failure
 });

then方法可以接受连个回调函数作为参数,第一个回调函数是promise对象的状态变为resolved时调用,第二个回调函数是promise对象的状态变为rejected时调用,其中,第二个函数是可选的,不一定要提供,这两个函数都接受promise对象传出的值作为参数;

promise对象的简单例子

function timeOut (ms) {
   return new Promise(function(resolve,reject) {
        return  setTimeout(resolve, ms, "done");
   })
}

timeOut(3000).then(function(value){
    console.log(value);
})

上面代码中,timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

Promise 新建后就会立即执行。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。

promise 例子

function test(name) {
    return new Promise(
        function (resolve, reject) {
            console.log(name + ' begin');
            setTimeout(function () {
                console.log(name + ' finished');
                resolve(name)
            }, Math.random() * 1000);
        }
    )
}

test('t1').then(function () {
    return test('t2')
});