Movatterモバイル変換


[0]ホーム

URL:


Signal
Tue 08 October 2019 by

Rust中的泛型,trait以及trait objects小记

泛型的使用

泛型的英文为Generic Types,顾名思义就是通用类型。泛型可以帮助我们减少样板代码的编写。设想一下,如果需要为在一个元素为i32的数组中找出最大的那个数,大部分人都会写一个将这部分代码写成一个函数。形如

fnlargest(list:&[i32])->i32{letmutlargest=list[0];for&iteminlist.iter(){ifitem>largest{largest=item;}}largest}

嗯~~ 正常运行。不过之后产品经理增加需求,需要同时支持i64数组,然后又想支持f64数组。在没有泛型的语言中,开发人员只能选择将这个函数复制粘贴n次,对参数类型稍加修改,最后改成不同的名字,如largest_i32largest_i64等。而且,这种情况下,当你需要改变代码时,也需要重复修改多次,这些都或多或少地增加了代码的维护成本。但是泛型的出现,可以减少开发者很多重复的劳动。Rust中的泛型:

fnlargest<T>(list:&[T])->T{letmutlargest=list[0];for&iteminlist.iter(){ifitem>largest{largest=item;}}largest}

乍一看,与第一个例子的代码基本相同,但是我们在函数签名中声明了一个泛型T,用来表示任何类型。这样一来,就无需为每一个具体类型定义一个专用函数了。不过不用高兴太早,上面的代码,Rust的编译器并不让通过。这是因为在函数中涉及了对泛型T的大小比较,但是并不是所有的类型都是支持大小比较的,所以还需要再做一点细微的工作来支持它。有兴趣的同学可以去试着让它通过编译。

在本小节的末尾,来说一下Rust中的泛型的工作原理。以上文提到的函数largest为例,编译器会在编译时,找出所有使用了它的具体类型,并为它们分别生成一个largest_xxx函数,这个过程叫做monomorphization,我翻译成特例化。看到这里,有木有很惊讶?Rust做了我们本来要做的脏活累活,它针对特定类型生成特定函数,这样的泛型实现方式是没有动态开销的,只有编译开销: )

Trait的使用

Rust中的Trait有点像其他语言中的Interface,但是还是有机制上的区别。Trait定义了一组共享方法,是一种抽象方式。比如:

traitHuman{fntalk(&self)->String;}fnlisten_to(person:implHuman){letsent=person.talk();println!("man say: {}",sent);}

我们定义了一个Human的trait,其中还有一个talk方法。接着,我们还定义了一个函数,参数类型为实现了trait的类型。trait不能直接作为函数的参数,但是可以作为trait bound使用,简单理解就是对具体类型的限定。比如,在上面的例子中,我们就限定了man的类型必须实现trait Human。

形如impl trait的写法是一种简便写法,通常写法是和泛型结合。

fnlisten_to<T:Human>(person:T){...}//或者fnlisten_to<T>(person:T)whereT:Human{...}

一个泛型可以添加多个trait bound,使用+串联。比如T: Human + Display

规定了trait限定之后,我们需要给函数传入具体的类型。该类型必须实现相应的trait。

structMan;implHumanforMan{fntalk(&self)->String{"Man: I am a man".into()}}fnmain(){letman=Man;listen_to(man);}

很简单,理解上也没有问题。不过,接下来就会说到一个问题。我们来看个例子

structWoman;implHumanforWoman{fntalk(&self)->String{"Woman: I am a woman".into()}}fnlisten_to<T:Human>(man:T,woman:T){println!("{}",man.talk());println!("{}",woman.talk());}fnmain(){letman=Man;letwoman=Woman;listen_to(man,woman);}

什么!编译器报错了.

|29|listen_to(man,woman);|^^^^^expectedstruct`Man`,foundstruct`Woman`|=note:expectedtype`Man`foundtype`Woman`

如果经常写Go的同学可能会奇怪了,listen_to函数的man和woman两个参数都是Human这个interface类型,我们实际传入的也是两个实现了Human的类型,为什么会报错呢?哈哈,这又是Rust的泛型机制在作怪了,我们对man和woman都使用了同一泛型T,这需要我们保证实际传入的类型必须为同一类型。

那么如何修改呢?有两个方法

// 方法一,使用两个泛型fnlisten_to<M:Human,W:Human>(man:M,woman:W){...}// 方法二,使用关键词implfnlisten_to(man:implHuman,W:implHuman){...}

目前看来,好像一切都相安无事了。但是由于Rust泛型机制的限制,貌似失去了动态能力。想象一下,如果我们需要一个包含了多个Human的Vec,里面既有Man,又有Woman,我们该怎么办?有的同学说简单,直接用不就好了吗?

letpeople:Vec<Human>=vec![];

不好意思,编译器不通过!如果要做到我们想要的效果,需要使用trait objects,编译器告诉你要使用dyn关键词。

letpeople:Vec<dynHuman>=vec![];

再次编译,吼吼,还是不通过!因为编译阶段无法知道trait objects的实际大小了,所以呢?需要我们使用Box将它放到堆上去。

