Python3----------抽象(多态、封装、继承等)

news2024/11/6 9:46:32

术语多态(polymorphism)源自希腊语,意思是“有多种形态”。这大致意味着即便你不知道变量指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异。

封装(encapsulation)指的是向外部隐藏不必要的细节。

# 下面来看一个使用了多态但没有使用封装的示例。假设你有一个名为OpenObject的类(如何创建类将在本章后面介绍)。
# >>> o = OpenObject() # 对象就是这样创建的
# >>> o.set_name('Sir Lancelot')
# >>> o.get_name()
# 'Sir Lancelot'

一、创建自定义类

# python  创建自定义类--case1
class Person:
    def set_name(self, name):
        self.name = name
    def get_name(self):
        return self.name
    def greet(self):
        print("Hello, world! I'm {}.".format(self.name))

foo = Person()
bar = Person()
foo.set_name('Luke Skywalker')
bar.set_name('Anakin Skywalker')

print(foo.greet())  # Hello, world! I'm Luke Skywalker.
print(bar.greet())  # Hello, world! I'm Anakin Skywalker.
# python  创建自定义类--case2
class Bird():
    def set_bird(self,bird):
        self.bird = bird
    def get_bird(self):
        return self.bird
    def greet(self):
        print("This is a %s ,and it can fly and bark!" % self.bird)

bird = Bird()
bird.set_bird("maotouyin")
print(bird.greet())

二、属性、函数和方法
实际上,方法和函数的区别表现在前一节提到的参数self上。方法(更准确地说是关联的方法)将其第一个参数关联到它所属的实例,因此无需提供这个参数。无疑可以将属性关联到一个普通函数,但这样就没有特殊的self参数了。

>>> class Class: 
... def method(self): 
... print('I have a self!') 
... 
>>> def function(): 
... print("I don't...") 
... 
>>> instance = Class() 
>>> instance.method() I have a self! 
>>> instance.method = function 
>>> instance.method() I don't... 
请注意,有没有参数self并不取决于是否以刚才使用的方式(如instance.method)调用方法。
实际上,完全可以让另一个变量指向同一个方法。
>>> class Bird: 
... song = 'Squaawk!' 
... def sing(self): 
... print(self.song) 
... 
>>> bird = Bird() 
>>> bird.sing() 
Squaawk! 
>>> birdsong = bird.sing 
>>> birdsong() 
Squaawk!
虽然最后一个方法调用看起来很像函数调用,但变量birdsong指向的是关联的方法
bird.sing,这意味着它也能够访问参数self(即它也被关联到类的实例)。

三、再谈隐藏

默认情况下,可从外部访问对象的属性。再来看一下前面讨论封装时使用的示例。
>>> c.name 
'Sir Lancelot' 
>>> c.name = 'Sir Gumby' 
>>> c.get_name() 
'Sir Gumby' 
有些程序员认为这没问题,但有些程序员(如Smalltalk①之父)认为这违反了封装原则。他
们认为应该对外部完全隐藏对象的状态(即不能从外部访问它们)。你可能会问,为何他们的立
场如此极端?由每个对象管理自己的属性还不够吗?为何要向外部隐藏属性?毕竟,如果能直接
访问ClosedObject(对象c所属的类)的属性name,就不需要创建方法setName和getName了。
关键是其他程序员可能不知道(也不应知道)对象内部发生的情况。例如,ClosedObject可
能在对象修改其名称时向管理员发送电子邮件。这种功能可能包含在方法set_name中。但如果直
接设置c.name,结果将如何呢?什么都不会发生——根本不会发送电子邮件。为避免这类问题,
可将属性定义为私有。私有属性不能从对象外部访问,而只能通过存取器方法(如get_name和
set_name)来访问。

Python没有为私有属性提供直接的支持,而是要求程序员知道在什么情况下从外部修改属性
是安全的。毕竟,你必须在知道如何使用对象之后才能使用它。然而,通过玩点小花招,可获得
类似于私有属性的效果。
要让方法或属性成为私有的(不能从外部访问),**只需让其名称以两个下划线打头即可**
class Secretive:
    def __inaccessible(self):
        print("Bet you can't see me ...")
    def accessible(self):
        print("The secret message is:")
        self.__inaccessible()

