首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

我Java8还没用呢,又让我学习Java14

云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 第14版包含的JEP(Java Enhancement Proposals,Java增强提案)比12版和13版加起来还要多。在这篇文章中,我将主要讨论以下几点: 改进的switch表达式,第一次出现在Java 12和13中,在Java 14中获得了完全的支持 instanceof支持模式匹配(语言特性) NullPointerException(JVM特性) 希望你在阅读完本文后,积极地代码中实验这些功能,为Java团队提供反馈,并为Java的发展做出贡献。 一、Switch表达式 java 14 中的switch表达式将会永久存在。 在之前的发布中,switch表达式只是一个“预览”阶段的特性。我想提醒一下,“预览”阶段的特性的目的是为了收集反馈,这些特性可能会随时改变,根据反馈结果,这些特性甚至可能会被移除,但通常所有的预览特性最后都会在java中固定下来。 新的switch表达式的优点是,不再有缺省跳过行为(fall-through),更全面,而且表达式和组合形式更容易编写,因此出现bug的可能性就更低。例如,switch表达式现在可以使用箭头语法,如下所示: [var log = switch (event) { case PLAY -> "User has triggered the play button"; case STOP, PAUSE -> "User needs a break"; default -> { String message = event.toString(); LocalDateTime now = LocalDateTime.now(); yield "Unknown event " + message + " logged on " + now; } };]() 二、文本块 Java 13引入的一个预览功能是文本块。有了文本块,多行的字符串字面量就很容易编写了。这个功能在Java 14中进行第二次预览,而且发生了一些变化。例如,多行文本的格式化可能需要编写许多字符串连接操作和转义序列。下面的代码演示了一个HTML的例子: String html = "<HTML>" + "\n\t" + "<BODY>" + "\n\t\t" + "<H1>\"Java 14 is here!\"</H1>" + "\n\t" + "</BODY>" + "\n" + "</HTML>"; 有了文本块,就可以简化这一过程,只需使用三引号作为文本块的起始和结束标记,就能编写出更优雅的代码: String html = """ <HTML> <BODY> <H1>"Java 14 is here!"</H1> </BODY> </HTML>"""; 与普通字符串字面量相比,文本块的表达性更好。更多的内容可以参考这篇文章 (https://blogs.oracle.com/javamagazine/text-blocks-come-to-java)。 Java 14引入了两个新的转义序列。第一,可以使用新的 s 转义序列来表示一个空格。第二,可以使用反斜杠 来避免在行尾插入换行字符。这样可以很容易地在文本块中将一个很长的行分解成多行来增加可读性。 例如,现在编写多行字符串的方式如下: String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " + "elit, sed do eiusmod tempor incididunt ut labore " + "et dolore magna aliqua."; 在文本块中使用 转义序列,就可以写成这样: String text = """ Lorem ipsum dolor sit amet, consectetur adipiscing \ elit, sed do eiusmod tempor incididunt ut labore \ et dolore magna aliqua.\ """; 三、instanceof的模式匹配 Java 14引入了一个预览特性,有了它就不再需要编写先通过instanceof判断再强制转换的代码了。例如,下面的代码: if (obj instanceof Group) { Group group = (Group) obj; // use group specific methods var entries = group.getEntries(); } 利用这个预览特性可以重构为: if (obj instanceof Group group) { var entries = group.getEntries(); } 由于条件检查要求obj为Group类型,为什么还要像第一段代码那样在条件代码块中指明obj为Group类型呢?这可能会引发错误。 这种更简洁的语法可以去掉Java程序里的大多数强制类型转换。 JEP 305解释了这项改变,并给出了Joshuoa Bloch的著作《Effective Java》中的一个例子,演示了下面两种等价的写法: @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString) && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); } 这段代码中冗余的CaseInsensitiveString强制类型转换可以去掉,转换成下面的方式: @Override public boolean equals(Object o) { return (o instanceof CaseInsensitiveString cis) && cis.s.equalsIgnoreCase(s); } 这个预览特性很值得尝试,因为它打开了通向更通用的模式匹配的大门。模式匹配的思想是为语言提供一个便捷的语法,根据特定的条件从对象中提取出组成部分。这正是instanceof操作符的用例,因为条件就是类型检查,提取操作需要调用适当的方法,或访问特定的字段。 换句话说,该预览功能仅仅是个开始,以后该功能肯定能够减少更多的代码冗余,从而降低bug发生的可能性。 四、Record 另一个预览功能就是record。与前面介绍的其他预览功能一样,这个预览功能也顺应了减少Java冗余代码的趋势,能帮助开发者写出更精准的代码。Record主要用于特定领域的类,它的位移功能就是存储数据,而没有任何自定义的行为。 我们开门见山,举一个最简单的领域类的例子:BankTransaction,它表示一次交易,包含三个字段:日期,金额,以及描述。定义类的时候需要考虑多个方面: 构造器 getter方法 toString() hashCode()和equals() 这些部分的代码通常由IDE自动生成,而且会占用很大篇幅。下面是生成的完整的BankTransaction类: public class BankTransaction { private final LocalDate date; private final double amount; private final String description; public BankTransaction(final LocalDate date, final double amount, final String description) { this.date = date; this.amount = amount; this.description = description; } public LocalDate date() { return date; } public double amount() { return amount; } public String description() { return description; } @Override public String toString() { return "BankTransaction{" + "date=" + date + ", amount=" + amount + ", description='" + description + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BankTransaction that = (BankTransaction) o; return Double.compare(that.amount, amount) == 0 && date.equals(that.date) && description.equals(that.description); } @Override public int hashCode() { return Objects.hash(date, amount, description); } } Java 14提供了一种方法可以解决这种冗余,可以更清晰地表达目的:这个类的唯一目的就是将数据整合在一起。Record会提供equals、hashCode和toString方法的实现。因此,BankTransaction类可以重构如下: public record BankTransaction(LocalDate date, double amount, String description) {} 通过record,可以“自动”地得到equals,hashCode和toString的实现,还有构造器和getter方法。 要想尝试这个例子,需要用preview标志编译该文件: javac --enable-preview --release 14 BankTransaction.java record的字段隐含为final。因此,record的字段不能被重新赋值。但要注意的是,这并不代表整个record是不可变的,保存在字段中的对象可以是可变的。 请继续关注该功能。从培养新一代的Java开发者的视角来看,Record也很有意思。例如,如果你要培养初级开发者,那么record应该什么时候讲呢?是在讲OOP之前还是之后? 五、NullPointerException 一些人认为,抛出NullPointerException异常应该当做新的“Hello World”程序来看待,因为NullPointerException是早晚会遇到的。玩笑归玩笑,这个异常的确会造成困扰,因为它经常出现在生产环境的日志中,会导致调试非常困难,因为它并不会显示原始的代码。例如,如下代码: var name = user.getLocation().getCity().getName(); 在Java 14之前,你可能会得到如下的错误: Exception in thread "main" java.lang.NullPointerException at NullPointerExample.main(NullPointerExample.java:5) 不幸的是,如果在第5行是一个包含了多个方法调用的赋值语句(如getLocation()和getCity()),那么任何一个都可能会返回null。实际上,变量user也可能是null。因此,无法判断是谁导致了NullPointerException。 在Java 14中,新的JVM特性可以显示更详细的诊断信息: Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Location.getCity()" because the return value of "User.getLocation()" is null at NullPointerExample.main(NullPointerExample.java:5) 该消息包含两个明确的组成部分 后果:Location.getCity()无法被调用 原因:User.getLocation()的返回值为null 增强版本的诊断信息只有在使用下述标志运行Java时才有效: -XX:+ShowCodeDetailsInExceptionMessages 下面是个例子: java -XX:+ShowCodeDetailsInExceptionMessages NullPointerExample 在以后的版本中,该选项可能会成为默认。 这项改进不仅对于方法调用有效,其他可能会导致NullPointerException的地方也有效,包括字段访问、数组访问、赋值等。 六、总结 Java 14提供了几个新的预览版语言特性和更新,能很好地帮助开发者完成日常工作。Java 14还引入了record,这是一种创建精确数据类的新方法。此外,NullPointerException的消息经过了改进,能显示明确的诊断信息。switch表达式也成了Java 14的一部分。文本块功能可以帮你处理多行字符串,这是在引入了两个新的转义序列之后的另一预览功能。还有一项改动就是JDK Flight Recorder的事件流。 可见,Java 14带来了许多创新。你应该尝试一下这些功能,然后反馈给Java的开发团队。 【云栖号在线课堂】每天都有产品技术专家分享!课程地址:https://yqh.aliyun.com/zhibo 立即加入社群,与专家面对面,及时了解课程最新动态!【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK 原文发布时间:2020-03-14本文作者:Raoul-Gabriel Urma本文来自:“互联网架构师公众号”,了解相关信息可以关注“互联网架构师”

优秀的个人博客,低调大师

Python学习路线:深入探讨 Python 的 import 机制:实现远程导入模块(精华版)

所谓的模块导入,是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。 也许你看到这个标题,会说我怎么会发这么基础的文章?(当然也会有基础的文章啦) 与此相反。恰恰我觉得这篇文章的内容可以算是 Python 的进阶技能,会深入地探讨并以真实案例讲解 Python import Hook 的知识点。 当然为了使文章更系统、全面,前面会有小篇幅讲解基础知识点,但希望你能有耐心的往后读下去,因为后面才是本篇文章的精华所在,希望你不要错过。 1. 导入系统的基础 1.1 导入单元构成 导入单元有多种,可以是模块、包及变量等。 对于这些基础的概念,对于新手还是有必要介绍一下它们的区别。 模块:类似 .py,.pyc, .pyd ,.so,*.dll 这样的文件,是 Python 代码载体的最小单元。 包 还可以细分为两种: Regular packages:是一个带有 __init__.py 文件的文件夹,此文件夹下可包含其他子包,或者模块 Namespace packages 关于 Namespace packages,有的人会比较陌生,我这里摘抄官方文档的一段说明来解释一下。 Namespace packages 是由多个 部分 构成的,每个部分为父包增加一个子包。各个部分可能处于文件系统的不同位置。部分也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。 命名空间包的 path 属性不使用普通的列表。而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) 发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。 命名空间包没有 parent/__init__.py 文件。实际上,在导入搜索期间可能找到多个 parent 目录,每个都由不同的部分所提供。因此 parent/one 的物理位置不一定与 parent/two 相邻。在这种情况下,Python 将为顶级的 parent 包创建一个命名空间包,无论是它本身还是它的某个子包被导入。 1.2 相对/绝对对导入 当我们 import 导入模块或包时,Python 提供两种导入方式: 相对导入(relative import ):import foo.bar 或者 form foo import bar 绝对导入(absolute import):from . import B 或 from ..A import B,其中.表示当前模块,..表示上层模块 你可以根据实际需要进行选择,但有必要说明的是,在早期的版本( Python2.6 之前),Python 默认使用的相对导入。而后来的版本中( Python2.6 之后),都以绝对导入为默认使用的导入方式。 使用绝对路径和相对路径各有利弊: 当你在开发维护自己的项目时,应当使用相对路径导入,这样可以避免硬编码带来的麻烦。 而使用绝对路径,会让你模块导入结构更加清晰,而且也避免了重名的包冲突而导入错误。 1.3 导入的标准写法 在 PEP8 中有一条,对模块的导入顺序提出了要求,不同来源模块导入,应该有清晰的界限,使用一空行来分开。 import 语句应当分行书写 bad import os,sys good import osimport sys import语句应当使用absolute import bad from ..bar import Bar good from foo.bar import test import语句应当放在文件头部,置于模块说明及docstring之后,全局变量之前 import语句应该按照顺序排列,每组之间用一个空格分隔,按照内置模块,第三方模块,自己所写的模块调用顺序,同时每组内部按照字母表顺序排列 内置模块 import osimport sys 第三方模块 import flask 本地模块 from foo import bar 2. import 的妙用 在 Python 中使用 import 关键字来实现模块/包的导入,可以说是基础中的基础。 但这不是唯一的方法,还有 importlib.import_module() 和 __import__() 等。 对于 import ,普通的开发者,可能就会比较陌生。 和 import 不同的是,__import__ 是一个函数,也正是因为这个原因,使得__import__ 的使用会更加灵活,常常用于框架中,对于插件的动态加载。 实际上,当我们调用 import 导入模块时,其内部也是调用了 import ,请看如下两种导入方法,他们是等价的。 使用 import import os 使用 import os = __import__('os')通过举一反三,下面两种方法同样也是等价的。 使用 import .. as .. import pandas as pd 使用 import pd = __import__('pandas')上面我说 import 常常用于插件的动态,事实上也只有它能做到(相对于 import 来说)。 插件通常会位于某一特定的文件夹下,在使用过程中,可能你并不会用到全部的插件,也可能你会新增插件。 如果使用 import 关键字这种硬编码的方式,显然太不优雅了,当你要新增/修改插件的时候,都需要你修改代码。更合适的做法是,将这些插件以配置的方式,写在配置文件中,然后由代码去读取你的配置,动态导入你要使用的插件,即灵活又方便,也不容易出错。 假如我的一个项目中,有 plugin01 、plugin02、plugin03 、plugin04 四个插件,这些插件下都会实现一个核心方法 run() 。但有时候我不想使用全部的插件,只想使用 plugin02、plugin04 ,那我就在配置文件中写我要使用的两个插件。 my.conf custom_plugins=['plugin02', 'plugin04']那我如何使用动态加载,并运行他们呢? main.py for plugin in conf.custom_plugins: __import__(plugin) sys.modules[plugin].run() 3. 理解模块的缓存 在一个模块内部重复引用另一个相同模块,实际并不会导入两次,原因是在使用关键字import 导入模块时,它会先检索 sys.modules 里是否已经载入这个模块了,如果已经载入,则不会再次导入,如果不存在,才会去检索导入这个模块。 来实验一下,在 my_mod02 这个模块里,我 import 两次 my_mod01 这个模块,按逻辑每一次 import 会一次 my_mod01 里的代码(即打印 in mod01),但是验证结果是,只打印了一次。 $ cat my_mod01.py print('in mod01') $ cat my_mod02.py import my_mod01 import my_mod01 $ python my_mod02.py in mod01 该现象的解释是:因为有 sys.modules 的存在。 sys.modules 是一个字典(key:模块名,value:模块对象),它存放着在当前 namespace 所有已经导入的模块对象。 test_module.py import sysprint(sys.modules.get('json', 'NotFound')) import jsonprint(sys.modules.get('json', 'NotFound')) 运行结果如下,可见在 导入后 json 模块后,sys.modules 才有了 json 模块的对象。 $ python test_module.pyNotFound 由于有缓存的存在,使得我们无法重新载入一个模块。 但若你想反其道行之,可以借助 importlib 这个神奇的库来实现。事实也确实有此场景,比如在代码调试中,在发现代码有异常并修改后,我们通常要重启服务再次载入程序。这时候,若有了模块重载,就无比方便了,修改完代码后也无需服务的重启,就能继续调试。 还是以上面的例子来理解,my_mod02.py 改写成如下 my_mod02.py import importlibimport my_mod01importlib.reload(my_mod01)使用 python3 来执行这个模块,与上面不同的是,这边执行了两次 my_mod01.py $ python3 my_mod02.pyin mod01in mod01 4. 查找器与加载器 如果指定名称的模块在 sys.modules 找不到,则将发起调用 Python 的导入协议以查找和加载该模块。 此协议由两个概念性模块构成,即 查找器 和 加载器。 一个 Python 的模块的导入,其实可以再细分为两个过程: 由查找器实现的模块查找 由加载器实现的模块加载 4.1 查找器是什么? 查找器(finder),简单点说,查找器定义了一个模块查找机制,让程序知道该如何找到对应的模块。 其实 Python 内置了多个默认查找器,其存在于 sys.meta_path 中。 但这些查找器对应使用者来说,并不是那么重要,因此在 Python 3.3 之前, Python 解释将其隐藏了,我们称之为隐式查找器。 Python 2.7 import syssys.meta_path [] 由于这点不利于开发者深入理解 import 机制,在 Python 3.3 后,所有的模块导入机制都会通过 sys.meta_path 暴露,不会在有任何隐式导入机制。 Python 3.7 import syssys.meta_path [, , , ] 观察一下 Python 默认的这几种查找器 (finder),可以分为三种: 一种知道如何导入内置模块 一种知道如何导入冻结模块 一种知道如何导入来自 import path 的模块 (即 path based finder)。 那我们能不能自已定义一个查找器呢?当然可以,你只要: 定义一个实现了 find_module 方法的类(py2和py3均可),或者实现 find_loader 类方法(仅 py3 有效),如果找到模块需要返回一个 loader 对象或者 ModuleSpec 对象(后面会讲),没找到需要返回 None定义完后,要使用这个查找器,必须注册它,将其插入在 sys.meta_path 的首位,这样就能优先使用。 import sys class MyFinder(object): @classmethod def find_module(cls, name, path, target=None): print("Importing", name, path, target) # 将在后面定义 return MyLoader() 由于 finder 是按顺序读取的,所以必须插入在首位 sys.meta_path.insert(0, MyFinder)查找器可以分为两种: object +-- Finder (deprecated) +-- MetaPathFinder +-- PathEntryFinder这里需要注意的是,在 3.4 版前,查找器会直接返回 加载器(Loader)对象,而在 3.4 版后,查找器则会返回模块规格说明(ModuleSpec),其中 包含加载器。 而关于什么是 加载器 和 模块规格说明, 请继续往后看。 4.2 加载器是什么? 查找器只负责查找定位找模,而真正负责加载模块的,是加载器(loader)。 一般的 loader 必须定义名为 load_module() 的方法。 为什么这里说一般,因为 loader 还分多种: object +-- Finder (deprecated) | +-- MetaPathFinder | +-- PathEntryFinder +-- Loader +-- ResourceLoader --------+ +-- InspectLoader | +-- ExecutionLoader --+ +-- FileLoader +-- SourceLoader通过查看源码可知,不同的加载器的抽象方法各有不同。 加载器通常由一个 查找器 返回。详情参见 PEP 302。 那如何自定义我们自己的加载器呢? 你只要: 定义一个实现了 load_module 方法的类 对与导入有关的属性(点击查看详情)进行校验 创建模块对象并绑定所有与导入相关的属性变量到该模块上 将此模块保存到 sys.modules 中(顺序很重要,避免递归导入) 然后加载模块(这是核心) 若加载出错,需要能够处理抛出异常( ImportError),若加载成功,则返回 module 对象若你想看具体的例子,可以接着往后看。 4.3 模块的规格说明 导入机制在导入期间会使用有关每个模块的多种信息,特别是加载之前。大多数信息都是所有模块通用的。模块规格说明的目的是基于每个模块来封装这些导入相关信息。 模块的规格说明会作为模块对象的 spec 属性对外公开。有关模块规格的详细内容请参阅 ModuleSpec。 在 Python 3.4 后,查找器不再返回加载器,而是返回 ModuleSpec 对象,它储存着更多的信息 模块名 加载器 模块绝对路径 那如何查看一个模块的 ModuleSpec ? 这边举个例子 $ cat my_mod02.pyimport my_mod01print(my_mod01.__spec__) $ python3 my_mod02.pyin mod01ModuleSpec(name='my_mod01', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000000000392DBE0>, origin='/home/MING/my_mod01.py') 从 ModuleSpec 中可以看到,加载器是包含在内的,那我们如果要重新加载一个模块,是不是又有了另一种思路了? 来一起验证一下。 现在有两个文件: 一个是 my_info.py my_info.py name='python'另一个是:main.py main.py import my_info print(my_info.name) 加一个断点 import pdb;pdb.set_trace() 再加载一次 my_info.__spec__.loader.load_module() print(my_info.name)在 main.py 处,我加了一个断点,目的是当运行到断点处时,我修改 my_info.py 里的 name 为 ming ,以便验证重载是否有效? $ python3 main.pypython /home/MING/main.py(9)() -> my_info.__spec__.loader.load_module()(Pdb) cming 从结果来看,重载是有效的。 4.4 导入器是什么? 导入器(importer),也许你在其他文章里会见到它,但其实它并不是个新鲜的东西。 它只是同时实现了查找器和加载器两种接口的对象,所以你可以说导入器(importer)是查找器(finder),也可以说它是加载器(loader)。 5. 远程导入模块 由于 Python 默认的 查找器和加载器 仅支持本地的模块的导入,并不支持实现远程模块的导入。 为了让你更好的理解 Python Import Hook 机制,我下面会通过实例演示,如何自己实现远程导入模块的导入器。 5.1 动手实现导入器 当导入一个包的时候,Python 解释器首先会从 sys.meta_path 中拿到查找器列表。 默认顺序是:内建模块查找器 -> 冻结模块查找器 -> 第三方模块路径(本地的 sys.path)查找器 若经过这三个查找器,仍然无法查找到所需的模块,则会抛出ImportError异常。 因此要实现远程导入模块,有两种思路。 一种是实现自己的元路径导入器; 另一种是编写一个钩子,添加到sys.path_hooks里,识别特定的目录命名模式。 我这里选择第一种方法来做为示例。 实现导入器,我们需要分别查找器和加载器。 首先是查找器 由源码得知,路径查找器分为两种 MetaPathFinder PathEntryFinder 这里使用 MetaPathFinder 来进行查找器的编写。 在 Python 3.4 版本之前,查找器必须实现 find_module() 方法,而 Python 3.4+ 版,则推荐使用 find_spec() 方法,但这并不意味着你不能使用 find_module(),但是在没有 find_spec() 方法时,导入协议还是会尝试 find_module() 方法。 我先举例下使用 find_module() 该如何写。 from importlib import abc class UrlMetaFinder(abc.MetaPathFinder): def __init__(self, baseurl): self._baseurl = baseurl def find_module(self, fullname, path=None): if path is None: baseurl = self._baseurl else: # 不是原定义的url就直接返回不存在 if not path.startswith(self._baseurl): return None baseurl = path try: loader = UrlMetaLoader(baseurl) # loader.load_module(fullname) except Exception: return None若使用 find_spec() ,要注意此方法的调用需要带有两到三个参数。 第一个是被导入模块的完整限定名称,例如 foo.bar.baz。第二个参数是供模块搜索使用的路径条目。对于最高层级模块,第二个参数为 None,但对于子模块或子包,第二个参数为父包 path 属性的值。如果相应的 path 属性无法访问,将引发ModuleNotFoundError。第三个参数是一个将被作为稍后加载目标的现有模块对象。导入系统仅会在重加载期间传入一个目标模块。 from importlib import abcfrom importlib.machinery import ModuleSpec class UrlMetaFinder(abc.MetaPathFinder): def __init__(self, baseurl): self._baseurl = baseurl def find_spec(self, fullname, path=None, target=None): if path is None: baseurl = self._baseurl else: # 不是原定义的url就直接返回不存在 if not path.startswith(self._baseurl): return None baseurl = path try: loader = UrlMetaLoader(baseurl) return ModuleSpec(fullname, loader, is_package=loader.is_package(fullname)) except Exception: return None 接下来是加载器 由源码得知,路径查找器分为两种 FileLoaderSourceLoader按理说,两种加载器都可以实现我们想要的功能,我这里选用 SourceLoader 来示范。 在 SourceLoader 这个抽象类里,有几个很重要的方法,在你写实现加载器的时候需要注意 get_code:获取源代码,可以根据自己场景实现实现。 exec_module:执行源代码,并将变量赋值给 module.dict get_data:抽象方法,必须实现,返回指定路径的字节码。 get_filename:抽象方法,必须实现,返回文件名 在一些老的博客文章中,你会经常看到 加载器 要实现 load_module() ,而这个方法早已在 Python 3.4 的时候就被废弃了,当然为了兼容考虑,你若使用 load_module() 也是可以的。 from importlib import abc class UrlMetaLoader(abc.SourceLoader): def __init__(self, baseurl): self.baseurl = baseurl def get_code(self, fullname): f = urllib2.urlopen(self.get_filename(fullname)) return f.read() def load_module(self, fullname): code = self.get_code(fullname) mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) mod.__file__ = self.get_filename(fullname) mod.__loader__ = self mod.__package__ = fullname exec(code, mod.__dict__) return None def get_data(self): pass def execute_module(self, module): pass def get_filename(self, fullname): return self.baseurl + fullname + '.py' 当你使用这种旧模式实现自己的加载时,你需要注意两点,很重要: execute_module 必须重载,而且不应该有任何逻辑,即使它并不是抽象方法。 load_module,需要你在查找器里手动执行,才能实现模块的加载。。 做为替换,你应该使用 execute_module() 和 create_module() 。由于基类里已经实现了 execute_module 和 create_module(),并且满足我们的使用场景。我这边可以不用重复实现。和旧模式相比,这里也不需要在设查找器里手动执行 execute_module()。 import urllib.request as urllib2 class UrlMetaLoader(importlib.abc.SourceLoader): def __init__(self, baseurl): self.baseurl = baseurl def get_code(self, fullname): f = urllib2.urlopen(self.get_filename(fullname)) return f.read() def get_data(self): pass def get_filename(self, fullname): return self.baseurl + fullname + '.py'查找器和加载器都有了,别忘了往sys.meta_path 注册我们自定义的查找器(UrlMetaFinder)。 def install_meta(address): finder = UrlMetaFinder(address) sys.meta_path.append(finder)所有的代码都解析完毕后,我们将其整理在一个模块(my_importer.py)中 my_importer.py import sysimport importlibimport urllib.request as urllib2 class UrlMetaFinder(importlib.abc.MetaPathFinder): def __init__(self, baseurl): self._baseurl = baseurl def find_module(self, fullname, path=None): if path is None: baseurl = self._baseurl else: # 不是原定义的url就直接返回不存在 if not path.startswith(self._baseurl): return None baseurl = path try: loader = UrlMetaLoader(baseurl) except Exception: return None class UrlMetaLoader(importlib.abc.SourceLoader): def __init__(self, baseurl): self.baseurl = baseurl def get_code(self, fullname): f = urllib2.urlopen(self.get_filename(fullname)) return f.read() def get_data(self): pass def get_filename(self, fullname): return self.baseurl + fullname + '.py' def install_meta(address): finder = UrlMetaFinder(address) sys.meta_path.append(finder) 5.2 搭建远程服务端 最开始我说了,要实现一个远程导入模块的方法。 我还缺一个在远端的服务器,来存放我的模块,为了方便,我使用python自带的http.server 模块用一条命令即可实现。 $ mkdir httpserver && cd httpserver$ cat>my_info.pyname='Python编程时光'print('ok')EOF $ cat my_info.pyname='Python编程时光'print('ok')$ $ python3 -m http.server 12800Serving HTTP on 0.0.0.0 port 12800 (http://0.0.0.0:12800/) ......一切准备好,我们就可以验证了。 from my_importer import install_metainstall_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder import my_info # 打印ok,说明导入成功 ok my_info.name # 验证可以取得到变量 'Python编程时光'至此,我实现了一个简易的可以导入远程服务器上的模块的导入器。

