⼀. 类的约束
⾸先, 你要清楚约束是对类的约束。 比如,现在你是⼀个项⽬经理,然后呢,你给⼿下的⼈分活。张三你处理⼀下普通⽤户登录, 李四你处理⼀下会员登录, 王五你处理⼀下管理员登录。那这个时候呢,他们就开始分别去写他们的功能了。但是呢,你要知道, 程序员不⼀定会有那么好的默契,很有可能三个⼈会写完全三个不同的⽅法,就比如这样:
class Normal: # 张三, 普通⼈登录
def login(self):
pass
class Member: # 李四, 会员登录
def denglu(self):
pass
class Admin: # 王五, 管理员登录
def login(self):
Pass
然后呢, 他们把这样的代码交给你了,你看了⼀眼,张三和王五还算OK 。这个李四写的是什么⿁? denglu.......难受不,但是好⽍能⽤,还能凑合。但是这时,你这边要使⽤了,问题就来了。
# 项⽬经理写的总⼊⼝
def login(obj):
print("准备验证码.......")
obj.login()
print("进⼊主⻚.......")
对于张三和王五的代码,没有问题,但是李四的,你是不是调⽤不了,那如何避免这样的问题呢? 我们要约束程序的结构,也就是说,在分配任务之前就应该把功能定义好,然后分别交给底下的程序员来完成相应的功能。
# 项⽬经理写的总⼊⼝
def login(obj):
print("准备验证码.......")
obj.login()
print("进⼊主⻚.......")
对于张三和王五的代码, 没有问题。 但是李四的,你是不是调⽤不了。 那如何避免这样的问题呢? 我们要约束程序的结构,也就是说,在分配任务之前就应该把功能定义好,然后分别交给底下的程序员来完成相应的功能。在python中有两种办法来解决这样的问题:
1. 提取⽗类,然后在⽗类中定义好⽅法,在这个⽅法中什么都不⽤⼲,就抛⼀个异常就可以了。这样所有的⼦类都必须重写这个⽅法,否则,访问的时候就会报错。
2. 使⽤元类来描述⽗类,在元类中给出⼀个抽象⽅法,这样⼦类就不得不给出抽象⽅法的具体实现,也可以起到约束的效果。
⾸先,我们先看第⼀种解决⽅案: ⾸先, 提取⼀个⽗类。在⽗类中给出⼀个⽅法,并且在⽅法中不给出任何代码,直接抛异常。
class Base:
def login(self):
raise Exception("你没有实现login⽅法()")
class Normal(Base):
def login(self):
pass
class Member(Base):
def denglu(self):
pass
class Admin(Base):
def login(self):
Pass
# 项⽬经理写的总⼊⼝
def login(obj):
print("准备验证码.......")
obj.login()
print("进⼊主⻚.......")
n = Normal()
m = Member()
a = Admin()
login(n)
login(m) # 报错.
login(a)
在执⾏到login(m)的时候程序会报错,原因是, 此时访问的login()是⽗类中的⽅法。但是⽗类中的⽅法会抛出⼀个异常,所以报错,这样程序员就不得不写login⽅法了,从⽽对⼦类进⾏了相应的约束。
在本⽰例中,要注意我们抛出的是Exception异常,⽽Exception是所有异常的根。我们⽆法通过这个异常来判断出程序是因为什么报的错,所以,最好是换⼀个比较专业的错误信息。最好是换成NotImplementError,其含义是: "没有实现的错误"。这样程序员或者项⽬经理可以⼀⽬了然的知道是什么错了。就好比,你犯错了,我就告诉你犯错了,你也不知道哪⾥错了。这时我告诉你, 你xxx错了,你改也好改。
第⼆套⽅案: 写抽象类和抽象⽅法。这种⽅案相对来说比上⼀个⿇烦⼀些,需要给⼤家先引入⼀个抽象的概念,什么是抽象呢? 想⼀下,动物的‘吃’你怎么描述? ⼀个动物到底应该怎么吃? 是不是描述不清楚?这⾥动物的吃就是⼀个抽象的概念,只是⼀个动作的概念,没有具体实现。这种就是抽象的动作,换句话说,我们如果写⼀个⽅法,不知道⽅法的内部应该到底写什么,那这个⽅法其实就应该是⼀个抽象的⽅法。如果⼀个类中包含抽象⽅法,那么这个类⼀定是⼀个抽象类,抽象类是不能有实例的。比如,你看看⼀些抽象派的画作,在现实中是不存在的,也就⽆法建立实例对象与之相对应,所以抽象类⽆法创建对象,创建对象的时候会报错。
在python中编写⼀个抽象类比较⿇烦,需要引入abc模块中的ABCMeta和abstractmethod这两个内容,来我们看⼀个例⼦:
from abc import ABCMeta, abstractmethod
# 类中包含了抽象⽅法,那此时这个类就是个抽象类. 注意: 抽象类可以有普通⽅法
class IGame(metaclass=ABCMeta):
# ⼀个游戏到底怎么玩⼉? 你能形容? 流程能⼀样么?
@abstractmethod
def play(self):
pass
def turn_off(self):
print("破游戏不玩了, 脱坑了")
class DNFGame(IGame):
# ⼦类必须实现⽗类中的抽象⽅法. 否则⼦类也是抽象类
def play(self):
print("dnf的玩⼉法")
# g = IGame() # 抽象类不能创建对象
dg = DNFGame()
dg.play()
#通过代码我们能发现,这⾥的IGame对DNFGame进⾏了约束,换句话说,⽗类对⼦类进⾏了约束。接下来,继续解决我们⼀开始的问题。
from abc import ABCMeta, abstractmethod。
class Base(metaclass=ABCMeta):
@abstractmethod
def login(self):
pass
class Normal(Base):
def login(self):
pass
class Member(Base):
def denglu(self): # 这个就没⽤了
pass
def login(self): # ⼦类对⽗类进⾏实现
pass
class Admin(Base):
def login(self):
pass
# 项⽬经理写的总⼊⼝
def login(obj):
print("准备验证码.......")
obj.login()
print("进⼊主⻚.......")
n = Normal()
m = Member()
a = Admin()
login(n)
login(m)
login(a)
总结: 约束,其实就是⽗类对⼦类进⾏约束。⼦类必须要写xxx⽅法. 在python中约束的⽅式和⽅法有两种:
1. 使⽤抽象类和抽象⽅法, 由于该⽅案来源是java和c#,所以使⽤频率还是很少的
2. 使⽤⼈为抛出异常的⽅案,并且尽量抛出的是NotImplementError这样比较专业, ⽽且错误比较明确(推荐)。
⼆. 异常处理
⾸先, 我们先说⼀下, 什么是异常? 异常是程序在运⾏过程中产⽣的错误。就好比,你在回家路上突然天塌了。那这个就属于⼀个异常,总之就是不正常。那如果程序出现了异常怎么处理呢? 来段代码:
def chu(a, b):
return a/b
try:#(1)
ret = chu(10, 0)
print(ret)
except Exception as e:(2)
print("除数不能是0")(3)
打印结果:
除数不能是0
可以把(1),(2),(3)行注释掉,再运行看看,就会抛出来一个异常。
那try...except是什么意思呢? 尝试着运⾏代码,出现了错误,就执⾏except后⾯的代码。在这个过程中,当代码出现错误的时候,系统会产⽣⼀个异常对象。然后这个异常会向外抛, 被except拦截,并把接收到的异常对象赋值给e。这⾥的e就是异常对象,这⾥的Exception是什么? Exception是所有异常的基类, 也就是异常的根。换句话说,所有的错误都是Exception的⼦类对象。像ZeroDivisionError(除数不可以是0), 其实就是Exception的⼦类。这样写好像有点⼉问题。Exception表示所有的错误,太笼统了,所有的错误都会被认为是Exception。当程序中出现多种错误的时候, 就不好分类了, 最好是出什么异常就⽤什么来处理。这样就更加合理了,所以在try...execpt语句中,还可以写更多的except。
try:
print("各种操作....")
except ZeroDivisionError as e:
print("除数不能是0")
except FileNotFoundError as e:
print("⽂件不存在")
except Exception as e:
print("其他错误")
此时,程序运⾏过程中,如果出现了ZeroDivisionError就会被第⼀个except捕获。如果出现了FileNotFountError就会被第⼆个except捕获,如果都不是这两个异常。那就会被最后的
Exception捕获,总之最后的Exception就是我们异常处理的最后⼀个守⻔员,这时我们最常⽤的⼀套写法。接下来,给出⼀个完整的异常处理写法:
try:
'''操作'''
except Exception as e:
'''异常的⽗类,可以捕获所有的异常'''
else:
'''保护不抛出异常的代码, 当try中⽆异常的时候执⾏'''
finally:
'''最后总是要执⾏我,不管有没有异常'''
解读: 程序先执⾏操作, 然后如果出错了会走except中的代码。如果不出错, 执⾏else中的代码。不论出不出错,最后都要执⾏finally中的语句。⼀般我们⽤try...except就够⽤了,顶多加上finally。finally⼀般⽤来作为收尾⼯作(例如:文件,数据库的关闭等)。
上⾯是处理异常,我们在执⾏代码的过程中如果出现了⼀些条件上的不对等。 根本不符合我的代码逻辑。比如,参数我要求你传递⼀个数字, 你非得传递⼀个字符串,那对不起,我没办法帮你处理,那如何通知你呢? 两个⽅案:
⽅案⼀:直接返回即可,我不管你还不⾏么?
⽅案⼆.:抛出⼀个异常,告诉你:我不好惹,乖乖的听话。
第⼀种⽅案是我们之前写代码经常⽤到的⽅案。但这种⽅案并不够好,⽆法起到警⽰作⽤。 所以,以后的代码中如果出现了类似的问题,直接抛⼀个错误出去,那怎么抛呢? 我们要⽤到raise关键字。
def add(a, b):
'''
给我传递两个整数. 我帮你计算两个数的和
:param :param a:
:param :param b:
:return :return:
'''
if not type(a) == int and not type(b) == int:
# 当程序运⾏到这句话的时候. 整个函数的调⽤会被中断. 并向外抛出⼀个异常.
raise Exception("不是整数, 朕不能帮你搞定这么复杂的运算.")
return a + b
# 如果调⽤⽅不处理异常. 那产⽣的错误将会继续向外抛. 最后就抛给了⽤户
# add("你好", "我叫Tom")
# 如果调⽤⽅处理了异常. 那么错误就不会丢给⽤户. 程序也能正常进⾏
try:
add("胡辣汤", "滋滋冒油的⼤腰⼦")
except Exception as e:
print("报错了. ⾃⼰处理去吧")
当程序运⾏到raise,程序会被中断。并实例化后⾯的异常对象,抛给调⽤⽅,如果调⽤⽅不处理。则会把错误继续向上抛出, 最终抛给⽤户。如果调⽤⽅处理了异常,那程序可以正常的进⾏执⾏。
说了这么多, 异常也知道如何抛出和处理了,但是我们现在⽤的都是⼈家python给的异常。如果某⼀天,你写的代码中出现了⼀个⽆法⽤现有的异常来解决问题。那怎么办呢? 别着急,python可以⾃定义异常。
⾃定义异常: 非常简单,只要你的类继承了Exception类,那你的类就是⼀个异常类,就这么简单。比如,你要写⼀个男澡堂⼦程序,那这时要是来个女的,你怎么办? 是不是要抛出⼀个性别异常啊? 好,我们来完成这个案例:
# 继承Exception. 那这个类就是⼀个异常类
class GenderError(Exception):
pass
class Person:
def __init__(self, name, gender):
self.name = name
self.gender = gender
def nan_zao_tang_xi_zao(person):
if person.gender != "男":
raise GenderError("性别不对. 这⾥是男澡堂⼦")
p1 = Person("alex", "男")
p2 = Person("eggon", "蛋")
# nan_zao_tang_xi_zao(p1)
# nan_zao_tang_xi_zao(p2) # 报错. 会抛出⼀个异常: GenderError
# 处理异常
try:
nan_zao_tang_xi_zao(p1)
nan_zao_tang_xi_zao(p2)
except GenderError as e:
print(e) # 性别不对, 这⾥是男澡堂⼦
except Exception as e:
print("反正报错了")
ok搞定。但是, 如果是真的报错了,我们在调试的时候, 最好是能看到错误源⾃于哪⾥?怎么办呢? 需要引入另⼀个模块traceback,这个模块可以获取到我们每个⽅法的调⽤信息。⼜被称为堆栈信息,这个信息对我们找错是很有帮助的。
import traceback
# 继承Exception. 那这个类就是⼀个异常类
class GenderError(Exception):
pass
class Person:
def __init__(self, name, gender):
self.name = name
self.gender = gender
def nan_zao_tang_xi_zao(person):
if person.gender != "男":
raise GenderError("性别不对. 这⾥是男澡堂⼦")
p1 = Person("alex", "男")
p2 = Person("eggon", "蛋")
# nan_zao_tang_xi_zao(p1)
# nan_zao_tang_xi_zao(p2) # 报错. 会抛出⼀个异常: GenderError
# 处理异常
try:
nan_zao_tang_xi_zao(p1)
nan_zao_tang_xi_zao(p2)
except GenderError as e:
val = traceback.format_exc() # 获取到堆栈信息
print(e) # 性别不对. 这⾥是男澡堂⼦
print(val)
except Exception as e:
print("反正报错了")
结果:
性别不对. 这⾥是男澡堂⼦
Traceback (most recent call last):
File "/Users/sylar/PycharmProjects/oldboy/⾯向对象/day05.py", line 155, in <module>
nan_zao_tang_xi_zao(p2)
File "/Users/sylar/PycharmProjeccts/oldboy/⾯向对象/day05.py", line 144, in
nan_zao_tang_xi_zao
raise GenderError("性别不对. 这⾥是男澡堂⼦")
GenderError: 性别不对. 这⾥是男澡堂⼦
搞定了,这样我们就能收放⾃如了。当测试代码的时候把堆栈信息打印出来,但是当到了线上的⽣产环境的时候把这个堆栈去掉即可。
四. MD5加密
MD5加密,来看下面代码:
import hashlib
obj = hashlib.md5()
obj.update("tom".encode("utf-8")) # 加密的必须是字节
miwen = obj.hexdigest()
print(miwen) # 34b7da764b21d298ef307d04d8152dc5
固定的套路,固定的代码,就是上面这一堆。理论上,MD5是不可逆的,但是,现在有些网站,总结了大量的数据,然后推出在线逆推,所以为了保证,这些网站不能逆推,可以在使用MD5时进行加盐操作。就是在obj = hashlib.md5(b‘ajkkajkjf’),加入一段固定的字符串,这样就可以保证加密后的md5就不容易被反推了。但是一定要记住,这段加盐的字符串不可以轻易的改变。否则,前后不一,就没法保证保存的数据是否是用户需要保存的了。
五.日志
为了解决线上程序出现的不好排查的问题,所以有了日志。线上跑着的程序出问题了,就把问题记录下来。看代码:
# filename: ⽂件名
# format: 数据的格式化输出. 最终在⽇志⽂件中的样⼦
# 时间-名称-级别-模块: 错误信息
# datefmt: 时间的格式
# level: 错误的级别权重, 当错误的级别权重⼤于等于leval的时候才会写⼊⽂件
logging.basicConfig(filename='x1.txt',
format='%(asctime)s - %((name)s - %(levelname)s -%
(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=0) # 当前配置表示 10以上的分数会被写⼊⽂件
# CRITICAL = 50
# FATAL = CRITICAL
# ERROR = 40
# WARNING = 30
# WARN = WARNING
# INFO = 20
# DEBUG = 10
# NOTSET = 0
logging.critical("我是critical") # 50分. 最贵的
logging.error("我是error") # 40分
logging.warning("我是警告") # 警告 30
logging.info("我是基本信息") # 20
logging.debug("我是调试") # 10
logging.log(2, "我是⾃定义") # ⾃定义. 看着给分
简单做个测试, 应⽤⼀下
class JackError(Exception):
pass
for i in range(10):
try:
if i % 3 == 0:
raise FileNotFoundError("⽂件不在啊")
elif i % 3 == 1:
raise KeyError("键错了")
elif i % 3 == 2:
raise JackError("杰克Exception")
except FileNotFoundError:
val = traceback.format_exc()
logging.error(val)
except KeyError:
val = traceback.format_exc()
logging.error(val)
except JackError:
val = traceback.format_exc()
logging.error(val)
except Exception:
val = traceback.format_exc()
logging.error(val)
最后, 如果你系统中想要把⽇志⽂件分开。比如,⼀个⼤项⽬, 有两个⼦系统, 那两个⼦系统要分开记录⽇志,⽅便调试,那怎么办呢? 注意,⽤上⾯的basicConfig是搞不定的, 我们要借助⽂件助⼿(FileHandler), 来帮我们完成⽇志的分开记录。
import logging
# 创建⼀个操作⽇志的对象logger(依赖FileHandler)
file_handler = logging.FileHandler('l1.log', 'a', encoding='utf-8')
file_handler.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s - %
(levelname)s -%(module)s: %(message)s"))
logger1 = logging.Logger('s1', level=logging.ERROR)
logger1.addHandler(file_handler)
logger1.error('我是A系统')
# 再创建⼀个操作⽇志的对象logger(依赖FileHandler)
file_handler2 = logging.FileHandler('l2.log', 'a', encoding='utf-8')
file_handler2.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s -
%(levelname)s -%(module)s: %(message)s"))
logger2 = logging.Logger('s2', level=logging.ERROR)
logger2.addHandler(file_handler2)
logger2.error('我是B系统')