枚举、元类和错误调试

2018-11-17  本文已影响5人  TAsama

枚举

在各种编程语言中常用的枚举类型在python中当然不例外的也是有的,创建一个枚举非常简单,如下:

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

创建了一个枚举类'Month',包含('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')十二个枚举
调用也很简单

Month.Jan

枚举数值是默认从0开始的,如果想要自定义枚举的数值可以用下面这种方式创建枚举类:

@unique # 装饰器用以检测没有重复值
class Gender(Enum):
    Male = 1000
    Female = 1001

创建了一个性别枚举

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

# 测试:
bart = Student('Bart', Gender.Male)
if bart.gender == Gender.Male:
    print('测试通过!')
else:
    print('测试失败!')

type函数的新用法

前面讲到过,type函数可以用来判断对象的类型,这次介绍type的新用法
type()函数既可以返回一个对象的类型,又可以创建出新的类型

def fn(self, name='world'): # 先定义函数
    print('Hello, %s.' % name)

Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
  1. class的名称;
  2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
  4. 通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

创建好一个新的类以后,我们就可以正常的调用他的方法和属性等:

h = Hello()
h.hello()

这是一种动态创建类的方法,可以灵活使用。

元类

类似OC中的基类去理解
创建一个元类:

class ListMetaclass(type):
      
      def __new__(cls, name, base, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, base, attrs)
  1. 当前准备创建的类的对象;
  2. 类的名字;
  3. 类继承的父类集合;
  4. 类的方法集合。
class MyList(list, metaclass=ListMetaclass):
      pass

L = MyList()
L.add(1)
print(L)

这样我们给Mylist添加了一个add方法,实际很难用到这种做法。
当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

错误调试、测试

高级语言通常都内置了一套try...except...finally...的错误处理机制,Python也不例外。

try:
    print('try...')
    r = 10 / 0
    print('result:', r)
except ZeroDivisionError as e:
    print('except:', e)
finally:
    print('finally...')
print('END')

错误应该有很多种类,如果发生了不同类型的错误,应该由不同的except语句块处理。

try:
    print('try...')
    r = 10 / int('a')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
finally:
    print('finally...')
print('END')

所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”
出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。
记录错误

Python内置的logging模块可以非常容易地记录错误信息:

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')

只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型

class FooError(ValueError):
      pass
def foo(s):
    n = int(s)
    if n == 0:
        raise FooError('invalid value: %s' % s)
    return 10 / n
foo('0')

raise语句如果不带参数,就会把当前错误原样抛出。此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型:

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

练习
运行下面的代码,根据异常信息进行分析,定位出错误源头,并修复:

# from functools import reduce

def str2num(s):
    try:
        return int(s)
    except ValueError as e:
        return float(s)

def calc(exp):
    ss = exp.split('+')
    ns = map(str2num, ss)
    return reduce(lambda x, y: x + y, ns)

def main():
    r = calc('100 + 200 + 345')
    print('100 + 200 + 345 =', r)
    r = calc('99 + 88 + 7.6')
    print('99 + 88 + 7.6 =', r)

main()
def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')

这个方法也是用pdb,但是不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点:

import pdb

s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print(10 / n)

虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。
把print()替换为logging

import logging
logging.basicConfig(level=logging.INFO)

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
上一篇 下一篇

猜你喜欢

热点阅读