在Java中很多老师告诉你用interface定义接口。给人一种感觉用interface定义的特殊结构。才是接口的重点。这种特殊结构,定义的方法没有实现。必须由子类来实现。但实际上这并不是接口的精髓。实际上即使你定义了普通类也可以作为接口。真正的接口的含义是,一个代码体中使用了没有写死的对象的方法和属性。就叫接口。像插座和插销一样。使用没有写死的对象的代码对对象有一系列的操作。如访问属性,调用方法,就像插座它需要三个口。传入的对象必须要支持相应的操作。就像插座提供了三个凸起。
什么是接口
只要一个代码块中使用没有写死的对象的方法或属性。就有接口的使用。接口的使用在程序中十分常见。比如
def add(a,b):
return a+b
一个很简单的函数。即使刚学python一个小时都可以很轻松的写开。但是现在思考一个问题。你怎么确定后续的参数。就一定支持加法操作。
在Java中我们依靠的强制的类型检查。定义函数时就限制了传入的对象必须是可计算类型的子类。但在python中,不采用类型检查。而是鸭子类型,更加灵活的使用接口。在Java中如果传入不可计算的类型。代码会报错。但实际上我们不强制限制参数的类型。在调用方法处报错。都是报错,而且参数进入代码内部的报错更加灵活。
什么叫写死
a=6
b=7
a="a"
b="b"
def add(a, b):
c = 1
return a + b + c
如果一个对象,可以在接口外部进行修改就叫没写死。在例子中a和b都没有写死。c写死。
我们在看一个例子。
class test:
def __init__(self):
self.a = 1
self.b = 2
def add(self):
c=0
return c+self.a + self.b
def my_random(self):
self.a = np.random.randint(0, 10)
self.b = np.random.randint(0, 10)
add方法中的对象可以被其它函数修改。
这些操作就会导致对象的不可控性。比如被修改成了不可相加的类型。
鸭子类型
如果一个东西长的像鸭子,走路像鸭子,那就是鸭子。而不需强制类型检查是不是鸭子。我们跟关注于传入对象的行为。比如add()直接检查是否可以相加,而不管它是不是int
优点
优点一:灵活
更加灵活。在Java中有函数重载。但python中不需要。这可不是只是简单的python不检查类型。实际上大有好处。我们知道list和int 的表现很不同。但都支持加法操作。如果我定义一个类也想变的可加。继承会引入额外的我不需要的方法。比如我继承了list.我的类就可以迭代了。但我只要支持加法。
自定义支持加法操作的类。
class add_o:
def __init__(self, num):
self.num = num
def __add__(self, other):
return self.num + 2*other.num
a = add_o(5)
b = add_o(10)
print(a + b)
这样我们就只需实现需要的属性。而无需被类型限制。我们用python模拟一下这种限制。在python中的类型检查用到了isinstance
class people:
def __init__(self, name):
self.name = name
def feeding(self,pet,food):
#喂食
if not isinstance(pet,Pet):
raise TypeError("pet is not a Pet")
pet.eat(food)
def walk(self,pet):
#散步
if not isinstance(pet,Pet):
raise TypeError("pet is not a Pet")
pet.walk()
def play(self,pet):
#玩
if not isinstance(pet,Pet):
raise TypeError("pet is not a Pet")
pet.play()
def petting(self,pet):
#抚摩
if not isinstance(pet,Pet):
raise TypeError("pet is not a Pet")
pet.pet()
class Pet:
def __init__(self,name):
self.name = name
def eat(self,food):
print(self.name + " is eating " + food)
def walk(self):
print(self.name + " is walking")
def play(self):
print(self.name + " is playing")
def pet(self):
print(self.name + " is petting")
class Dog(Pet):
def __init__(self,name):
super().__init__(name)
class Cat(Pet):
def __init__(self,name):
super().__init__(name)
这里我们定义了人,它可以和宠物进行互动,按照强制类型检查。我们定义了宠物类狗和猫
mike=people("小王")
wangwang=Dog("旺财")
mike.feeding(wangwang,"骨头")
看起来没问题。好现在我们引入鱼。它也是一种宠物。它不支持散步这个操作。怎么办,甚至它不可以触摸。如果我继承pet它就变成了可以散步,可以被抚摩。
class Fish(Pet):
def __init__(self,name):
super().__init__(name)
mike=people("小王")
fish=Fish("鱼")
mike.petting(fish)
我们今天又去动物园了,去喂老虎。又怎么办。如何复用eat。老虎不支持玩,散步和抚摩等操作。
如果我们强制类型检查。需要不断的重构,修改抽象。
最后变成了这样。
class Animal:
"""
动物
"""
def eat(self,food):
print("eat",food)
class PetsThatCanBeInteractedWith(Animal):
"""
支持互动的宠物
"""
def pet(self):
print("pet")
def walk(self):
print("walk")
def play(self):
print("play")
class PetsThatCanNotBeInteractedWith(Animal):
"""
不可互动的宠物,不能散步和抚摩
"""
def play(self):
print("play")
class Dog(PetsThatCanBeInteractedWith):
pass
class Cat(PetsThatCanBeInteractedWith):
pass
class Fish(PetsThatCanNotBeInteractedWith):
pass
class Tigger(Animal):
pass
class People:
def __init__(self, name):
self.name = name
def feeding(self,pet,food):
if not isinstance(pet,Animal):
raise TypeError("pet is not a Animal")
pet.eat(food)
def walk(self,pet):
if not isinstance(pet,PetsThatCanBeInteractedWith):
raise TypeError("pet is not a Pet")
pet.walk()
def play(self,pet):
if not isinstance(pet,PetsThatCanBeInteractedWith):
raise TypeError("pet is not a Pet")
pet.play()
def petting(self,pet):
if not isinstance(pet,PetsThatCanBeInteractedWith):
raise TypeError("pet is not a Pet")
pet.pet()
people=People("小明")
dog=Dog()
people.feeding(dog,"骨头")
people.petting(dog)
people.walk(dog)
people.play(dog)
cat=Cat()
people.feeding(cat,"鱼")
people.petting(cat)
people.walk(cat)
people.play(cat)
fish=Fish()
people.feeding(fish,"饲料")
people.play(fish)
tigger=Tigger()
people.feeding(tigger,"肉")
我们引用了多层抽象。动物只可以喂,然后提供了可以一起散步和抚摩的动物。不可以的动物。
但是明确告诉你,如果你在python里这样写绝对是在过度设计。
python风格的接口
#模拟强制类型检查带来的不便
# 模拟强制类型检查带来的不便
class Pet:
def eat(self, food):
print("吃", food)
def walk(self):
print("走路")
def play(self):
print("玩")
def pet(self):
print("抚摩")
class Dog(Pet):
pass
class Cat(Pet):
pass
class Fish:
def eat(self, food):
print("吃", food)
def play(self):
print("玩")
class Tigger:
def eat(self, food):
print("吃", food)
class People:
def __init__(self, name):
self.name = name
def feeding(self, pet, food):
pet.eat(food)
def walk(self, pet):
pet.walk()
def play(self, pet):
pet.play()
def petting(self, pet):
pet.pet()
people = People("小明")
dog = Dog()
people.feeding(dog, "骨头")
people.petting(dog)
people.walk(dog)
people.play(dog)
cat = Cat()
people.feeding(cat, "鱼")
people.petting(cat)
people.walk(cat)
people.play(cat)
fish = Fish()
people.feeding(fish, "饲料")
people.play(fish)
tigger = Tigger()
people.feeding(tigger, "肉")
不检查类型,直接定义类的行为。这叫鸭子类型。只要行为满足要求就行。
而不去管它是什么。在python中我们无需去过度设计类的抽象。
优点二 内部处理
我们定义了一个方法
def list_add(a,b):
a=int(a)
b=int(b)
return a+b
如果我们在传参时,参数检查,int和string如何设计一个统一的抽象。当传入的是字符。如果是Java更根本无法插入。参数压根就进不到函数体内部。但实际上我们加法可以稍微放宽一点,你传字符类型的数字也是可以的。Python因为异常是在函数内部调用行为时报错。可以更灵活的处理。
抽象基类
在框架设计时接口是应用最多的。比如为什么我们写一个类。就可以加载到框架里。理解即插即用。需要准确明白接口的含义。事实上就是框架在调用你的对象的行为。但是问题来了,你如何知道别人需要什么方法呢。一种是基于协议。即翻看文档。一种是定义抽象基类。提供模板。
模拟框架,这种属于基于协议,即翻看文档。
#模拟框架
import queue
from collections import namedtuple
import threading
routing={}
def Url(url):
def wrapper(func):
if routing.get(url) is None:
routing[url]=func
else:
raise Exception("url已经存在")
def inner(*args,**kwargs):
func(*args,**kwargs)
return inner
return wrapper
@Url("/login")
def login(request):
pass
return "登录成功"
@Url("/register")
def register(request):
pass
return "注册成功"
que=queue.Queue()
def listener():
while True:
url=input("请输入路径")
#命名元组
UserInfo=namedtuple("UserInfo",["username","password"])
userinfo=UserInfo("admin","123456")
if routing.get(url) is None:
print("404")
else:
que.put((url,userinfo))
def main():
print(routing)
t=threading.Thread(target=listener)
t.start()
while True:
request=que.get()
url=request[0]
args=request[1]
result=routing[url](args)
print(result)
if __name__ == '__main__':
main()
运行效果
好那么接口在那呢。我们
你取得函数后传参了吧。那么你怎么确定我就可以传呢。所以接口的概念像前面讲的只要是没写死的对象都是接口
比如
@Url("/logout")
def logout():
pass
return "退出成功"
我们不支持参数
出错。
这里我们接口使用的是文档。你通过教程知道我的该如何定义一个路由。这种适合比较简单的情况。但是复杂时。我们可以通过抽象基类去提示使用者我需要什么。但实际上接口的产生和核心是调用方法和属性时就产生了。而不是定义抽象基类处。抽象基类是为了告知使用者我的接口需要你具备什么。
def main(marketing):
result=marketing.start()
if result=="success":
print("营销策略成功")
else:
marketing.faild()
如果你学会了,应该没有疑问,这是接口。因为我们不知道传进来的是什么。这个函数执行一个营销策略。需要提供策略开始和失败时的逻辑。
def main(marketing):
result=marketing.start()
if result=="success":
print("营销策略成功")
else:
marketing.faild()
class Marketing1:
def start(self):
print("降价")
def faild(self):
print("继续降价")
class Marketing2:
def start(self):
print("广告")
def faild(self):
print("停止投放")
main(Marketing1())
main(Marketing2())
只要行为都有就可以插入我的服务。但是我可能不知道你需要什么行为
class Marketing(ABC):
@abstractmethod
def start(self):
pass
@abstractmethod
def faild(self):
pass
这里需要提一下。除非你是框架设计师。否则不要自定义抽象基类。你的使用场景顶多是继承一个抽象基类。和java中表现一样。继承接口就必须实现方法。但是区别是。python中不是很鼓励这样。只有少数情况你可能需要提供抽象基类。而且维护一个教程。直接告知需要的行为也是一种很好的替代。而不是抽象基类。
天鹅类型
既然python支持鸭子类型,又为何给出了类型检查的isinstance,同样这种情况也很少见。那就是你认为行为可能看起来一样。但实际上不一样。比如天鹅和鸭子很像。但是天鹅肉和鸭子肉的口感不一样。
我们下面的例子。不以鸭子举例换个更有趣的。狗和狼。我们刷到过新闻。有人把小狼当成狗。他们的行为很像。但本质不同。
import time
class Wolf():
def __init__(self):
self.name = "狼"
self.eat_count = 0
def interact(self):
print(f"{self.name}与人互动")
def eat(self):
print(f"{self.name}吃饭")
self.eat_count += 1
if self.eat_count >= 10:
print(f"{self.name}把人吃了")
class Dog():
def __init__(self):
self.name = "狗"
def interact(self):
print("互动")
def eat(self):
print("吃饭")
class Human():
def interact(self,pet):
print(f"和{pet.name}交互")
pet.interact()
def feedingself(self,pet):
print(f"给{pet.name}喂")
pet.eat()
human=Human()
wolf=Wolf()
wolf.name="旺财"
dog=Dog()
dog.name="小黄"
for i in range(12):
human.feedingself(wolf)
human.feedingself(dog)
human.interact(wolf)
human.interact(dog)
狗和狼在初期很像,你可以喂它。但有一天狼把你吃掉了。
class Human():
def interact(self,pet):
if isinstance(pet,Wolf):
print("不能和狼交互")
return
print(f"和{pet.name}交互")
pet.interact()
def feedingself(self,pet):
if isinstance(pet,Wolf):
print("不能给狼喂")
return
print(f"给{pet.name}喂")
pet.eat()
此时可以提供更加细致的类型检查。
魔术方法
python中定义的一种特殊方法如
class add_o:
def __init__(self, num):
self.num = num
def __add__(self, other):
return self.num + 2*other.num
a = add_o(5)
b = add_o(10)
print(a + b)
__add__就是魔术方法。我们也可以从接口的角度去思考魔术方法。python解释器也是个程序。它定义遇到你写的文本后该如何处理。这里python的逻辑就是。碰到加号。就去找你的__add__方法。此时就要求你必须实现了接口需要的东西。
同时我们上文提到基于文档来告知需要实现什么。魔术方法也是。这类似于一种协议。只要你想让我解释器去运行你。那么只要实现这个就好了。就像和python解释器达成约定。协议。