首页 文章 精选 留言 我的

精选列表

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

"深度"学习让照片动起来

Facebook最近推出了“3D照片”功能,让普通的照片能有3D显示效果:可以随着手机姿态的变化显示照片的不同视角,打开链接即可体验 https://www.facebook.com/applealmondblog/posts/1922679287846447 (手机上陀螺仪控制,PC上鼠标控制) 可以看到虽然视角变化被限制在较小的范围,但是确实有3D的感觉,其实这样结合陀螺仪的简单3D效果也挺常见的,比如王者荣耀的启动界面: 晃动手机,前后景会随着手机姿态位移变化;一些H5页面也有这种效果,实现就更简单了,前后景两张或多张图片层叠,根据陀螺仪数据改变图片相对位置即可 可以看出,产生3D效果的关键在于z轴,即深度(depth),当图层或像素的深度有差别时,就可以在不同视角下计算出图层或像素新的位置,渲染出当前视角对应的图像。我们把每个像

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

java8学习:CompletableFuture方法使用

下面是自己总结的一部分的api的使用,当然下面有些方法后面加上Async就会变为异步的 /** * 变换:参数为Function接口 * thenApply:将上一个结果当做这次thenApply的参数传入,加以处理后,返回一个值 * 可以传入T返回T也可以传入T返回U */ public void thenApply() { CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> 1).thenApply(i -> String.valueOf(i)); String i = future.join(); System.out.println(i); //1 } /** * 消费 * 参数为Consumer接口 * thenAccept:将上一个的结果当做这次的参数传入,但是无返回值 */ public void thenAccept() { CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> 1).thenAccept((integer -> System.out.println(integer))); future.join(); //1 } public void thenCompose() { CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 1) //提供第一个CompletableFuture结果值 .thenCompose((i) -> //将上一个结果值传入下一个CompletableFuture CompletableFuture.supplyAsync(() -> i + 2)); //对传入的结果值进行处理 Integer join = future.join(); System.out.println(join); } /** * 结合两个结果得到一个最终值 * 逻辑处理需要的是BiFunction接口 * 需要两个CompletableFuture结果,并且需要有处理两个结果的逻辑 * 两个CompletableFuture返回的结果不需要一致 */ public void thenCombine() { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello ") //第一个CompletableFuture返回结果 .thenCombine(CompletableFuture.supplyAsync(() -> 1), //第二个CompletableFuture返回结果 (future1, future2) -> future1 + future2); //处理两个结果的逻辑 String join = future.join(); System.out.println(join); //Hello World } /** * 跟上面的差不多,只不过这里处理逻辑需要的是一个BiConsumer接口,无返回值而已 */ public void thenAcceptBoth() { CompletableFuture<Void> both = CompletableFuture.supplyAsync(() -> "Hello ") //第一个CompletableFuture返回结果 .thenAcceptBoth(CompletableFuture.supplyAsync(() -> "World"), //第二个CompletableFuture返回结果 (future1, future2) -> System.out.println(future1 + future2));//处理两个结果的逻辑 both.join(); //Hello World } /** * 两个CompletableFuture一起执行,哪个先执行完就用哪个CompletableFuture的结果 * 处理逻辑需要的参数是一个Function接口 * 下面整体代码的逻辑是返回1的睡眠一秒,返回2的睡眠0.3秒,所以返回2的肯定比1要快,所以结果为2,然后应用到处理逻辑上 * 最终结果就是为 2 done * 两个CompletableFuture返回的参数需要一致 * 这适用于两种渠道完成同一个事情,就可以调用这个方法,找一个最快的结果进行处理,最终有返回值。 */ public void applyToEither() { CompletableFuture<String> apply = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "1"; }).applyToEither( CompletableFuture.supplyAsync(() -> { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } return "2"; }), (future) -> future + " done" //对于首先完成的CompletableFuture任务之后执行的逻辑 ); String join = apply.join(); System.out.println(join);//2 done } /** * 与上面使用方法一致,只是多加了一个选项 * 结果为3 three */ public void applyToEithers() { CompletableFuture<String> apply = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "1"; }).applyToEither( CompletableFuture.supplyAsync(() -> { try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } return "2"; }),(future) -> future + " done" //对于首先完成的CompletableFuture任务之后执行的逻辑 ).applyToEither( CompletableFuture.supplyAsync(() -> { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } return "3"; }),(future -> future + " three") ); String join = apply.join(); System.out.println(join);//2 done } /** * 对于出错的处理过程进行补偿,即如果future出错,会运行exceptionally里的Function函数 * 当然没有错就不会运行后面指定的函数 */ @Test public void exceptionally() { CompletableFuture<String> exceptionally = CompletableFuture.supplyAsync(() -> { int i = 1 / 0 ; return "1"; }).exceptionally(ex -> { ex.printStackTrace(); return "error"; }); String join = exceptionally.join(); System.out.println(join); } /** * 输出 * error * java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero * at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273) * at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280) * at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592) * at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582) * at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) * at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) * at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) * at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) * Caused by: java.lang.ArithmeticException: / by zero * at com.qidai.demotest.MyTestx.lambda$exceptionally$18(MyTestx.java:125) * at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590) * ... 5 more */ /** * 与上面的thenAccept相比较,这个方法并不需要输入参数,相同点是都是无返回值 */ public void thenRun() { CompletableFuture<Void> runnable = CompletableFuture.supplyAsync(() -> "1").thenRun(() -> System.out.println("runnable")); runnable.join(); }

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

java8学习:使用流(1)

