随笔2

Posted yanhuazidi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随笔2相关的知识,希望对你有一定的参考价值。

 

判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

 


模块内的变量隐藏 加_ 类中的变量隐藏 加 __

在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,
特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,
但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器
对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:
>>> bart._Student__name
‘Bart Simpson‘
但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
最后注意下面的这种错误写法:
>>> bart = Student(‘Bart Simpson‘, 59)
>>> bart.get_name()
‘Bart Simpson‘
>>> bart.__name = ‘New Name‘ # 设置__name变量!
>>> bart.__name
‘New Name‘
表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!
内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。

 


模块标准开头
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
‘‘‘a test module‘‘‘
__author__ = ‘Michael Liao‘


Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:
>>> import sys
>>> sys.path
[‘‘, ‘/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip‘,
‘/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6‘, ...,
‘/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages‘]
如果我们要添加自己的搜索目录,有两种方法:

一是直接修改sys.path,添加要搜索的目录:

>>> import sys
>>> sys.path.append(‘/Users/michael/my_py_scripts‘)
这种方法是在运行时修改,运行结束后失效。

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


偏函数
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:

int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

>>> int(‘12345‘)
12345
但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

>>> int(‘12345‘, base=8)
5349
>>> int(‘12345‘, 16)
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):
return int(x, base)
这样,我们转换二进制就非常方便了:

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

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2(‘1000000‘)
64
>>> int2(‘1010101‘)
85
所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

>>> int2(‘1000000‘, base=10)
1000000
最后,创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数,当传入:

int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base,也就是:

int2(‘10010‘)
相当于:

kw = { ‘base‘: 2 }
int(‘10010‘, **kw)
当传入:

max2 = functools.partial(max, 10)
实际上会把10作为*args的一部分自动加到左边,也就是:

max2(5, 6, 7)
相当于:

args = (10, 5, 6, 7)
max(*args)
结果为10。

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

 


装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

>>> def now():
... print(‘2015-3-25‘)
...
>>> f = now
>>> f()
2015-3-25
函数对象有一个__name__属性,可以拿到函数的名字:
>>> now.__name__
‘now‘
>>> f.__name__
‘now‘
现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