现在从外部不能访问__inaccessible,但在类中(如accessible中)依然可以使用它。
>>> s = Secretive() 
>>> s.__inaccessible() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
AttributeError: Secretive instance has no attribute '__inaccessible' 
>>> s.accessible() 
The secret message is: 
Bet you can't see me ...
虽然以两个下划线打头有点怪异,但这样的方法类似于其他语言中的标准私有方法。然而,
幕后的处理手法并不标准:在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头
加上一个下划线和类名。
>>> Secretive._Secretive__inaccessible 
<unbound method Secretive.__inaccessible> 
只要知道这种幕后处理手法,就能从类外访问私有方法,然而不应这样做。
>>> s._Secretive__inaccessible() 
Bet you can't see me ... 
总之,你无法禁止别人访问对象的私有方法和属性,但这种名称修改方式发出了强烈的信号,
让他们不要这样做。
如果你不希望名称被修改,又想发出不要从外部修改属性或方法的信号,可用一个下划线打
头。这虽然只是一种约定,但也有些作用。例如,
from module import *
不会导入以一个下划线
打头的名称。

四、类的命名空间

下面两条语句大致等价:
def foo(x): 
	return x * x 
foo = lambda x: 
	x * x
它们都创建一个返回参数平方的函数,并将这个函数关联到变量foo。可以在全局(模块)
作用域内定义名称foo,也可以在函数或方法内定义。定义类时情况亦如此:在class语句中定义
的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命
名空间。类定义其实就是要执行的代码段,并非所有的Python程序员都知道这一点,但知道这一
点很有帮助。例如,在类定义中,并非只能包含def语句。
>>> class C: 
... print('Class C being defined...') 
... 
Class C being defined... 
>>> 
这有点傻,但请看下面的代码:
class MemberCounter: 
 members = 0 
 def init(self): 
 MemberCounter.members += 1 
>>> m1 = MemberCounter() 
>>> m1.init() 
>>> MemberCounter.members 
1 
>>> m2 = MemberCounter() 
>>> m2.init() 
>>> MemberCounter.members 
2

上述代码在类作用域内定义了一个变量,所有的成员(实例)都可访问它,这里使用它来计
算类实例的数量。注意到这里使用了init来初始化所有实例,将把这个初始化过程自动化,
也就是将init转换为合适的构造函数。
每个实例都可访问这个类作用域内的变量,就像方法一样。
>>> m1.members 
2 
>>> m2.members 
2 
如果你在一个实例中给属性members赋值,结果将如何呢?
>>> m1.members = 'Two' 
>>> m1.members 
'Two' 
>>> m2.members 
2 
新值被写入m1的一个属性中,这个属性遮住了类级变量。这类似于函数中局部变量和全局变量之间的关系。

五、指定超类

本文前面讨论过,子类扩展了超类的定义。要指定超类,可在class语句中的类名后加上超
类名,并将其用圆括号括起。
class Filter: 
	def init(self): 
    	self.blocked = [] 
 	def filter(self, sequence): 
 		return [x for x in sequence if x not in self.blocked] 
class SPAMFilter(Filter): # SPAMFilter是Filter的子类
 	def init(self): # 重写超类Filter的方法init 
 		self.blocked = ['SPAM']
Filter是一个过滤序列的通用类。实际上,它不会过滤掉任何东西。
>>> f = Filter() 
>>> f.init() 
>>> f.filter([1, 2, 3]) 
[1, 2, 3]
Filter类的用途在于可用作其他类(如将'SPAM'从序列中过滤掉的SPAMFilter类)的基类
(超类)。
>>> s = SPAMFilter() 
>>> s.init() 
>>> s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM']) 
['eggs', 'bacon'] 
请注意SPAMFilter类的定义中有两个要点。
1、以提供新定义的方式重写了Filter类中方法init的定义。
2、直接从Filter类继承了方法filter的定义,因此无需重新编写其定义。
第二点说明了继承很有用的原因:可以创建大量不同的过滤器类,它们都从Filter类派生而
来,并且都使用已编写好的方法filter。这就是懒惰的好处。

六、深入探讨继承

