· 最近开始用Python做一些工作,发现Python真的是非常友好的一门语言,虽然运行效率比不上C和C++这种语言,但是开发效率真实高的不行,代码量少,学习曲线平滑。虽然有这么多的优点,但是想玩得更溜还是不容易的,那就需要深入了解它语言特性背后的原因,所以看上了这本《深入理解Python特性》,准备近期把它啃完,并把学习笔记也分享出来,本文就是第一篇。
· 由于用的是python2的环境,所以有些python3特有的内容就会略过,请各位见谅。
· assert断言语句是一种调试工具,用来测试某个断言条件。如果为真就正常执行,如果为假则引发 AssertionError异常,并显示相关错误。
· 断言是为了告诉开发人员程序中发生了不可恢复的错误。对于可以预料的错误,用户可以予以纠正,这时不要使用断言。
· 断言用于程序内部自检,如声明了一些代码中不可能出现的条件。如果触发了条件,则说明程序存在BUG。
· 断言是一种调试辅助功能,不是用来处理运行时错误的机制。用断言的目的是让开发人员更快速的找到可能导致BUG的根本原因。
def apply_discount(product, discount):
price = int(product['price'] * (1.0 - discount))
assert 0 <= price <= product['price']
return price
本函数中的assert为了保证折扣永远不会低于0。
(1)断言语法
assert_stmt ::= "assert" expression1 ["," expression2]
if __debug__:
if not expression1: raise AssertionError(expression2)
也就是说
用法1:只有一个参数表达书
assert 0 <= price
用法2:增加了断言提示,可以同步显示
assert False, ("asert note")
(2)注意事项:
这个时候要用if来判断
永不失败的断言
非空元组总为真值。比如下面的的语句总是不会产生断言。
assert(1 == 2, 'This should fail')
比如有如下列表
names = ['Alice', 'Bob', 'Dilbert']
如果修改了一点点,通过git diff不太容易看出差异,
改变为
names = [
'Alice',
'Bob',
'Dilbert'
]
但是这样,如果少一个逗号,则不容易看出来,所以改成如下形式,都有逗号,非常整齐:
names = [
'Alice',
'Bob',
'Dilbert',
]
(1)基本特性
· with是一个非常有效的特性,有助于编写更为清晰易读的Python代码。
比如:
with open('hello.txt','w') as f:
f.write('hello,world!')
· 打开文件一版建议使用with,这样执行完with语句之后会自动关闭这个文件。
· 以上代码可以住转化为:
f = open('hello.txt','w')
try:
f.write('hello,world!')
finally:
f.close()
· 这里的try finally语句很重要,保证了当f.write失败时,也会将文件资源释放。
· 而with明显简化了代码,5行变2行。
· 许多资源都可以使用with方法,比如锁。
(2)自定义对象中支持with
class ManagedFile:
def __init__(self, name):
self.name = name;
def __enter__(self):
self.file = open(self.name, 'w');
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
with ManagedFile("hello.txt") as f:
f.write('hello,world!')
f.write('bye,now!')
· 当执行流程进入with语句上下文时,python会调用__enter__获取资源,当离开with上下文时,会调用__exit__释放资源。
· 还有一种基于contextlib模块的装饰器contextmanager可以简化实现。等后续看到装饰器这块内容再来补充。
(1)前置单下划线_var:命令约定,用来表示该名称仅在内部使用。
(2)后置单下划线var_:命名约定,用于避免与Python关键字发生命名冲突。
(3)前置双下划线__var:在类环境中使用时会触发名称改写,对Python解释器有特殊含义。
(4)前后双下划线__var__:表示由Python语言定义的特殊方法,自定义变量要避免使用。
(5)单下划线_:有时表示临时或者无意义的变量名称,表示不关心。
总结:1,2项可以自己定义使用,3和5可以在特殊场景使用,4不要使用。
(1)"旧式"字符串格式化(python2.7可用, 不推荐使用)
和C语言中printf用法相似:
errno = 987654321
name = 'Bob'
print 'errno=%d,name=%s' % (errno,name)
>>> errno=987654321,name=Bob
· 这种风格的好处就是可以进行自动格式的转换,比如%x用来做16进制显示。
(2)"新式"字符串格式化(python2.7可用)
· 新式格式使用{}来替代旧格式中的字符位置,打印和上个例子一样的做法
print 'errno={},name={}'.format(errno,name)
如果想转换成16进制,就使用:x
print 'errno={:x},name={}'.format(errno,name)
还可以使用别名
print 'errno={e:x},name={n}'.format(e=errno,n=name)
(3)字符串字面值插值(Python3.6+)
略
(4)模板字符串
此功能并不强大,但是有时候是你所需要的。
但是需要使用内置字符串模块中的Template类,变量则使用类似shell使用的$var方式。
from string import Template
t = Template('Hey,$name')
print t.substitute(name=name)
>>>Hey,Bob
但是模板字符串不能使用格式说明符,因此需要手动转换成16进制字符串:
templ_string = 'errno=$errno,name=$name'
print Template(templ_string).substitute(errno=hex(errno),name=name)
>>>errno=0x3ade68b1,name=Bob
其实模板字符串非常简单,用起来并不方便,但是因为简单,所以安全,可以用在处理用户提供的字符串。
攻击模式演示:
SECRET = 'this-is-a-secret'
class Error:
def __init__(self):
pass
err = Error()
user_input = '{error.__init__.__globals__[SECRET]}'
print user_input.format(error=err)
>>>this-is-a-secret
不明白的可以把这个值打印出来,__globals__这个里面是所有的符号表
print err.__init__.__globals__
也就是通过这种方式泄露了全局变量的值,及其危险,换成模板字符串之后,这字符串谁都不认得,所以安全!