def log(func):
def wrapper(*args, **kw):
print(‘call %s():‘ % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
print(‘2015-3-25‘)
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2015-3-25

把@log放到now()函数的定义处,等价于 now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

def log(text):
def decorator(func):
def wrapper(*args, **kw):
print(‘%s %s():‘ % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:

@log(‘execute‘)
def now():
print(‘2015-3-25‘)
执行结果如下:

>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的: now = log(‘execute‘)(now)

我们来剖析上面的语句,首先执行log(‘execute‘),返回的是decorator函数,再调用返回的函数,
参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,
它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的‘now‘变成了‘wrapper‘:

>>> now.__name__
‘wrapper‘
因为返回的那个wrapper()函数名字就是‘wrapper‘,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,
所以,一个完整的decorator的写法如下:

import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(‘call %s():‘ % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:

import functools

def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print(‘%s %s():‘ % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上
@functools.wraps(func)即可。

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

匿名函数
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数lambda x: x * x实际上就是:

def f(x):
return x * x
关键字lambda表示匿名函数,冒号前面的x表示函数参数。

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

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):
return lambda: x * x + y * y

 

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

我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:

def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
调用函数f时,才真正计算求和的结果:

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

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

>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False
f1()和f2()的调用结果互不影响。

闭包
注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:

>>> f1()
9
>>> f2()
9
>>> f3()
9
全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
再看看结果:

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

 

Python内置的sorted()函数就可以对list进行排序:
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs处理过的list:

list = [36, 5, -12, 9, -21]

keys = [36, 5, 12, 9, 21]
然后sorted()函数按照keys进行排序,并按照对应关系返回list相应的元素:

keys排序结果 => [5, 9, 12, 21, 36]
| | | | |
最终结果 => [5, 9, -12, -21, 36]
我们再看一个字符串排序的例子:

>>> sorted([‘bob‘, ‘about‘, ‘Zoo‘, ‘Credit‘])
[‘Credit‘, ‘Zoo‘, ‘about‘, ‘bob‘]
默认情况下,对字符串排序,是按照ASCII的大小比较的,由于‘Z‘ < ‘a‘,结果,大写字母Z会排在小写字母a的前面。

现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

这样,我们给sorted传入key函数,即可实现忽略大小写的排序:

>>> sorted([‘bob‘, ‘about‘, ‘Zoo‘, ‘Credit‘], key=str.lower)
[‘about‘, ‘bob‘, ‘Credit‘, ‘Zoo‘]
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True:

>>> sorted([‘bob‘, ‘about‘, ‘Zoo‘, ‘Credit‘], key=str.lower, reverse=True)
[‘Zoo‘, ‘Credit‘, ‘bob‘, ‘about‘]
从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

小结
sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。

 

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

例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

def is_odd(n):
return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]


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

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比方说对一个序列求和,就可以用reduce实现:

>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
当然求和运算可以直接用Python内建函数sum(),没必要动用reduce。

但是如果要把序列[1, 3, 5, 7, 9]变换成整数13579,reduce就可以派上用场:

>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我
们就可以写出把str转换为int的函数:

>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... digits = {‘0‘: 0, ‘1‘: 1, ‘2‘: 2, ‘3‘: 3, ‘4‘: 4, ‘5‘: 5, ‘6‘: 6, ‘7‘: 7, ‘8‘: 8, ‘9‘: 9}
... return digits[s]
...
>>> reduce(fn, map(char2num, ‘13579‘))
13579
整理成一个str2int的函数就是:

from functools import reduce

DIGITS = {‘0‘: 0, ‘1‘: 1, ‘2‘: 2, ‘3‘: 3, ‘4‘: 4, ‘5‘: 5, ‘6‘: 6, ‘7‘: 7, ‘8‘: 8, ‘9‘: 9}

def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
还可以用lambda函数进一步简化成:

from functools import reduce

DIGITS = {‘0‘: 0, ‘1‘: 1, ‘2‘: 2, ‘3‘: 3, ‘4‘: 4, ‘5‘: 5, ‘6‘: 6, ‘7‘: 7, ‘8‘: 8, ‘9‘: 9}

def char2num(s):
return DIGITS[s]

def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))

 

map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,
因此通过list()函数让它把整个序列都计算出来并返回一个list。
list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘]

 

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等;

一类是generator,包括生成器和带yield的generator function。

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

可以使用isinstance()判断一个对象是否是Iterable对象:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance(‘abc‘, Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,
直到最后抛出StopIteration错误表示无法继续返回下一个值了。

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

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

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance(‘abc‘, Iterator)
False
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter(‘abc‘), Iterator)
True

Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,
直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知
道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在
需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。
而使用list是永远不可能存储全体自然数的。

小结
Iterable 可迭代对象有 __iter__ 方法
Iterator 迭代器对象有 __iter__ 和 __next__ 方法

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
pass
实际上完全等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break


生成器:generator
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

定义generator的另一种方法。如果一个函数定义中包含yield关键字,
那么这个函数就不再是一个普通函数,而是一个generator:
generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。
而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次
返回的yield语句处继续执行。generator,在执行过程中,遇到yield就中断,下次又继续执行。

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,
没有更多的元素时,抛出StopIteration的错误。
当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。
对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,
for循环随之结束。
请注意区分普通函数和generator函数,普通函数调用直接返回结果:
>>> r = abs(6)
>>> r
6
generator函数的“调用”实际返回一个generator对象:
>>> g = fib(6)
>>> g
<generator object fib at 0x1022ef948>

 

列出当前目录下的所有文件和目录名,可以通过一行代码实现:

import os # 导入os模块,模块的概念后面讲到,os.listdir可以列出文件和目录
[d for d in os.listdir(‘.‘)] # 列出当前工作目录下的所有文件和目录
[‘.emacs.d‘, ‘.ssh‘, ‘.Trash‘, ‘Adlm‘, ‘Applications‘, ‘Desktop‘, ‘Documents‘, ‘Downloads‘,
‘Library‘, ‘Movies‘, ‘Music‘, ‘Pictures‘, ‘Public‘, ‘VirtualBox VMs‘, ‘Workspace‘, ‘XCode‘]