要确定一个类是否是另一个类的子类,可使用内置方法issubclass>>> issubclass(SPAMFilter, Filter) 
True 
>>> issubclass(Filter, SPAMFilter) 
False 
如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__。
>>> SPAMFilter.__bases__ 
(<class __main__.Filter at 0x171e40>,) 
>>> Filter.__bases__ 
(<class 'object'>,) 
同样,要确定对象是否是特定类的实例,可使用isinstance>>> s = SPAMFilter() 
>>> isinstance(s, SPAMFilter) 
True 
>>> isinstance(s, Filter)
True 
>>> isinstance(s, str) 
False
如你所见,s是SPAMFilter类的(直接)实例,但它也是Filter类的间接实例,因为SPAMFilter
是Filter的子类。换而言之,所有SPAMFilter对象都是Filter对象。从前一个示例可知,isinstance
也可用于类型,如字符串类型(str)。
如果你要获悉对象属于哪个类,可使用属性__class__。
>>> s.__class__ 
<class __main__.SPAMFilter at 0x1707c0>

七、多个超类

在前一节,你肯定注意到了一个有点奇怪的细节:复数形式的__bases__。前面说过,你可
使用它来获悉类的基类,而基类可能有多个。为说明如何继承多个类,下面来创建几个类。
class Calculator: 
	def calculate(self, expression): 
 		self.value = eval(expression) 
class Talker: 
	def talk(self): 
 		print('Hi, my value is', self.value) 
class TalkingCalculator(Calculator, Talker): 
	pass 
子类TalkingCalculator本身无所作为,其所有的行为都是从超类那里继承的。关键是通过从
Calculator那里继承calculate,并从Talker那里继承talk,它成了会说话的计算器。
>>> tc = TalkingCalculator() 
>>> tc.calculate('1 + 2 * 3') 
>>> tc.talk() 
Hi, my value is 7 
这被称为多重继承,是一个功能强大的工具。然而,除非万不得已,否则应避免使用多重继
承,因为在有些情况下,它可能带来意外的“并发症”。
使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法(即有多
个同名方法),必须在class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面
的类的方法。因此,在前面的示例中,如果Calculator类包含方法talk,那么这个方法将覆盖Talker
类的方法talk(导致它不可访问)。如果像下面这样反转超类的排列顺序:
class TalkingCalculator(Talker, Calculator): pass 
将导致Talker的方法talk是可以访问的。多个超类的超类相同时,查找特定方法或属性时访
问超类的顺序称为方法解析顺序(MRO),它使用的算法非常复杂。所幸其效果很好,你可能根
本无需担心。

八、接口和内省

接口这一概念与多态相关。处理多态对象时,你只关心其接口(协议)——对外暴露的方
法和属性。在Python中,不显式地指定对象必须包含哪些方法才能用作参数。例如,你不会像
在Java中那样显式编写接口,而是假定对象能够完成你要求它完成的任务。如果不能完成,程
序将失败。
通常,你要求对象遵循特定的接口(即实现特定的方法),但如果需要,也可非常灵活地提
出要求:不是直接调用方法并期待一切顺利,而是检查所需的方法是否存在;如果不存在,就改
弦易辙。
>>> hasattr(tc, 'talk') 
True 
>>> hasattr(tc, 'fnord') 
False 
在上述代码中,你发现tc(本章前面介绍的TalkingCalculator类的实例)包含属性talk(指
向一个方法),但没有属性fnord。如果你愿意,还可以检查属性talk是否是可调用的。
>>> callable(getattr(tc, 'talk', None)) 
True 
>>> callable(getattr(tc, 'fnord', None)) 
False 
请注意,这里没有在if语句中使用hasattr并直接访问属性,而是使用了getattr(它让我能
够指定属性不存在时使用的默认值,这里为None),然后对返回的对象调用callable。

注意 setattrgetattr功能相反,可用于设置对象的属性:
>>> setattr(tc, 'name', 'Mr. Gumby') 
>>> tc.name 
'Mr. Gumby'

九、抽象基类