letpeople:Vec<Box<dynHuman>>=vec![];

终于过了,不容易吼。

fnlisten_to_people(people:Vec<Box<dynHuman>>){foroneinpeople{println!("{}",one.talk());}}fnmain(){letmutpeople:Vec<Box<dynHuman>>=vec![];people.push(Box::new(Man));people.push(Box::new(Woman));people.push(Box::new(Man));listen_to_people(people);}

trait objects涉及动态匹配,会有运行时开销。

补充一下,只有类型安全且不含泛型参数的trait才可以作为trait objects.

  • 返回类型不能为Self
  • 不能还有泛型参数

比如,Clone这个trait就不能作为trait objects,因为方法的返回类型为Self

pubtraitClone{fnclone(&self)->Self;}

这一篇,篇幅较短。就是简要记录一下泛型、Trait以及Trait objects的使用。如有错误,敬请指教。

This entry was tagged on#rust

Fri 06 September 2019 by

Vscode和Rust nightly的简单配置

本人的常用IDE是小巧的vscode,之前写Rust的时候,使用的Rust版本是stable,vscode插件是Rust(rls)。一直以来工作良好,除了有时候rls会hanging。最近要使用nightly版本的rust,vscode提示没有rls,然后选择安装,发现安装失败。于是就鼓捣了一番,决定将过程记录下来。

nightly版本,顾名思义就是每晚构建的开发版本了。我以前安装nightly时的命令是

rustup toolchain install nightly

这个命令呢会安装最新构建的nightly版本。

rustup update

这个命令呢会更新安装的所有toolchain至最新。但是最新构建的nightly版本,有可能还没有与之对应的rls组件,这样的话在IDE里就没法好好开发了,毕竟在rust里,编译器是程序员的爹。没有rls,就会像失去了眼睛一样难受。搜寻了一番,找到一个地址,通过网页可以快捷的知道对于平台的nightly版本,其对于组件的发布情况如何。如果你们以后发现自己的rust没有对于的rls,就可以检查下是不是nightly太新了,如果是的话,就需要安装前几个版本的nightly了。

https://rust-lang.github.io/rustup-components-history/x86_64-apple-darwin.html

如何安装指定版本的nightly呢?

rustup toolchain install nightly-[year]-[month]-[day]// 比如 nightly-2019-09-05,因为这个版本目前有rlsrustup default nightly-[year]-[month]-[day]rustup component add rls

我使用的rust插件,会主动提示选择toolchain,如果选择的toolchain没有安装需要的开发组件的话,也会提示安装。如果组件本身没有缺失的话,就可以安装成功!

This entry was tagged on#rust and#vscode

Fri 06 September 2019 by

Rust反序列JSON的小技巧

serde是Rust中最流行的序列化和反序列化crate,这一篇就来记录下如何使用serde_json来反序列化连续的json对象。

比如有这样一段json数据:

{"start_pos":0,"end_pos":10}{"start_pos":10,"end_pos":30}{"start_pos":30,"end_pos":40}

这段数据中,是三个连续的字段相同json对象,那么如何对这多个数据进行反序列化呢?

useserde::{Deserialize,Serialize};useserde_json::Deserializer;#[derive(Deserialize, Debug)]structIndex{start_pos:u32,end_pos:u32,}fnmain(){letdata=r#"    {"start_pos": 0, "end_pos": 10}{"start_pos": 10, "end_pos": 30}    {"start_pos": 30, "end_pos": 40}    "#;letmutindices=Deserializer::from_str(data).into_iter::<Index>();whileletSome(idx)=indices.next(){println!("offset {}",indices.byte_offset()asu64);println!("idx => {:?}",idx.unwrap());}}

Deserializer对象还有一个方法from_reader支持从文件对象中读取数据。

完结撒花!

This entry was tagged on#json,#rust and#serde

Thu 05 September 2019 by

Rust的macro小记(一)

这篇记录一下Rust中的Function-like macros,也就是形如println!(...)形式的宏。

先来一个简单的

macro_rules!hello{()=>{println!("Hello, World!");}}fnmain(){hello!();}

上面这个简单的例子中, 定义了一个叫做hello的宏,匹配规则为空。作用就是在使用该宏的地方,插入代码println!("Hello, World!");。接下来我们加入匹配规则,来让这个宏更加的灵活.

macro_rules!hello{()=>{println!("Hello, World!");};($name:expr)=>{println!("Hello, {}!",$name);}}fnmain(){hello!();hello!("rust");}

现在增加了一个匹配规则,这样编译器在在工作时会按规则匹配,使用不同的代码分支。那可不可以让hello支持多个参数呢?当然可以!宏的匹配规则支持重复模式!

  • * 模式重复零次或多次
  • + 模式重复一次或多次
  • ? 模式出现零次或一次

让我们来稍加改造一下:

macro_rules!hello{()=>{println!("Hello, World!");};($($name:expr),*)=>{$(println!("Hello, {}!",$name);)*}}fnmain(){hello!();hello!("rust");hello!("rust","macro");}