如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
>>> from collections import Iterable
>>> isinstance(‘abc‘, Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

使用__slots__
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

>>> class Student(object):
... pass
...
然后,尝试给实例绑定一个属性:

>>> s = Student()
>>> s.name = ‘Michael‘ # 动态给实例绑定一个属性
>>> print s.name
Michael
还可以尝试给实例绑定一个方法:

>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s, Student) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
但是,给一个实例绑定的方法,对另一个实例是不起作用的:

>>> s2 = Student() # 创建新的实例
>>> s2.set_age(25) # 尝试调用方法
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ‘Student‘ object has no attribute ‘set_age‘
为了给所有实例都绑定方法,可以给class绑定方法:

>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = MethodType(set_score, None, Student)
给class绑定方法后,所有实例均可调用:

>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99
通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

使用__slots__
但是,如果我们想要限制class的属性怎么办?比如,只允许对Student实例添加name和age属性。

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

>>> class Student(object):
... __slots__ = (‘name‘, ‘age‘) # 用tuple定义允许绑定的属性名称
...
然后,我们试试:

>>> s = Student() # 创建新的实例
>>> s.name = ‘Michael‘ # 绑定属性‘name‘
>>> s.age = 25 # 绑定属性‘age‘
>>> s.score = 99 # 绑定属性‘score‘
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ‘Student‘ object has no attribute ‘score‘
由于‘score‘没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

使用__slots__要注意,__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的:

>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
除非在子类中也定义__slots__,这样,子类允许定义的属性就是自身的__slots__加上父类的__slots__
,子类中定义__slots__,父类不定义,则定义无效

 

__call__
在Python中,函数其实是一个对象:

>>> f = abs
>>> f.__name__
‘abs‘
>>> f(-123)
123
由于 f 可以被调用,所以,f 被称为可调用对象。

所有的函数都是可调用对象。

一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()。

我们把 Person 类变成一个可调用对象:

class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender

def __call__(self, friend):
print ‘My name is %s...‘ % self.name
print ‘My friend is %s...‘ % friend
现在可以对 Person 实例直接调用:

>>> p = Person(‘Bob‘, ‘male‘)
>>> p(‘Tim‘)
My name is Bob...
My friend is Tim...
单看 p(‘Tim‘) 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

 

零宽断言:
零宽断言是正则表达式的一种方法,用于查找在某些内容(但并不包括这些内容)之前或者之后的东西,也就是说他们像(匹配一个单词边界,也就是单词和空格间的位置,正则表达式的匹配有两种概念,一种是匹配字符,一种是匹配位置,这里的就是匹配位置,例如,“er”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”),^(匹配输入字行首),$(匹配输入字行尾)那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。断言用来声明一个应该为真的事实,正则表达式中只有当断言为真时才会继续进行匹配。
零宽断言一共分为四种
1.零宽度正预测先行断言 (?=exp)匹配exp前面的位置
例:w+(?=ing),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I’m singing while you’re dancing.时,它会匹配sing和danc。
2.零宽度正回顾后发断言 (?<exp)匹配exp后面的位置
例:比如(?<=re)w+会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
3.零宽度负预测先行断言 (?!exp)匹配后面跟的不是exp的位置
例:例如:d{3}(?!d)匹配三位数字,而且这三位数字的后面不能是数字;((?!abc)w)+匹配不包含连续字符串abc的单词。
4.零宽度负回顾后发断言 (?<!exp)匹配前面不是exp的位置
例:(?<![a-z])d{7}匹配前面不是小写字母的七位数字。


s表示,只要出现空白就匹配,包括空格、换行、tab缩进等所有的空白;
S表示,非空白就匹配;
这样一正一反下来,就表示所有的字符,完全的,一字不漏的。
另外,[]这个符号,表示在它里面包含的单个字符不限顺序的出现,比如下面的正则:
[ace] 这表示,只要出现a/c/e这三个任意的字母,都会被匹配。
它们的组合,表示所有的都匹配,与它相对应的,有[wW]等,意义完全相同、
另外要说的一点是,为什么有".“这个通配符了,还要这样的用法。
其实,[sS] [wW]这样的用法,比较”.“所匹配的还要多,因为”."是不会匹配换行的,
所有出现有换行匹配的时候,人们就习惯 使用[sS]或者[wW]这样的完全通配模式。