然而,有比手工检查各个方法更好的选择。在历史上的大部分时间内,Python几乎都只依赖
于鸭子类型,即假设所有对象都能完成其工作,同时偶尔使用hasattr来检查所需的方法是否存
在。很多其他语言(如Java和Go)都采用显式指定接口的理念,而有些第三方模块提供了这种理
念的各种实现。最终,Python通过引入模块abc提供了官方解决方案。这个模块为所谓的抽象基
类提供了支持。一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实
现的一组抽象方法。下面是一个简单的示例:
from abc import ABC, abstractmethod 
class Talker(ABC): 
 @abstractmethod 
 def talk(self): 
 pass 
形如@this的东西被称为装饰器。这里的要点是你使用
@abstractmethod来将方法标记为抽象的——在子类中必须实现的方法。

抽象类(即包含抽象方法的类)最重要的特征是不能实例化。
>>> Talker() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
TypeError: Can't instantiate abstract class Talker with abstract methods talk 
假设像下面这样从它派生出一个子类:
class Knigget(Talker): 
 pass 
由于没有重写方法talk,因此这个类也是抽象的,不能实例化。如果你试图这样做,将出现
类似于前面的错误消息。然而,你可重新编写这个类,使其实现要求的方法。
class Knigget(Talker): 
 def talk(self): 
 print("Ni!") 
现在实例化它没有任何问题。这是抽象基类的主要用途,而且只有在这种情形下使用
isinstance才是妥当的:如果先检查给定的实例确实是Talker对象,就能相信这个实例在需要的
情况下有方法talk。
>>> k = Knigget()
>>>> isinstance(k, Talker) 
True 
>>> k.talk() 
Ni! 
然而,还缺少一个重要的部分——让isinstance的多态程度更高的部分。正如你看到的,抽
象基类让我们能够本着鸭子类型的精神使用这种实例检查!我们不关心对象是什么,只关心对象
能做什么(它实现了哪些方法)。因此,只要实现了方法talk,即便不是Talker的子类,依然能
够通过类型检查。下面来创建另一个类。
class Herring: 
 def talk(self): 
 print("Blub.") 
这个类的实例能够通过是否为Talker对象的检查,可它并不是Talker对象。
>>> h = Herring() 
>>> isinstance(h, Talker) 
False 
诚然,你可从Talker派生出Herring,这样就万事大吉了,但Herring可能是从他人的模块中
导入的。在这种情况下,就无法采取这样的做法。为解决这个问题,你可将Herring注册为Talker
(而不从Herring和Talker派生出子类),这样所有的Herring对象都将被视为Talker对象。
>>> Talker.register(Herring) 
<class '__main__.Herring'> 
>>> isinstance(h, Talker) 
True 
>>> issubclass(Herring, Talker) 
True 
然而,这种做法存在一个缺点,就是直接从抽象类派生提供的保障没有了。
>>> class Clam: 
... pass 
... 
>>> Talker.register(Clam) 
<class '__main__.Clam'> 
>>> issubclass(Clam, Talker) 
True 
>>> c = Clam() 
>>> isinstance(c, Talker) 
True 
>>> c.talk() 
Traceback (most recent call last): 
 File "<stdin>", line 1, in <module> 
AttributeError: 'Clam' object has no attribute 'talk'
换而言之,应将isinstance返回True视为一种意图表达。在这里,Clam有成为Talker的意图。
本着鸭子类型的精神,我们相信它能承担Talker的职责,但可悲的是它失败了。
标准库(如模块collections.abc)提供了多个很有用的抽象类,有关模块abc的详细信息,
请参阅标准库参考手册。

十、小结

 对象:对象由属性和方法组成。属性不过是属于对象的变量,而方法是存储在属性中的
函数。相比于其他函数,(关联的)方法有一个不同之处,那就是它总是将其所属的对象
作为第一个参数,而这个参数通常被命名为self。
 类:类表示一组(或一类)对象,而每个对象都属于特定的类。类的主要任务是定义其
实例将包含的方法。
 多态:多态指的是能够同样地对待不同类型和类的对象,即无需知道对象属于哪个类就
可调用其方法。
 封装:对象可能隐藏(封装)其内部状态。在有些语言中,这意味着对象的状态(属性)
只能通过其方法来访问。在Python中,所有的属性都是公有的,但直接访问对象的状态时
程序员应谨慎行事,因为这可能在不经意间导致状态不一致。
 继承:一个类可以是一个或多个类的子类,在这种情况下,子类将继承超类的所有方法。
