缺省参数在 python 中是与函数绑定在一起的。
也就是说,一个函数中定义了一个缺省参数,那么这个参数会随着被调用而改变。
- def extendList(val, list=[]):
- list.append(val)
- return list
- list1 = extendList(10)
- list2 = extendList(123, [])
- list3 = extendList('a')
- print("list1 = %s" % list1)
- print("list2 = %s" % list2)
- print("list3 = %s" % list3)
- 结果为:
- list1 = [10, 'a']
- list2 = [123]
- list3 = [10, 'a']
许多人会错误的认为 list1 应该等于 [10] 以及 list3 应该等于 ['a']。认为 list 的参数会在 extendList 每次被调用的时候会被设置成它的默认值 []。
尽管如此,实际发生的事情是,新的默认列表 list 仅仅只在函数被定义时创建一次。随后当 extendList 没有被指定的列表参数调用的时候,其使用的是同一个列表 list。
因此,list1 和 list3 是操作的相同的列表(也就是 [] 对象的引用相同,id 值也就相同)。而 list2 是操作它创建独立的列表(通过传递它自己的空列表作为 list 参数的值)
所以这一点一定要切记切记.
下面代码,我们把 list 置为 None 就可以避免一些麻烦了
- def extendList(val, list=None):
- if list is None:
- list = []
- list.append(val)
- return list
- 结果为:
- list1 = [10]
- list2 = [123]
- list3 = ['a']
None 是一个常量, 是一个不可变对象, 每次调用 myfunc() 时 value 都是 None。
但是 id 值每次都是不同的,每次 list 都是一个新的 list,因此每次 id(value) 都不一样。
在 python 中,函数通过 def 关键字、函数名和可选的参数列表定义。通过 return 关键字返回值。我们举例来说明如何定义和调用一个简单的函数:
- def foo():
- return 1
- foo()
- 1
方法体(当然多行也是一样的)是必须的,通过缩进来表示,在方法名的后面加上双括号 () 就能够调用函数
在 python 中,函数会创建一个新的作用域。
python 开发者可能会说函数有自己的命名空间,差不多一个意思。
这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。让我们写一个简单的函数看一下 本地作用域 和 全局作用域有什么不同:
- a_string = "This is a global variable"
- def foo():
- print locals()
- print globals() # doctest: +ELLIPSIS
- {, 'a_string': 'This is a global variable'}
- foo() # 2
- {}
内置的函数 globals 返回一个包含所有 python 解释器知道的变量名称的字典(为了干净和洗的白白的,我省略了 python 自行创建的一些变量)。在 #2 我调用了函数 foo 把函数内部本地作用域里面的内容打印出来。我们能够看到,函数 foo 有自己独立的命名空间,虽然暂时命名空间里面什么都还没有。
当然这并不是说我们在函数里面就不能访问外面的全局变量。在 python 的作用域规则里面,创建变量一定会在当前作用域里创建一个变量,但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域里面进行查找。所以如果我们修改函数 foo 的实现让它打印全局的作用域里的变量也是可以的:
- a_string = "This is a global variable"
- def foo():
- print a_string # 1
- foo()
- This is a global variable
在 #1 处,python 解释器会尝试查找变量 a_string,当然在函数的本地作用域里面是找不到的,所以接着会去上层的作用域里面去查找。
但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:
- a_string = "This is a global variable"
- def foo():
- a_string = "test" # 1
- print locals()
- foo()
- {'a_string': 'test'}
- a_string # 2
- 'This is a global variable'
我们能够看到,全局变量能够被访问到(如果是可变数据类型 (像 list,dict 这些) 甚至能够被更改)但是赋值不行。在函数内部的 #1 处,我们实际上新创建了一个局部变量,隐藏全局作用域中的同名变量。我们可以通过打印出局部命名空间中的内容得出这个结论。我们也能看到在#2 处打印出来的变量 a_string 的值并没有改变。
值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:
- def foo():
- x = 1
- foo()
- print x # 1
- Traceback (most recent call last):
- NameError: name 'x' is not defined
- #1处发生的错误不仅仅是因为作用域规则导致的(尽管这是抛出了NameError的错误的原因)它还和python以及其它很多编程语言中函数调用实现的机制有关。在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量x的值,因为它这个时候压根不存在!函数foo的命名空间随着函数调用开始而开始,结束而销毁。
python 允许我们向函数传递参数,参数会变成本地变量存在于函数内部。
- def foo(x):
- print locals()
- foo(1)
- {'x': 1}
在 Python 里有很多的方式来定义和传递参数,完整版可以查看 python 官方文档。我们这里简略的说明一下:函数的参数可以是必须的位置参数或者是可选的命名参数(也叫默认参数)或者可变参数或者关键字参数。
- def foo(x, y=0): # 1
- return x - y
- foo(3, 1) # 2
- 2
- foo(3) # 3
- 3
- foo() # 4
- Traceback (most recent call last):
- TypeError: foo() takes at least 1 argument (0 given)
- foo(y=1, x=3) # 5
- 2
在 #1 处我们定义了函数 foo, 它有一个位置参数 x 和一个命名参数 y。
在 #2 处我们能够通过常规的方式来调用函数,尽管有一个命名参数,但参数依然可以通过位置传递给函数。在调用函数的时候,对于命名参数 y 我们也可以完全不管就像#3 处所示的一样。如果命名参数没有接收到任何值的话,python 会自动使用声明的默认值也就是 0。需要注意的是我们不能省略第一个位置参数 x, 否则的话就会像 #4 处所发生错误。
目前还算简洁清晰吧, 但是接下来可能会有点令人困惑。python 支持函数调用时的命名参数(个人觉得应该是命名实参)。看看 #5 处的函数调用,我们传递的是两个命名实参,这个时候因为有名称标识,参数传递的顺序也就不用在意了。
当然相反的情况也是正确的:函数的第二个形参是 y,但是我们通过位置的方式传递值给它。在 #2 处的函数调用 foo(3,1),我们把 3 传递给了第一个参数 x,把 1 传递给了第二个参数 y,尽管第二个参数是一个命名参数。
桑不起,感觉用了好大一段才说清楚这么一个简单的概念:函数的参数可以有名称和位置。这意味着在函数的定义和调用的时候会稍稍在理解上有点儿不同。我们可以给只定义了位置参数的函数传递命名参数(实参),反之亦然!如果觉得不够可以查看官方文档
Python 允许创建嵌套函数。这意味着我们可以在函数里面定义函数而且现有的作用域和变量生存周期依旧适用。
- def outer():
- x = 1
- def inner():
- print x # 1
- inner() # 2
- outer()
- 1
这个例子有一点儿复杂,但是看起来也还行。
想一想在 #1 发生了什么:python 解释器需找一个叫 x 的本地变量,查找失败之后会继续在上层的作用域里面寻找,这个上层的作用域定义在另外一个函数里面。对函数 outer 来说,变量 x 是一个本地变量,但是如先前提到的一样,函数 inner 可以访问封闭的作用域(至少可以读和修改)。在#2 处,我们调用函数 inner,非常重要的一点是,inner 也仅仅是一个遵循 python 变量解析规则的变量名,python 解释器会优先在 outer 的作用域里面对变量名 inner 查找匹配的变量.
显而易见,在 python 里函数和其他东西一样都是对象。(此处应该大声歌唱)啊!包含变量的函数,你也并不是那么特殊!
- issubclass(int, object) # all objects in Python inherit from a common baseclass
- True
- def foo():
- pass
- foo.__class__ # 1
- <type 'function'>
- issubclass(foo.__class__, object)
- True
你也许从没有想过,你定义的函数居然会有属性。没办法,函数在 python 里面就是对象,和其他的东西一样,也许这样描述会太学院派太官方了点:在 python 里,函数只是一些普通的值而已和其他的值一毛一样。这就是说你可以把函数当参数一样传递给其他的函数或者说从函数里面返回函数!如果你从来没有这么想过,那看看下面这个例子:
- def add(x, y):
- return x + y
- def sub(x, y):
- return x - y
- def apply(func, x, y): # 1
- return func(x, y) # 2
- apply(add, 2, 1) # 3
- 3
- apply(sub, 2, 1)
- 1
这个例子对你来说应该不会很奇怪。add 和 sub 是非常普通的两个 python 函数,接受两个值,返回一个计算后的结果值。
在 #1 处你们能看到准备接收一个函数的变量只是一个普通的变量而已,和其他变量一样。在#2 处我们调用传进来的函数:"()" 代表着调用的操作并且调用变量包含的值。
在 #3 处,你们也能看到传递函数并没有什么特殊的语法。
函数的名称只是与其他变量一样的标识符而已。
你们也许看到过这样的行为:"python 把频繁要用的操作变成函数作为参数进行使用,想通过传递一个函数给内置排序函数的 key 参数,从而来自定义排序规则。"
那把函数当做返回值回事这样的情况呢:
- def outer():
- def inner():
- print "Inside inner"
- return inner # 1
- foo = outer() #2
- foo # doctest:+ELLIPSIS
- <function inner at 0x>
- foo()
- Inside inner
这个例子看起来也许会更加的奇怪。在 #1 处我把恰好是函数标识符的变量 inner 作为返回值返回出来。这并没有什么特殊的语法:"把函数 inner 返回出来,否则它根本不可能会被调用到。" 还记得变量的生存周期吗?每次函数 outer 被调用的时候,函数 inner 都会被重新定义,如果它不被当做变量返回的话,每次执行过后它将不复存在,也就是没有变量来接收返回的值。
在 #2 处我们捕获住返回值 – 函数 inner,将它存在一个新的变量 foo 里。
我们能够看到,当对变量 foo 进行求值,它确实包含函数 inner,而且我们能够对他进行调用。初次看起来可能会觉得有点奇怪,但是理解起来并不困难是吧。坚持住,因为奇怪的转折马上就要来了
我们先不急着定义什么是闭包,先来看看一段代码,仅仅是把上一个例子简单的调整了一下:
- def outer():
- x = 1
- def inner():
- print x # 1
- return inner
- foo = outer()
- foo.func_closure
- (<cell at 0x: int object at 0x>,)
在上一个例子中我们了解到,inner 作为一个函数被 outer 返回,保存在一个变量 foo,并且我们能够对它进行调用 foo()。不过它会正常的运行吗?我们先来看看作用域规则。
所有的东西都在 python 的作用域规则下进行工作:"x 是函数 outer 里的一个局部变量。当函数 inner 在 #1 处打印 x 的时候,python 解释器会在 inner 内部查找相应的变量,当然会找不到,所以接着会到封闭作用域里面查找,并且会找到匹配。
但是从变量的生存周期来看,该怎么理解呢?我们的变量 x 是函数 outer 的一个本地变量,这意味着只有当函数 outer 正在运行的时候才会存在。根据我们已知的 python 运行模式,我们没法在函数 outer 返回之后继续调用函数 inner,在函数 inner 被调用的时候,变量 x 早已不复存在,可能会发生一个运行时错误。
万万没想到,返回的函数 inner 居然能够正常工作。Python 支持一个叫做函数闭包的特性,用人话来讲就是,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。
这能够通过查看函数的 (py2 是 func_closure,py3 是 - closure-) 属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值(或者叫被引用的值),比如 x,如果在 outer 里面还定义了其他的值,封闭作用域里面是不会有的)
记住,每次函数 outer 被调用的时候,函数 inner 都会被重新定义。现在变量 x 的值不会变化,所以每次返回的函数 inner 会是同样的逻辑,假如我们稍微改动一下呢?
- def outer(x):
- def inner():
- print x # 1
- return inner
- print1 = outer(1)
- print2 = outer(2)
- print1()
- 1
- print2()
- 2
从这个例子中你能够看到闭包 – 被函数记住的封闭作用域 – 能够被用来创建自定义的函数,本质上来说是一个硬编码的参数。事实上我们并不是传递参数 1 或者 2 给函数 inner,我们实际上是创建了能够打印各种数字的各种自定义版本。
闭包单独拿出来就是一个非常强大的功能, 在某些方面,你也许会把它当做一个类似于面向对象的技术:outer 像是给 inner 服务的构造器,x 像一个私有变量。
不过,我们现在不会用闭包做这么 low 的事 (⊙o⊙)…!相反,让我们再爽一次,写一个高大上的装饰器!
装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数。我们一步步从简到繁来瞅瞅:
- def outer(some_func):
- def inner():
- print "before some_func"
- ret = some_func() # 1
- return ret + 1
- return inner
- def foo():
- return 1
- decorated = outer(foo) # 2
- decorated()
- before some_func
- 2
仔细看看上面这个装饰器的例子。们定义了一个函数 outer,它只有一个 some_func 的参数,在他里面我们定义了一个嵌套的函数 inner。inner 会打印一串字符串,然后调用 some_func,在 #1 处得到它的返回值。在 outer 每次调用的时候 some_func 的值可能会不一样,但是不管 some_func 的值如何,我们都会调用它。最后,inner 返回 some_func() + 1 的值 – 我们通过调用在 #2 处存储在变量 decorated 里面的函数能够看到被打印出来的字符串以及返回值 2,而不是期望中调用函数 foo 得到的返回值 1。
我们可以认为变量 decorated 是函数 foo 的一个装饰版本,一个加强版本。事实上如果打算写一个有用的装饰器的话,我们可能会想愿意用装饰版本完全取代原先的函数 foo,这样我们总是会得到我们的 "加强版"foo。想要达到这个效果,完全不需要学习新的语法,简单的赋值给变量 foo 就行了:
- foo = outer(foo)
- foo # doctest: +ELLIPSIS
- <function inner at 0x>
现在,任何怎么调用都不会牵扯到原先的函数 foo,都会得到新的装饰版本的 foo,现在我们还是来写一个有用的装饰器。
想象我们有一个库,这个库能够提供类似坐标的对象,也许它们仅仅是一些 x 和 y 的坐标对。不过可惜的是这些坐标对象不支持数学运算符,而且我们也不能对源代码进行修改,因此也就不能直接加入运算符的支持。我们将会做一系列的数学运算,所以我们想要能够对两个坐标对象进行合适加减运算的函数,这些方法很容易就能写出:
- class Coordinate(object):
- def __init__(self, x, y):
- self.x = x
- self.y = y
- def __repr__(self):
- return "Coord: " + str(self.__dict__)
- def add(a, b):
- return Coordinate(a.x + b.x, a.y + b.y)
- def sub(a, b):
- return Coordinate(a.x - b.x, a.y - b.y)
- one = Coordinate(100, 200)
- two = Coordinate(300, 200)
- add(one, two)
- Coord: {'y': 400, 'x': 400}
如果不巧我们的加减函数同时也需要一些边界检查的行为那该怎么办呢?搞不好你只能够对正的坐标对象进行加减操作,任何返回的值也都应该是正的坐标。所以现在的期望是这样:
- one = Coordinate(100, 200)
- two = Coordinate(300, 200)
- three = Coordinate(-100, -100)
- sub(one, two)
- Coord: {'y': 0, 'x': -200}
- add(one, three)
- Coord: {'y': 100, 'x': 0}
我们期望在不更改坐标对象 one, two, three 的前提下 one 减去 two 的值是 {x: 0, y: 0},one 加上 three 的值是 {x: 100, y: 200}。与其给每个方法都加上参数和返回值边界检查的逻辑,我们来写一个边界检查的装饰器!
- def wrapper(func):
- def checker(a, b): # 1
- if a.x < 0 or a.y < 0:
- a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
- if b.x < 0 or b.y < 0:
- b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
- ret = func(a, b)
- if ret.x < 0 or ret.y < 0:
- ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
- return ret
- return checker
- add = wrapper(add)
- sub = wrapper(sub)
- sub(one, two)
- Coord: {'y': 0, 'x': 0}
- add(one, three)
- Coord: {'y': 200, 'x': 100}
这个装饰器能想先前的装饰器例子一样进行工作,返回一个经过修改的函数,但是在这个例子中,它能够对函数的输入参数和返回值做一些非常有用的检查和格式化工作,将负值的 x 和 y 替换成 0。
显而易见,通过这样的方式,我们的代码变得更加简洁:将边界检查的逻辑隔离到单独的方法中,然后通过装饰器包装的方式应用到我们需要进行检查的地方。另外一种方式通过在计算方法的开始处和返回值之前调用边界检查的方法也能够达到同样的目的。但是不可置否的是,使用装饰器能够让我们以最少的代码量达到坐标边界检查的目的。事实上,如果我们是在装饰自己定义的方法的话,我们能够让装饰器应用的更加有逼格。
Python 支持使用标识符 @将装饰器应用在函数上,只需要在函数的定义前加上 @和装饰器的名称。在上一节的例子里我们是将原本的方法用装饰后的方法代替:
add = wrapper(add)
这种方式能够在任何时候对任意方法进行包装。但是如果我们自定义一个方法,我们可以使用 @进行装饰:
- @wrapper
- def add(a, b):
- return Coordinate(a.x + b.x, a.y + b.y)
需要明白的是,这样的做法和先前简单的用包装方法替代原有方法是一毛一样的, python 只是加了一些语法糖让装饰的行为更加的直接明确和优雅一点。
我们已经完成了一个有用的装饰器,但是由于硬编码的原因它只能应用在一类具体的方法上,这类方法接收两个参数,传递给闭包捕获的函数。如果我们想实现一个能够应用在任何方法上的装饰器要怎么做呢?再比如,如果我们要实现一个能应用在任何方法上的类似于计数器的装饰器,不需要改变原有方法的任何逻辑。这意味着装饰器能够接受拥有任何签名的函数作为自己的被装饰方法,同时能够用传递给它的参数对被装饰的方法进行调用。
非常巧合的是 Python 正好有支持这个特性的语法。可以阅读 Python Tutorial 获取更多的细节。当定义函数的时候使用了,意味着那些通过位置传递的参数将会被放在带有前缀的变量中, 所以:
- def one(*args):
- print args # 1
- one()
- ()
- one(1, 2, 3)
- (1, 2, 3)
- def two(x, y, *args): # 2
- print x, y, args
- two('a', 'b', 'c')
- a b ('c',)
第一个函数 one 只是简单地讲任何传递过来的位置参数全部打印出来而已,你们能够看到,在代码 #1 处我们只是引用了函数内的变量 args, *args 仅仅只是用在函数定义的时候用来表示位置参数应该存储在变量 args 里面。Python 允许我们制定一些参数并且通过 args 捕获其他所有剩余的未被捕捉的位置参数,就像 #2 处所示的那样。
操作符在函数被调用的时候也能使用。意义基本是一样的。当调用一个函数的时候,一个用标志的变量意思是变量里面的内容需要被提取出来然后当做位置参数被使用。同样的,来看个例子:
- def add(x, y):
- return x + y
- lst = [1,2]
- add(lst[0], lst[1]) # 1
- 3
- add(*lst) # 2
- 3
- #1处的代码和#2处的代码所做的事情其实是一样的,在#2处,python为我们所做的事其实也可以手动完成。这也不是什么坏事,*args要么是表示调用方法大的时候额外的参数可以从一个可迭代列表中取得,要么就是定义方法的时候标志这个方法能够接受任意的位置参数。
接下来提到的会稍多更复杂一点,代表着键值对的参数字典,和 * 所代表的意义相差无几,也很简单对不对:
- def foo(**kwargs):
- print kwargs
- foo()
- {}
- foo(x=1, y=2)
- {'y': 2, 'x': 1}
当我们定义一个函数的时候,我们能够用 kwargs 来表明,所有未被捕获的关键字参数都应该存储在 kwargs 的字典中。如前所诉,args 和 kwargs 并不是 python 语法的一部分,但在定义函数的时候,使用这样的变量名算是一个不成文的约定。和 * 一样,我们同样可以在定义或者调用函数的时候使用。
- dct = {
- 'x': 1,
- 'y': 2
- }
- def bar(x, y) : return x + y bar( * *dct) 3
有了这招新的技能,我们随随便便就可以写一个能够记录下传递给函数参数的装饰器了。先来个简单地把日志输出到界面的例子:
- def logger(func):
- def inner(*args, **kwargs): #1
- print "Arguments were: %s, %s" % (args, kwargs)
- return func(*args, **kwargs) #2
- return inner
请注意我们的函数 inner,它能够接受任意数量和类型的参数并把它们传递给被包装的方法,这让我们能够用这个装饰器来装饰任何方法。
- @logger
- def foo1(x, y=1):
- return x * y
- @logger
- def foo2():
- return 2
- foo1(5, 4)
- Arguments were: (5, 4), {}
- 20
- foo1(1)
- Arguments were: (1,), {}
- 1
- foo2()
- Arguments were: (), {}
- 2
随便调用我们定义的哪个方法,相应的日志也会打印到输出窗口,和我们预期的一样。
每个线程互相独立,相互之间没有任何关系。现在假设这样一个例子:有一个全局的计数 num,每个线程获取这个全局的计数,根据 num 进行一些处理,然后将 num 加 1。代码如下:
- # encoding: UTF-8
- import threading
- import time
- class MyThread(threading.Thread):
- def run(self):
- global num
- time.sleep(1)
- num = num+1
- msg = self.name+' set num to '+str(num)
- print msg
- num = 0
- def test():
- for i in range(5):
- t = MyThread()
- t.start()
- if __name__ == '__main__':
- test()
- 但是运行结果是不正确的:
- Thread-5 set num to 2
- Thread-3 set num to 3
- Thread-2 set num to 5
- Thread-1 set num to 5
- Thread-4 set num to 4
问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为 "线程不安全"。
上面的例子引出了多线程编程的最常见问题:数据共享。当多个线程都修改某一个共享数据的时候,需要进行同步控制。
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定 / 非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为 "锁定",其他线程不能更改;直到该线程释放资源,将资源的状态变成 "非锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading 模块中定义了 Lock 类,可以方便的处理锁定:
- #创建锁
- mutex = threading.Lock()
- #锁定
- mutex.acquire([timeout])
- #释放
- mutex.release()
其中,锁定方法 acquire 可以有一个超时时间的可选参数 timeout。如果设定了 timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。
使用互斥锁实现上面的例子的代码如下:
- import threading
- import time
- class MyThread(threading.Thread):
- def run(self):
- global num
- time.sleep(1)
- if mutex.acquire(1):
- num = num+1
- msg = self.name+' set num to '+str(num)
- print msg
- mutex.release()
- num = 0
- mutex = threading.Lock()
- def test():
- for i in range(5):
- t = MyThread()
- t.start()
- if __name__ == '__main__':
- test()
- 运行结果:
- Thread-3 set num to 1
- Thread-4 set num to 2
- Thread-5 set num to 3
- Thread-2 set num to 4
- Thread-1 set num to 5
可以看到,加入互斥锁后,运行结果与预期相符。
当一个线程调用锁的 acquire() 方法获得锁时,锁就进入"locked"状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为"blocked"状态,称为" 同步阻塞 "
直到拥有锁的线程调用锁的 release() 方法释放锁之后,锁进入"unlocked" 状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
- .#-*- encoding=utf-8 -*-
- .print '----------------------方法1--------------------------'
- .#方法1,实现__new__方法
- .#并在将一个类的实例绑定到类变量_instance上,
- .#如果cls._instance为None说明该类还没有实例化过,实例化该类,并返回
- .#如果cls._instance不为None,直接返回cls._instance
- .class Singleton(object):
- . def __new__(cls, *args, **kw):
- . if not hasattr(cls, '_instance'):
- . cls._instance = super(Singleton, cls).__new__(*args, **kw)
- . return cls._instance
- .
- .class MyClass(Singleton):
- . a = 1
- .
- .one = MyClass()
- .two = MyClass()
- .
- .
- .print '----------------------方法2--------------------------'
- .#方法2,共享属性;所谓单例就是所有引用(实例、对象)拥有相同的状态(属性)和行为(方法)
- .#同一个类的所有实例天然拥有相同的行为(方法),
- .#只需要保证同一个类的所有实例具有相同的状态(属性)即可
- .#所有实例共享属性的最简单最直接的方法就是__dict__属性指向(引用)同一个字典(dict)
- .class Borg(object):
- . _state = {}
- . def __new__(cls, *args, **kw):
- . ob = super(Borg, cls).__new__(cls, *args, **kw)
- . ob.__dict__ = cls._state
- . return ob
- .
- .class MyClass2(Borg):
- . a = 1
- .
- .one = MyClass2()
- .two = MyClass2()
- .
- .
- .print '----------------------方法3--------------------------'
- .#方法3:本质上是方法1的升级(或者说高级)版
- .#使用__metaclass__(元类)的高级python用法
- .class Singleton2(type):
- . def __init__(cls, *args):
- . super(Singleton2, cls).__init__(*args)
- . cls._instance = None
- .
- . def __call__(cls, *args, **kwargs):
- . if cls._instance is None:
- . cls._instance = super(Singleton2, cls).__call__(*args, **kwargs)
- . return cls._instance
- .
- .
- .class MyClass(object):
- . __metaclass__ = Singleton2
- .
- .one = MyClass3()
- .two = MyClass3()
- .
- .
- .print '----------------------方法4--------------------------'
- .#方法4:也是方法1的升级(高级)版本,
- .#使用装饰器(decorator),
- .#这是一种更pythonic,更elegant的方法,
- .#单例类本身根本不知道自己是单例的,因为他本身(自己的代码)并不是单例的
- .def singleton(cls, *args, **kw):
- . instances = {}
- . def _singleton():
- . if cls not in instances:
- . instances[cls] = cls(*args, **kw)
- . return instances[cls]
- . return _singleton
- .
- .@singleton
- .class MyClass4(object):
- . a = 1
- . def __init__(self, x=0):
- . self.x = x
- .
- .one = MyClass4()
- .two = MyClass4()
- import os
- def GetFileList(dir, fileList):
- if os.path.isfile(dir):
- fileList.append(dir)
- elif os.path.isdir(dir):
- for s in os.listdir(dir):
- # 如果需要忽略某些文件夹,使用以下代码
- # if s == "xxx":
- # continue
- newDir = os.path.join(dir, s)
- GetFileList(newDir, fileList)
- return fileList
- list = GetFileList('/home/xiaoke/ml_project/day01', [])
- for e in list:
- print(e)
- import os
- def iterbrowse(path):
- for home, dirs, files in os.walk(path):
- for filename in files:
- yield os.path.join(home, filename)
- for fullname in iterbrowse("/home/xiaoke/ml_project/day01"):
- print(fullname)
递归就是在过程或函数里调用自身
必须有一个明确的递归结束条件,称为递归出口。
一般情况在递归内部需要一个分支判断,如:
- def fab(n):
- if n<2:
- return 1
- else
- return fab(n-1)+fab(n-2)
递归使代码看起来更加整洁、优雅
可以用递归将复杂任务分解成更简单的子问题
使用递归比使用一些嵌套迭代更容易
递归缺点:
递归的逻辑很难调试、跟进
递归调用的代价高昂(效率低),因为占用了大量的内存和时间。
过深的调用会导致栈溢出。
案例阐述,递归函数
我们来计算阶乘 n! = 1 x 2 x 3 x … x n,用函数 fact(n) 表示,可以看出:
1 fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n) 可以表示为 n x fact(n-1),只有 n=1 时需要特殊处理。
于是,fact(n) 用递归的方式写出来就是:
- def fact(n):
- if n==1:
- return 1
- return n * fact(n - 1)
上面就是一个递归函数。可以试试:
- >>> fact(1)
- 1
- >>> fact(5)
- 120
- >>> fact(100)
- 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
- 如果我们计算fact(5),可以根据函数定义看到计算过程如下:
- ===> fact(5)
- ===> 5 * fact(4)
- ===> 5 * (4 * fact(3))
- ===> 5 * (4 * (3 * fact(2)))
- ===> 5 * (4 * (3 * (2 * fact(1))))
- ===> 5 * (4 * (3 * (2 * 1)))
- ===> 5 * (4 * (3 * 2))
- ===> 5 * (4 * 6)
- ===> 5 * 24
- ===> 120
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
可以试试 fact(1000):
- >>> fact(1000) Traceback(most recent call last) : File "<stdin>",
- line 1,
- in<module > File "<stdin>",
- line 4,
- infact...File "<stdin>",
- line 4,
- infact RuntimeError: maximum recursion depth exceeded
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return 语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的 fact(n) 函数由于 return n * fact(n - 1) 引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
- def fact(n):
- return fact_iter(1, 1, n)
- def fact_iter(product, count, max):
- if count > max:
- return product
- return fact_iter(product * count, count + 1, max)
可以看到,return fact_iter(product * count, count + 1, max) 仅返回递归函数本身,product * count 和 count + 1 在函数调用前就会被计算,不影响函数调用。
fact(5) 对应的 fact_iter(1, 1, 5) 的调用如下:
- ===> fact_iter(1, 1, 5)
- ===> fact_iter(1, 2, 5)
- ===> fact_iter(2, 3, 5)
- ===> fact_iter(6, 4, 5)
- ===> fact_iter(24, 5, 5)
- ===> fact_iter(120, 6, 5)
- ===> 120
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python 解释器也没有做优化,所以,即使把上面的 fact(n) 函数改成尾递归方式,也会导致栈溢出。
Python 针对尾递归优化的装饰器源码
有一个针对尾递归优化的 decorator,可以参考源码:
- def tail_call_optimized(g):
- """ This function decorates a function with tail call optimization. It does this by throwing an exception if it is it's own grandparent, and catching such exceptions to fake the tail call optimization. This function fails if the decorated function recurses in a non-tail context. """
- def func(*args, **kwargs):
- f = sys._getframe()
- if f.f_back and f.f_back.f_back \
- and f.f_back.f_back.f_code == f.f_code:
- raise TailRecurseException(args, kwargs)
- else:
- while 1:
- try:
- return g(*args, **kwargs)
- except TailRecurseException, e:
- args = e.args
- kwargs = e.kwargs
- func.__doc__ = g.__doc__
- return func
现在,只需要使用这个 @tail_call_optimized 装饰器,就可以顺利计算出 fact(1000)
django-admin startproject 项目名称
python manage.py startapp 应用 app 名
项目文件夹下的组成部分
manage.py 是项目运行的入口,指定配置文件路径。
与项目同名的目录,包含项目的配置文件
___init.py 是一个空文件,作用是这个目录可以被当作包使用。
settings.py 是项目的整体配置文件。
urls.py 是项目的 URL 配置文件。
wsgi.py 是项目与 WSGI 兼容的 Web 服务器入口
对 MVT 的理解
M 全拼为 Model,与 MVC 中的 M 功能相同,负责和数据库交互,进行数据处理。
V 全拼为 View,与 MVC 中的 C 功能相同,接收请求,进行业务处理,返回应答。
T 全拼为 Template,与 MVC 中的 V 功能相同,负责封装构造要返回的 html。
Django 中的中间件是一个轻量级、底层的插件系统,可以介入 Django 的请求和响应处理过程,修改 Django 的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了 Django 框架的健壮性
1)初始化:无需任何参数, 服务器接收第一个请求时会被调用一次,而且只调用一次,用于确定是否启用当前中间件(也可以确定自定义的中间件是否启用)。
def init():
pass
2)在进行 url 匹配之前被调用,在每个请求上调用, 返回 None 或 HttpResponse 对象。
def process_request(request):
pass
3)在 url 匹配之后,视图函数调用之前被调用,在每个请求上调用, 返回 None 或 HttpResponse 对象。
def process_view(request, view_func, view_args, view_kwargs):
pass
4) 视图函数之后会被调用:所有响应返回浏览器之前被调用,在每个请求上调用,返回 HttpResponse 对象。
def process_response(request, response):
pass
5)异常处理:当视图函数抛出异常时调用,在每个请求上调用,返回一个 HttpResponse 对象。
def process_exception(request,exception):
pass
字段查询
all(): 返回模型类对应表格中的所有数据。
例:查询图书所有信息。
BookInfo.objects.all();->select * from booktest_bookinfo;
get(): 返回表格中满足条件的一条数据。
如果查到多条数据,则抛异常:MultipleObjectsReturned
查询不到数据,则抛异常:DoesNotExist
例:查询图书 id 为 3 的图书信息。
BookInfo.objects.get(id=3) –> select * from booktest_bookinfo where id = 3;
filter(): 参数写查询条件,返回满足条件 QuerySet 集合数据。
条件格式:
** 模型类属性名 **__条件名 = 值
注意:此处是模型类属性名,不是表中的字段名
关于 filter 具体案例如下:
1. 判等 exact。
例:查询编号为 1 的图书。
BookInfo.object.filter(id=1)
BookInfo.object.filter(id__exact=1) 此处的__exact 可以省略
2. 模糊查询 like
例:查询书名包含'传'的图书。contains
contains BookInfo.objects.filter(btitle__contains='传')
例:查询书名以'部'结尾的图书 endswith 开头: startswith BookInfo.objects.filter(btitle__endswith='部')
BookInfo.objects.filter(btitle__startswith='天')
3. 空查询 where 字段名 isnull
例:查询书名不为空的图书。isnull
BookInfo.objects.filter(btitle__isnull=False)
4. 范围查询 where id in (1,3,5)
例:查询编号为 1 或 3 或 5 的图书。In
BookInfo.objects.filter(id__in=[1,3,5])
5. 比较查询 gt lt(less than) gte(equal) lte
例:查询编号大于等于 3 的图书。
BookInfo.objects.filter(id__gte=3)
6. 日期查询
例:查询 1980 年发表的图书。
BookInfo.objects.filter(bpub_date__year = 1980)
例:查询 1980 年 1 月 1 日后发表的图。
BookInfo.objects.filter(bpub_date__gt = date(1980,1,1))
7.exclude: 返回不满足条件的数据。
例:查询 id 不为 3 的图书信息。
BookInfo.objects.exclude(id=3)
F 对象
作用:用于类属性之间的比较条件。
使用之前需要先导入:
from django.db.models import F
例:查询图书阅读量大于评论量图书信息。where bread > bcomment BookInfo.objects.filter(bread__gt = F('bcomment'))
例:查询图书阅读量大于 2 倍评论量图书信息。 BookInfo.objects.filter(bread__gt=F('bcomment')*2)
Q 对象
作用:用于查询时的逻辑条件。可以对 Q 对象进行 &|~ 操作。
使用之前需要先导入:
from django.db.models import Q
例:查询 id 大于 3 且阅读大于 30 的图书的信息。
BookInfo.objects.filter(id__gt=3, bread__gt=30)
BooInfo.objects.filter(Q(id__gt=3) & Q(bread__gt=3))
例:查询 id 大于 3 或者阅读大于 30 的图书的信息。
BookInfo.objects.filter(Q(id__gt=3) | Q(bread__gt=30))
例:查询 id 不等于 3 图书的信息。
BookInfo.objects.filter(~Q(id=3))
order_by 返回 QuerySet
作用:对查询结果进行排序。
例:查询所有图书的信息,按照 id 从小到大进行排序。
BookInfo.objects.all().order_by('id')
例:查询所有图书的信息,按照 id 从大到小进行排序。
BookInfo.objects.all().order_by('-id')
例:把 id 大于 3 的图书信息按阅读量从大到小排序显示;
BookInfo.objects.filter(id__gt=3).order_by('-bread')
聚合函数
作用:对查询结果进行聚合操作。
sum count max min avg
aggregate:调用这个函数来使用聚合。
使用前需先导入聚合类:
from django.db.models import Sum,Count,Max,Min,Avg
例:查询所有图书的数目。select count(*) from booktest_bookinfo; BookInfo.objects.aggregate(Count('id'))
{'id__count': 5} 注意返回值类型及键名
例:查询所有图书阅读量的总和。
BookInfo.objects.aggregate(Sum('bread'))
{'bread__sum':120} 注意返回值类型及键名
count 函数
作用:统计满足条件数据的数目。
例:统计所有图书的数目。
BookInfo.objects.all().count()
例:统计 id 大于 3 的所有图书的数目。
BookInfo.objects.filter(id__gt = 3).count()
模型类关系
1)一对多关系
例:图书类 - 英雄类
models.ForeignKey() 定义在多的类中。
2)多对多关系
例:新闻类 - 新闻类型类
models.ManyToManyField() 定义在哪个类中都可以。
3)一对一关系
例:员工基本信息类 - 员工详细信息类
models.OneToOneField() 定义在哪个类中都可以。
创建管理员的用户名和密码
python manage.py createsuperuser
按提示填写用户名、邮箱、密码
控制最后显示的空白表格,默认 3 条,设置为 2 条
extra = 2
每页显示 10 条数据
list_per_page = 10
在底部显示控制选项
actions_on_bottom = True
在顶部显示控制选项
actions_on_top = False
控制列表页显示表的哪些字段
list_display = ['id', 'atitle', 'title', 'parent']
侧边栏过滤框
list_filter = ['atitle']
搜索框
search_fields = ['atitle']
以下二者只能选其一
fields = ['aParent', 'atitle']
fieldsets = [('基本', {'fields':['atitle']}), ('高级', {'fields':['aParent']}) ]
uWSGI 是一个 Web 服务器,它实现了 WSGI 协议、uwsgi 协议、http 等协议。
注意 uwsgi 是一种通信协议,它用于定义传输信息的类型,而 uWSGI 是实现 uwsgi 协议和 WSGI 协议的 Web 服务器。
uWSGI 具有超快的性能、低内存占用和多 app 管理等优点,并且搭配着 Nginx 就是一个生产环境了,能够将用户访问请求与应用 app 隔离开,实现真正的部署 。相比来讲,支持的并发量更高,方便管理多进程,发挥多核的优势,提升性能。
为什么有了 uWSGI 为什么还需要 nginx?
因为 nginx 具备优秀的静态内容处理能力,然后将动态内容转发给 uWSGI 服务器,这样可以达到很好的客户端响应。
uwsgi 的参数:
-M 开启 Master 进程
-p 4 开启 4 个进程
-s 使用的端口或者 socket 地址
-d 使用 daemon 的方式运行, 注意, 使用 - d 后, 需要加上 log 文件地址, 比如 - d /var/log/uwsgi.log
-R 10000 开启 10000 个进程后, 自动 respawn(复位)下
-t 30 设置 30s 的超时时间, 超时后, 自动放弃该链接
–limit-as 32 将进程的总内存量控制在 32M
-x 使用配置文件模式
runserver 方法是调试 Django 时经常用到的运行方式,它使用 Django 自带的
WSGI Server 运行,主要在测试和开发中使用,并且 runserver 开启的方式也是单进程 。
1,用任何编程语言来开发程序,都是为了让计算机干活,比如下载一个 MP3,编写一个文档等等,而计算机干活的 CPU 只认识机器指令,所以,尽管不同的编程语言差异极大,最后都得 "翻译" 成 CPU 可以执行的机器指令。而不同的编程语言,干同一个活,编写的代码量,差距也很大。
比如,完成同一个任务,C 语言要写 1000 行代码,Java 只需要写 100 行,而 Python 可能只要 20 行。
3,用 Python 可以做什么?可以做日常任务,比如自动备份你的 MP3;可以做网站,很多著名的网站包括 YouTube 就是 Python 写的;可以做网络游戏的后台,很多在线游戏的后台都是 Python 开发的。国内的话,豆瓣、知乎用 python 写的
4,Python 当然也有不能干的事情,比如写操作系统,这个只能用 C 语言写;写手机应用,只能用 Swift/Objective-C(针对 iPhone)和 Java(针对 Android(Kotlin)); 写 3D 游戏,最好用 C 或 C++。
5,Python 是一种解释型语言。这就是说,与 C 语言和 C 的衍生语言不同,Python 代码在运行之前不需要编译。
6,Python 是动态类型语言,指的是你在声明变量时,不需要说明变量的类型。你可以直接编写类似 x=111 和 x="I'm a string" 这样的代码,程序不会报错。
7,Python 非常适合面向对象的编程(OOP),因为它支持通过组合(composition)与继承(inheritance)的方式定义类(class)。Python 中没有访问说明符(access specifier,类似 java 中的 public 和 private),这么设计的依据是 "大家都是成年人了"。
在 Python 语言中,函数是第一类对象(first-class objects)。这指的是它们可以被指定给变量,函数既能返回函数类型,也可以接受函数作为输入。类(class)也是第一类对象。
8,Python 代码编写快,但是运行速度比编译语言通常要慢。好在 Python 允许加入基于 C 语言编写的扩展,因此我们能够优化代码,消除瓶颈,这点通常是可以实现的。numpy 就是一个很好地例子,它的运行速度真的非常快,因为很多算术运算其实并不是通过 Python 实现的。
9,Python 用途非常广泛——网络应用,自动化,科学建模,大数据应用,等等。它也常被用作 "胶水语言",帮助其他语言和组件改善运行状况。
· 动态类型语言:在运行期进行类型检查的语言,也就是在编写代码的时候可以不指定变量的数据类型,比如 Python 和 Ruby
· 静态类型语言:它的数据类型是在编译期进行检查的,也就是说变量在使用前要声明变量的数据类型,这样的好处是把类型检查放在编译期,提前检查可能出现的类型错误,典型代表 C/C++ 和 Java
· 强类型语言,一个变量不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换。举个例子:如果你定义了一个 double 类型变量 a, 不经过强制类型转换那么程序 int b = a 无法通过编译。典型代表是 Java。
· 弱类型语言:它与强类型语言定义相反, 允许编译器进行隐式的类型转换,典型代表 C/C++。
弱类型:语言在运行时会隐式的做数据类型转换
强类型:语言运行时确保不会发生未授意类型转换
静态类型:编译期进行数据类型检查
动态类型:运行期才做类型检查
python 内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。
引用计数增加的情况:
1,一个对象分配一个新名称
2,将其放入一个容器中(如列表、元组或字典)
引用计数减少的情况:
1,使用 del 语句对对象别名显示的销毁
2,引用超出作用域或被重新赋值
sys.getrefcount( ) 函数可以获得对象的当前引用计数
多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。
1,当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
2,当两个对象 a 和 b 相互引用时,del 语句可以减少 a 和 b 的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问循环的对象并删除它们。也就是分代收集技术(三代)。
Python 的内存机制以金字塔行,-1,-2 层主要由操作系统进行操作,
第 0 层是 C 中的 malloc,free 等内存分配和释放函数进行操作;
第 1 层和第 2 层是内存池,有 Python 的接口函数 PyMem_Malloc 函数实现,当对象小于 256 个字节时有该层直接分配内存;
第 3 层是最上层,也就是我们对 Python 对象的直接操作;如整数,浮点数和 List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。
在 C 中如果频繁的调用 malloc 与 free 时, 是会产生性能问题的. 再加上频繁的分配与释放小块的内存会产生内存碎片.
Python 在这里主要干的工作有:
如果请求分配的内存在 - 5~256 字节之间就使用自己的内存管理系统, 否则直接使用 malloc.
这里还是会调用 malloc 分配内存, 但每次会分配一块大小为 256k 的大块内存.
经由内存池登记的内存到最后还是会回收到内存池, 并不会调用 C 的 free 释放掉. 以便下次使用. 对于简单的 Python 对象,例如数值、字符串,元组(tuple 不允许被更改) 采用的是复制的方式 (深拷贝),也就是说当将另一个变量 B 赋值给变量 A 时,虽然 A 和 B 的内存空间仍然相同,但当 A 的值发生变化时,会重新给 A 分配空间,A 和 B 的地址变得不再相同
赋值(=),就是创建了对象的一个新的引用,修改其中任意一个变量都会影响到另一个。
浅拷贝:创建一个新的对象,但它包含的是对原始对象中包含项的引用(如果用引用的方式修改其中一个对象,另外一个也会改变){1, 完全切片方法;2,工厂函数,如 list();3,copy 模块的 copy() 函数}
深拷贝:创建一个新的对象,并且递归的复制它所包含的对象(修改其中一个,另外一个不会改变){copy 模块的 deep.deepcopy() 函数}
Iterables(迭代器)
当你创建了一个列表, 你可以一个一个的读取它的每一项, 这叫做 iteration:
所有你可以用在 for…in… 语句中的都是可迭代的: 比如 lists,strings,files… 因为这些可迭代的对象你可以随意的读取所以非常方便易用, 但是你必须把它们的值放到内存里, 当它们有很多值时就会消耗太多的内存.
Generators(生成器)
生成器也是迭代器的一种, 但是你只能迭代它们一次. 原因很简单, 因为它们不是全部存在内存里, 它们只在要调用的时候在内存里生成:
生成器和迭代器的区别就是用 () 代替[], 还有你不能用 for i in mygenerator 第二次调用生成器: 首先计算 0, 然后会在内存里丢掉 0 去计算 1, 直到计算完 4.
yield
yield 的用法和关键字 return 差不多
要理解 Yield 你必须先理解当你调用函数的时候, 函数里的代码并没有运行. 函数仅仅返回生成器对象, 这就是它最微妙的地方。
然后呢, 每当 for 语句迭代生成器的时候你的代码才会运转.
现在, 到了最难的部分:
当 for 语句第一次调用函数里返回的生成器对象, 函数里的代码就开始运作, 直到碰到 yield, 然后会返回本次循环的第一个返回值. 所以下一次调用也将运行一次循环然后返回下一个值, 直到没有值可以返回. 一旦函数运行并没有碰到 yeild 语句就认为生成器已经为空了. 原因有可能是循环结束或者没有满足 if/else 之类的.
yield 简单说来就是一个生成器,生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用, 跳转至该函数上次 yield 返回值的位置接着往下执行,而上次调用的所有局部变量都保持不变。
理解迭代的内部机制
迭代是可迭代对象 (对应 iter() 方法)和迭代器 (对应 next() 方法)的一个过程.
可迭代对象就是任何你可以迭代的对象.
迭代器就是可以让你迭代可迭代对象的对象。
另外一种通俗的解释:
( 1)迭代器是一个更抽象的概念,对任何类如果它有 next
方法和 iter 方法返回自己本身 。对于 string、list、dict、tuple
等这类容器对象,使用 for 循环遍历是很方便的。在后台 for 语句对容器象调用 iter() 函数, iter() 是 python 的内置函数。
iter() 会返回一个定义 next() 方法的迭代器对象,它在容器中逐个访问容器内元素, next() 也是 python 的内置函数。在没有后续元素时,next() 会 抛出一个 StopIter 异常
( 2)生成器( Generator)是创建迭代器的简单而强大工具。它们写起来就像是正规的函数,只在需要返回数据时候使用 yield 语句。每次 next() 被调用,生成器会返回它脱离的位置,记忆语句最后一次执行和所有数据。
区别:生成器能做到迭代的所有事 , 而且因为自动创建了 iter() 和 next() 方法 , 生成器显得特别简洁 , 而且生成器也是高效的 ,使用生成器表 达式取代列表解析式可以同时节省 内存。除了创建和保存程序状态的自动方法, 当发生器终结时 , 还会自动抛出 StopIteration 异常。
用 * args 和 **kwargs 只是为了方便并没有强制使用它们.
当你不确定你的函数里将要传递多少参数时你可以用 * args. 例如, 它可以传递任意数量的参数:
相似的,**kwargs 允许你使用没有事先定义的参数名:
你也可以混着用. 命名参数首先获得参数值然后所有的其他参数都传递给 * args 和 **kwargs. 命名参数在列表的最前端. 例如:
def table_things(titlestring, **kwargs)
*args 和 **kwargs 可以同时在函数的定义中, 但是 * args 必须在 **kwargs 前面.
当调用函数时你也可以用和 * 语法.
就像你看到的一样, 它可以传递列表 (或者元组) 的每一项并把它们解包. 注意必须与它们在函数里的参数相吻合. 当然, 你也可以在函数定义或者函数调用时用 *.
集合、线性结构、树形结构、图状结构
集合结构: 除了同属于一种类型外,别无其它关系
线性结构: 元素之间存在一对一关系常见类型有: 数组, 链表, 队列, 栈, 它们之间在操作上有所区别. 例如: 链表可在任意位置插入或删除元素, 而队列在队尾插入元素, 队头删除元素, 栈只能在栈顶进行插入, 删除操作.
树形结构: 元素之间存在一对多关系, 常见类型有: 树 (有许多特例: 二叉树、平衡二叉树、查找树等)
图形结构: 元素之间存在多对多关系, 图形结构中每个结点的前驱结点数和后续结点多个数可以任意
①时间复杂度:同样的输入规模(问题规模)花费多少时间
②空间复杂度:同样的输入规模花费多少空间(主要是内存)
以上两点越小越好
③稳定性:不会因为输入的不同而导致不稳定的情况发生
④算法思路是否简单:越简单越容易实现越好
如何在 mysql 查找效率慢的 SQL 语句呢?这可能是困然很多人的一个问题,MySQL 通过慢查询日志定位那些执行效率较低的 SQL 语句,用–log-slow-queries[=file_name] 选项启动时,mysqld 会写一个包含所有执行时间超过 long_query_time 秒的 SQL 语句的日志文件,通过查看这个日志文件定位效率较低的 SQL 。
MySQL 数据库有几个配置选项可以帮助我们及时捕获低效 SQL 语句
1,slow_query_log
这个参数设置为 ON,可以捕获执行时间超过一定数值的 SQL 语句。
2,long_query_time
当 SQL 语句执行时间超过此数值时,就会被记录到日志中,建议设置为 1 或者更短。
3,slow_query_log_file
记录日志的文件名。
4,log_queries_not_using_indexes
这个参数设置为 ON,可以捕获到所有未使用索引的 SQL 语句,尽管这个 SQL 语句有可能执行得挺快。
^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$
^1[3|4|5|7|8][0-9]{9}$
- class Solution(object):
- def diameterOfBinaryTree(self, root):
- if not root:
- return 0
- max_dia_left = self.diameterOfBinaryTree(root.left)
- max_dia_right = self.diameterOfBinaryTree(root.right)
- # max: 1.当前结点最大距离;2.左、右子结点的最大距离
- max_dia = max(self.get_depth(root.left) + self.get_depth(root.right), max_dia_left, max_dia_right)
- return max_dia
- # 计算以当前结点为根时,树的最大深度;
- def get_depth(self, root):
- if not root:
- return 0
- else:
- return max(1 + self.get_depth(root.left), 1 + self.get_depth(root.right))
g = lambda x, y: x + y
print(g(1, 2))
Python 其实有 3 个方法, 即静态方法 (staticmethod), 类方法(classmethod) 和实例方法, 如下:
def foo(x):
print "executing foo(%s)"%(x)
class A(object):
def foo(self,x):
print "executing foo(%s,%s)"%(self,x)
- @classmethod
- def class_foo(cls,x):
- print "executing class_foo(%s,%s)"%(cls,x)
- @staticmethod
- def static_foo(x):
- print "executing static_foo(%s)"%x
a=A()
这个 self 和 cls 是对实例或者类的绑定,
对于一般的函数来说我们可以这么调用 foo(x), 这个函数就是最常用的, 它的工作跟任何东西 (类, 实例) 无关.
对于实例方法, 我们知道在类里每次定义方法的时候都需要绑定这个实例, 就是 foo(self, x), 为什么要这么做呢? 因为实例方法的调用离不开实例, 我们需要把实例自己传给函数, 调用的时候是这样的 a.foo(x)(其实是 foo(a, x)).
类方法一样, 只不过它传递的是类而不是实例, A.class_foo(x). 注意这里的 self 和 cls 可以替换别的参数, 但是 python 的约定是这俩, 还是不要改的好.
对于静态方法其实和普通的方法一样, 不需要对谁进行绑定, 唯一的区别是调用的时候需要使用 a.static_foo(x) 或者 A.static_foo(x) 来调用.
|\ | 实例方法 | 类方法 | 静态方法 |
|a = A() |a.foo(x) |a.class_foo(x) |a.static_foo(x)|
|A | 不可用 |A.class_foo(x) |A.static_foo(x)|
li = [x for x in range(100)]
print(li)
1、什么是全局解释器锁
在同一个进程中只要有一个线程获取了全局解释器(cpu)的使用权限,那么其他的线程就必须等待该线程的全局解释器(cpu)使用权消失后才能使用全局解释器(cpu), 即使多个线程直接不会相互影响,在同一个进程下也只有一个线程使用 cpu,这样的机制称为全局解释器锁(GIL)。
2、全局解释器锁的好处
1、避免了大量的加锁解锁的好处
2、使数据更加安全,解决多线程间的数据完整性和状态同步
3、全局解释器的缺点
多核处理器退化成单核处理器,只能并发不能并行。
4、如图所示
看图可知:同一时刻的某个进程下的某个线程只能被一个 cpu 所处理,所以在 GIL 锁下的线程只能被并发,不能被并行。
5,实例
- import time
- import threading
- num = 100
- li = []
- def sub():
- global num # 声明为全局变量
- num = num - 1
- time.sleep(2)
- for i in range(100): # 开100线程
- t = threading.Thread(target=sub, args=())
- t.start()
- li.append(t)
- # 给每个线程都加上join,只要子线程都运行完后主线程才能运行
- for i in li:
- print(i)
- i.join()
- print(num)
由打印结果可知:当第一个线程拿到 cpu 后,其他线程只能等待,2 秒后剩下的线程都并发执行
1、什么是互斥锁?
同一时刻的一个进程下的一个线程只能使用一个 cpu,要确保这个线程下的程序在一段时间内被 cpu 执行,那么就要用到互斥锁同步线程。
2、为什么用互斥锁锁?
因为有可能当一个线程在使用 cpu 时,该线程下的程序可能会遇到 io 操作,那么 cpu 就会切到别的线程上去,这样就有可能会影响到该程序结果的完整性。
3、怎么使用互斥锁?
只需要在对公共数据的操作前后加上,上锁和释放锁的操作即可。
- lock = threading.Lock()
- lock.acquire()
- dosomething……
- lock.release()
4,实例
- import time
- import threading
- num = 100
- li = []
- th = threading.Lock()
- def sub():
- global num
- th.acquire() # 加锁,相当于调用对象下的函数
- num1 = num
- time.sleep(1)
- num = num1 - 1
- th.release()
- time.sleep(2)
- for i in range(100): # 开100线程
- t = threading.Thread(target=sub, args=())
- t.start()
- li.append(t)
- # 给每个线程都加上join,只要子线程都运行完后主线程才能运行
- for i in li:
- print(i)
- i.join()
- print(num)
由打印结果看出:100 线程每隔 1 秒顺序的打印出来
5,额外知识:
1、GIL 的作用:多线程情况下必须存在资源的竞争,GIL 是为了保证在解释器级别的线程唯一使用共享资源(cpu)。
2、互斥锁的作用:为了保证解释器级别下的自己编写的程序唯一使用共享资源
1、什么是死锁?
指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象,如图所示。
- import threading
- import time
- class MyThread1(threading.Thread):
- def run(self):
- # 对mutexA上锁
- if mutexA.acquire():
- print(self.name + '----mutexA上锁----')
- time.sleep(1)
- if mutexB.acquire():
- print(self.name + '----mutexA中mutexB上锁----')
- mutexB.release()
- mutexA.release()
- class MyThread2(threading.Thread):
- def run(self):
- # 对mutexB上锁
- if mutexB.acquire():
- print(self.name + '----mutexB上锁----')
- time.sleep(1)
- if mutexA.acquire():
- print(self.name + '----mutexB中mutexA上锁----')
- mutexA.release()
- mutexB.release()
- mutexA = threading.Lock()
- mutexB = threading.Lock()
- if __name__ == '__main__':
- t1 = MyThread1()
- t2 = MyThread2()
- t1.start()
- t2.start()
- t1.join()
- t2.join()
- # 死锁的结果如下:
- # Thread-1----mutexA上锁----
- # Thread-2----mutexB上锁----
- # 我手写的:一直停留在这里不动
2、什么是递归锁?
在 Python 中为了支持同一个线程中多次请求同一资源,Python 提供了可重入锁。这个 RLock 内部维护着一个 Lock 和一个 counter 变量,counter 记录了 acquire 的次数,从而使得资源可以被多次 require。直到一个线程所有的 acquire 都被 release,其他的线程才能获
得资源。
- import threading
- import time
- class MyThread1(threading.Thread):
- def run(self):
- # 对mutexA上锁
- if mutex.acquire():
- print(self.name + '----mutexA上锁----')
- time.sleep(1)
- if mutex.acquire():
- print(self.name + '----mutexA中mutexB上锁----')
- mutex.release()
- mutex.release()
- class MyThread2(threading.Thread):
- def run(self):
- # 对mutexB上锁
- if mutex.acquire():
- print(self.name + '----mutexB上锁----')
- time.sleep(1)
- if mutex.acquire():
- print(self.name + '----mutexB中mutexA上锁----')
- mutex.release()
- mutex.release()
- mutex = threading.RLock()
- if __name__ == '__main__':
- t1 = MyThread1()
- t2 = MyThread2()
- t1.start()
- t2.start()
- t1.join()
- t2.join()
- # RLock递归锁的结果如下:
- # Thread-1----mutexA上锁----
- # Thread-1----mutexA中mutexB上锁----
- # Thread-2----mutexB上锁----
- # Thread-2----mutexB中mutexA上锁----
信号量:是指同时开几个线程并发。比如厕所有 5 个坑,那最多只允许 5 个人上厕所,后面的人只能等里面的 5 个人都出来了,才能再进去。
- import threading, time
- class myThread(threading.Thread):
- def run(self):
- # 加把锁,可以放进去多个(相当于5把锁,同时有5个线程)
- if semaphore.acquire():
- print(self.name)
- time.sleep(3)
- semaphore.release()
- if __name__ == "__main__":
- # 同时能有几个线程进去(设置为5就是一次5个线程进去)
- semaphore = threading.Semaphore(5)
- thread_list = [] # 空列表
- for i in range(200): # 200个线程
- thread_list.append(myThread()) # 加线程对象
- for t in thread_list:
- t.start() # 分别启动
孤儿进程指的是在其父进程执行完成或被终止 后仍继续运行的一类进程。
在类 UNIX 系统中,僵尸进程是指完成执行(通过 exit 系统调用,或运行时发生致命错误或收到终止信号所致)但在操作系统的进程表中仍然有一个表项(进程控制块 PCB),处于 "终止状态" 的进程。
守护进程(英语:daemon,英语发音:/ˈdiːmən / 或英语发音:/ˈdeɪmən/)是一種在后台执行的电脑程序。 此类程序会被以进程的形式初始化。 守护进程程序的名称通常以字母 "d" 结尾:例如,syslogd 就是指管理系统日志的守护进程。
更详细的解释如下:
1、一般情况下,子进程是由父进程创建,而子进程和父进程的退出是无顺序的,两者之间都不知道谁先退出。正常情况下父进程先结束会调用 wait 或者 waitpid 函数等待子进程完成再退出,而一旦父进程不等待直接退出,则剩下的子进程会被 init(pid=1) 进程接收,成会孤儿进程。(进程树中除了 init 都会有父进程)。
2、如果子进程先退出了,父进程还未结束并且没有调用 wait 或者 waitpid 函数获取子进程的状态信息,则子进程残留的状态信息( task_struct 结构和少量资源信息)会变成僵尸进程。
3、守护进程( daemon) 是指在后台运行,没有控制终端与之相连的进程。它独立于控制终端,通常周期性地执行某种任务 。 守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断 。
危害:
孤儿进程结束后会被 init 进程善后,并没有危害,而僵尸进程则会一直占着进程号,操作系统的进程数量有限则会受影响。
解决:
一般僵尸进程的产生都是因为父进程的原因,则可以通过 kill 父进程解决,这时候僵尸进程就变成了孤儿进程,被 init 进程接收
守护进程的编写:
在不同 Unix 环境下,守护进程的具体编程细节并不一致。但所幸的是,守护进程的编程原则其实都一样,区别仅在于具体的实现细节不同,这个原则就是要满足守护进程的特性。
简单一句话的意思就是:
编译型语言要先编译再运行,而解释性语言直接 "运行" 源代码。
编译型语言:在执行之前需要一个专门的编译过程,把程序编译成为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如 C、C++、Delphi 等
解释性语言:源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。比如 Python/JavaScript / Perl /Shell 等都是解释型语言。所以运行速度相对于编译型语言要慢
Python 解释器:运行 Python 的程序
Python 字节码:Python 程序编译后的所得到的底层形式,Python 自动将字节码保存为名为. pyc 的文件。
Python 没有 build 和 make 的步骤,代码写好后立即运行。
Python 是解释型语言,运行的时候将程序翻译成机器语言,所以运行速度相对于编译型语言要慢其速度介于编译语言和解释语言之间)
录入的源码转换为字节码 -> 字节码在 PVM(Python 虚拟机)中运行 -> 代码自动被编译
Accept
例子中的 Accept 字段是这样子的:Accept:text/html,a pplication/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8。意思是:浏览器支持的 MIME 类型分别是 text/html、application/xhtml+xml、application/xml 和 /,优先顺序是它们从左到右的排列顺序。
Accept 表示浏览器支持的 MIME 类型;
MIME 的英文全称是 Multipurpose Internet Mail Extensions(多功能 Internet 邮件扩充服务),它是一种多用途网际邮件扩充协议,在 1992 年最早应用于电子邮件系统,但后来也应用到浏览器。
text/html,application/xhtml+xml,application/xml 都是 MIME 类型,也可以称为媒体类型和内容类型,斜杠前面的是 type(类型),斜杠后面的是 subtype(子类型);type 指定大的范围,subtype 是 type 中范围更明确的类型,即大类中的小类。
Text:用于标准化地表示的文本信息,文本消息可以是多种字符集和或者多种格式的;
text/html 表示 html 文档;
Application:用于传输应用程序数据或者二进制数据;
application/xhtml+xml 表示 xhtml 文档;
application/xml 表示 xml 文档。
Cache-Control
Cache-Control 指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置 Cache-Control 并不会影响到另一个消息处理过程中的缓存处理过程。
请求时的缓存指令包括:no-cache, no-store, max-age, max-stale, min-fresh, only-if-cached。
响应消息中的指令包括:public, private, no-cache, no-store, no-transform, must-revalidate, proxy-revalidate, max-age。
各个指令的含义:
Public:指示响应可被任何缓存区缓存。
Private:指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当前用户的部分响应消息,此响应消息对于其他用户的请求无效。
no-cache:指示请求或响应消息不能缓存
no-store:用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
max-age:指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
min-fresh:指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
max-stale:指示客户机可以接收超出超时期间的响应消息。如果指定 max-stale 消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
User-Agent
User-Agent 的值是:用户使用的客户端的一些必要信息,比如操作系统、浏览器及版本、浏览器渲染引擎等。
Transfer-Encoding
transfer-encoding 的可选值有:chunked,identity,从字面意义可以理解,前者指把要发送传输的数据切割成一系列的块数据传输,后者指传输时不做任何处理,自身的本质数据形式传输。举个例子,如果我们要传输一本 "红楼梦" 小说到服务器,chunked 方式就会先把这本小说分成一章一章的,然后逐个章节上传,而 identity 方式则是从小说的第一个字按顺序传输到最后一个字结束。
(1)浏览器会缓存 DNS 一段时间,一般 2-30 分钟不等。如果有缓存,直接返回 IP,否则下一步。
(2)缓存中无法找到 IP,浏览器会进行一个系统调用,查询 hosts 文件。如果找到,直接返回 IP,否则下一步。(在计算机本地目录 etc 下有一个 hosts 文件,hosts 文件中保存有域名与 IP 的对应解析,通常也可以修改 hosts 科学上网或破解软件。)
(3)进行了(1)(2)本地查询无果,只能借助于网络。路由器一般都会有自己的 DNS 缓存,ISP 服务商 DNS 缓存,这时一般都能够得到相应的 IP。如果还是无果,只能借助于 DNS 递归解析了。
(4)这时,ISP 的 DNS 服务器就会开始从根域名服务器开始递归搜索,从. com 顶级域名服务器,到 baidu 的域名服务器。
到这里,浏览器就获得了 IP。在 DNS 解析过程中,常常会解析出不同的 IP。比如,电信的是一个 IP,网通的是另一个 IP。这是采取了智能 DNS 的结果, 降低运营商间访问延时,在多个运营商设置主机房,就近访问主机。电信用户返回电信主机 IP,网通用户返回网通主机 IP。当然,劫持 DNS,也可以屏蔽掉一 部分网点的访问,某防火长城也加入了这一特性。
浏览器利用 IP 直接与网站主机通信。浏览器发出 TCP(SYN 标志位为 1)连接请求,主机返回 TCP(SYN,ACK 标志位均为 1)应答报文,浏览器收到 应答报文发现 ACK 标志位为 1,表示连接请求确认。浏览器返回 TCP()确认报文,主机收到确认报文,三次握手,TCP 链接建立完成。
浏览器向主机发起一个 HTTP-GET 方法报文请求。请求中包含访问的 URL,也就是 http://www.baidu.com/ ,还有 User-Agent 用户浏览器操作系统信息,编码等。值得一提的是 Accep-Encoding 和 Cookies 项。Accept- Encoding 一般采用 gzip,压缩之后传输 html 文件。Cookies 如果是首次访问,会提示服务器建立用户缓存信息,如果不是,可以利用 Cookies 对应键值,找到相应缓存,缓存里面存放着用户名,密码和一些用户设置项。
返回状态码 200 OK,表示服务器可以相应请求,返回报文,由于在报头中 Content-type 为 "text/html",浏览器以 HTML 形式呈现,而不是下载文件。
但是,对于大型网站存在多个主机站点,往往不会直接返回请求页面,而是重定向。返回的状态码就不是 200 OK,而是 301,302 以 3 开头的重定向码,浏览器在获取了重定向响应后,在响应报文中 Location 项找到重定向地址,浏览器重新第一步访问即可。
补充一点的就是,重定向是为了负载均衡或者导入流量,提高 SEO 排名。利用一个前端服务器接受请求,然后负载到不同的主机上,可以大大提高站点的业务并发 处理能力;重定向也可将多个域名的访问,集中到一个站点;由于 baidu.com,www.baidu.com 会被搜索引擎认为是两个网站,照成每个的链 接数都会减少从而降低排名,永久重定向会将两个地址关联起来,搜索引擎会认为是同一个网站,从而提高排名。
client (浏览器)与 server 通过 http 协议通讯,http 协议属于应用层协议,http 基于 tcp 协议,所以 client 与 server 主要通过 socket 进行通讯;
而 tcp 属于传输层协议、如果走 https 还需要会话层 TLS、SSL 等协议;
传输层之下网络层,这里主要是路由协议 OSPF 等进行路由转发之类的。
再向下数据链路层主要是 ARP、RARP 协议完成 IP 和 Mac 地址互解析,再向下到最底层物理层基本就是 IEEE 802.X 等协议进行数据比特流转成高低电平的的一些定义等等;
当浏览器发出请求,首先进行数据封包,然后数据链路层解析 IP 与 mac 地址的映射,然后上层网路层进行路由查表路由,通过应用层 DNS 协议得到目标地址对应的 IP ,在这里进行 n 跳的路由寻路;而传输层 tcp 协议可以说下比较经典的三次握手、四次分手的过程和状态机,这里放个图可以作为参考:
数据交换主要通过 http 协议, http 协议是无状态协议,这里可以谈一谈 post、get 的区别以及 RESTFul 接口设计,然后可以讲服务器 server 模型 epoll、select 等,接着可以根据实际经验讲下 server 处理流程,
比如我: server 这边 Nginx 拿到请求,进行一些验证,比如黑名单拦截之类的,然后 Nginx 直接处理静态资源请求,其他请求 Nginx 转发给后端服务器,这里我用 uWSGI, 他们之间通过 uwsgi 协议通讯,uWSGI 拿到请求,可以进行一些逻辑, 验证黑名单、判断爬虫等,根据 wsgi 标准,把拿到的 environs 参数扔给 Django ,Django 根据 wsgi 标准接收请求和 env, 然后开始 start_response ,先跑 Django 相关后台逻辑,Django 拿到请求执行 request middleware 内的相关逻辑,然后路由到相应 view 执行逻辑,出错执行 exception middleware 相关逻辑,接着 response 前执行 response middleware 逻辑,最后通过 wsgi 标准构造 response, 拿到需要返回的东西,设置一些 headers,或者 cookies 之类的,最后 finish_response 返回,再通过 uWSGI 给 Nginx ,Nginx 返回给浏览器。
来源: http://blog.csdn.net/u014745194/article/details/78835270