若在正则表达式模式或模式的一部分两侧加上括号,就会导致表达式的一部分被存储到临时缓冲区中。
每个捕获的子匹配项按照它们在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。可以使用 来访问每个缓冲区,其中n是标识特定缓冲区的一位或两位十进制数字。
比如(?<=<(w+)>).*(?=</1>)匹配不包含属性的简单html标签内里的内容。
1则是一个反向引用,引用的正是捕获的第一组,前面的(w+)匹配的内容,这样如果前缀实际上是的话,后缀就是了

 

mysql多级排序:
select * from users order by age desc,id asc;
先通过 age 排序,age相同的再通过 id 排序

 

匹配 . * ? ^ $ 等元字符需要用转义或 []括起来
需要使用.来匹配一个小数点
当然如果是在[.]里面的话 是不需要加的

空字符可以用s表示 如 : ([.]|[*]|s|[?])

 

模块的 __file__ 保存的是 运行模块的绝对路径,包含文件名

 


<form action="aaaaaaaa" method="GET">
<input type="text" name="mizi">
<input type="radio" name="sex" value="男"><label>男</label>
<input type="radio" name="sex" value="女"><label>女</label>
<input type="checkbox" name="aihao" value="read"><label>read</label>
<input type="checkbox" name="aihao" value="write"><label>write</label>
<input type="checkbox" name="aihao" value="show"><label>show</label>
<input type="submit" value=‘提交‘>
</form>
GET form >> http://176.17.8.32:8888/[action]aaaaaaaa?mizi=sfd&sex=男&aihao=read&aihao=write
POST form >>{‘METHOD‘: ‘POST‘, ‘PATH_INFO‘: [action]‘/user_denglu/‘}
...q=0.8 [form]tell=15058870932&password=0000000000

<button onclick="toggle()">点击</button>
元素内联绑定的事件函数,必须是全局函数

python 内置文件传输服务器
cd到文件目录下
python3 -m http.server
只需要在浏览器上链接IP port 即可显示目录下载,port 默认8000。

在面对对象语言中 成员访问符 . 使用时,有属性访问属性,没属性,添加属性
python javascript

python 是自上而下执行的,后执行会改变前面.

文件描述符:系统中每一个IO操作系统都会分配一个整数作为编号
该整数即为这个IO操作的文件描述符文件描述符是系统识别IO的标志
f.fileno()
sockfd.fileno()

正则表达式的“匹配”有两种概念:一种是匹配字符,一种是匹配位置
^ $  B就是匹配位置的

. [] ^ $ 四个字符是所有语言都支持的正则表达式,所以这四个是基础的正则表达式。

正则等价的概念
等价是等同于的意思,表示同样的功能,用不同符号来书写。
?,*,+,d,w 都是等价字符
  ?等价于匹配长度{0,1}
  *等价于匹配长度{0,}
  +等价于匹配长度{1,}
  d等价于[0-9]
D等价于[^0-9]
  w等价于[A-Za-z_0-9]
W等价于[^A-Za-z_0-9]

常用运算符与表达式:
  ^ 开始
  () 域段
  [] 包含,默认是一个字符长度
  [^] 不包含,默认是一个字符长度
  {n,m} 匹配长度
  . 任何单个字符(. 字符点)
  | 或
   转义
  $ 结尾
  [A-Z] 26个大写字母
  [a-z] 26个小写字母
  [0-9] 0至9数字
[A-Za-z0-9] 26个大写字母、26个小写字母和0至9数字

分割语法:
  [A,H,T,W] 包含A或H或T或W字母
  [a,h,t,w] 包含a或h或t或w字母
  [0,3,6,8] 包含0或3或6或8数字

语法与释义:
  基础语法 "^([]{})([]{})([]{})$"
  正则字符串 = "开始([包含内容]{长度})([包含内容]{长度})([包含内容]{长度})结束"
  