内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。书籍购买地址:java8实战 筛选和切片 filter方法 //取出是素菜的菜单 menu.stream() .filter(Dish::isVegetarian) //代表是否是素食,这里条件并不一定是方法调用,只要的你条件能够返回boolean就可以 .collect(Collectors.toList()); limit方法 //取出菜单的前三个 menu.stream().limit(3) //跟sql中limit一样,不过这个是短路limit,也就是说,它取出了三个后就不再遍历后面的元素了,直接返回 .collect(Collectors.toList()); distinct方法:去重 menu.stream() .distinct() //去重操作,根据元素的hashCode和equals方法判断是否相等 .collect(Collectors.toList()); skip方法:跳过指定数量的元素 //取菜单中第三个到第五个 menu.stream() .skip(2) //跳过开头两个元素 .limit(3) //然后截取第三个到第五个 .forEach(System.out::println); //如上可以看出skip和limit是互补的,因为limit没有这样的形式:limit(start,end) 映射 提到这个概念,就是把你stream里的每个元素都应用到自己指定的表达式之上然后返回,就比如 List<String> list = Arrays.asList("1","2","3"); list.stream() .映射方法(str -> str + "dada") .forEach(System.out::println); //以上为伪代码 //说明的意思就是list中的每个元素:1,2,3都会应用到表达式:str + "dada" //并返回表达式的值 //所以结果就是1dada,2dada,3dada map方法 //这里提到的map方法,完全可以替换上面的伪代码:映射方法,上面代码是传入A类型返回A类型的演示 //也就是传入字符串1,2,3返回的也是字符串 //map当然可以传入A类型返回B类型,比如 List<String> list = Arrays.asList("1","2","3"); list.stream() .map(Integer::parseInt) .forEach(System.out::println); //上面就是传入String,返回的是int //当然也可以是返回字符串的长度 考虑一个场景,把n个单词的里面的字母去重然后输出 就比如["abc","bce"],里面的字母就是["a","b","c","b","c","e"],去重后是:["a","b","c","e"],现在来实现它 List<String> list = Arrays.asList("abc","bce"); List<String[]> collect = list.stream() .map(str -> str.split("")) .distinct() .collect(Collectors.toList()); collect.forEach(System.out::println); 上面的感觉是对的,拆分,然后去重,但是一点需要注意,当lambda把第一个元素"abc"进行映射操作的时候,split方法返回的是一个String数组对象,这也就能解释为什么List的泛型是String数组了,数组跟数组distinct那肯定是去重不成功的。 流的过程是这样的["abc","bce"] -> [{"a","b","c"},{"b","c","e"}],然后拿{"a","b","c"}和{"b","c","e"}去重肯定行不通 现在遇到的问题就是:我们希望把abc和bce切分然后合并成一个流,然后进行去重,我们接着实验 Array::stream Array::stream可以接受一个数组并产生一个流,如下 String[] str = {"abc","bce"}; Stream<String> stream = Arrays.stream(str); stream.forEach(System.out::println); //abc //bce 我们在上面的split方法返回的就是数组,我们尝试将split返回的数组映射到此方法上,看看能不能合并成一条流 List<String> list = Arrays.asList("abc","bce"); List<Stream<String>> collect = list.stream() .map(str -> str.split("")) .map(Arrays::stream) .distinct() .collect(Collectors.toList()); //java.util.stream.ReferencePipeline$Head@2aae9190 //java.util.stream.ReferencePipeline$Head@2f333739 但是并不尽如人意,因为他返回的泛型是Stream,也就是说,Array::stream方法只是把一个数组转换为一个流,流中的元素是String所以这个流就是这样的Stream,这种情况类似上面遇到的返回的是两个数组,现在返回的List中装入的并不是String,而是两条流,流跟流做去重也是行不通的 解决方案 现在遇到的问题就是:他们都是将一个list元素单独转化为一条流或者一个数组,我们需要的是转换为流或者数组之后,再将这些返回的数组或者流接为一个流或者数组,就像接水管一样,让他们连起来 尝试方法:flatmap List<String> list = Arrays.asList("abc","bce"); List<String> collect = list.stream() .map(str -> str.split("")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList()); collect.forEach(System.out::println); //a b c e 如上的代码终于是返回了一个LIst,所以结果也是我们期待的达到了去重的效果,那么它的流的处理过程是怎么样的呢?如下 传入"abc"->切分为["a","b","c"] --->flatMap合并["a","b","c","b","c","e"]->去重->ok 传入"bce"->切分为["b","c","e"] 上面能看懂吗...懒得画图了,第一行和第三行是flatMap前面的split过程,然后是第二行存入flatMap进行合并流 flatMap也就是把n个流合并为一个流 小实战 要求两个list:["123"],["456"],然后组成元组:(1,4),(1,5),(1,6),(2,4),(2,5)... List<String> first = Arrays.asList("123"); List<String> second = Arrays.asList("456"); first.stream() .map(str -> str.split("")) .forEach(strs -> { Arrays.stream(strs).forEach(f -> { second.stream() .map(sec -> sec.split("")) .forEach(secs -> Arrays.stream(secs).forEach(s -> System.out.println("("+f+","+s+")"))); }); }); //上面代码是直接输出的,那么下面的就是将元组组合成一个LIst返回了 List<String> first = Arrays.asList("123"); List<String> second = Arrays.asList("456"); List<String> collect = first.stream() .map(str -> str.split("")) .flatMap(strs -> //将strs后的表达式返回的stream都合并为一个 Arrays.stream(strs).flatMap(f -> second.stream() .map(sec -> sec.split("")) .flatMap(secs -> Arrays.stream(secs).map(s -> "(" + f + "," + s + ")"))) ).collect(Collectors.toList()); collect.forEach(System.out::println); //耐心看..... 查找和匹配 anyMatch:至少匹配一个 List<String> list = Arrays.asList("1","2","3","4"); boolean b = list.stream().anyMatch(s -> s.equals("1")); //true allMatch:全部匹配 List<String> list = Arrays.asList("1","2","3","4"); boolean b = list.stream().allMatch(s -> Integer.parseInt(s) < 5); //true noneMatch:全部不匹配 List<String> list = Arrays.asList("1","2","3","4"); boolean b = list.stream().noneMatch(s -> Integer.parseInt(s) > 5); //true 如上的方法都具有短路效果,意思就是主要遇到一个不匹配条件的元素就立刻返回true或者false findAny:找任意一个 List<String> list = Arrays.asList("1","2","3","4"); Optional<String> any = list.stream().findAny(); System.out.println(any.get()); //虽然书上说是任意一个,但是我一直返回的是1 //自己测试的将元素添加到十个,stream依旧是1,但是使用并行流parallelStream,将随机返回,但是依然是区间比较小 Option以后的帖子会说到的,你可以看一下我相关的帖子,只要记住Option是一个值的容器,因为findAny找的可能是个空列表,所以他可能会返回null,然后将返回的这个值包装在Option中,然后调用get方法就会出现返回的值,如果Option中没有值还get那么就会报错,现在只要知道这点就可以了 findFirst:找到第一个 List<String> list = Arrays.asList("1","2","3","4"); Optional<String> any = list.stream().findFirst(); System.out.println(any.get()); //1 归约reduce 上面的映射和这词提到的归约可以一起使用,类似hadoop中的mr模型 元素求和 int[] is = {1,2,3,4,5,6,7,8,9}; Arrays.stream(is).reduce(0,Integer::sum);//45 //以0为起始值,所以如果数组内没有元素,也不至于null,然后依次相加 //0+1=1 //1+2=3 //3+3=6 //6+4=10 .... int[] is = {1,2,3,4,5,6,7,8,9}; OptionalInt reduce = Arrays.stream(is).reduce(Integer::sum); System.out.println("reduce = " + reduce.getAsInt());//45 //OptionalInt的出现就是因为它没有初始值进行累加,所以如果数组为空,那么将返回null //OptionalInt使用方法和Optional一样,只是方法名变了一下,目前只知道这些就好了 元素最大值和最小值 //max int[] is = {1,2,3,4,5,6,7,8,9}; OptionalInt reduce = Arrays.stream(is).reduce(Integer::max); System.out.println("reduce = " + reduce.getAsInt()); //9 //求最小改为min方法即可,也可以自己实现 OptionalInt reduce = Arrays.stream(is).reduce((a,b) -> a > b ? a : b ); 归约方法的优势和并行化 求和方法:定义一个int变量,然后迭代去加。相比之下reduce将其转换为了内部迭代。而且迭代要去求和并且更新我们的一个int共享变量,这对于并行化来说并不容易实现,如果加入了同步,可能线程切换的开销就已经抵消了并行带来的性能提升。(可变的变量累计器对于并行来说并不好),如上的代码为了实现并行只需要把stream方法改为parallelStream()即可 int[] is = {1,2,3,4,5,6,7,8,9}; int sum = 0; //int共享变量 //这只是单线程的,如果是多线程的话,为了保证sum的正确肯定要sync。 //所以这就是说的共享变量并不适合于并行化 for (int i : is) { sum += i; //更新共享变量 } 流操作的有状态和无状态 如果你购买了书可以先去看看书的定义,下面是我自己的理解 int[] is = {1,2,3,4,5,6,7,8,9}; Arrays.stream(is) .filter(i -> i > 3) //无状态 .map(i -> i + 1) //无状态 .distinct() //有状态 .max(); //有状态 对于上面来说,filter和map只是接收一个元素然后过滤映射一个元素,这个元素处理完就交给下面的方法处理了,自己并不保留这个元素,这样的叫做无状态 那distinct和max来说,他不能接收一个处理一个然后再交给下面的方法处理,因为它需要拿到整个元素来去重和去最大值,如果拿到部分他肯定是不能做这些操作的,元素就暂时的保留在了方法中,所以这样的叫做有状态 接下来的东西下一篇讲

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

SQL数据库学习之路(八)

数据库介绍: 在网页上的主程序中进行注册操作,然后把数据发送给人,人传递这些数据到数据库当中。 为什么学ADO.NET:之前我们所学的只能在查询分析器中查看数据,操作数据。我们不能让一个普通用户使用SQL去操作,所以我们搭建一个界面(Web Winform)让用户方便地去使用数据库中的数据。 什么是ADO.NET:ADO.NET就是一组类库,这组类库可以让我们通过程序去访问数据库,就像System.IO下的类用类操作文件一样,System.Data这组类是用来操作数据库,它提供了统一的编程接口。 数据提供程序(常用类): 1.connection :用来连接数据库 2.command :用来执行SQL语句 3.datareader :只读、只进的结果集,一条一条读取数据。 4dataadapter :一个封装了上面三个对象的对象。 数据集(DataSet):临时数据库,断开式数据操作 VS中连接数据库 1.打开VS2015,在左上角文件中选择新建项目。选择C#中的控制台应用程序,自己定义命名,点击确定,出现如下界面。 2.打开服务器资源管理器,或者视图,服务器资源管器 。 右键数据连接,创建新连接后如图所示 。 .服务器名是你要连接的服务器名,可以是你自己的服务器也可以是别人的服务器,点击下拉框按钮会出现在局域网中所有的服务器。身份验证当你连接你自己的服务器可以是Windows身份验证,如果连接局域网的服务器必须是SQL server 验证,需提供登录名和密码。连接到数据库就是你自己想用的服务器的数据库。 3.点击在数据连接中出现的数据库,右键选择属性,在属性中复制连接字符串的内容。 Data Source=****;(这是指服务器的名字)Initial Catalog=Text;(这是要连接的数据库)Integrated Security=True(以Windows身份登录的) 4.在调用类时增加这一句using System.Data.SqlClient; main()函数代码: int r=0; //连接字符串 string str = "Data Source=********;Initial Catalog=Text;Integrated Security=True"; using (SqlConnection con=new SqlConnection(str)) //连接数据库 { string sql = " ";//输入需要使用的SQL语句 using (SqlCommand cmd =new SqlCommand(sql,con))//要执行的SQL语句 { con.Open(); //打开数据库 //增删改 r = cmd.ExecuteNonQuery(); } Console.WriteLine(r > 0 ? "操作成功" : "操作失败"); //如果r>0控制台显示操作成功,r<0控制台显示操作失败 Console.ReadKey(); }

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

