共计 3851 个字符,预计需要花费 10 分钟才能阅读完成。
日常的写在前面
难得的周末,有大段的时间可以用来学习,体验就和工作日的晚上完全不一样了。好好的沉下心学习下~ 即刻很喜欢了!
好好学习的分割线
打打打鸡血!!!!!!
面向对象高级编程
前天的定制类的__call__通过小佳扬的一语惊醒梦中人,就是把对象函数化了。感觉有点囫囵吞枣,看完教程后要还好好地归纳下。
枚举类
定义常量时候使用,例如定义月份。
JAN = 1
FEB = 2
MAR = 3
…
NOV = 11
DEC = 12
但是此时的类型是 int。通过 Python 提供的 Enum 类来实现为枚举类型定义一个 class 类型。
from enum import Enum
Month = Enum(‘Month’, (‘Jan’, ‘Feb’, ‘Mar’, ‘Apr’, ‘May’, ‘Jun’, ‘Jul’, ‘Aug’, ‘Sep’, ‘Oct’, ‘Nov’, ‘Dec’))
这样就获得了 Month 类型的枚举类,可以使用 Month.Jan 来引用一个常量,或者枚举成员。
for name, member in Month.__members__.items():
print(name, ‘=>’, member, ‘,’, member.value)
value 属性是自动赋给成员的值,默认从 1 开始计数。
如果需要更精确地控制枚举类型,可以从 Enum 派生出自定义类。
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun 的 value 被设定为 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique 装饰器可以帮助我们检查保证没有重复值。
使用元类
type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。type() 函数可以查看类型或者变量的类型,class 的类型就是 type。而且 type() 函数既可以返回一个对象的类型,又可以创建出新的类型。
#用 type() 函数创造出 hello 类
def fn(self, name=’world’): # 先定义函数
print(‘Hello, %s.’ % name)
Hello = type(‘Hello’, (object,), dict(hello=fn))
用 type() 创建类,需要依次传入三个参数:
class 的名称;
继承的父类集合,注意 Python 支持多重继承,如果只有一个父类,别忘了 tuple 的单元素写法;
class 的方法名称与函数绑定,这里我们把函数 fn 绑定到方法名 hello 上
这样和直接定义 class 的写法没有差异,但是这样的话就可以在代码运行的过程中,动态创建类,这和静态语言有很大不同。
metaclass
除了使用 type() 动态创建类以外,要控制类的创建行为,还可以使用 metaclass。先定义 metaclass,就可以创建类,最后创建实例。据说很难理解的魔术代码,还是认真的努力理解下吧!看下大牛的代码!这个 metaclass 可以给我们自定义的 MyList 增加一个 add 方法:定义 ListMetaclass,按照默认习惯,metaclass 的类名总是以 Metaclass 结尾,以便清楚地表示这是一个 metaclass。
# metaclass 是类的模板,所以必须从 `type` 类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs[‘add’] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
有了 ListMetaclass,我们在定义类的时候还要指示使用 ListMetaclass 来定制类,传入关键字参数 metaclass:
class MyList(list, metaclass=ListMetaclass):
pass
这样 MyList 创建的时候,需要通过 ListMetaclass.__new__() 来创建。__new__() 方法接收到的参数依次是:
当前准备创建的类的对象;
类的名字;
类集成的父类集合;
类的方法集合。
而 MyList() 可以调用 add 方法,但普通 list() 就没有。list 即类的对象。这个复杂的有点变态的定义方式,在一些场景下,例如 ORM 的编写中,会很有用。
ORM 全称“Object Relational Mapping”,即对象 - 关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作 SQL 语句。
这块有点复杂,虽然看懂了,但是还要好好琢磨下
错误、调试和测试
错误处理
高级语言都内置了一套 try…except…finally… 的错误处理机制,Python 小可爱也有。
try
当有错误时候,会打断代码的进行,跳转到 except 处,一旦有 finally 的话就一定会执行, 无论有没有发生错误。可以有多个 except 获取不同的错误,但是注意父类和子类的问题,如果一旦包括了父类错误,子类所在的 except 就不会被执行。except 后也可以加 else 语句,当没有错误发生的时候,会执行 else 语句。
Python 所有的错误都是从 BaseException 类派生的,常见的错误类型和继承关系有这些:https://docs.python.org/3/lib…
也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写 try…except…finally 的麻烦。
调用栈
如果错误没有被捕获,它就会一直往上抛,最后被 Python 解释器捕获,打印一个错误信息,然后程序退出。所以找错误栈的时候,一定要找到准确的最里面那层。
错误记录
等 Python 解释器打印错误栈的信息,程序也结束了。既然可以捕获,就在捕获的同时打印错误信息并分析原因,让程序继续下去。Python 内置的 logging 可以很容易的记录错误信息。
# err_logging.py
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar(‘0’)
except Exception as e:
logging.exception(e)
main()
print(‘END’)
这样打印完错误信息后还会打印 END, 即运行了后续的代码。将 logging 通过配置记录到日志文件中方便后续的排查。
抛出错误
捕获的错误其实是错误 class 的一个实例,错误也是需要定以后才能抛出然后被捕获到的。如果要抛出错误,首先根据需要,可以定义一个错误的 class,选择好继承关系,然后,用 raise 语句抛出一个错误的实例.
# err_raise.py
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError(‘invalid value: %s’ % s)
return 10 / n
foo(‘0’)
另一种抛出错误的例子如下。
# err_reraise.py
def foo(s):
n = int(s)
if n==0:
raise ValueError(‘invalid value: %s’ % s)
return 10 / n
def bar():
try:
foo(‘0’)
except ValueError as e:
print(‘ValueError!’)
raise #错误又被抛出
bar()
这种方式也很常见,在抛出问题后继续抛回上一级,由顶曾调用者进行处理。raise 语句如果不带参数,就会把当前错误原样抛出。此外,在 except 中 raise 一个 Error,还可以把一种类型的错误转化成另一种类型。
调试
用 print() 打印有问题的变量麻烦在还得删掉或注释掉相应语句
断言
凡是可以用 print 打印的地方,都可以用 asset 断言来代替。
def foo(s):
n = int(s)
assert n != 0, ‘n is zero!’# 判断此处 n!= 0 是否为 True, 如果非则抛出 AssertionError
return 10 / n
def main():
foo(‘0’)
可以在启动 Python 解释器的时候关闭 assert
$python -0
logging
同样是替换 print,logging 不会抛出错误,而且可以输出到文件。
import logging
logging.basicConfig(level=logging.INFO)
s = ‘0’
n = int(s)
logging.info(‘n = %d’ % n)
print(10 / n)
这就是 logging 的好处,它允许你指定记录信息的级别,有 debug,info,warning,error 等几个级别,当我们指定 level=INFO 时,logging.debug 就不起作用了。同理,指定 level=WARNING 后,debug 和 info 就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。logging 的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如 console 和文件。
pdb
启动 Python 的调试器 pdb,让程序以单步方式运行。
pdb.set_trace()
这个方法也是用 pdb,但是不需要单步执行,我们只需要 import pdb,然后,在可能出错的地方放一个 pdb.set_trace(),就可以设置一个断点