优秀的个人博客,低调大师

ASP.NET Core on K8S深入学习(3-2)DaemonSet与Job

​ 一、DaemonSet 1.1 DaemonSet是个啥? Deployment的部署可以指定副本Pod分布在多个Node节点上,且每个Node都可以运行多个Pod副本。而DaemonSet呢,它倔强地保证在每个Node上都只运行一个Pod副本。 回想一下项目经历,有哪些场景满足这个特质呢?是不是一些集群的日志、监控或者其他系统管理应用? 日志收集,比如fluentd,logstash等 系统监控,比如Prometheus Node Exporter,collectd,New Relic agent,Ganglia gmond等 系统程序,比如kube-proxy,kube-dns,glusterd,ceph等 Prometheus Node Exporter Dashboard 1.2 K8S中的DaemonSet 在K8S中,就有一些默认的使用DaemonSet方式运行的系统组件,比如我们可以通过下面一句命令查看: kubectl get daemonset --namespace=kube-system 可以看到,kube-flannel-ds 和 kube-proxy 是K8S以DaemonSet方式运行的系统组件,分别为K8S集群负责提供网络连接支持和代理支持,这里不深入讨论它们的详细情况,只需要了解它们负责什么就可以了。在通过查看Pod副本,看看各个节点的分布情况: kubectl get pod --namespace=kube-system -o wide 可以看到,它们两分布在各个Node节点上(这里是我的K8S集群中的所有节点了),且每个节点上只有一个Pod副本。 1.3DaemonSet的创建和运行 同之前的创建资源方式一样,仍然采用通过YAML配置文件的方式进行创建,只需要指定kind: DaemonSet即可: apiVersion: apps/v1 kind: DaemonSet 这里我们以Prometheus Node Exporter为例演示一下如何运行我们自己的DaemonSet。 _PS:_Prometheus是流行的系统监控方案,而Node Exporter负责收集节点上的metrics监控数据,并将数据推送给Prometheus。Prometheus则负责存储这些数据,Grafana最终将这些数据通过网页以图形的形式展现给用户。 下面是yaml配置文件对于DaemonSet资源清单的定义: apiVersion: apps/v1 kind: DaemonSet metadata: name: node-exporter-daemonset namespace: agent spec: selector: matchLabels: app: prometheus template: metadata: labels: app: prometheus spec: hostNetwork: true containers: - name: node-exporter image: prom/node-exporter imagePullPolicy: IfNotPresent command: - /bin/node_exporter - --path.procfs - /host/proc - --path.sysfs - /host/sys - --collector.filesystem.ignored-mount-points - ^/(sys|proc|dev|host|etc)($|/) volumeMounts: - name: proc mountPath: /host/proc - name: sys mountPath: /host/sys - name: root mountPath: /rootfs volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys - name: root hostPath: path: / 这里暂且不纠结其中的配置内容,包括Host网络、容器启动命令以及Volume,后面会专题介绍。 同样,通过kubectl创建资源: kubectl apply -f node-exporter.yaml 然后,通过kubectl查看Pod分布情况: 可以看出,我们的Prometheus Node Exporter部署成功,且分别在两个Node节点都只部署了一个Pod副本。 二、Job 2.1 关于Job 对于ReplicaSet、Deployment、DaemonSet等类型的控制器而言,它希望Pod保持预期数目并且持久运行下去,除非用户明确删除,否则这些对象一直存在,因此可以说他们说持久服务型任务的。 对于非耐久性任务,比如压缩文件,任务完成后,Pod需要结束运行,不需要Ppod继续保持在系统中,这个时候就要用到Job。因此也可以说,Job是对ReplicaSet、Deployment、DaemonSet等持久性控制器的补充。 2.2 Job的创建与运行 同之前的创建资源方式一样,仍然采用通过YAML配置文件的方式进行创建,需要指定apiVersioin: batch 以及 kind: Job即可: apiVersion: batch/v1 (1)第一个Job 这里我们以一个简单的小Job为例,看看一个简单的Job:当Job启动后,只运行一个Pod,Pod运行结束后整个Job也就立刻结束。 apiVersion: batch/v1 kind: Job metadata: name: edc-job-hello-job namespace: jobs spec: template: metadata: labels: app: edc-job-hello-job spec: containers: - name: hello-job image: busybox imagePullPolicy: IfNotPresent command: ["echo", "hello edison's k8s job!"] restartPolicy: Never 这里需要注意的是,对Job而言,其restartPolicy只能为Never或者OnFailure,这也是它与其他控制器的差别(如Deployment控制器还允许设置为Always)。这个Job要执行的任务也很简单,就是输出一段话“hello edison's k8s job!”就结束其生命了。 _PS:_这里用到了一个busybox的镜像,busybox是一个软件工具箱,里边集成了Linux中几百个常用的Linux命令以及工具。如果我们只需要一个小型的Linux运行环境跑命令,完全可以使用这个busybox镜像,而不用拉取一个CentOS镜像。 通过查看Job运行情况可以知道,其运行结束就结束了,如下图所示,变成了Completed状态。 kubectl get pod -n jobs 还可以通过查看Log看看这个Job留下的足迹: kubectl get pod -n jobs --show-all kubectl logs edc-job-hello-job-whcts (2)并行Job 如果希望能够同时并行运行多个Pod以提高Job的执行效率,Job提供了一个贴心的配置:parallesim。例如下面的配置,我们将上面的小Job改为并行运行的Pod数量设置为3。 apiVersion: batch/v1 kind: Job metadata: name: edc-job-hello-job namespace: jobs spec: parallelism: 3 template: metadata: labels: app: edc-job-hello-job spec: containers: - name: hello-job image: busybox imagePullPolicy: IfNotPresent command: ["echo", "hello edison's k8s job!"] restartPolicy: OnFailure _PS:_默认parallelism值为1 使用上面的配置文件创建了资源后,通过以下命令查看验证: kubectl get job -n jobs kubectl get pod -o wide -n jobs 可以看出,Job一共启动了3个Pod,都是同时结束的(可以看到三个Pod的AGE都是相同的)。 此外,Job还提供了一个completions属性使我们可以设置Job完成的Pod总数,还是上面的例子: apiVersion: batch/v1 kind: Job metadata: name: edc-job-hello-job namespace: jobs spec: parallelism: 3 completions: 6 template: metadata: labels: app: edc-job-hello-job spec: containers: - name: hello-job image: busybox imagePullPolicy: IfNotPresent command: ["echo", "hello edison's k8s job!"] restartPolicy: OnFailure _PS:_默认completions也为1 上面的配置意思就是:每次运行3个Pod,直到总共有6个Pod就算成功完成。同样通过命令验证一下: 可以看到,状态和AGE都符合预期,第一批3个Pod的AGE为12s,第二批3个Pod的AGE为14s。 2.3 CronJob的创建与运行 我们都知道在Linux中,Cron程序可以定时执行任务,而在K8S中也提供了一个CronJob帮助我们实现定时任务。 继续以上面的例子,我们增加一些配置: apiVersion: batch/v1beta1 kind: CronJob metadata: name: edc-cron-job namespace: jobs spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: cron-job image: busybox imagePullPolicy: IfNotPresent command: ["echo", "hello edison's k8s cron job!"] restartPolicy: OnFailure 上面加粗的配置是CronJob的独有配置,需要注意的是schedule,它的格式和Linux Cron一样,这里的"*/1 * * * *"代表每一分钟启动执行一次。对于CronJob,它需要的是jobTemplate来定义Job的模板。 同样,隔几分钟之后,通过命令来验证一下: 可以看到,在过去的三分钟里,每一分钟都启动了一个Pod,符合预期。 三、小结 Deployment可以满足我们大部分时候的应用部署(无状态服务类容器),但是针对一些特殊的场景应用,Deployment就无法胜任了。比如日志收集、系统监控等场景,就可以使用今天介绍的DaemonSet。又比如批处理定时任务,则可以使用今天介绍的Job/CronJob。 参考资料 (1)CloudMan,《每天5分钟玩转Kubernetes》 (2)李振良,《一天入门Kubernets教程》 (3)马哥(马永亮),《Kubernetes快速入门》 (4)阿龙,《Kubernetes系列-07.Pod控制器详解》 (5)elvis,《K8S-Job与CronJob的使用》 (6)五星上炕,《Kubernetes之Job详解》