Python高级知识点学习(九)

并发、并行,同步、异步,阻塞、非阻塞 并发、并行 并发是在一个时间段内,有几个程序在同一个cpu上运行,但是任意时刻只有一个程序在cpu上运行。 并行是任意时刻点上,有多个程序同时运行在多个cpu上。 同步、异步 同步是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式。 异步是指代码调用IO操作时,不必等待IO操作完成就返回的调用方式。 阻塞、非阻塞 阻塞是指调用函数时候当前线程被挂起。 非阻塞是指调用函数时候当前线程不会被挂起,而是立即返回。 阻塞和非阻塞的概念和同步异步感觉很像,但是其实它们之间是有区别的。 区别: 同步和异步实际上是消息通信的一种机制,可以把IO操作看做一个消息,调用IO操作时,相当于发一个消息给另外一个线程(或者说另外一个协程),让它去执行某些操作,在提交数据之后立刻得到future,后边就可以通过future拿到结果,实际上是消息之间的通信机制。 阻塞和非阻塞是不同于同步异步的,它是函数调的一种机制。 IO 多路复用 (select、poll 和 epoll) unix中五种I/O模型 阻塞式I/O 非阻塞式I/O I/O复用 信号驱动式I/O (用的比较少) 异步I/O (POSIX的aio_系列函数) 以上五种是递进式的发展。 I/O多路复用: select方法也是阻塞的方法,select本身是阻塞式的,select可以监听多个文件句柄和socket,select在某一个文件句柄或者socket准备好的话就会返回,这时候立刻可以做业务逻辑处理。 I/O多路复用带来的好处是: 比如现在同时发起了100个非阻塞式的请求,这时候直接使用select去监听这100个socket,这样的话一旦有一个发生状态变化,我们就可以立马处理它。 I/O多路复用中,将数据从内核复制到用户空间这段时间消耗还是省不了。 异步IO: 这里的异步IO是真正意义上的异步IO(aio),我们现在接触到很多高并发框架实际上都没有使用异步IO,实际上在很大程度上使用的都是io多路复用技术,IO多路复用很成熟很稳定,异步IO对于IO多路复用性能提升并没有达到很明显的程度,但是编码难度有很大提升,所以当前情况下IO多路复用用的比较多。 异步IO节省掉了数据从内核拷贝到用户空间这一步骤。 select、poll、epoll: select、poll、epoll都是I/O多路复用的机制。 I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般就是读就绪或写就绪),能够通知程序进行相应的读写操作。 但select、poll、epoll本质上都是同步I/O,因为它们都需要在读写事件就绪后自己负责进行读写(数据从内核拷贝到用户区),也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。 select是什么? select 函数监视的文件描述符分三类,分别是writefds、readfds、exceptfds。调用select后会阻塞,直到有描述符就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset来找到就绪的描述符。 select目前几乎在所有的平台上支持,其良好的跨平台也是它的一个优点。select的一个缺点在于单个进程监视的文件描述符的数量有最大限制,在linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一性质,但是这样也会造成效率的降低。 poll是什么? 不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。pollfd结构包含了要监视的event和发生的event,不再使用select "参数-值" 传递的方式。同时,pollfd并没有最大数量限制(但是数量过大性能也会下降)。和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。 从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪的状态,因此随着监视的描述符数量的增长,其效率也会线性下降。 epoll是什么? epoll是在2.6内核中提出的,epoll是之前的select和poll的增强版本。相对于select和poll来讲,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需要一次。 epoll它的查询使用了数据结构中性能很高的一个:红黑树。 nginx就是使用了epoll。 epoll并不代表一定比select好: 在并发高的情况下,连接活跃度不是很高, epoll比select。 并发性不高,同时连接很活跃, select比epoll好。 非阻塞I/O实现http请求 上示例代码: import socket from urllib.parse import urlparse def get_url(url): # 通过socket请求html url = urlparse(url) host = url.netloc path = url.path if path == "": path = "/" # 建立socket连接 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 这里会导致后边抛异常,但是连接请求已经发出去了 client.setblocking(False) # 捕获异常 try: client.connect((host, 80)) # 阻塞不会消耗cpu except BlockingIOError as e: pass # 不停的询问连接是否建立好, 需要while循环不停的去检查状态 # 做计算任务或者再次发起其他的连接请求 while True: try: client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) break except OSError as e: pass data = b"" while True: # 这里还会抛异常,读不到就继续读 try: d = client.recv(1024) except BlockingIOError as e: continue if d: data += d else: break data = data.decode("utf8") html_data = data.split("\r\n\r\n")[1] #打印返回的数据 print(html_data) client.close() if __name__ == "__main__": get_url("http://www.baidu.com") 非阻塞I/O整个过程依赖前后的监测,整个过程不停的做while循环检测状态,但是返回时间没有变,所以并没有提高并发。 select+回调+事件循环实现http请求 目前开源的高性能框架,一般都是使用这种方式实现并发。 使用select + 回调 + 事件循环实现下载网页,并发性高且是单线程。 select方法本尊是在import select这个包里边,但是有另外一个包把select基础上进行了封装,用起来更简单:from selectors import DefaultSelector,DefaultSelector一般使用DefaultSelector这个比较多。 看代码示例: import socket import time from urllib.parse import urlparse from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE selector = DefaultSelector() urls = [] stop = False class Fetcher: def connected(self, key): selector.unregister(key.fd) self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(self.path, self.host).encode("utf8") selector.register(self.client.fileno(), EVENT_READ, self.readable) # 当socket可读时,读数据,全部都是cpu操作 def readable(self, key): d = self.client.recv(1024) if d: self.data += d else: # 数据读完为空 selector.unregister(key.fd) data = self.data.decode("utf8") html_data = data.split("\r\n\r\n")[1] print(html_data[:30]) self.client.close() urls.remove(self.spider_url) if not urls: global stop stop = True def get_url(self, url): self.spider_url = url url = urlparse(url) self.host = url.netloc self.path = url.path self.data = b"" if self.path == "": self.path = "/" # 建立 socket 连接 self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client.setblocking(False) try: self.client.connect((self.host, 80)) # 阻塞不会消耗cpu except BlockingIOError as e: pass selector.register(self.client.fileno(), EVENT_WRITE, self.connected) # 驱动整个事件循环 def loop(): while not stop: ready = selector.select() for key, mask in ready: call_back = key.data call_back(key) if __name__ == "__main__": # 计时开始 start_time = time.time() for url in range(60): url = "http://www.baidu.com" urls.append(url) fetcher = Fetcher() fetcher.get_url(url) loop() print(time.time()-start_time) 上边代码中,Fetcher类包含三个方法,get_url简历socket连接,connected和readable是两个回调函数。 loop函数负责驱动整个事件循环。 回调的缺点 可读性差 共享状态异常处理 异常处理困难

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

Python高级知识点学习(八)