? ,* , +, d, w 这些都是简写的,完全可以用[]和{}代替,
在(?:)(?=)(?!)(?<=)(?<!)(?i)(*?)(+?)这种特殊组合情况下除外。
  可以忽略?,*,+,d,w一些简写标示符,学会了基础使用再按表自己去等价替换

实例:
  字符串;tel:086-0666-88810009999
  原始正则:"^tel:[0-9]{1,3}-[0][0-9]{2,3}-[0-9]{8,11}$"
  速记理解:开始 "tel:普通文本"[0-9数字]{1至3位}"-普通文本"[0数字][0-9数字]{2至3位}"-普通文本"[0-9数字]{8至11位} 结束"
  等价简写后正则写法:"^tel:d{1,3}-[0]d{2,3}-d{8,11}$" ,简写语法不是所有语言都支持。

正则表达式应用——数字替换
希望把
asdadas123asdasdas456asdasdasd789asdasd
替换为:
asdadas[123]asdasdas[456]asdasdasd[789]asdasd
查找([0-9]+) 替换:[1]

IS-A关系,也即继承关系
HAS-A关系,即非继承关系

对于判断可以先设一个BOOL旗杆,根据返回真假处理数据。

注意全局变量与局部变量,特别是函数内调用其他函数。

可变容器: 列表,集合,字典

>>> a = [‘Mary‘, ‘had‘, ‘a‘, ‘little‘, ‘lamb‘]
>>> for i in range(len(a)):
... print(i, a[i])
0 Mary
1 had
2 a
3 little
4 lamb

我们已经见过的 for 语句就是这样一个迭代器。list() 函数是另外一个( 迭代器 ),它从可迭代(对象)中创建列表:

函数 调用 会为函数局部变量生成一个新的符号表。确切的说,所有函数中的变量赋值都是将值存储在局部符号表。
变量引用首先在局部符号表中查找,然后是包含函数的局部符号表,然后是全局符号表,最后是内置名字表。
因此,全局变量不能在函数中直接赋值(除非用 global 语句命名),尽管他们可以被引用。

方法是一个“属于”某个对象的函数

变量 sys.ps1 和 sys.ps2 定义了主提示符和辅助提示符字符串:
>>> sys.ps2
‘... ‘
>>> sys.ps1 = ‘C> ‘
C> print(‘Yuck!‘)


对于 if - eilf -else 语句,把大概率发生情况优先执行可提高
   代码运行速度。

表达式 if 真值表达式 else 表达式   可以多重嵌套,完成多重判断

循环结合输入使用,不加 break 语句。
x=int(input())
while x!=o:
x=int(input())

for循环为可迭代对象绑定循环,可迭代对象是控制循环的变量,
可迭代对象最好不要放入循环里,会改变循环甚至报错。
for _ in range(n):
n+=1

for循环是while 的一种特殊形式,for 循环都可以写成while循环
L=[1,2,3,4,5]
it=iter(L)
while 1:
try:
x=next(it)
print(x)
except StopIteration:
break

递归函数也是一种循环,也可以写成while循环
牛顿法求平方根,用循环代替递归
def squareroot(n):
root = n/2 #initial guess will be 1/2 of n
for k in range(20):
root = (1/2)*(root + (n / root))
return root
squareroot(9)
3.0
squareroot(4563)
67.549981495186216

文件打开后只能读一次,第二次为空,f已指向文件尾,不能再向后了. f为文件读写指针,会记录读写位置。

f=open("/home/tarena/test/aaa.mp3")
f1=f.read()
print(f1)
for x in f:
print(x,end=‘‘)
f.close()

文件以写模式打开 w | a | x ,当没找到文件时都会创建文件。
当有文件时:w 清空文件
a 追加结尾
x 报错
r + 为读写文件 必须已有文件,且是清空原文件后写入

使用 with 语句打开文件,不需要file.close() with自带关闭文件功能
使用 with 打开文件时,open返回的文件对象只能在with代码块内使用,出代码块就关闭文件了。

把输出定向到文件
with open(‘we.txt‘,‘a‘) as f:
print(data,file=f)

如果到了文件末尾,f.read() 会返回一个空字符串(‘‘)