这下我们可以让hello接收多个参数了, 每个参数都会被展开成一句print!代码。如果你观察仔细的话,会发现我们在定义模式的时候有一个逗号,但是使用的参数macro后面没有逗号,但是规则却能正常匹配,超级方便有木有!

到目前为止,我们使用的方式还是停留在函数调用的形式,接下来来一个复杂一点的例子。我相信很多人都接触过rpc,那么接下来就来定义我们自己的rpc client。

首先我们确定我们想要的形式

pubstructPing{}pubstructPong{}service!{servicedemo{rpcping(Ping)returns(Pong);}}fnmain(){useself::demo::{Client};letc=Client::new();letrsp=c.ping(Ping{});assert!(rsp,Pong{});}

好,我们已经知道想要的形式了,接下来就让我们实现我们的service宏吧!

macro_rules!service{(service$srv_name:ident{$(rpc$method_name:ident($input:tt)returns($output:tt);)*})=>{}}

这部分我们定义了宏的rule, 可以看到是对应我们之前提出的使用形式的。而且,再规则的内层使用重复的形式允许我们定义多个rpc方法。在这里给出参数的含义

  • $srv_name: service的名称
  • $method_name: rpc方法的名称
  • $input: rpc方法的输入参数
  • $output: rpc方法的输出结果

然后,我们来定义宏的输出部分

macro_rules!service{(service$srv_name:ident{$(rpc$method_name:ident($input:tt)returns($output:tt);)*})=>{pubmod$srv_name{$(usesuper::$input;)*$(usesuper::$output;)*pubstructClient{}implClient{pubfnnew()->Self{Client{}}$(pubfn$method_name(&self,_input:$input)->$output{})*}}}}

到了这里,基本完成了输出部分的主体代码。现在遇到了一个问题,就是如何实现结果的返回呢。在实际使用中,client会接收的远程服务返回的字节数据,然后使用特定的protocol来完成结果的解码,所以我们的输入输出类型均要实现解码编码的功能,由于例子的ping方法简单,所以只在这里为方法的返回类型pong实现一个简易的解码函数.

pubtraitMessage{fndecode()->Self;}pubstructPong{}implMessageforPong{fndecode()->Self{Pong{}}}

我们定义了一个traitMessage, 目前只有一个decode方法。这里的decode方法简化了参数, 正常情况下会输入字节数据进行反序列化。然后,我们又为Pong实现了Message。接下来就是使用它们的时候了。