线程同步 - condition介绍 多线程中的另外一个重要点就是condition:条件变量。 condition是python多线程编程中用于复杂线程间通信的一个锁 叫做条件变量。 cond = threading.Condition() with self.cond: cond.notify() cond.wait() condition有两层锁, 一把底层锁会在线程调用了wait方法的时候释放, 上面的锁会在每次调用wait的时候分配一把并放入到cond的等待队列中,等到notify方法的唤醒。 有关condition的详情请查阅资料。(这里作者自己暂时还没理清楚原理,见谅) 线程同步 - Semaphore 介绍 信号量,Semaphore。 Semaphore 是用于控制进入数量的锁,控制进入某段代码的线程数。 文件, 读、写, 写一般只是用于一个线程写,读可以允许有多个。 ThreadPoolExecutor线程池 多线程和多进程对比 运算,耗cpu的操作,用多进程编程 对于io操作来说, 使用多线程编程 由于进程切换代价要高于线程,所以能使用线程就不用进程。 耗费cpu的操作: def fib(n): if n<=2: return 1 return fib(n-1)+fib(n-2) if __name__ == "__main__": with ThreadPoolExecutor(3) as executor: all_task = [executor.submit(fib, (num)) for num in range(1, 10)] start_time = time.time() for future in as_completed(all_task): data = future.result() print("exe result: {}".format(data)) print("last time is: {}".format(time.time()-start_time)) 模拟IO操作: def random_sleep(n): time.sleep(n) return n if __name__ == "__main__": with ProcessPoolExecutor(3) as executor: all_task = [executor.submit(random_sleep, (num)) for num in [2]*30] start_time = time.time() for future in as_completed(all_task): data = future.result() print("exe result: {}".format(data)) print("last time is: {}".format(time.time()-start_time)) multiprocessing 多进程 使用os.fork创建子进程,fork只能用于linux/unix中。 import os import time # fork新建子进程 fork只能用于linux/unix中 pid = os.fork() print("a") if pid == 0: print('子进程id:{} ,父进程id是: {}.' .format(os.getpid(), os.getppid())) else: print('我是父进程, 我fork出的子进程id是:{}.'.format(pid)) time.sleep(2) 运行结果: a 我是父进程, 我fork出的子进程id是:3093. a 子进程id:3093 ,父进程id是: 3092. 运行结果中,可以看到打印了两次a,因为在执行完pid = os.fork()这行代码后,就创建了一个子进程,且子进程把父进程中的数据原样拷贝了一份到自己的进程中,所以父进程中打印一次,子进程中又打印一次。 多进程编程: import multiprocessing # 多进程编程 import time def get_html(n): time.sleep(n) print("sub_progress success") return n if __name__ == "__main__": progress = multiprocessing.Process(target=get_html, args=(2,)) print(progress.pid) progress.start() print(progress.pid) progress.join() print("main progress end") 使用进程池: import multiprocessing # 多进程编程 import time def get_html(n): time.sleep(n) print("sub_progress success") return n if __name__ == "__main__": # 使用进程池 pool = multiprocessing.Pool(multiprocessing.cpu_count()) result = pool.apply_async(get_html, args=(3,)) # 等待所有任务完成 pool.close() pool.join() print(result.get()) 进程池另一种方法: import multiprocessing # 多进程编程 import time def get_html(n): time.sleep(n) print("sub_progress success") return n if __name__ == "__main__": # 使用进程池 pool = multiprocessing.Pool(multiprocessing.cpu_count()) for result in pool.imap_unordered(get_html, [1, 5, 3]): print("{} sleep success".format(result)) 进程间通信 Queue、Pipe,Manager 共享全局变量不能适用于多进程编程,可以适用于多线程。 进程间通信和线程间通信有相同也有不同,不同点是之前在多线程中用的线程间通信的类和线程间同步的锁在多进程中是不能用的。 使用multiprocessing中的Queue实现进程通信 import time from multiprocessing import Process, Queue, Pool, Manager, Pipe def producer(queue): queue.put("a") time.sleep(2) def consumer(queue): time.sleep(2) data = queue.get() print(data) if __name__ == "__main__": queue = Queue(10) my_producer = Process(target=producer, args=(queue,)) my_consumer = Process(target=consumer, args=(queue,)) my_producer.start() my_consumer.start() my_producer.join() my_consumer.join() 一定要使用multiprocessing中的Queue,如果使用import queue这个queue是不行的。 pool中的进程间通信需要使用manager中的queue multiprocessing中的queue不能用于pool进程池。 pool中的进程间通信需要使用manager中的queue def producer(queue): queue.put("a") time.sleep(2) def consumer(queue): time.sleep(2) data = queue.get() print(data) if __name__ == "__main__": queue = Manager().Queue(10) pool = Pool(2) pool.apply_async(producer, args=(queue,)) pool.apply_async(consumer, args=(queue,)) pool.close() pool.join() 使用Manager,多进程修改同一变量: def add_data(p_dict, key, value): p_dict[key] = value if __name__ == "__main__": progress_dict = Manager().dict() from queue import PriorityQueue first_progress = Process(target=add_data, args=(progress_dict, "a", 22)) second_progress = Process(target=add_data, args=(progress_dict, "b", 23)) first_progress.start() second_progress.start() first_progress.join() second_progress.join() print(progress_dict) 可以看到两个进程对一个dict变量做值得填充,最终主进程中打印出了最终的dict。 通过pipe实现进程间通信: pipe的性能高于queue。 pipe只能适用于两个进程。 def producer(pipe): pipe.send("a") def consumer(pipe): print(pipe.recv()) if __name__ == "__main__": recevie_pipe, send_pipe = Pipe() #pipe只能适用于两个进程 my_producer = Process(target=producer, args=(send_pipe, )) my_consumer = Process(target=consumer, args=(recevie_pipe,)) my_producer.start() my_consumer.start() my_producer.join() my_consumer.join() my_producer进程给my_consumer进程发送的a变量可以正常打印。

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

Python高级知识点学习(七)

HTTP、Socket、TCP概念 socket属于常用的http协议之下的让我们可以使用tcp/ip协议的一个接口。 socket编程 image.png socket编程的模式其实是非常固定的。 上图: 左侧server端 右侧client端 server必须是随时处于一个监听的状态和服务的状态,因为不知道客户端什么时候会发送来请求。 绑定协议、地址、端口。 每一个应用程序只能占用一个端口,服务器a到服务器b发数据时,数据是不知道是由哪个应用程序接受的,这时候就需要端口机制,每一个应用程序提供一个端口。 socket连接后,只要不关闭连接,服务端可以一直给客户端发送请求,但是在http中,只完成一次发送数据就停止了。 编写测试代码: 新建文件sever.py import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定ip 端口 server.bind(('0.0.0.0', 8000)) # 监听 server.listen() sock, addr = server.accept() # 接受client端发来的数据 data = sock.recv(1024) # 打印数据 print(data.decode('utf8')) # 给client发数据 sock.send("hello".encode("utf8")) # 关闭 server.close() sock.close() 新建文件client.py import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 8000)) # 给server端发数据 client.send("allen".encode("utf-8")) # 接受server端发来的数据 data = client.recv(1024) # 打印数据 print(data.decode('utf8')) # 关闭 client.close() 首先运行server.py,再运行client.py,观察打印结果,可以看到,数据发送接收已经是实现。 socket实现简单聊天 要实现双向交流,肯定不能做close操作,改为一直while循环, 代码: 修改srever.py import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("0.0.0.0", 8000)) server.listen() sock, addr = server.accept() while True: # 接受client端发来的数据 data = sock.recv(1024) # 打印数据 print(data.decode('utf8')) re_data = input() # 给client发数据 sock.send(re_data.encode("utf8")) 修改client.py: import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 8000)) while True: # 输入消息 re_data = input() client.send(re_data.encode("utf8")) # 接受client端发来的数据 data = client.recv(1024) # 打印数据 print(data.decode('utf8')) 运行server.py ,再运行client.py,在client.py中输入要发送的文字,在server.py中观察接收到的文字,再在server.py中发送文字,在client.py中查看。 以上就是实现了最初级的基本聊天过程。 如果要在网页上做一个聊天模块,一般都是需要用web socket来实现。 socket多用户聊天 所谓多用户聊天,其实平时我们也经常遇到。 假如你是一位线上客服人员,你需要接待的人员可能同时有多位,当你和A用户聊天时,并不妨碍和B用户C用户给你发消息,而你回消息,回给A用户的消息只有A用户可以看到,B用户是看不到的,接着看如何实现这种功能。 首先client.py的代码不用改动,只需修改server.py: import socket import threading server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('0.0.0.0', 8000)) server.listen() def handle_sock(sock, addr): while True: data = sock.recv(1024) print(data.decode("utf8")) re_data = input() sock.send(re_data.encode("utf8")) while True: sock, addr = server.accept() #用线程去处理新接收的连接(用户) client_thread = threading.Thread(target=handle_sock, args=(sock, addr)) client_thread.start() 上边代码把接受处理逻辑放到了线程中,每一个线程存放一个不同的socket,主线程来查看有哪些线程进入。 运行server.py,再运行多个client.py,client.py给server发消息,测试可发现可实现上边的客服功能。 注:真正客服系统要比这个复杂得多,以上代码仅供测试。 socket模拟http请求 我们平常所用到的requests包,是基于 urllib,urllib实际上是基于socket上来完成的。 requests - urlib - socket 如何通过socket去完成类似urllib中http请求呢? http请求无非就是在tcp协议之上加了一些协议,只要按照这个协议发,就会返回数据。 看代码: import socket from urllib.parse import urlparse def request_demo(url): # url拆分 url = urlparse(url) host = url.netloc path = url.path if path == "": path = "/" # 建立socket连接 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((host, 80)) client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path, host).encode("utf8")) data = b"" # 每次读取1024大小,循环知道读取完毕 while True: d = client.recv(1024) if d: data += d else: break data = data.decode("utf8") print(data) client.close() if __name__ == "__main__": url = 'http://www.baidu.com' request_demo(url) 运行结果包含两部分: 第一部分request header 第二部分html源码 建立连接的过程是比较费时的,一般在使用socket编程都是为了解决长连接的问题,而不是说每发送一个请求数据返回就把它关掉。 很多时候我们需要一个交互的过程,这时候socket就派上用场了,有了socket后我们的代码灵活性高,它完全可以让我们将整个过程变得可以操控。

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

Python高级知识点学习(六)

