学习第十五天

浏览: 1332

使用枚举类

当我们在定义常量时,一个方法就是用大写变量通过整数来定义,这种方法简单,但是缺点是类型是int,并且仍然是变量,更好的方法时为这样的枚举类型定义一个class类型,然后,每个常量都是class的唯一的一个实例。

枚举的实现:

1.首先要导入enum模块

2.枚举定义要用class关键字,继承Enum类

3.用于定义枚举的class和定义类的class是有区别的。

示例:

from enum import Enum
class Color(Enum):
    red = 1
    orange = 2
    yellow = 3
    green = 4
    blue = 5
    indigo = 6
    purple = 7

代码分析:

1.我们定义了颜色的枚举Color

2.有7个成员,每个成员都有自己的名字。例如:Colour.red成员的名称就是:red。值是1.

3.每个成员的数据类型就是它属的枚举。

注意:

1.定义枚举时,成员名称不能重复,不然会出现TypeError错误。

2.默认情况下,不同的成员值允许相同,但是两个相同值得成员,第二个成员名称视作是第一个成员名称的别名。在通过值获取枚举成员时,只能获取到第一个成员。

3.如果要限制枚举的成员不能有相同的值,可以使用装饰器@unique

from enum import Enum,unique
@unique
class Colour(Enum):
    red = 1
    yellow = 1

执行就会出现:ValueError: duplicate values found in <enum 'Colour'>: yellow -> red

访问枚举:

1.通过成员名称获取成员

Color['red']

<Color.red: 1>

2.通过成员值获取

Color(5)

<Color.blue: 5>

2.通过成员,来获取它的名称和值

yanse = Color.blue
yanse.name

'blue'

yanse.value

5

迭代枚举成员

1.遍历

for color in Color:
    print(color)

输出为:Color.redColor.orangeColor.yellowColor.greenColor.blueColor.indigoColor.purple

2.如果成员值相同,就会遍历第一个成员

from enum import Enum
class Color(Enum):
    red = 1
    orange = 2
    yellow = 3
    green = 4
    blue = 5
    indigo = 6
    purple = 7
    red_wai = 1
for color in Color:
    print(color)

Color.redColor.orangeColor.yellowColor.greenColor.blueColor.indigoColor.purple

3.如果想把重复值也遍历出来,就要在枚举时加一个特殊属性__member__

from enum import Enum
class Color(Enum):
    red = 1
    orange = 2
    yellow = 3
    green = 4
    blue = 5
    indigo = 6
    purple = 7
    red_wai = 1
    
for color in Color.__members__.items():
    print(color)

输出为:('red', <Color.red: 1>)('orange', <Color.orange: 2>)('yellow', <Color.yellow: 3>)('green', <Color.green: 4>)('blue', <Color.blue: 5>)('indigo', <Color.indigo: 6>)('purple', <Color.purple: 7>)('red_wai', <Color.red: 1>)

比较:

1.同一性比较

Color.red is Color.red

True

Color.red is not Color.blue

True

2.等值比较

Color.blue == Color.red 

False

3.不能进行大小比较

Color.red > Color.blue

TypeError: '>' not supported between instances of 'Color' and 'Color'


参考资料:

廖雪峰博客:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143191235886950998592cd3e426e91687cdae696e64b000

航行博客:https://www.cnblogs.com/ucos/p/5896861.html


使用元类

type()

在定义类型时,我们通常用class 类名称(object).......来定义,但在Python解释器中,遇到class定义,仅仅只是扫描一下,然后就调用type()函数创建出class。也就是说我们通过定义type()也可以创建出类来,

type()函数创建类要依次传入三个参数:

1.class的名称

2.继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法

3.class的方法名称与函数绑定

示例:

def fn(self,name = 'world'):      #先定义函数
    print('Hello,%s.' %name)
    
Hello = type('Hello',(object,), dict(hello = fn))  #创建Hello class

调用:

h = Hello()
h.hello()

Hello,world.

print(type(Hello))