macro_rules!service{(service$srv_name:ident{$(rpc$method_name:ident($input:tt)returns($output:tt);)*})=>{pubmod$srv_name{// 引入Messageusesuper::Message;$(usesuper::$input;)*$(usesuper::$output;)*pubstructClient{}implClient{pubfnnew()->Self{Client{}}$(pubfn$method_name(&self,_input:$input)->$output{// TODO call the remote server$output::decode();})*}}}}

友情提示,可以使用cargo的小工具expand展开宏代码,得到

pubmoddemo{usesuper::Message;usesuper::Ping;usesuper::Pong;pubstructClient{}implClient{pubfnnew()->Self{Client{}}pubfnping(&self,_input:Ping)->Pong{Pong::decode()}}}

终于顺利完成啦!

This entry was tagged on#rust

Wed 04 September 2019 by

Rust使用match的小发现

今天在读一个库的代码时候,发现了之前使用match的时候没有注意的一个地方。

enumFoo{One,Two,Three,}fnmain(){letfoo=Foo::One;matchfoo{Foo::One=>println!("It's one"),others=>println!("It's others"),};}

这里没有使用_去匹配其他分支,而是随意指定了一个名字others。貌似对可读性有一定提升。

This entry was tagged on#rust

Mon 16 January 2017 by

Golang接口型函数

packagemainimport("fmt")typeHandlerinterface{Do(k,vinterface{})}typeHandlerFuncfunc(k,vinterface{})func(fHandlerFunc)Do(k,vinterface{}){f(k,v)}funcEach(mmap[interface{}]interface{},hHandler){fork,v:=rangem{h.Do(k,v)}}funcEachFunc(mmap[interface{}]interface{},ffunc(k,vinterface{})){Each(m,HandlerFunc(f))}funcSayHello(name,ageinterface{}){fmt.Printf("Hello, I am %s, %d years old!\n",name,age)}funcmain(){students:=make(map[interface{}]interface{})students["Marry"]=20students["Tom"]=23EachFunc(students,SayHello)}

This entry was tagged on#Go

Mon 20 June 2016 by

go网络编程积累(一)

//server.gopackagemainimport("fmt""net""os""io")funcmain(){ln,err:=net.Listen("tcp",":8888")iferr!=nil{fmt.Println("Can't listen")os.Exit(2)}for{conn,err:=ln.Accept()iferr!=nil{fmt.Println("Can't accept a conn")continue}gohandleConn(conn)}}funchandleConn(connnet.Conn){buf:=make([]byte,30*1024)for{n,err:=conn.Read(buf)ifn>0{fmt.Println(buf[:n])}iferr==io.EOF{fmt.Println("Conn was closed.")return}iferr!=nil{fmt.Println(err)return}}}
//client.gopackagemainimport("net""fmt""os""time")funcmain(){conn,err:=net.Dial("tcp",":8888")iferr!=nil{fmt.Println(err)os.Exit(2)}deferconn.Close()buf:=[]byte("Hello, my server")for{n,err:=conn.Write(buf)ifn>0{fmt.Printf("Write %s to conn",buf[:n])}iferr!=nil{fmt.Println(err)os.Exit(2)}time.Sleep(1*time.Second)}}

最近在看unix网络编程这本书,顺便结合go来试试手。在写完这个简单的客户端服务器程序后,发现了几个以前一直没注意的问题。

发现一: Reader.Read

func(*Reader)Readfunc(b*Reader)Read(p[]byte)(nint,errerror)Readreadsdataintop.Itreturnsthenumberofbytesreadintop.ItcallsReadatmostonceontheunderlyingReader,hencenmaybelessthanlen(p).AtEOF,thecountwillbezeroanderrwillbeio.EOF.

将数据读取到提供的byte切片p中,读取的数据量与p的长度有关。这一点以前一直没有注意到,以致于程序一直有问题。以前我一直这样使用:

varbuf[]byten,err:=conn.Read(buf)ifn>0{fmt.Println(buf)}

由于buf在声明之后没有分配内存,所以一直是nil,因此len(buf)当然也是0,所以在Read数据的时候,返回的n一直都是0,这导致了无法正常显示客户端发送的数据。正确的用法是在Read之前先使用make为buf分配一定长度的空间:

varbuf=make([]byte,30*1024)n,err:=conn.Read(buf)ifn>0{fmt.Println(buf[:n])}

上述例子中,首先为buf分配了30*1024字节的内存空间。这样就能正常读取连接中的数据了。n总是小于等于len(buf)。当EOF时,Read返回0和一个io.EOF错误,表示无法读取更多的数据。另外,如果返回了io.EOF以外的错误,则表示还没读取完全之前就遇到了某个错误。

发现二:判断是否实现了特定接口

在go中,无需显式声明自己实现了某个接口。如果类型实现了某个接口定义的全部方法,则该类型实现了该接口。在某些特定的时候,我们需要在代码逻辑中判断接口的实现。如果实现了某接口,还需要调用该接口中的方法。

typeReaderinterface{Read(p[]byte)(nint,errerror)}funcmain(){conn,err:=net.Dial("tcp",":8080")ifrd,ok:=conn.(Reader);ok{buf:=make([]byte,30*1024)n,err:=rd.Read(buf)}}

发现三:defer应该在error判断之后

使用defer的时候经常会写出以下代码

funcmain(){conn,err:=net.Dial("tcp",":8080")deferconn.Close()iferr!=nil{fmt.Println(err)}}

这样写保证了函数main函数执行退出时必定能关闭tcp连接。但是在没有建立正常连接时,conn为nil,并没有实现Closer接口,也就没有Close方法可供调用,这样的代码就会引发panic。

This entry was tagged on#web

Wed 18 May 2016 by

Greenlet: 轻量级并发编程

动机

greenlet这个包拆分自Stackless。Stackless是一个CPython的版本,实现并支持一种叫做tasklets的微线程。Tasklets会以一种伪并发的方式运行(通常运行在单个或者一些系统级的线程中),它们之间通过channels来同步数据。

一个greenlet,从另一方面来说,依然是一种很原始的没有隐式调度的微线程。换句话说,就是协程(coroutine)。这在你想要完全控制代码的运行时是非常有用的。你可以在greenlet之上构建采用自定义调度方式的微线程。然而,使用greenlet来制作先进的控制流结构是很有用的。举个例子,我们可以重新创造生成器。和Python自带的生成器所不同的是,我们的生成器可以调用网状的方法,而且这些网状的方法也可以yield出值。(另外,你不需要再使用yield这个关键词了)

举例

我们来思考一个程序,用户可以在一个类终端控制台中输入命令来控制该程序。假设命令是一个字符一个字符的输入。在这样一个系统中,通常会存在如下一个循环:

defprocess_commands(*args):whileTrue:line=''whilenotline.endswith('\n'):line+=read_next_char()ifline=='quit\n':print"are you sure?"ifread_next_char()!='y':continue# ignore the commandprocess_command(line)

如果现在假设你想要将这个程序插入到用户界面中。大部分的用户界面工具都是基于事件驱动的,它们会在用户输入一个字符后调用回调函数。在这种设定下,编写上述代码所需的read_next_char()函数是非常难的,将会有如下两个冲突的函数:

defevent_keyworn(key):??defread_next_char():??应该等待下一个event_keydown()函数的调用

显然,上述情况在串行运行是不可能的。你也许想到了使用多个线程来完成。使用Greenlets作为替代方法,可以免去关联锁和程序退出的相关问题。你可以在程序运行主干中分裂出一个greenlet来专门运行process_commands()函数,你可以使用如下方式来交换用户的按键情况:

defevent_keydown(key):# 跳转至g_processor, 并且把用户所按键发送给g_processorg_processor.switch(key)defprocess_commands():whileTrue:line=''whilenotline.endswith('\n'):line+=read_next_char()ifline=='quit\n':print"are you sure?"ifread_next_char()!='y':continue# ignore the commandprocess_command(line)defread_next_char():# 在这个例子里,g_self就是g_processorg_self=greenlet.getcurrent()# 跳转到父greenlet(主greenlet)中,等待下一个按键next_char=g_self.parent.switch()returnnext_charg_processor=greenlet(process_commands)g_processor.switch(*args)# input arguments to process_commands()gui.mainloop()

在这个例子中,执行过程如下: 当read_next_char()被调用时,身处于g_processor这个greenlet中,所以它的父greenlet也就是派生g_processor的根greenlet。当它显示切换回父greenlet的时候,程序会重新回到顶层继续GUI事件监听循环。当事件监听循环回调event_keydown()函数的时候, 又切换回g_processor,这意味着程序会跳转到目标greenlet之前被挂起的地方继续执行-在这个例子里,将会跳转会read_next_char()函数中调用switch()的地方继续执行,并且在event_keydown()中调用switch()时所给的参数key会被做为返回值,并赋值给变量next_char。

注意,read_next_char()函数在被挂起和恢复时,它的调用栈会得到保护。所以它会在process_commands()中的不同位置返回,这主要取决于它原先被调用的位置。这使程序的执行逻辑以一种很好的控制流得以保持。我们不需要完全重写process_commands()来使其转化为状态机。

使用

简介

一个greenlet其实是一个独立的微伪线程。把它想象成一个小型的frame栈。最外层的frame就是你调用的初始函数,最里层的frame就是greenlet目前正停留的frame。在你使用greenlets的时候,就是通过创建大量的这种栈,并且在它们之间跳跃执行。跳跃(切换)永远都是显式的。一个greenlet必须显式切换至目标greenlet,这样会使前者的执行被挂起,而目标greenlet会在之前挂起的地方被恢复过来继续执行。greenlets之间的跳跃被称为切换

当你创建了一个greenlet,它会的到一个初始的空栈;当第一次切换到这个greenlet的时候,它开始执行一个指定的函数,在这个函数中又可能会调用其他函数,切换至其他greenlet。最终当最外层的函数执行完毕后,整个greenlet的调用栈再次变空,那么这个greenlet就死了。greenlet也可能死于未被捕获的异常。

举个例子:

fromgreenletimportgreenletdeftest1():print12gr2.switch()print34deftest2():print56gr1.switch()print78gr1=greenlet(test1)gr2=greenlet(test2)gr1.switch()

最后一行跳转至函数test1,打印出12。又跳转至函数test2,打印了56。重新跳转会test1,打印了34。 然后test1所在的greenlet执行完成并且死亡。此时,程序回到最开始的gr1.switch()继续执行。注意,78将永远不会被执行。

父级

让我们看看当某个greenlet死亡的的时候,程序会如何执行。每一个greenlet都会有一个父greenlet。顾名思义,父greenlet就是分裂出子greenlet的greenlet(不过可以随时通过greenlet.parent来修改某一个greenlet的父greenlet)。当greenlet结束的时候,程序会回到他的父greenlet继续执行。这样,所有的greenlet形成树结构。树的根节点其实是隐式的主greenlet,所以不在用户自定义的greenlet中执行的代码都会在主greenlet中被执行。

在上面的例子中,gr1和gr2的父greenlet都是最外层的主greenlet,不论是它们中谁结束了,程序就会回到主greenlet继续执行。

greenlet未被捕获的异常也会往外抛给父级greenlet。举个例子,如果上面例子中的函数test2包含一个拼写错误,那么所产生的NameError异常会干死gr2,程序便会直接回到主greenlet执行。而traceback会包含test2,但不会包含test1。记住,switch不是调用,而是在多个并行的栈容器之间切换执行。父级表示了它的栈在逻辑上是在当前greenlet的下方的。

实例

greenlet.greenlet是greenlet类型, 它支持以下操作:

greenlet(run=None, parent=None)创建新的greenlet对象(不会运行)。run参数执行需要执行的函数,parent指定它的父级greenlet,默任的话就是当前的greenlet。

greenlet.getcurrent()返回当前所在的greenlet(即调用该函数的greenlet)

greenlet.GreenletExit这个特定的异常并不会被往外抛给其父级greenlet;使用它可以杀死一个greenlet。

greenlet类也可以被继承。greenlet的run属性一般是在greenlet创建时被设置,调用run可以启动该greenlet。但当你定义greenlet的子类时,重写其run方法比在构造函数中传入run参数来的有意义。

切换

greenlet之间的切换发生在某个greenlet的switch函数被调用的时候,亦或是某个greenlet结束的时候(程序会返回父级greenlet继续执行)。在切换期间,一个对象或异常会被发送给目标greenlet。这可以作为一种方便的方法在greenlet之间传递信息。比如:

deftest1(x,y):z=gr2.switch(x+y)printzdeftest2(u):printugr1.switch(42)gr1=greenlet(test1)gr2=greenlet(test2)gr1.switch("hello"," world")

上述程序将会打印"hello, world"和42。值的注意的是,函数test1和函数test2的参数不是在greenlet被创建时所提供,而是在第一次切换到它们执行时提供。

g.switch(*args, **kwargs)将执行权切换给greenletg, 并将指定参数发送给它。特别的是,如果g还没有启动,那么此时g将会启动。

greenlet之死如果greenlet的run函数执行完了,函数的返回值将会被发送给其父级。如果run函数被异常所终止,则异常也会被抛给其父级(除非是greenlet.GreenletExit异常,该异常会被捕获,函数执行结束并返回父级)。

除了上述之外,目标greenlet通常接收对象作为调用之前已被挂起的greenlet的switch()函数的返回值。实际上,尽管对于switch()函数的调用不会立即返回,但是仍然会在未来的某个时刻返回(可能是别的greenlet切换时给出的参数,也可能是之前switch的greenlet的函数返回值)。此时,程序会回到之前被挂起的位于对于switch()函数的调用处。这表示 x = g.switch(y) 会把y发送给greenletg,然后过段时间之后,会接收到一个对象并赋值给x。

注意,对于任何已死greenlet的切换操作都会走到它们的父辈,或者父父辈,以此类推。最后的父级greenlet就是整棵树的根节点maingreenlet,因为它永远不会死亡。

greenlet的属性和方法

g.switch(*args, **kwargs)切换至greenletg

g.rungreenlet启动时将会执行的函数。当greenletg 启动后该属性将不复存在。

g.parent父级greenlet。该属性可修改,但是不允许形成循环父子结构。

g.gr_frameThe current top frame, or None.当前顶层frame,没有则是None。

g.dead如果greenletg已经执行结束了,则返回True。

bool(g)如果greenletg还活着,则返回True,结束活着还未开始都是False。

g.throw([typ, [val, [tb]]])切换至greenletg,不过会立刻在g中抛出给定的异常。如果没有指定任何参数,那么默认抛出greenlet.GreenletExit,那么g就结束了。调用这个函数就相当于下面的过程:

defraiser():raisetyp,val,tbg_raiser=greenlet(raiser,parent=g)g_raiser.switch()

注意,上述代码对于greenlet.GreenletExit无效,因为该异常不会被抛给父级greenletg

greenlets和Python线程

greenlet可以和Python的线程结合使用;这种情况下,每一个线程将包含一个主greenlet和大量子greenlet,形成树形结构。对于属于不同线程的greenlets,混合或者切换操作是不可能的。

greenlet的垃圾回收

如果对于一个greenlet对象的所有引用都不存在了(包括来自其他greenlet的parent属性的引用),然后就没有办法再切换到这个greenlet了。这种情况下,GreenletExit异常就会在该greenlet中产生,这是一个greenlet唯一的一种异步获取执行权的情况。你可以使用try:finally:语句块来清理该greenlet所占有的资源。这个特性同时也支持那种使用死循环接收并处理数据的编码风格。当对于greenlet的最后一个引用被干掉后,死循环就会自动终止了。

通过在某处保存对于某个greenlet的新引用可以认为这个greenlet可以正常死亡或重新恢复。只要捕获并忽略GreenletExit异常就可以让这个greenlet进入死循环。

Greenlet不参与垃圾回收;greenlet中的循环引用将不会被发现,循环保存对于greenlet的引用可能会导致内存泄露。

调用跟踪支持

标准的Python调用跟踪和性能分析在greenlet中不能正常工作,这是因为栈和frame的切换发生在同一个线程之中。使用传统的方法来发现可靠的greenlet切换操作是很困难的,所以greenlet模块为基于greenlet的代码提供了新的调试,追踪和分析功能:

greenlet.gettrace()返回先前设定的调用跟踪函数,如果没有则返回None。

greenlet.settrace(callback)设定新的调用跟踪函数并返回之前设定的调用跟踪函数,如果没有则返回None。回调函数会被不同的事件所调用,并且需要行如:

defcallback(event,args):ifevent=='switch':origin,target=args# Handle a switch from origin to target.# Note that callback is running in the context of target# greenlet and any exceptions will be passed as if# target.throw() was used instead of a switch.returnifevent=='throw':origin,target=args# Handle a throw from origin to target.# Note that callback is running in the context of target# greenlet and any exceptions will replace the original, as# if target.throw() was used with the replacing exception.return

如果事件类型既有'switch'又有'throw', 那么对于参数元组args的解包就非常重要。这样的话,API就可以像sys.settrace()那样被扩展为更多的事件类型。

两个官方给出的例子

1. 简单的生成器
importunittestfromgreenletimportgreenletclassgenlet(greenlet):def__init__(self,*args,**kwds):self.args=argsself.kwds=kwdsdefrun(self):fn,=self.fnfn(*self.args,**self.kwds)def__iter__(self):returnselfdef__next__(self):self.parent=greenlet.getcurrent()result=self.switch()ifself:returnresultelse:raiseStopIteration# Hack: Python < 2.6 compatibilitynext=__next__defYield(value):g=greenlet.getcurrent()whilenotisinstance(g,genlet):ifgisNone:raiseRuntimeError('yield outside a genlet')g=g.parentg.parent.switch(value)defgenerator(func):classgenerator(genlet):fn=(func,)returngeneratorclassGeneratorTests(unittest.TestCase):deftest_generator(self):seen=[]defg(n):foriinrange(n):seen.append(i)Yield(i)g=generator(g)forkinrange(3):forjing(5):seen.append(j)self.assertEqual(seen,3*[0,0,1,1,2,2,3,3,4,4])
2. 网状调用生成器
importunittestfromgreenletimportgreenletclassgenlet(greenlet):def__init__(self,*args,**kwds):self.args=argsself.kwds=kwdsself.child=Nonedefrun(self):fn,=self.fnfn(*self.args,**self.kwds)def__iter__(self):returnselfdefset_child(self,child):self.child=childdef__next__(self):ifself.child:child=self.childwhilechild.child:tmp=childchild=child.childtmp.child=Noneresult=child.switch()else:self.parent=greenlet.getcurrent()result=self.switch()ifself:returnresultelse:raiseStopIteration# Hack: Python < 2.6 compatibilitynext=__next__defYield(value,level=1):g=greenlet.getcurrent()whilelevel!=0:ifnotisinstance(g,genlet):raiseRuntimeError('yield outside a genlet')iflevel>1:g.parent.set_child(g)g=g.parentlevel-=1g.switch(value)defGenlet(func):classGenlet(genlet):fn=(func,)returnGenletdefg1(n,seen):foriinrange(n):seen.append(i+1)yieldidefg2(n,seen):foriinrange(n):seen.append(i+1)Yield(i)g2=Genlet(g2)defnested(i):Yield(i)defg3(n,seen):foriinrange(n):seen.append(i+1)nested(i)g3=Genlet(g3)defa(n):ifn==0:returnforiiinax(n-1):Yield(ii)Yield(n)ax=Genlet(a)defperms(l):iflen(l)>1:foreinl:# No syntactical sugar for generator expressions[Yield([e]+p)forpinperms([xforxinlifx!=e])]else:Yield(l)perms=Genlet(perms)defgr1(n):foriiinrange(1,n):Yield(ii)Yield(ii*ii,2)gr1=Genlet(gr1)defgr2(n,seen):foriiingr1(n):seen.append(ii)gr2=Genlet(gr2)classNestedGeneratorTests(unittest.TestCase):deftest_layered_genlets(self):seen=[]foriiingr2(5,seen):seen.append(ii)self.assertEqual(seen,[1,1,2,4,3,9,4,16])deftest_permutations(self):gen_perms=perms(list(range(4)))permutations=list(gen_perms)self.assertEqual(len(permutations),4*3*2*1)self.assertTrue([0,1,2,3]inpermutations)self.assertTrue([3,2,1,0]inpermutations)res=[]foriiinzip(perms(list(range(4))),perms(list(range(3)))):res.append(ii)self.assertEqual(res,[([0,1,2,3],[0,1,2]),([0,1,3,2],[0,2,1]),([0,2,1,3],[1,0,2]),([0,2,3,1],[1,2,0]),([0,3,1,2],[2,0,1]),([0,3,2,1],[2,1,0])])# XXX Test to make sure we are working as a generator expressiondeftest_genlet_simple(self):forgin[g1,g2,g3]:seen=[]forkinrange(3):forjing(5,seen):seen.append(j)self.assertEqual(seen,3*[1,0,2,1,3,2,4,3,5,4])deftest_genlet_bad(self):try:Yield(10)exceptRuntimeError:passdeftest_nested_genlets(self):seen=[]foriiinax(5):seen.append(ii)

结尾

了解greenlet主要是为了深入了解gevent做铺垫,翻译呢主要是为了加深自己的记忆:)

This entry was tagged on#concurrent and#gevent

Tue 10 May 2016 by

Golang编程练习

完整代码戳这里

1. 验证给定字符串是否为合法身份证号码

算法:前17位纯数字分别乘以相应的因子,然后求和后除以11取余数。使用该余数取得校验字节数组中相位位置的字节与身份证最后一位字节做比较。如果相等即为合法身份证!

packagemainimport"fmt"import"strconv"var(Factories[17]int=[17]int{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2}ValidateBits[]byte=[]byte("10X98765432"))funcIsValidIDCode(idstring)bool{idBytes:=[]byte(id)iflength:=len(idBytes);length!=18{returnfalse}sum:=0lastByte:=idBytes[17]idBytes=idBytes[:17]fori,b:=rangeidBytes{value,err:=strconv.Atoi(string(b))iferr!=nil{returnfalse}sum+=value*Factories[i]}index:=sum%11ifValidateBits[index]==lastByte{returntrue}returnfalse}funcmain(){myID:="身份证号码"fmt.Println(IsValidIDCode(myID))}
2. 排序之快速排序

快速排序的本质其实很简单: 在一趟排序中,在需要排序的序列中选出一个基准数(pivot), 然后将比它的数放左边, 比它小的放右边。然后采用分治和递归,再去分别对小数序列和大数序列排序。

以前学的一种实现使用while循环,老是在right, left等于的时候搞不清楚啊。在参考了wiki百科之后,发现以下写法清晰易懂。允许随便选取一个基准(不过我写的时候,默认使用第一位了), 先把基准放置到最后一位,然后遍历前n-1个数,把小于基准数的数往前放,同时使用一个游标来记录。遍历完之后,将基准数交换到游标处。自此,基准数之前全是小于基准数的,之后都是小于等于基本数的。然后分别继续递归。

packagemainimport"fmt"funcmain(){a:=[]int{5,5,4,3,2,3,5,1,1,5}IntQuickSort(a,0,9)fmt.Println(a)}funcIntQuickSort(slice[]int,left,rightint){ifright-left<=1{return}// 默认将第一位作为基准位pivotIndex:=left// 把基准交换到最后一位slice[pivotIndex],slice[right]=slice[right],slice[pivotIndex]storeIndex:=leftfori:=left;i<right;i++{ifslice[i]<slice[right]{slice[storeIndex],slice[i]=slice[i],slice[storeIndex]storeIndex++}}slice[storeIndex],slice[right]=slice[right],slice[storeIndex]IntQuickSort(slice,left,storeIndex-1)IntQuickSort(slice,storeIndex+1,right)}
3. 排序之归并排序

merge sort

上图非常清楚的表现了归并排序的过程: 先不停的对半分组,直到分成一个数字一组, 然后相邻的两组作比较,归并成有序的一组,不断向上归并。

packagemainimport"fmt"funcIntMergeSort(slice[]int,left,rightint){ifleft>=right{return}cache:=make([]int,len(slice))intMergeSort(slice,cache,left,right)}funcintMergeSort(slice,cache[]int,left,rightint){length:=right-leftiflength<=0{return}middle:=length>>1+leftleft1,right1:=left,middleleft2,right2:=middle+1,rightintMergeSort(slice,cache,left1,right1)intMergeSort(slice,cache,left2,right2)index:=leftforleft1<=right1&&left2<=right2{ifslice[left1]<slice[left2]{cache[index]=slice[left1]left1++}else{cache[index]=slice[left2]left2++}index++}forleft1<=right1{cache[index]=slice[left1]left1++index++}forleft2<=right2{cache[index]=slice[left2]left2++index++}fori:=left;i<=right;i++{slice[i]=cache[i]}}funcmain(){slice:=[]int{4,5,6,7,1,2,3,9}IntMergeSort(slice)fmt.Println(slice)}
4. 排序之插入排序

insertion sort

如上图所示,插入排序的过程很简单: 遍历后边的无序数字,将他们插入前边已经排好序的序列中。插入方式就是取出数字后,按照从后往前的顺序跟前面有序序列中的数字比较,如果大于等于它,就把这些数字向后移一位,直到找到比它小的(或者超出边界), 然后将该数字插入到该位置。

packagesortfuncIntInsertionSort(slice[]int,left,rightint){ifright-left<=0{return}fori:=left+1;i<=right;i++{temp:=slice[i]j:=ifor;j>left&&temp<slice[j-1];j--{slice[j]=slice[j-1]}slice[j]=temp}}

This entry was tagged on#Go

Sat 30 April 2016 by

简单的Acfun自动签到

acfun

1.概述

Acfun有一个签到机制,每天签到可以领取经验和香蕉。之前在玩supervisor,celerybeat和rabbitmq的时候搞了一个自动签到的定时任务,但是由于服务器的原因,每天都会出问题。而且前段时间,那台服务器也到期了,所以现在有必要再搞一个简单的自动签到任务了。:)

2. 签到

为了简便,这里就不使用Python了。直接curl搞定吧!上代码:

curl --cookie-jar cookies -d"username=name&password=passwd" https://www.acfun.tv/login.aspx&&echocurl --cookie cookies -d"channel=0&date=`date +%s`000" https://www.acfun.tv/webapi/record/actions/signin&&echo

简单说明一下, A站签到要求先登录,所以我们脚本的第一步使用自己的账户密码登录并且把cookie存放在本地文件中。然后第二步直接读取cookie完成签到。其实问题就在于签到请求需要带有date参数,它其实是JavaScript的时间戳。将上述代码保存为checkin.sh 就基本上完成了代码部分。是不是很简单?

3. 自动化

使用linux的crontab来完成定时任务。在*nix下为当前用户添加一个crontab任务吧。

  1. 开始编辑crontab -e
  2. 添加一行0 1 * * * sh /path/to/checkin.sh > /path/to/checkin.log
  3. 保存退出

这样就会在每天一点的时候执行一次签到。:)

4. 结束

哎,感觉好水啊!

This entry was tagged on#contab,#curl and#shell

Page 1 / 3»

 

Blogroll

Social Network

Categories

Tags

    This Blog generated byPelican usingPujangga theme. All content on this blog, unless stated otherwise, is licensed under theCC BY-SA 4.0 International license.


    [8]ページ先頭

    ©2009-2025 Movatter.jp