围棋少年 Python中的迭代协议 迭代协议有两个概念: 可迭代类型(Iterable) 迭代器(Iterator) 迭代器:迭代器是访问集合内元素的一种方式, 一般用来遍历数据。 迭代器和以下标的访问方式不一样, 迭代器是不能返回的, 迭代器提供了一种惰性方式数据的方式。 可迭代对象(Iterable) 和 迭代器(Iterator) 是不同的。 可迭代对象: 实现__iter__这个魔法函数 迭代器: 实现__next__这个魔法函数 实现__iter__这个魔法函数 from collections.abc import Iterable, Iterator a = [1, 2] print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) 打印结果: True False 上边代码,因为a是一个list,而list是一个可迭代对象并不是迭代器,因为list对象中没有__next__方法。 生成器 生成器函数:函数里只要有yield关键字,它就是生成器对象。 生成器对象在python编译字节码的时候就产生了。 生成器对象也是实现了迭代器协议的,所以可以使用for循环遍历到它的值。 def gen_func(): yield 1 gen = gen_func() for value in gen: print(value) 打印结果: 1 Python 中的GIL GIL:global interpreter lock (cpython) GIL:全局解释器锁。 python中一个线程对应于c语言中的一个线程。 GIL使得同一个时刻只有一个线程在一个cpu上执行字节码, 也就意味着无法将多个线程映射到多个cpu上执行。 GIL锁分配给某一线程后,并不是说这个线程执行完了之后它才会释放把它交给另外一个线程,它不是整个过程完全占有,它实际上是会在适当的时刻释放的,是结合了字节码执行的行数比如他执行了1000行字节码之后,它会释放,然后另外一个线程就可以得到运行。 GIL释放: 会根据执行的字节码行数以及时间片释放gil。 gil在遇到io的操作时候主动释放。 total = 0 def add(): global total for i in range(1000000): total += 1 def desc(): global total for i in range(1000000): total -= 1 thread1 = threading.Thread(target=add) thread2 = threading.Thread(target=desc) thread1.start() thread2.start() thread1.join() thread2.join() print(total) 上边代码两个线程分别执行两个函数,两个函数对同一变量做加减操作,本来应该先加到1000000再减1000000最终打印出0,但事实上是不会打印0的。 多线程编程 IO密集型时,适合多线程。 CPU密集型时,适合多进程。 多线程编程是我们几乎所有编程语言中都会遇到的问题。 操作系统能够切换和调度的最小单元是线程。 在最开始的时候,操作系统能够调度的最小单元是进程,但是由于进程对系统资源消耗非常大,所以后期就演变出了线程。 第一种方式:通过Thread类实例化 def get_detail_html(url): print("get detail html started") time.sleep(2) print("get detail html end") def get_detail_url(url): print("get detail url started") time.sleep(4) print("get detail url end") if __name__ == "__main__": thread1 = threading.Thread(target=get_detail_html, args=("",)) thread2 = threading.Thread(target=get_detail_url, args=("",)) # 设置为守护线程,随着主线程退出,子线程也退出 #thread1.setDaemon(True) #thread2.setDaemon(True) start_time = time.time() thread1.start() thread2.start() # 等待线程1,2执行完成 再执行完主线程; thread1.join() thread2.join() print(time.time() - start_time) 上边代码中,两个线程分别执行两个函数,主线程下有两个子线程。 thread1.setDaemon(True)这个操作是把thread1设置为守护线程,随着主线程退出,thread1也退出。thread1.join()这个操作是把主线程等待thread1执行完再执行完主线程。 第二种方式:通过重载Thread来实现多线程 def get_detail_html(url): print("get detail html started") time.sleep(2) print("get detail html end") def get_detail_url(url): print("get detail url started") time.sleep(4) print("get detail url end") class GetDetailHtml(threading.Thread): def __init__(self, name): super().__init__(name=name) def run(self): print("get detail html started") time.sleep(2) print("get detail html end") class GetDetailUrl(threading.Thread): def __init__(self, name): super().__init__(name=name) def run(self): print("get detail url started") time.sleep(4) print("get detail url end") if __name__ == "__main__": thread1 = GetDetailHtml("get_detail_html") thread2 = GetDetailUrl("get_detail_url") start_time = time.time() thread1.start() thread2.start() thread1.join() thread2.join() print("last time: {}".format(time.time()-start_time)) 上边代码继承threading.Thread必须重载run方法。 线程同步Lock、RLock 为什么要线程同步? 现有两个函数,分别是对全局变脸a进行加减操作,两个函数使用两个线程来运行,一个线程负责把a加一,另一个负责把a减一,上代码: a = 0 def add(a): a += 1 def desc(a): a-=1 首先使用内置方法dis()看一下两个函数字节码是什么样子的: import dis def add(a): a += 1 def desc(a): a-=1 print(dis.dis(add)) print(dis.dis(desc)) 打印结果: 63 0 LOAD_FAST 0 (a) 2 LOAD_CONST 1 (1) 4 INPLACE_ADD 6 STORE_FAST 0 (a) 8 LOAD_CONST 0 (None) 10 RETURN_VALUE None 66 0 LOAD_FAST 0 (a) 2 LOAD_CONST 1 (1) 4 INPLACE_SUBTRACT 6 STORE_FAST 0 (a) 8 LOAD_CONST 0 (None) 10 RETURN_VALUE None 上边代码打印结果: 看下add的里边字节码: LOAD_FAST:首先把 a LOAD 到内存中 LOAD_CONST :再把1 LOAD 到内存中 INPLACE_ADD:执行加的操作 STORE_FAST:将加完的值赋值给 a desc里边也是一样的,分四步,不同的是desc里执行的是减法。 如果同时执行add字节码和desc字节码时,之前笔记中提过,执行以上四个步骤时随时都可能释放gil锁,因为字节码的数量已经满了,以上四步任何一步骤都可能释放gil锁切换到另外一个线程,所以有可能造成一个结果,就是a要么等于1,要么等于-1,但是我们期望的是a 等于0,这时候就需要线程同步来解决问题。 我们希望在执行add函数的代码段时,另一个线程中的desc代码段是停止的,这就是线程同步机制。 python给我们提供了一个机制,叫做锁:from threading import Lock 在运行一个代码段时,加一把锁,等运行完了,再释放锁。 from threading import Lock total = 0 # 声明一把锁 lock = RLock() def add(): global lock global total for i in range(1000000): # 获得锁 lock.acquire() total += 1 # 释放锁 lock.release() def desc(): global total global lock for i in range(1000000): lock.acquire() total -= 1 lock.release() import threading thread1 = threading.Thread(target=add) thread2 = threading.Thread(target=desc) thread1.start() thread2.start() thread1.join() thread2.join() print(total) 打印结果: 0 上边代码不管累加多少次最终结果都是0。 如果没有释放锁,会导致死锁。 使用锁会影响性能。 RLock: 在同一个线程里面,可以连续调用多次acquire, 一定要注意acquire的次数要和release的次数相等。 在同一个线程中,可以使用RLock。

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

Python高级知识点学习(五)