关键字传参和关键字形参不是一个意思,
位置形参 可以位置传参和关键字传参,
关键字形参必须是关键字传参

quit(str) 与 exit(str) 可以在脚本中运行

r‘‘ 对出现在行尾的  无效,  只能用 \ 打印出来 

a,b,c = 1 # typeErroy
a=b=c = 1 Ok

对任意地方的字符串都可以使用格式化字符串方法,如 input()中

使用  if x in []: 可以很方便解决区间判断问题,
finally:
if ‘file‘ in locals():
file.close()

要注意方法是否有返回 return
a = ‘s‘
b = set()
b = b.add(a)
print(b) # None
方法add()不返回,b=b.add(a) 赋值为空.

赋值语句:当右边为值时创建对象并赋值给变量,
当右边为变量时赋值变量的绑定关系。

获取命令行参数
import sys
sys.argv 属性获取命令行内容 以字符串形式收集为一个列表

>>> itemdict = {"item":"banana","cost":24}
>>> print("The %(item)s costs %(cost)7.1f cents"%itemdict

format()
功能:通过参数合成字符串
“{}dfsdfsd{}".format(‘aaa‘,‘bbb‘) 按位置传参
"{1}sfafsf{0}".format(‘aaa‘,‘bbb‘) 按花括号的序号传参
"{a}dfdgef{b}".format(a = ‘aaa‘,b = ‘bbb‘) 按关键字传参
print(‘{0:2d} {1:3d} {2:4d}‘.format(x, x*x, x*x*x)) 指定宽度


1.在python中,空白字符泛指任何非打印字符,如空格、制表符、换行符等

2.S.lstrip() S.strip() S.rstrip() 剔除空白字符,在文件读写时常用。

3. L.pop(index) 删除列表中的元素并返回值,pop方法共性
del 变量[index] 语句 不返回

4. L.insert(index,obj) 插入元素

5. L.remove(obj) 值删除, 可先把值赋值给变量。

6. L.sort(reverse=False/True) 原地改变列表对象的排序,不可恢复,不能对不可变对象操作
sorted(iterable,key=None,reverse=False/True) 生成排序后的新列表对象,任何可迭代对象可用。
L.reverse() 反转列表对象元素。

7. L[-3:] 倒数三个元素
L[:] 复制列表(左边为赋值,右边为创建新对象) 不可变对象切片是会创建新的对象的


8. S.isdigit() 判断是否为字符数字,包括字节串(b‘123‘)
S.isdecimal() 判断是否为十进制字符数字,不包括字节串
S.numeric() 判断是否为数字字符,包含汉语数字(三,二),罗马数字(Ⅴ,Ⅸ)

10. while 语句是非常灵活的。
while [element]:列表循环 while x in [element]: 列去重
[].pop [].remove(x)


11.函数名要全小写,
模块名和实例名要小写,并用下划线分隔单词
类名要单词首字母大写不使用下划线.遵守驼峰命名规则.

12.子类调用父类时,父类必须在当前文件中,并在子类前面.
子类享有父类所有属性和行为.

13.在子类方法中调用父类的方法: super().方法名(参数)

14.当父类的方法不符合子类的行为时,可对其重写(创建同名方法,进行覆盖).
继承共享精华,重写剔除糟粕.

15.类与类之间用两个空行分隔,方法与方法之间用一个空行分隔,
导入模块时先导入标准库的,隔一空行再导入第三方的模块,区分来自何方。

16.使用异常处理可以避免代码信息暴露,并自由决定让用户知道多少信息。

17.为自己的代码写测试是一个优秀程序员的基本素质。
测试进程根据项目的进程发展,先为重要动作写测试用例,再写为全覆盖测试用例。
以一个函数和类为单位写测试,对每一种情况写单元测试。


sys.exc_info()[0])