你可指定多个超类,通过这样做可组合正交(独立且不相关)的功能。为此,一种常见
的做法是使用一个核心超类以及一个或多个混合超类。
 接口和内省:一般而言,你无需过于深入地研究对象,而只依赖于多态来调用所需的方
法。然而,如果要确定对象包含哪些方法或属性,有一些函数可供你用来完成这种工作。
 抽象基类:使用模块abc可创建抽象基类。抽象基类用于指定子类必须提供哪些功能,却
不实现这些功能。
 面向对象设计:关于该如何进行面向对象设计以及是否该采用面向对象设计,有很多不
同的观点。无论你持什么样的观点,都必须深入理解问题,进而创建出易于理解的设计。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1115301.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序连接数据库与WXS的使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《微信小程序开发实战》。&#x1f3af;&#x1f3a…

下载运行ps软件提示因为计算机中丢失d3dcompiler_47.dll解决方法

在计算机系统中&#xff0c;DLL文件&#xff08;动态链接库&#xff09;是一种重要的共享库&#xff0c;它包含了可被多个程序使用的代码和数据。然而&#xff0c;当某些DLL文件丢失或损坏时&#xff0c;可能会导致程序无法正常运行。本文将介绍四种解决D3DCompiler_47.dll缺失…

使用esp32的H2 来看看zigbee~

1.首先看下esp-idf的SDK的结构&#xff0c;目前使用的版本是v5.1的。 可能随时间推移&#xff0c;目录有变化。 上图 我们 主要关注zigbee的网关部分 以及子设备部分 看下 zigee角色部分 我们接下来看下终端节点的部分代码&#xff1a;

入户的第一眼,玄关设计小技巧!福州中宅装饰,福州装修

玄关是进门的第一印象&#xff0c;这个地方的颜值&#xff0c;关系到别人是否会直接被你给惊艳到&#xff0c;特别是有小区的邻居、亲戚朋友来参观&#xff0c;只要一打开门。小编今天要分享的就是一些玄关的功能。 玄关&#xff0c;又称门厅&#xff0c;是指建筑物入门处到正厅…

Chrome插件精选 — 标签效率管理插件

Chrome实现同一功能的插件往往有多款产品&#xff0c;逐一去安装试用耗时又费力&#xff0c;在此为某一类型插件挑选出比较好用的一款或几款&#xff0c;尽量满足界面精致、功能齐全、设置选项丰富的使用要求&#xff0c;便于节省一个个去尝试的时间和精力。 1. OneTab Plus 下…

SoftPlc on docker 测试

软件主页 https://github.com/fbarresi/SoftPlc 安装 docker stop softplc docker rm softplc docker pull fbarresi/softplc:latest-linux docker run -d -p 7080:80 -p 7443:443 -p 20012:102 --restartalways --name softplc fbarresi/softplc:latest-linux netstat -na|…

Chrome插件精选 — 历史记录管理插件

Chrome实现同一功能的插件往往有多款产品&#xff0c;逐一去安装试用耗时又费力&#xff0c;在此为某一类型插件挑选出比较好用的一款或几款&#xff0c;尽量满足界面精致、功能齐全、设置选项丰富的使用要求&#xff0c;便于节省一个个去尝试的时间和精力。 1. Better History…

【electron】实战技巧(持续更新,不要错过喔)

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ nvm处理多node环境避免node版本切换指定32位/64位 2️⃣ 常用node库npm-run-all&#xff08;脚本运行工具&#xff09;cross-env&#xff08;配置环境变量&#xff09;dotenv&#xff08;配置文件&#xff09;minimist&#x…

洛谷月赛 P5588 小猪佩奇爬树

题目描述 佩奇和乔治在爬树。 给定 n 个节点的树 T(V,E)&#xff0c;第 i 个节点的颜色为 wi​&#xff0c;保证有1≤wi​≤n。 对于1≤i≤n&#xff0c;分别输出有多少对点对(u,v)&#xff0c;满足u<v&#xff0c;且恰好经过所有颜色为 i 的节点&#xff0c;对于节点颜色…