dict的子类 首先,不建议自己编写代码继承dict和list这种原生使用c语言编写的,因为有时候,用c语言写的dict不会调用python写的覆盖的方法。 如果确实有继承dict来写代码的需求,可以使用UserDict,继承这个UserDict。 UserDict这个内部使用了python语言实现了c语言写的逻辑。 from collections import UserDict class Mydict(UserDict): def __setitem__(self, key, value): super().__setitem__(key, value*2) my_dict = Mydict(one=1) print (my_dict) 打印结果: {'one': 2} set和fronzenset set集合: 无序 不重复 set 接受一个可迭代对象 frozenset集合: 一旦设置好就无法修改。 frozenset为不可变类型。 相对于可变类型来说的好处,可以作为dict的key。 set的初始化方法: set([a, b, c]) a = {a, b, c} 两种都可以初始化 dict、set实现原理 当我们了解了背后的实现原理,就可以判断什么情况下使用dict以及为什么要使用dict。 dict查找的性能远远大于list。 在list中随着list数据的增大 查找时间会增大。 在dict中查找元素不会随着dict的增大而增大。 dict原理实际上就是利用hash算法。 数组和链表相比来说最大的优势,就是它可以做到任何一个位置直接存取而不需要从头到尾遍历,因为数组是一段连续的空间,数组取数据的时间复杂度是O(1)。 dict的key或者set的值,都必须是可以hash的。不可变对象都是可hash的, str, fronzenset, tuple。 自己实现的类重载__hash__这个魔法函数,让它可以变为可哈希对象。 dict的内存花销大,但是查询速度快, 自定义的对象或者python内部的对象都是用dict包装的。 dict的存储顺序和元素添加顺序有关,添加数据有可能改变已有数据的顺序。 Python中的变量是什么 python和java中的变量本质不一样,python的变量实质上是一个指针。我们可以理解变量就是一个便利贴, 例如:a = 1 先成对象,然后贴便利贴,把a贴在1上面。 a = [1, 2, 3] b = a print(id(a), id(b)) 打印结果: a和b的地址值一样。 is 和 == 的区别 is是判断对象的id是否相同,但是注意看下边例子: a = [1, 2, 3, 4] b = [1, 2, 3, 4] print (id(a), id(b)) print (a is b) 输出结果: 4446597320 4446597640 False 上边这种用法得到的结果很正常,再看下边代码: a = 1 b = 1 print (id(a), id(b)) print (a is b) 运行结果: 4325627840 4325627840 True 这种情况下,a和b指向的是同一个。这是由于Python内部机制决定的,将小正数建立一个全局唯一的对象,小段字符串也是一样的。 a = [1, 2, 3] b = [1, 2, 3] print(a==b) 返回True 因为: list里边实现了一个魔法函数 __eq__,当我们调用a==b这种模式的时候,会调用__eq__这个魔法函数,从而来判断值是否相等。 它们的值是相等的,只是不是同一个对象而已,所有a==b是True。 垃圾回收和del语句 cpython中垃圾回收的算法是采用 引用计数 a = 1 b = a 此时,1这个对象上就又有一个计数器,a = 1 时会在计数器上加1,b如果指向的还是1,1上边的计数器会再加1,当不使用时,执行del a,他就会将引用计数器减1,当引用计数器减到0时,python解释器会将对象回收。 对象只有在计数器减到0时,才会被回收,del只是减计数器的功能。 a = object() b = a del a print(b) print(a) 打印结果: <object object at 0x104a86100> NameError: name 'a' is not defined 可以看到,执行del后,只是把a销毁了,b还在。 __del__魔法函数: 可以在__del__魔法函数中实现自己的逻辑,当python解释器回收对象的时候,会调用对象的__del__魔法函数,它可以帮我们在回收对象时做一些事。 @property的用法 我们在读源码时,往往会看到这这种方法: @property def hello(self): pass @property 这个装饰器会把函数变为属性描述符,怎么说? 看代码: class Allen(object): def word(self): return 'word' @property def hello(self): return 'hello' a = Allen() print(a.hello) print(a.word()) 运行结果: hello word 可以发现,@property这个装饰器把取方法的模式变为取属性。 魔法函数__getattr__、__getattribute__介绍 魔法函数是python解释器内部需要用的方法,它是整个python动态特性的最根本原因。 __getattr__:就是在查找不到属性的时候调用。 例1: class User: def __init__(self, info): self.info = info def __getattr__(self, item): return '2' if __name__ == "__main__": user = User('allen') print(user.info) print(user.age) 运行结果: allen 2 例2: class User: def __init__(self, info={}): self.info = info def __getattr__(self, item): return self.info[item] if __name__ == "__main__": user = User(info={"name": "allen", "age": "3"}) print(user.name) 打印结果: allen __getattribute__ : 比__getattr__更高级,只要查找属性,就会首先进入__getattribute__这个魔法函数,强制进入,无条件的。 __getattribute__这个魔法函数尽量不要去重写,因为如果一旦写不好,整个类的属性访问就会崩溃掉,一般写框架时会用到这个魔法函数。 class User: def __init__(self,info={}): self.info = info def __getattr__(self, item): return self.info[item] def __getattribute__(self, item): return "10" if __name__ == "__main__": user = User(info={"name":"allen"}) print(user.name) print(user.test) 打印结果: 10 10 属性描述符和属性查找过程 一个类只需要实现__get__、__set__、__delete__这三个中的任意一个方法,它就算是属性描述符。 通过属性描述符,可以控制在赋值的时候它的行为,在属性设置的时候参数检查。 属性描述符有两种: 数据属性描述符:实现__get__ 和 __set__就是数据属性描述符。 非数据属性描述符:只实现一个__get__方法就是非数据属性描述符。 数据属性描述符 和 非数据属性描述符 它们的属性查找过程是不一样的。 前边提到的属性查找过程,先查找实例中的属性,然后查找类中的属性,实际上它有更加详细的查找过程。 魔法函数__new__ 和 __init__ 区别 __new__ 魔法函数在python新式类才会有 python2.2之前没有这个。 class User: def __new__(cls, *args, **kwargs): print(" in new ") return super().__new__(cls) def __init__(self): print(" in init") pass if __name__ == "__main__": user = User() 打印结果: in new in init __new__魔法函数允许在生成对象之前加逻辑,自定义对象生成过程,传递进来的是类。 __init__方法传递进去的是对象。 __new__ 在 __init__ 之前调用。 __new__中必须return super().__new__(cls)才会调用__init__方法。 def __new__(cls, *args, **kwargs):这个中的*args,和**kwargs,代表的是传入的参数。

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

Python高级知识点学习(三)

mro算法 类属性和实例属性的查找顺序 何为类属性:定义在类内部的的一些变量或者方法,都统称为类属性 何为实例属性:定义在对象内部的的一些变量或者方法,都统称为实例属性 对象也就是实例的意思。 class A: aa = 1 def __init__(self, x, y): self.x = x self.y = y a = A(2, 3) 类也是对象,看上边代码,实际上有两个空间,A 和 a 两个不同的空间。单继承时,属性查找方式,向上查找,首先查找对象里,再查找类中 在多继承时,会很复杂 python2.2之前,python里的类叫经典类,经典类继承方式如果不显式继承object,实际上是不会自动继承object,Python3中,经典类已经不存在了,都叫做新式类。经典类中,深度优先查找 。 Python2.3之后,广度优先也没有了,至今都采用C3算法Python3多重继承C3算法: #新式类 class D: pass class E: pass class C(E): pass class B(D): pass class A(B, C): pass print(A.__mro__) 打印结果:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)可以看到继承顺序是 A - B - D - C - E - object 类方法静态方法和实例方法 实例方法:实例方法很常见,通常我们在类里定义的都是实例方法, 只是针对于实例进行操作,实例方法中第一个参数:self,self代表的就是实例本身 静待方法: 带@staticmethod装饰器的方法叫静态方法,静态方法不需要接受cls或self,和普通的函数用法一样 类方法: 带@classmethod装饰器的方法叫做类方法,类方法第一个参数是cls,代表的是类本身,(这里的cls可以修改为任意形式的代表) def a(self): pass @staticmethod def b(): pass @classmethod def c(cls): pass Python中的私有属性 双下划綫开头表示私有属性,私有属性的访问,只能在类中的公共方法中访问,类外部防问不到,无法通过实例访问。 私有属性不仅仅是变量 还可以是函数。 class User: def __init__(self, birthday): self.__birthday = birthday user = User(2000) print(user.birthday) 运行结果: AttributeError: 'User' object has no attribute 'birthday' 以上代码块结果就是访问不到私有属性。 但是,私有不是绝对的,只是加了一个小技巧,Python中将私有属性的访问变形成这种: class User: def __init__(self, birthday): self.__birthday = birthday user = User(2000) print(user._User__birthday) 运行结果: 2000 可以看到,通过变量_User__birthday这个就可以访问到私有属性。 Java中的反射机制也是无法做到绝对安全的,从语言层面讲,没有绝对的私有属性,Python简单一些 Java麻烦一些。 Python自省 Python对象自省何为自省?自省是通过一定的机制查询到对象的内部结构使用__dict__魔法函数,dict是用C语言写的 性能高,做了很多优化,推荐使用。 也可以使用dir() ,dir()会列出类中所有属性,推荐使用。 class Student(): def __init__(self, scool_name): self.scool_name = scool_name if __name__ == "__main__": user = Student("zhao") #通过__dict__查询属性 print(user.__dict__) 打印结果: {'scool_name': 'zhao'} class Student(): def __init__(self, name): self.name = name if __name__ == "__main__": user = Student("zhao") user.__dict__["school_addr"] = "北京市" print(user.school_addr) print(user.name) a = [1, 2] print(dir(a)) print(dir(user)) 打印结果: ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'school_addr', 'scool_name'] ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] Python中super()函数 super()就是调用父类。 class A: def __init__(self): print('is A') class B(A): def __init__(self): print('is B') # 在某些情况下,我们希望在运行完以上代码调用父类的init方法,一般在Python3中使用下面这种方法调用 super().__init__() if __name__ == "__main__": b = B() 打印结果: is B is A super()就是调用父类,其实这样讲并不准确,super()函数调用其实是按照mro查找的顺序调用的。 class A: def __init__(self): print("A") class B(A): def __init__(self): print("B") super().__init__() class C(A): def __init__(self): print("C") super().__init__() class D(B, C): def __init__(self): print("D") super().__init__() if __name__ == "__main__": print(D.__mro__) d = D() 打印结果: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) D B C A 既然我们重写子类的构造函数, 为什么还要去调用super再次调用父类构造函数?有些时候,为了使用父类中的一些已经写好的方法,所以会有使用super的情况。 上下文管理器 首先介绍下这种用法: try: print("code started") raise KeyError return 1 except KeyError as e: print ("key error") return 2 else: print("other error") return 3 finally: print ("finally") return 4 result = exe_try() print (result) 运行结果: code started key error finally 4 在finally语句块中的代码,不管上边代码是否发生异常都会执行finally中的代码,优先finally中的return,其次return上边的。 上下文管理器,也就是with语句,实质上就是为了解放try finally这种写法而诞生的。 上下文管理器是如何完成的呢?python是基于协议进行编程的,上下文管理器就是一种协议:上下文管理器协议。 上下文管理器协议可以使用是因为实现了两个魔法函数:__enter__和__exit__ class Sample: def __enter__(self): print("enter") # 获取资源 return self def __exit__(self, exc_type, exc_val, exc_tb): # 释放资源 print("exit") def do_something(self): print("doing something") with Sample() as sample: sample.do_something() 运行结果: enter doing something exit 还有一种方法,可以简便的实现上下文管理器的功能:@contextlib.contextmanager 可以将一个函数变为上下文管理器 import contextlib @contextlib.contextmanager def file_open(file_name): print ("file open") yield {} print ("file end") with file_open("a.txt") as f_opened: print("file processing") 运行结果: file open file processing file end @contextlib.contextmanager内部会做一些逻辑,其实是利用了生成器的特性。

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