这个函数返回一个包含三个值的元组,该元组提供有关当前正在处理的异常的信息。返回的信息既针对当前线程也针对当前堆栈帧。如果当前堆栈帧没有处理异常,则从调用堆栈帧或其调用者那里获取信息,直到找到处理异常的堆栈帧为止。这里,“处理异常”被定义为“执行或执行了except子句”。对于任何堆栈帧,只能访问有关最近处理的异常的信息。
如果堆栈中的任何位置没有异常处理,则返回包含三个None值的元组。否则,返回的值是(type,value,traceback)。它们的含义是:type获取正在处理的异常的异常类型(一个类对象);值获取异常参数(与其关联的值或第二个引发的参数,如果异常类型是类对象,它总是一个类实例);回溯得到一个回溯对象(见参考手册),它在最初发生异常的地方封装了调用堆栈。
如果调用exc_clear(),则此函数将返回三个None值,直到在当前线程中引发另一个异常,或者执行堆栈返回到正在处理另一个异常的帧。
警告将回溯返回值分配给处理异常的函数中的局部变量将导致循环引用。这将防止在同一个函数中由局部变量引用的任何东西,或者通过垃圾收集回溯。由于大多数函数不需要访问回溯,所以最好的解决方案是使用像exctype,value = sys.exc_info()[:2]之类的东西来仅提取异常类型和值。如果您确实需要回溯,请确保在使用后删除它(最好使用try ... finally语句完成)或在不处理异常的函数中调用exc_info()。
注意从Python 2.2开始,当启用垃圾回收并且它们变得无法访问时,会自动回收这些周期,但避免创建周期仍然更有效。

Process进程
1、Process对象可以创建进程,但Process对象本身不是进程。
2、主进程执行完毕后会默认等待子进程结束后回收资源,不需要手动回收资源;
join()函数来控制父子进程的结束顺序,待子进程结束后清理子进程所有信息并回收资源。
3、采用Process对象创建进程后,子进程会将主进程的Process对象完全复制一份,
这样父子进程各有一个Process对象,p.start()启动子进程,主进程中的Process对象作为静态对象存在,不执行。
4、当子进程执行完毕后,产生的“僵尸进程”会被join()函数回收;或者再有另一条进程开启,
start()函数也会回收“僵尸进程”,所以不一定需要写join函数。
5、window系统平台中的子进程结束后会立即自动清除子进程的Process对象,而linux系统平台中的
子进程Process对象结束后,如果没有join()函数或start()函数时则会在主进程结束后统一清除。


Python标准异常总结

AssertionError 断言语句(assert)失败
AttributeError 尝试访问未知的对象属性
EOFError 用户输入文件末尾标志EOF(Ctrl+d)
FloatingPointError 浮点计算错误
GeneratorExit generator.close()方法被调用的时候
ImportError 导入模块失败的时候
IndexError 索引超出序列的范围
KeyError 字典中查找一个不存在的关键字
KeyboardInterrupt 用户输入中断键(Ctrl+c)
MemoryError 内存溢出(可通过删除对象释放内存)
NameError 尝试访问一个不存在的变量
NotImplementedError 尚未实现的方法
OSError 操作系统产生的异常(例如打开一个不存在的文件)
OverflowError 数值运算超出最大限制
ReferenceError 弱引用(weak reference)试图访问一个已经被垃圾回收机制回收了的对象
RuntimeError 一般的运行时错误
StopIteration 迭代器没有更多的值
SyntaxError Python的语法错误
IndentationError 缩进错误
TabError Tab和空格混合使用
SystemError Python编译器系统错误
SystemExit Python编译器进程被关闭
TypeError 不同类型间的无效操作
UnboundLocalError 访问一个未初始化的本地变量(NameError的子类)
UnicodeError Unicode相关的错误(ValueError的子类)
UnicodeEncodeError Unicode编码时的错误(UnicodeError的子类)
UnicodeDecodeError Unicode解码时的错误(UnicodeError的子类)
UnicodeTranslateError Unicode转换时的错误(UnicodeError的子类)
ValueError 传入无效的参数
ZeroDivisionError 除数为零


以下是 Python 内置异常类的层次结构:

BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
      +-- StopIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning
---------------------




























































































































































































































































































































































































































































































































































































































以上是关于随笔2的主要内容,如果未能解决你的问题,请参考以下文章

Beta冲刺-用心聚落步

随笔2

随随笔随笔新新

随笔标题

随笔测试

笔新新随随笔 新随