目录
- 引论
- 将常量集中在一个文件里
- 编程惯用法
- 使用assert
- 数据交换不推荐用中间变量
- 充分利用lazy_evaluation的特性
- 少用eval()
- 使用enumerate()获取序列迭代的索引和值
- ==和is
- 基础语法
- 节制的使用from_import
- i+=1不等于i++
- 使用with自动关闭资源
- finally中的陷阱
- 使用join和format
- 可变对象和不可变对象
- xx解析式
- 默认参数
- 慎重的使用变长参数
- str()和repr()
- statimethod和classmethod的使用场景
- 库
- 字符串
- sort()和sorted()
- 使用copy模块深拷贝对象
- 使用Counter
- 多个库的介绍
引论
回到目录
常量收集
包内构建一个`constant.py`文件,专门存储包内变量
constant.py
class _const:
class ConstError(TypeError):
pass
class ConstCaseError(ConstError):
pass
def __setattr__(self, name, value):
# 确保没有该键
if name in self.__dict__.keys():
raise self.ConstError("Can not change const.{name}".format(name=name))
# 其他需求,比如要求值全小写
if not name.isupper():
raise self.ConstCaseError("const name '{name}' is not all uppercase".format(name=name))
# 赋值
self.__dict__[name] = value
import sys
sys.modules[__name__] = _const()
"""
使用sys.modules[name]可以获取一个模块对象,并可以通过该对象获取模块的属性,
这儿使用了sys.modules向系统字典中注入了一个_const对象
从而实现了在执行import本py文件时实际获取了一个_const实例的功能
"""
from moduleA import constant
# 设置常量
constant.MY_CONSTANT = 1
main.py
import sys
print(dir())
print(sys.modules.items())
from moduleA import constant
print(constant.MY_CONSTANT) # 可以打印1
if "moduleA.constant" in sys.modules.keys():
print(dir(constant))
print(sys.modules.items()) # 多了moduleA, moduleA.constant
回到目录
编程惯用法
回到目录
使用assert
x = 1
y = 2
assert x == y, "no equals"
# 等同于
if __debug__ and not x == y:
raise AssertionError("no equals")
- 使用:
- 在函数调用后,需要确认返回值是否合理可以使用
- 当条件是业务逻辑继续下去的先决条件时可以使用断言
回到目录
数据交换不推荐用中间变量
- python表达式的计算顺序是从左到右
但是表达式赋值时,表达式右边的操作数先与左边的操作数计算e1, e2 = e3, e4
的计算顺序是e3, e4
->e1, e2
- 顺序如下:
- 计算表达式右边
y
,x
,先在内存中创建元组(y, x)
,其标识符和值分别为y
,x
和对应的值
其中y
,x
是在初始化时已经存在于内存中的对象. - 计算表达式左边的值并赋值,元组被依次分配给左边的标识符
通过unpacking,元组第一标识符(y)分配给左边第一个元素(x); 元组第二标识符(x)分配给左边第第二个元素(y)
import dis
def swap1():
x = 2
y = 1
x, y = y, x
def swap2():
x = 2
y = 1
temp = x
x = y
y = temp
dis.dis(swap1)
print("*"*60)
dis.dis(swap2)
输出
16 0 LOAD_CONST 1 (2)
2 STORE_FAST 0 (x)
17 4 LOAD_CONST 2 (1)
6 STORE_FAST 1 (y)
18 8 LOAD_FAST 1 (y)
10 LOAD_FAST 0 (x)
12 ROT_TWO
14 STORE_FAST 0 (x)
16 STORE_FAST 1 (y)
18 LOAD_CONST 0 (None)
20 RETURN_VALUE
************************************************************
22 0 LOAD_CONST 1 (2)
2 STORE_FAST 0 (x)
23 4 LOAD_CONST 2 (1)
6 STORE_FAST 1 (y)
24 8 LOAD_FAST 0 (x)
10 STORE_FAST 2 (temp)
25 12 LOAD_FAST 1 (y)
14 STORE_FAST 0 (x)
26 16 LOAD_FAST 2 (temp)
18 STORE_FAST 1 (y)
20 LOAD_CONST 0 (None)
22 RETURN_VALUE
-
ROT_TWO
的主要作用是置换两个栈的栈顶元素
回到目录
充分利用lazy_evaluation的特性
if x and y
: 当x
为false时,y不计算;if x or y
: 当x为true时, y不计算。- 对于
and
条件表达式应该将值为true
可能性较高的放在and
后面
对于or
条件表达式应该将值为true
可能性较高的变量写在or
前面 - 另一个用途是生成器
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
from itertools import islice
print(list(islice(fib(), 5)))
回到目录
少用eval()
如题。
回到目录
使用enumerate()获取序列迭代的索引和值
仅当列表使用
l = [i for i in range(5)]
for i, e in enumerate(l):
print("index is {index}, element is {element}".format(index=i, element=e))
print("*"*60)
类似
def enumerate_1(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
for i, e in enumerate_1(l):
print("index is {index}, element is {element}".format(index=i, element=e))
回到目录
==和is
is
表示的是对象标识符(object identity)==
表示的是相同(equal)is
的作用是用来检查对象的标识符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间。==
是用来检验两个对象的值是否相等,实际调用了内部__eq__()
方法。a==b
相当于a.__eq__(b)
- 所以
==
可以重载 - 一般
is
为True
则==
为True
(NaN除外)
a = float("NaN")
print(a is a)
print(a == a)
- python中的string interning(字符串驻留)机制:
- 对于小的字符串,为了提高系统性能会保留其值的一个副本,当创建新的字符串的时候直接指向该副本即可。
- 对于长字符串并不会驻留。
回到目录
基础语法
回到目录
节制的使用from_import
结构
moduleA.py
a = 1
- 对于用户定义的模块,
import
机制会创一个新的module
将其加入当前的局部命名空间中,sys.modules
也会加入该模块的相关信息。但本质上引用同一个对象 - .pyc文件为解释器生成的模块相对应的字节码
from a import B
将B暴露于当前局部空间,将a加载到sys.modules
集合。- 当加载一个模块时,解释器完成以下动作
- 在sys.modules中进行搜索看看该模块是否已经存在,如果存在,则将其导入到当前局部命名空间,加载结束
- 如果在sys.modules中找不到对应模块的名称,则为需要导入的模块创建一个字段对象,并将该对象信息插入sys.modules中
- 加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译。
- 执行动态加载,在当前模块的命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中。
main.py
import sys
# from package.moduleA import a
# print(a)
#
# print(dir()) # 多了a
# print(sys.modules.items()) # 多了package package.moduleA
# from package import moduleA
# print(moduleA.a)
#
# print(dir()) # 多了a
# print(sys.modules.items()) # 多了package package.moduleA
import package.moduleA
print(package.moduleA.a)
print(dir()) # 多了package
print(sys.modules.items()) # 多了package package.moduleA
回到目录
i+=1不等于i++
会将++i
解释成+(+1)
同理将--i
解释为-(-i)
回到目录
使用with自动关闭资源
- 实现了__enter__(self)和__exit__(self, exception_type, exception_value, traceback),即实现了上下文协议
class MyContextManager:
def __enter__(self):
print("entering")
def __exit__(self, exception_type, exception_value, traceback):
print("leaving")
if exception_type is None:
print("no exception")
return False
elif exception_type is ValueError:
print("value error!")
return True
else:
print("other error!")
return True
- 测试一
with MyContextManager():
print("testing one")
"""
entering
testing one
leaving
no exception
"""
- 测试二
with MyContextManager():
print("testing two")
raise ValueError
entering
testing two
leaving
value error!
回到目录
finally中的陷阱
def finally_test():
print("starting")
while True:
try:
print("trying")
raise TypeError("随便抛一个异常")
except NameError as e:
print("NameError happended {}".format(e))
break
finally:
print("finally")
break # 这里中断了
finally_test()
打印
starting
trying
finally
在try
中发生了异常时,如果except
中没有对应的异常,则该异常会被临时保存
在finally
执行完毕时,临时保存的异常会被再次抛出
但是
如果在finally
中发生了异常或者break
或者return
,则该临时保存的异常会丢失,导致异常屏蔽
def return_test(value):
try:
if value == 1:
raise ValueError("居然是1!")
else:
return value
except ValueError as e:
print(e)
finally:
print("END!")
return -10086
print(return_test(1))
print(return_test(100))
居然是1!
END!
-10086
后面的-10086
是抛出ValueError
后进入finally
,然后return -10086
END!
-10086
else
要返回100,但是在返回100前会先执行finally
中的语句,但是此时finally
中返回了-10086
,就直接返回了,而抛弃了100
所以并不推荐在finally中使用return语句进行返回
回到目录
使用join和format
如题
回到目录
可变对象和不可变对象
class Student:
def __init__(self, name, course=[]): # 注意这个[]
self.__name = name
self.__course = course
def add_course(self, course_name):
self.__course.append(course_name)
def print_course(self):
print(self.__course)
jack = Student("jack")
jack.add_course("English")
jack.add_course("Chinese")
jack.print_course()
"""
['English', 'Chinese']
"""
lee = Student("lee")
lee.add_course("Math")
lee.print_course()
"""
['English', 'Chinese', 'Math']
"""
由于__init__(self, name, course=[])
中course
是默认参数,且默认参数在被调用的时候仅仅被评估一次。
因此实际上对象空间里面course
所指向的是list的地址,每次操作的实际上是list所指向的具体列表。
这是将可变对象作为参数默认参数的时候要特别警惕的事情,对可变对象的更改会直接影响原对象。
对该问题的解决是,传入None
作为默认参数。比如:
def __init__(self, name, course=None):
self.__name = name
if course is None:
course = []
self.__course = course
对于不可变对象
a = 1
print(id(a))
print(id(1))
a += 2
print(id(a))
print(id(3))
"""
1483696896
1483696896
1483696960
1483696960
"""
a
中存放的是数值在内存中的地址。数值本身才是不可变对象。
对a
的操作只是改变a
所指向的对象的地址a+=2
重新分配另一块内存地址存放结果,并将a
的引用改为该内存地址。
字符串不会改变,所以多个对象同时指向一个字符串对象的时候,对其中一个对象的操作不会影响另一个对象。
回到目录
xx解析式
列表解析式不建议使用在大数据上,容易MemoryError
# 多重嵌套
alist = [["one", "two"], ["three", "four"]]
alist = [[one.upper() for one in two]for two in alist]
print(alist)
# 支持多重迭代
# 表达式可以是简单,复杂,也可以是函数
def func(num):
return num % 2
alist = [func(num) for num in range(20)]
print(alist)
def foo(a):
"""
参数接收一个可迭代对象时,调用时可以使用元组的简写形式
"""
for i in a:
print(i)
foo((i for i in range(3)))
回到目录
默认参数
def
在python中是一个可执行的语句。
当解释器执行def
时,默认参数也会被计算,存在与 函数.__defaults__
属性中
由于python中函数参数传递的是对象,可变对象在调用者和被调用者之间共享。
函数被反复调用时,默认参数不会重新计算
def appendtest(newitem, lista=[]): # 默认参数在函数的__defaults__中
lista.append(newitem)
return lista
appendtest(1) # [1]
alist = appendtest(2) # [1,2]
print(alist)
print(appendtest.__defaults__) # [[1,2]]
appendtest.__defaults__[0][:] = []
print(appendtest.__defaults__) # [[], ]
appendtest(1) # [1]
alist = appendtest(2) # [1, 2]
print(alist)
实例2
import time
def report(when = time.time()):
print(when)
pass
def report2(when = time.time):
print(when())
pass
# 以下内容相同
report()
print(report.__defaults__)
time.sleep(1)
report()
print(report.__defaults__)
# 动态生成,所以内容不一致
report2()
print(report2.__defaults__)
time.sleep(1)
report2()
print(report2.__defaults__)
回到目录
慎重的使用变长参数
*args *kwargs
建议下列情况使用(不仅限于下列情况)
1. 添加装饰器
2. 参数数目不确定
3. 实现函数的多台或者继承情况下子类需要调用父类的某些方法时super(subClass, self).somefunc(*args, **kwargs)
回到目录
str()和repr()
- str()面对用户,目的是可读性,返回用户友好型和可读性较强的字符串类型。
repr()面对python解释器,目的是准确性,返回表示python解释器内部的含义,常作为debug用途 - 解释器中直接输入obj默认调用repr()函数, print(obj)则调用str()函数
- repr()的返回值一般可以用eval()函数来还原对象,一般有如下等式:
obj == eval(repr(obj)) # 不是绝对成立 - 分别调用内建的__str__()和__repr__().
一般类中要定义__repr__().
如果类中没有定义__str__(),则调用__repr__()
PS: 实现__repr__()时最好保证其返回值可以用eval()方法将对象重新还原
class A:
def __str__(self):
return "str"
def __repr__(self):
return "repr"
a = A()
print(a) # str
print(repr(a)) # repr
回到目录
staticmethod和classmethod的使用场景
class Fruit:
def __init__(self, num):
self.num = num
@staticmethod
def Init_Num(num):
num = num
fruit = Fruit(num) # 只会返回Fruit
return fruit
class Apple(Fruit):
pass
class Banana(Fruit):
pass
app1 = Apple(20)
ban1 = Banana.Init_Num(20) # 返回的是Fruit实例
print(isinstance(app1, Apple)) # True
print(isinstance(ban1, Banana)) # False, ban1是Fruit实例
class Fruit:
def __init__(self, num):
self.num = num
@classmethod
def Init_Num(cls, num):
num = num
fruit = cls(num) # 根据调用者的类型进行返回
return fruit
class Apple(Fruit):
pass
class Banana(Fruit):
pass
app1 = Apple(20)
ban1 = Banana.Init_Num(20) # 返回的是自身的实例
print(isinstance(app1, Apple)) # True
print(isinstance(ban1, Banana)) # True
静态方法在类中定义,可以有效的将代码组织起来
回到目录
库
回到目录
字符串
- 判定是否包含子串的判定不推荐调用
find()族
,index()族
前者找不到返回-1
, 后者找不到抛异常ValueError
- 推荐使用
in
和not in
s = "haha ha ni zai shuo shenme ne?"
if 'haha' in s:
print('haha is here')
if 'ha' in s:
print("ha is here")
回到目录
sort()和sorted()
-
sort()
一般作用于列表,元组不可。 -
sort()
是原址更改,返回值为None
sorted()
返回一个排序后的列表,原有列表不变 - 传入参数
key
比参数cmp
效率高。前者针对每个元素仅作一次处理,后者需要调用多次。
回到目录
使用copy模块深拷贝对象
浅拷贝, 拷贝时遇见引用也仅仅拷贝地址。会被其他引用对象影响
深拷贝,拷贝时遇见引用会递归拷贝,不会被其他引用对象影响。
import copy
class Student:
def __init__(self, name, course=None):
self._name = name
if course is None:
course = []
self._course = course
def add_course(self, item):
self._course.append(item)
def get_course(self):
return self._course
jack = Student("jack")
jack.add_course("math")
print(jack.get_course()) # ['math']
lee = copy.copy(jack) # 浅拷贝
print(lee.get_course()) # ['math']
lee.add_course("chinese")
print(lee.get_course()) # ['math', 'chinese']
print(jack.get_course()) # ['math', 'chinese']
print("*"*50)
# 深拷贝
jack = Student("jack")
jack.add_course("math")
print(jack.get_course()) # ['math']
lee = copy.deepcopy(jack) # 深拷贝
print(lee.get_course()) # ['math']
lee.add_course("chinese")
print(lee.get_course()) # ['math', 'chinese']
print(jack.get_course()) # ['math']
回到目录
使用Counter
from collections import Counter
三种初始化
a = "ashfkoahkjdg"
b = {'a': 1, "b": 2, 3: 3}
aa = Counter(a) # 可迭代参数(list、string)
bb = Counter(b) # 字典
cc = Counter(s=1, a=3, d=9) # 关键字参数
print(aa)
# Counter({'a': 2, 'h': 2, 'k': 2, 's': 1, 'f': 1, 'o': 1, 'j': 1, 'd': 1, 'g': 1})
print(bb)
# Counter({3: 3, 'b': 2, 'a': 1})
print(cc)
# Counter({'d': 9, 'a': 3, 's': 1})
Counter(obj).elements()
获取Counter中的key值
print(list(aa.elements())) # ['a', 'a', 's', 'h', 'h', 'f', 'k', 'k', 'o', 'j', 'd', 'g']
print(list(bb.elements())) # ['a', 'b', 'b', 3, 3, 3]
print(list(cc.elements())) # ['s', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']
# Counter(obj).most_common(n)
找出前n个频率最高的元素和对应的次数
print(aa.most_common(2)) # [('a', 2), ('h', 2)]
print(bb.most_common(2)) # [(3, 3), ('b', 2)]
访问不存在的key
时,返回0而不是异常
print(aa["sakjghfkagkjfa"]) # 0
# Counter(obj).update()
并没有替换而是添加进原有的计数器对象
aa.update(s=30)
print(aa)# Counter({'s': 31, 'a': 2, 'h': 2, 'k': 2, 'f': 1, 'o': 1, 'j': 1, 'd': 1, 'g': 1})
# Counter(obj).subtract()
对计数器对象中元素统计值相减
aa.subtract("ssss")
print(aa) # Counter({'s': 27, 'a': 2, 'h': 2, 'k': 2, 'f': 1, 'o': 1, 'j': 1, 'd': 1, 'g': 1})
回到目录
多个库的使用
- 序列化: 简单使用json 复杂使用cPickle
pickle的限制: 不能保证操作的原子侠
存在安全性问题。可以继承类pickle.Unpickler重写find_class()方法
不能兼容其他语言。
json在序列化datetime会抛出TypeError异常 需要对json的JSONEncoder进行扩展 - 获取栈信息
使用traceback获取栈信息 - 日志
使用logging
建议:
1. 为logging取一个名字而不是默认。比如使用 代码块2 中的代码为自己模块的logger命名,在main.py中获取本模块的logger
2. 命名规则: 以模块或者class命名。 遵循"."划分。 根是root logger, logger a.b的父logger对象是a
3. logging只是线程安全。不支持 多进程 写入同一个日志文件。对于多个进程需要配置不同的日志文件。
例子:
# moduleA.py
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__) # 新建一个命名logger
print(id(logger))
main.py
import moduleA
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(moduleA.__name__) # 调用其他模块的logger
print(id(logger))
logger = logging.getLogger(__name__) # 调用本模块的logger
print(id(logger))
输出
322481229720
322481229720
322481067904
回到目录
- 多线程
建议使用threading而不是_thread
原因:
1. threading基于_thread封装,除非特殊需要,不使用多线程底层支持模块的_thread
2. threading模块对同步原语的支持更加完善和丰富。
3. threading模块在主线程和子线程交互上更加友好。threading.join()
4. threading支持守护进程。 - 使用Queue使多线程编程更加安全
回到目录