Python高级知识点学习(二)

深入类和对象 鸭子类型问:什么是鸭子类型?答:当看到一只鸟走起路来像鸭子,游泳像鸭子,叫起来也像鸭子,那么这只鸟就可以被看做鸭子。(所有的类或对象,都实现了共同的方法,方法名要一样,这样的话这些类就归为一种类型,在调用时同时调用同样的方法) 在java中,要实现多态,所有子类必须继承父类并重写父类的方法;在python中,python中对象和java不同,变量是动态的可以指向任何一个类型。 class Cat(): def say(): pass class Dog(): def say(): pass class Duck(): def say(): pass 三个类共同实现同一个方法且方法名相同,就实现了多态。Python多态比Java简单正是因为python是动态语言。 抽象基类abc模块 何为抽象基类?继续用java做比较,Python这里抽象基类可以当做java中的接口,java中是无法实现多继承的,java只能继承一个类,但是可以继承多个接口,接口是不能用来实例化的;所以Python里边抽象基类也是不能实例化的。 python是动态语言,动态语言是没有变量类型的,Python中变量只是个符号而已,它可以指向任何类型的对象。 所以在Python中也就不存在多态这种概念,我们可以赋值任何类型数据给Python中的变量,而且它是可以修改的,所以说它就不需要像java中一样去实现多态,因为本身从语言层面讲就是支持多态的语言。 动态语言和静态语言非常大的区别之一就是静态语言不需要我们指明变量类型,所以动态语言中也就少了编译时检查错误的环节,python中写错了代码只能在运行中发现错误。 不同魔法函数就赋予了类不同特性,和java非常大的区别就是不需要去继承某一个指定的类。 鸭子类型和魔法函数构成了Python语言的基础,也就是Python中的协议。我们只需要去实现指定的魔法函数,类就可以是某种类型的对象,不同魔法函数具有的特性不同,这种使用魔法函数事先约定好的做法我们都可以称之为一种协议。 抽象基类用途: 我们在某些情况之下希望判定某个对象的类型 我们需要强制某个子类必须实现某些方法 抽象基类用法: 在基础类中,我们去设定好一些方法,所有继承这个基类的类都必须覆盖抽象基类的方法, 抽象基类是无法实例化的 所有的抽象基类继承的必须是metaclass=ABCMeta 在做框架编程时,往往我们需要使用框架者在使用类时必须实现类中的一些方法,这时候可以使用抽象基类,举了例子: class CacheBase(metaclass=abc.ABCMeta): @abc.abstractmethod def get(self, key): pass @abc.abstractmethod def set(self, key, value): pass class RedisCache(CacheBase): pass redis_cache = RedisCache() 运行结果:TypeError: Can't instantiate abstract class RedisCache with abstract methods get, set在实例化RedisCache()这个时,就开始报错了,错误提示让我们必须实现get set方法。 补全这两个方法后运行就不报错了 class RedisCache(CacheBase): def set(self, key, value): pass def get(self, key): pass redis_cache = RedisCache() type和isinstance区别 尽量使用isinstance判断类型,避免使用type产生误差is 和 == 符号 区别: is用法判断id是否相同 ==用法判断值是否相等 类变量和实例变量(对象变量) 先看代码: class A: # 类变量 aa = 1 def __init__(self, x, y): # self是类的实例 # 实例变量 self.x = x self.y = y # 上边赋值后 x y 都属于实例了 不再属于类了 a是实例 A是类 aa是类变量 类变量在所有实例中是共享的 a = A(2, 3) A.aa = 11 a.aa = 100 # a实例新建一个属性 把aa放到a实例中 a.aa和A.aa是独立的 print(a.x, a.y, a.aa) print(A.aa) b = A(3, 5) print(b.aa) 运行结果: 2 3 100 11 11 解释代码: 新建了一个名为A的类,类中新建类属性aa = 1类中新建构造函数,绑定两个变量x y。 属性查找方式,向上查找,首先查找对象里有没有,再查找类中有没有类变量和实例变量是独立的存在 类变量在所有实例中是共享的 a.aa = 100:a实例新建一个属性 把aa放到a实例中 a.aa和A.aa是独立的。 建议以上代码反复测试加以记忆。

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

Python高级知识点学习(四)

序列类型 Python中的序列类型,序列类型可以使用for循环遍历。 序列类,序列是python中非常重要的协议,如何通过实现这个协议,将类变为序列类。 Python中的序列分类两个维度区分: 容器序列:可以放置任意类型的数据。 可变序列、不可变序列。 序列类型的一些协议 from collections import abc 跟容器相关的数据结构的抽象基类都是放到abc里的,可以查看源码看下继承关系: image.png image.png list中的extend方法、+、 +=、以及append区别: a = [1, 2] # 新对象上+ [3, 4] c = a + [3, 4] #就地加, 还是a对象上边操作 a += (3, 4) a.extend(range(3)) a.append((1, 2)) print(a) 打印结果: [1, 2, 3, 4, 0, 1, 2, (1, 2)] 可切片对象 列表的切片:模式[start:end:step] start:从哪开始 end:从哪结束 step:步长 例如:a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]a[::] 切片操作返回的是一个新的列表,不会改变原列表。 实现可切片对象,最关键的是要实现一个魔法函数:__getitem__ 什么时候不使用列表List 某些情况最好选择Python中其他内置的数据结构:array和deque。 array 和 List 重要区别: array只能存放指定的数据类型,List不同 array性能比List高 列表推导式、生成器表达式、字典推导式 列表推导式:又名列表生成式,通过一行代码来生成列表。 列表生成式性能高于列表操作, 第一:能用尽量用, 因为效率高 第二:如果逻辑过于复杂,建议不要使用,会失去高可读性 生成器表达式:改为小括号即可:(i for i in range(21) if i % 2 == 1) 转换为lsit ,list((i for i in range(21) if i % 2 == 1)) 字典推导式: 一行代码生成 dict。 dict的abc继承关系 from collections.abc import Mapping, MutableMapping dict属于 mapping 类型 from collections.abc import Mapping, MutableMapping # dict属于 mapping 类型 a = {} print(type(a)) print(isinstance(a, MutableMapping)) 打印结果: <class 'dict'> True 上边代码中,a 并不是继承了MutableMapping,只是实现了MutableMapping中的一些魔法函数。 dict常用方法: clear方法:清空dict copy方法:返回浅拷贝 浅拷贝的时候会互相影响: one = { "a": {"name": "zhao"}, "b": {"age": "20"} } two = one.copy() two['a']['name'] = 'wang' print(two) print(one) 打印结果: {'a': {'name': 'wang'}, 'b': {'age': '20'}} {'a': {'name': 'wang'}, 'b': {'age': '20'}} 深拷贝: import copy one = { "a": {"name": "zhao"}, "b": {"age": "20"} } two = copy.deepcopy(one) two['a']['name'] = 'wang' print(two) print(one) 打印结果: {'a': {'name': 'wang'}, 'b': {'age': '20'}} {'a': {'name': 'zhao'}, 'b': {'age': '20'}} fromkeys方法:快速生成以list元素为key的dict,fromkeys参数接受可迭代对象。 new_list = ["a", "b"] new_dict = dict.fromkeys(new_list, {"age": "18"}) 运行结果: {'a': {'age': '18'}, 'b': {'age': '18'}} get方法: new_dict = {"a": "a", "b": "b"} print(new_dict.get('c', 'c')) 打印结果: c setdefault方法:去dict中取值,如果没有值,则设置完默认值后再取值,如果有值,则只取值。 # dict没有值: a = {"a": "a", "b": "b"} b = a.setdefault("c", "c") print(b) print(a) 打印结果: c {'a': 'a', 'b': 'b', 'c': 'c'} # dict有值 a = {"a": "a", "b": "b", "c": "2"} b = a.setdefault("c", "c") print(b) print(a) 打印结果: 2 {'a': 'a', 'b': 'b', 'c': '2'} update方法:合并两个dict qrcode_for_gh_60369a20e6f5_258.jpg 可以关注我的微信公众号,会更新Python知识点,也会有其他语言和软件开发中的各种坑,都会记录在公众号。

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