2023年【金属非金属矿山(地下矿山)安全管理人员】考试内容及金属非金属矿山(地下矿山)安全管理人员考试报名

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 金属非金属矿山&#xff08;地下矿山&#xff09;安全管理人员考试内容根据新金属非金属矿山&#xff08;地下矿山&#xff09;安全管理人员考试大纲要求&#xff0c;安全生产模拟考试一点通将金属非金属矿山&#xf…

leetcode分类刷题:如何更好地理解递归

文章目录 概念含义递归三要素递归算法的编程模型递归问题分类递归vs循环(迭代)参考文献 参考知乎上递归下的一个高赞回答&#xff0c;觉得写的非常好&#xff0c;挑选有助于自己理解的内容进行简单总结。 概念含义 1、递归(Recursion)是指在函数的定义中调用函数自身的方法&…

一篇文章教会你C++11入门知识点

C11入门 列表初始化1. {}初始化2. initializer_list 声明1. auto2. decltype3. nullptr 范围for循环STL新增容器1. array2. forward_list3. unordered_map和unordered_set 右值引用和移动语义1. 左值引用和右值引用2. 左值引用和右值引用比较3. 右值引用使用场景和意义4. 右值引…

16-spring AOP核心对象的创建

文章目录 1. aop的几个重要概念2. aop bean definition3. AspectJPointcutAdvisor4.AopConfigUtils5.AnnotationAwareAspectJAutoProxyCreator6. 循环依赖1. aop的几个重要概念 参考官方解释:https://docs.spring.io/spring-framework/docs/5.2.9.RELEASE/spring-framework-r…

操作系统备考学习 day10

操作系统备考学习 day10 第三章 内存管理3.2 虚拟内存管理3.2.1 虚拟内存的基本概念传统存储管理方式的特征、缺点局部性原理虚拟内存的定义和特征如何实现虚拟内存技术 3.2.2 请求分页管理方式页表机制缺页中断机构地址变换机构 3.2.3 页面置换算法最佳置换算法&#xff08;OP…

总结一下vue中v-bind的常见用法

文章目录 &#x1f415;前言&#xff1a;&#x1f3e8;简述一下v-bind命令其它写法 &#x1f415;前言&#xff1a; 本篇博客主要总结一下v-bind命令在vue中的常见用法 &#x1f3e8;简述一下v-bind命令 v-bind命令是将动态的数据属性与咱们的标签进行一个绑定,它可以绑定标…

二维码智慧门牌管理系统:革新小区安全管理的新力量

文章目录 前言一、外采人员的数据采集二、二维码智慧门牌管理系统的创新性三、居民的便捷体验四、面临的挑战 前言 在科技快速发展的今天&#xff0c;智能化和数字化已经深刻影响着我们的生活的各个方面。近期备受关注的话题之一是二维码智慧门牌管理系统&#xff0c;这一系统…

缓存失效方案

一、背景 WRITE &#xff1a; 数据写入Mysql 和 Redis缓存&#xff0c; READ&#xff1a;先从 Redis 缓存中取数据&#xff0c;拿不到再从Mysql中加载&#xff0c;更新到Redis 上图第三阶段&#xff0c;接收Mysql的binlog变更消息&#xff0c;可以参考阿里的 Canal&#xff0…

tooltip里面画echarts图

第一步: 第二步; 完整代码: //协作工作受理分析圆柱形workLineChart(xData, yData) {let that this;let option {backgroundColor: "#061326",grid: {top: "10%",bottom: "5%",left: "5%",right: "5%",containLabel: true…

【C语言必知必会 | 子系列第一篇】深入剖析顺序结构(1)

引言 C语言是一门面向过程的、抽象化的通用程序设计语言&#xff0c;广泛应用于底层开发。它在编程语言中具有举足轻重的地位。 此文为【C语言必知必会子系列】第一篇&#xff0c;基于进行C语言顺序结构的选择题专项练习&#xff0c;结合专题优质题目&#xff0c;带领读者从0开…

软件测试01

一、认识软件及测试 1、什么是软件 控制计算机硬件工作的工具 2、软件的基本组成 页面客户端------请求----->代码服务器-------请求------>数据服务器 3、软件产生过程 需求产生------->需求文档------->设计效果图------->产品开发-------->产品测试 …