优秀的个人博客,低调大师

【个人学习JAVA---学后的梳理】三、浅纳Java的数据类型

Java数据类型的简单归纳打理,顺带补上 常量与变量 1. 编程元素---常量与变量 1.1:常量 顾名思义,常量就是固定不变的量(不变的是其直接对象地址),即:定义完成就无法被改变。 声明常量的语法如下: final <数据类型> <常量名>[=赋值]; 进一步说明,常量的名称就采用全大写字母,以便区分于其他变量。其中定义的关键字final,不仅定义于变量,还用于定义类的成员方法,这就意味着被final定义的方法将不会再被改变。 1.2: 变量 变量是利用声明的方式,将内存中的某个块保留下来供该程序使用。 不管变量的值如何更改,所指向的,使用的,都是同一内存空间。变量的作用范围:作用范围,也就是意味着在范围内,该变量名才会有意义、有作用。 那么,不同范围内的变量名是可以存在重名的,程序的运行时访问原则是就近原则,向上查找该变量名。 在一个类中,变量根据范围,有两种类型: 成员变量类主体中定义的变量为成员变量。其作用范围是在整个类都能访问到。 局部变量在某个方法,某个代码块,某个循环里所定义的变量,其只在这些局部的范围内有意义。 2. Java的数据类型 在Java中规定了8种基本数据类型,也存在引用类型的数据类型。如下图示: 图1-Java数据类型图 2.1:8种基本数据类型 数据类型 说明 位 可表示的数据范围 默认初值 long 长整数 64 能保存宇宙中最大的整数(正负) 0L int 整数 32 -2147483648 ~ 2147483647 0 short 短整数 16 -32768 ~ 32767 (short)0 byte 位 8 -128 ~ 127 (byte)0 char 字符 2 0~255 u000(空) float 单精度 32 可有6、7个小数位的精确度 0.0f double 双精度 64 可有15个小数位的精确度 0.0d 2.1.1: byte类型 1个字节有8位。 byte类型数据占据1个字节的内存空间。 2.1.2. short类型 赋值整数前以"(short)"表示该整数类型是short类型,否则是int型。 short类型数据占据2个字节的内存空间。 2.1.3: int类型 Java中默认的整数数据类型就是int型。数据溢出时,溢出的数值将与该类型的最小数值相加后再赋值。也就是说,MAX_VALUE+n后的值是MIN_VALUE+[n-1]。 int类型数据占据4个字节的内存空间。 2.1.4: long类型 long类型数据占据8个字节的内存空间。 2.1.5: char类型 char类型数据占据2个字节的内存空间。 2.1.6: float类型 float类型数据占据4个字节的内存空间。 2.1.7: double类型 double类型数据占据8个字节的内存空间。 2.1.8: boolean类型 boolean类型数据只有true、false两个值,默认初值为false。 明显,布尔值是确定判断的真假。当然,真假的表示不仅仅是true与false,还能用数字,也就是说:非正数表false,正数表true。 2.2:引用数据类型 引用,就是一个指向了类的实例化对象的变量,那么该变量就属于引用数据类型。 3. 数据类型的转换 数据类型的有着严格的限制,实现类型转换须经过严格的步骤和规定。 转换方式有着两种。 3.1. 自动类型转换 自动转换即是向上类型转换。 自动转换的两个前提条件: 转换前的数据类型与转换后的类型兼容; 转换后的数据类型的表示范围比转换前的类型大(向上)。 3.2. 强制类型转换 强制转换须以“(转换后的类型)oldData”格式进行。如果强制向下转换,转换后的数据可能相比转换前,会丢失精度。 结束 2019年8月6日 15:49:58