SQL数据库学习之路(四)

要求:通过SQL语句创建以下基本表: 教师关系 T(T#, TNAME,TITLE) 课程关系 C(C#,CNAME,T#) 学生关系 S(S#,SNAME,AGE,SEX) 选课关系SC(S#,C#,SCORE) 班级关系CLASS(CLASSID,CLASSNAME) 其中红色粗体为主键,带下划线的属性为外键。 通过SQL语句在CLASS表的CLASSID列上创建聚集索引IDX_CLASSID 通过SQL语句创建在S#和C#两个列上创建索引IDX_S#_C#,并指定索引按S#降序,C#升序有序。 通过SQL语句实现以下操作: 撤销索引IDX_CLASSID及IDX_S#_C# 在学生关系中增加班级号属性列CLASSID 撤销学生关系中的班级号属性列CLASSID 撤销班级关系CLASS 方法: 1.打开SQL Server Mangement Studio, 连接到本地数据库。在对象资源管理 器中,点击数据库,然后选择新建查询。输入创建数据库的代码,点击执行,在消息窗口出现命令已成功完成。在存放的文件夹中产生mdf主数据文件和ldf日志文件。在对象资源管理器中右键数据库,选择刷新,将会出现work数据库。 createdatabasework onprimary( name='work',--主数据文件的逻辑名称 filename='F:\SQL\work1\work.mdf',--主数据文件的物理名称 size=10mb, --主数据文件的初始大小 filegrowth=10mb --主数据文件的增长率 ) logon( name='work_log',--日志文件的逻辑名称 filename='F:\SQL\work1\work_log.ldf',--日志文件的物理名称 size=5mb, --日志文件的初始大小 filegrowth=10% --日志文件的增长率 ) 2.输入创建各个表的代码,选择这些代码点击执行,刷新work数据库,会出现创建的表。 createtableT --创建表教师关系T (T# char(4)notnull, TNAME char(8)notnull, TITLE char(10), PRIMARYKEY(T#) --设置主键为T# ); createtableC --创建表课程关系T (C# char(4), CNAME char(10)notnull, T# char(4), PRIMARYKEY(C#),--设置主键为C# FOREIGNKEY(T#)REFERENCEST(T#) --设置外键为T# ); createtableS --创建表学生关系S (S# char(4)notnull, SNAME char(8)notnull, AEG char(1), PRIMARYKEY(S#) --设置主键为S# ); createtableSC --创建表选课关系SC (S# char(4), C# char(4), SCORE SMALLINT, PRIMARYKEY(S#,C#),--设置主键为S#,C# FOREIGNKEY(S#)REFERENCESS(S#),----设置外键为S# FOREIGNKEY(C#)REFERENCESC(C#)----设置外键为C# ); createtableclass --创建表班级关系class ( CLASSID char(4), CLASSNAME char(8) ); 3.输入代码,右键class表,点击设计。出现表class,然后右键CLASSID,选择索引。SC表统一操作。 createuniqueindexIDX_CLASSID onclass(CLASSID);--在CLASS表的CLASSID列上创建聚集索引IDX_CLASSID createuniqueindexIDX_S#_C# onSC(S# DESC,C# ASC);--创建在S#和C#两个列上创建索引IDX_S#_C#,并指定索引按S#降序,C#升序有序 4.撤销索引IDX_CLASSID及IDX_S#_C#。代码为: DROPINDEXIDX_CLASSID onclass;--撤销索引IDX_CLASSID DROPINDEXIDX_S#_C# onSC; --撤销索引IDX_S#_C# 在学生关系中增加班级号属性列CLASSID。代码为: altertableS addCLASSID char(4);--在学生关系中增加班级号属性列CLASSID 撤销学生关系中的班级号属性列CLASSID,代码为: altertableS dropcolumnCLASSID; --撤销学生关系中的班级号属性列CLASSID 撤销班级关系CLASS,代码为: droptableclass ; --撤销班级关系CLASS

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

16.Swift学习之结构体

结构体的介绍 概念介绍 结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合 结构体是值类型 结构体既可以定义属性又可以定义方法 定义语法 struct 结构体名称 { // 属性和方法 } 举例 struct Person { var name = "Zhangsan" var age = 10 var sex = "man" func say(){ print("人会说话") } } 解读 定义了一个名叫 Person的结构体 这个结构体拥有两个存储属性 name、 age和 sex 这个结构体拥有一个方法say 结构体实例 实例化结构体最简单的是在结构体名字后面写上(),任何属性都被初始化为它们的默认值 var p1 = Person() 所有的结构体都有一个自动生成的成员构造函数来实例化结构体,可以使用它来初始化所有的成员属性 var p2= Person(name: "Lisi", age: 20, sex: "woman") 访问属性和方法 可以用.来访问一个结构体实例的属性和方法 访问时如果使用了赋值语句就是设置属性 //访问 p2.age //设置 p2.age = 30 //通过.调用结构体中的属性和方法 p1.name p1.age p1.sex p1.say() 结构体是值类型 值类型是一种当它被赋值给一个常量或者变量,或者被传递给函数时会被拷贝的类型 Swift 中的结构体(包括枚举)是值类型,它在代码传递中总是会被拷贝 //值类型拷贝 var p3 = p2 //此时改变p3并不会改变p2的值 p3.name = "Wangwu" p3.age = 30 p2.age //20 p2.name //Lisi p3.age //30 p3.name //Wangwu 常用的结构体 CGRect /* Rectangles. */ public struct CGRect { public var origin: CGPoint public var size: CGSize public init() public init(origin: CGPoint, size: CGSize) } CGSize /* Sizes. */ public struct CGSize { public var width: CGFloat public var height: CGFloat public init() public init(width: CGFloat, height: CGFloat) } CGPoint /* Points. */ public struct CGPoint { public var x: CGFloat public var y: CGFloat public init() public init(x: CGFloat, y: CGFloat) } 字符串,数组和字典的赋值与拷贝行为 Swift 中的 String , Array 和 Dictionary类型是作为结构体来实现的,这意味着String , Array 和 Dictionary在它们被赋值到一个新的常量或者变量,或它们本身被传递到一个函数或方法中的时候,其实是传递了拷贝。 OC中的 NSString, NSArray和 NSDictionary,它们是作为类来实现的,所以NSString , NSArray 和 NSDictionary实例总是作为一个引用而不是拷贝来赋值和传递。

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

学习笔记】hive 之行拆列explode

1、explode explode(ARRAY) 列表中的每个元素生成一行explode(MAP) map中每个key-value对,生成一行,key为一列,value为一列限制:1、No other expressions are allowed in SELECT SELECT pageid, explode(adid_list) AS myCol... is not supported 2、UDTF's can't be nested SELECT explode(explode(adid_list)) AS myCol... is not supported 3、GROUP BY / CLUSTER BY / DISTRIBUTE BY / SORT BY is not supported SELECT explode(adid_list) AS myCol ... GROUP BY myCol is not supported 2、lateral view 可使用lateral view解除以上限制,语法: lateralView: LATERAL VIEW explode(expression) tableAlias AS columnAlias (',' columnAlias)*fromClause: FROM baseTable (lateralView)* 案例: table名称为pageAds SELECT pageid, adid FROM pageAds LATERAL VIEW explode(adid_list) adTable AS adid; 输出结果: 3、多个lateral view from语句后面可以带多个lateral view语句 案例: 表名:baseTable from后只有一个lateral view: SELECT myCol1, col2 FROM baseTable LATERAL VIEW explode(col1) myTable1 AS myCol1; 结果: 多个lateral view: SELECT myCol1, myCol2 FROM baseTable LATERAL VIEW explode(col1) myTable1 AS myCol1 LATERAL VIEW explode(col2) myTable2 AS myCol2; 结果: 4、Outer Lateral Views 如果array类型的字段为空,但依然需返回记录,可使用outer关键词。 比如:select * from src LATERAL VIEW explode(array()) C AS a limit 10; 这条语句中的array字段是个空列表,这条语句不管src表中是否有记录,结果都是空的。 而:select * from src LATERAL VIEW OUTER explode(array()) C AS a limit 10; 结果中的记录数为src表的记录数,只是a字段为NULL。 比如: 238 val_238 NULL86 val_86 NULL311 val_311 NULL27 val_27 NULL165 val_165 NULL409 val_409 NULL255 val_255 NULL278 val_278 NULL98 val_98 NULL 官方文档: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+LateralView https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF#LanguageManualUDF-explode

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

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

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

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

用户登录
用户注册