<class 'type'>

print(type(h))

<class '__main__.Hello'>

type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type。而h是一个实例,它的类型就是class Hello。

type()函数既可以返回函数类型,又可以创建新的函数。

但正常情况下,我们都用class Xxx.....来定义类,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言非常不同。


metaclass

metaclass直译就是元类,我们可以通过其创建出类:

先定义metaclass,就可以创建类,最后创建实例。

metaclass是Python面向对象里最难理解的,也是最难使用的魔术代码。正常情况下,不会碰到需要使用metaclass的情况。

所以暂且不看了。下面是SQL语句,没看懂。


错误、调试和测试

错误处理

在程序运行时,我们通常会遇到错误,如果返回错误码的话,我们在查看错误时非常不方便,因为函数本身应该返回的正常结果和错误码混在一起,造成调用者必须用大量的代码来判断是否出错。所以在python中内置了一套语句:try.........except..........finally........

try

如果我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳至错误处理代码,即except后,如果有finally语句块,则执行此语句块,至此,执行完毕。

举例:

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

int()函数可能会抛出ValueError,还有ZeroDivisionError错误,所以用except来排除。也可以在except语句块后加一个else语句,当没有错误发生时,就会自动执行else语句块。

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

注意:在Python中,错误也是一个class,也有父类和子类。所有的BaseException,在使用except时需要注意的是,它不光捕获该类型的错误,还将其子类‘一网打尽’。

使用try......except还有一个好处,可以跨越多层调用比如说main()函数调用foo()函数,foo()调用bar(),结果bar()出错了,这时只要我们捕获到main()函数就可以处理错误了,也就是说我们不需要再每一个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。大大减小了写try......except.......finally的麻烦了。


调用栈

如果错误没有被捕获,那么错误就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。来解读下错误信息。举例:

#err.py:
def foo(s):
    return 10/int(s)
def bar(s):
    return foo(s)* 2
def main():
    bar('0')
    
    
main()

image.png

因为int(s)返回的是0,在计算10/0时出错了。这是错误的源头。

在出错的时候,一定要分析错误的调用栈的信息,才能定位错误的位置。


记录错误

我们不捕获错误可以让解释器来打印出错误堆栈,但同时程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,同时让程序继续执行下去。然后分析错误信息。

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

image.png

通过配置,logging还可以把错误记录到日志文件里,方便日后排查。


抛出错误

错误不是凭空出现的,而是有意创建的,Python内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。

如果要抛出一个错误。根据需要,可以定义一个错误的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')

错误中就出现了我们自己定义的错误。

image.png

只有在必要的时候我们才自己定义错误类型,尽量使用python内置的错误类型。


有时候我们不知道错误如何处理,在捕获到错误时我们会继续往上抛,让顶层调用者去处理,

raise语句如果不带参数,就会把当前的错误原样抛出。

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

此外,在except中raise一个Error,还可以把一种错误转化成另一个错误:

try:
    10/0
except ZeroDivisonError:
    raise ValueError('input error!')

但前提是转换逻辑要合理,不能转化成毫不相关的错误类型。


调试

程序写完能正常运行的概率很小,基本不会超过1%,总会出现或简单或复杂的bug,所以我们需要一整套的调试方法去修复这些bug。

print()语句:简单的方法就是把它打印出来,用print()语句把可能出现问题的变量打印出来看看。但这种办法是在修正后还要在删除掉print(),所以有了下面的方法:

断言(assert

凡是print()语句来辅助查看的地方,都可以用断言(assert)来替代。

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

例子中的assert意思是,表达式 n != 0应该是True,否则根据程序运行的逻辑,后面的代码肯定会出错的。如果断言失败,assert语句本身会抛出AssertionError

AssertionError: n is zero!

但如果程序中充满着assert,和print()语句相比也好不哪去,不过,我们可以启动python解释器时可以用-0参数来关闭assert。关闭后,可以把所有的assert语句当成pass来看。



推荐 1
本文由 小白学Python 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册