优秀的个人博客,低调大师

【翻译】Sklearn 与 TensorFlow 机器学习实用指南 —— 第11章 训练深层神经网络(上)

第 10 章介绍了人工神经网络,并训练了我们的第一个深度神经网络。 但它是一个非常浅的 DNN,只有两个隐藏层。 如果你需要解决非常复杂的问题,例如检测高分辨率图像中的数百种类型的对象,该怎么办? 你可能需要训练更深的 DNN,也许有 10 层,每层包含数百个神经元,通过数十万个连接来连接。 这不会是闲庭信步: 首先,你将面临棘手的梯度消失问题(或相关的梯度爆炸问题),这会影响深度神经网络,并使较低层难以训练。 其次,对于如此庞大的网络,训练将非常缓慢。 第三,具有数百万参数的模型将会有严重的过拟合训练集的风险。 在本章中,我们将依次讨论这些问题,并提出解决问题的技巧。 我们将从解释梯度消失问题开始,并探讨解决这个问题的一些最流行的解决方案。 接下来我们将看看各种优化器,与普通梯度下降相比,它们可以加速大型模型的训练。 最后,我们将浏览一

优秀的个人博客,低调大师

【翻译】Sklearn 与 TensorFlow 机器学习实用指南 —— 第11章 训练深层神经网络(下)

训练稀疏模型 所有刚刚提出的优化算法都会产生密集的模型,这意味着大多数参数都是非零的。 如果你在运行时需要一个非常快速的模型,或者如果你需要它占用较少的内存,你可能更喜欢用一个稀疏模型来代替。 实现这一点的一个微不足道的方法是像平常一样训练模型,然后摆脱微小的权重(将它们设置为 0)。 另一个选择是在训练过程中应用强 l1 正则化,因为它会推动优化器尽可能多地消除权重(如第 4 章关于 Lasso 回归的讨论)。 但是,在某些情况下,这些技术可能仍然不足。 最后一个选择是应用双重平均,通常称为遵循正则化领导者(FTRL),一种由尤里·涅斯捷罗夫(Yurii Nesterov)提出的技术。 当与 l1 正则化一起使用时,这种技术通常导致非常稀疏的模型。 TensorFlow 在FTRLOptimizer类中实现称为 FTRL-Proxima

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册