python中的接口

在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解释器达成约定。协议。

  • 30
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

又跑神了吗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值