from_fromkeys - CSDN
精华内容
参与话题
  • from 表单提交

    万次阅读 2013-12-10 21:48:00
    因为是转载文章 在此标明出处,以前有文章是转的没标明的请谅解,因为有些已经无法找到出处,或者与其它原因。 如有冒犯请联系本人,或删除,或标明出处。 因为好的文章,以前只想收藏,但连接有时候会失效,...

    因为是转载文章 在此标明出处,以前有文章是转的没标明的请谅解,因为有些已经无法找到出处,或者与其它原因。

    如有冒犯请联系本人,或删除,或标明出处。

    因为好的文章,以前只想收藏,但连接有时候会失效,所以现在碰到好的直接转到自己这里。

    原文 出处http://www.cnblogs.com/fish-li/archive/2011/07/17/2108884.html



    Form(表单)对于每个WEB开发人员来说,应该是再熟悉不过的东西了,可它却是页面与WEB服务器交互过程中最重要的信息来源。 虽然Asp.net WebForms框架为了帮助我们简化开发工作,做了很完美的封装,让我们只需要简单地使用服务端控件就可以直接操作那些 HTML表单元素了。但我认为了解一些基础的东西,可以使我们不必束缚在WebForms框架上,以及遇到一些奇怪问题时, 可以更从容地解决它们。

    今天,我将和大家来聊聊表单,这个简单又基础的东西。我将站在HTML和单纯的Asp.net框架的角度来解释它们的工作方式, 因此,本文不演示WebForms服务器控件的相关内容。

    简单的表单,简单的处理方式

    好了,让我们进入今天的主题,看看下面这个简单的HTML表单。

    <form action="Handler1.ashx" method="post" >
    <p>客户名称: <input type="text" name="CustomerName" style="width: 300px" /></p>
    <p>客户电话: <input type="text" name="CustomerTel" style="width: 300px" /></p>
    <p><input type="submit" value="提交" /></p>
    </form>
    

    在这个HTML表单中,我定义了二个文本输入框,一个提交按钮,表单将提交到Handler1.ashx中处理,且以POST的方式。
    注意哦,如果我们想让纯静态页面也能向服务器提交数据,就可以采用这样方式来处理:将action属性指向一个服务器能处理的地址。

    说明:当我们使用WebForms的服务器表单控件时,一般都会提交到页面自身来处理(action属性指向当前页面), 这样可以方便地使用按钮事件以及从服务器控件访问从浏览器提交的控件输入结果。
    如果在URL重写时,希望能在页面回传时保持URL不变,即:action为重写后的URL,那么可以Page类中执行以下调用:

    Form.Action = Request.RawUrl;    // 受以下版本支持:3.5 SP1、3.0 SP1、2.0 SP1
    
    

    好了,我们再回到前面那个HTML表单,看一下如果用户点击了“提交”按钮,浏览器是如何把表单的内容发出的。 在此,我们需要Fiddler工具的协助,请在提交表单前启动好Fiddler。我将这个表单的提交请求过程做了如下截图。

    上图是将要提交的表单的输入情况,下图是用Fiddler看到的浏览器发出的请求内容。

    在这张图片中,我们可以看到浏览器确实将请求发给了我前面在action中指定的地址,且以POST形式发出的。 表单的二个控件的输入值放在请求体中,且做了【编码】处理,编码的方式用请求头【Content-Type】说明, 这样,当服务端收到请求后,就知道该如何读取请求的内容了。 注意:表单的数据是以name1=value1&name2=value2 的形式提交的,其中name,value分别对应了表单控件的相应属性。

    我们还可以在Fiddler中,将视图切换到WebForms选项卡,这样能更清楚地只查看浏览器提交的数据,如下图。

    看了客户端的页面和请求的内容,我们再来看看在服务端如何获取浏览器提交的表单的输入吧,代码如下:

    string name = context.Request.Form["CustomerName"];
    string tel = context.Request.Form["CustomerTel"];
    

    代码很简单,直接根据表单控件的name属性访问Request.Form就可以了。

    表单提交,成功控件

    我们再来看一下浏览器是如何提交表单的,或者说,浏览器在提交表单时,要做哪些事情。

    浏览器并不是将所有的表单控件全部发送到服务器的,而是会查找所有的【成功控件】,只将这些成功控件的数据发送到服务端, 什么是成功控件呢?
    简单地来说,成功控件就是:每个表单中的控件都应该有一个name属性和”当前值“, 在提交时,它们将以 name=value 的形式做为提交数据的一部分。
    对于一些特殊情况,成功控件还有以下规定:
    1. 控件不能是【禁用】状态,即指定【disabled="disabled"】。即:禁用的控件将不是成功控件。
    2. 如果一个表单包含了多个提交按键,那么仅当用户点击的那个提交按钮才算是成功控件。
    3. 对于checkbox控件来说,只有被用户勾选的才算是成功控件。
    4. 对于radio button来说,只有被用户勾选的才算是成功控件。
    5. 对于select控件来说,所有被选择的选项都做为成功控件,name由select控件提供。
    6. 对于file上传文件控件来说,如果它包含了选择的文件,那么它将是一个成功控件。
    此外,浏览器不会考虑Reset按钮以及OBJECT元素。

    注意:
    1. 对于checkbox, radio button来说,如果它们被确认为成功控件,但没有为控件指定value属性, 那么在表单提交时,将会以"on"做为它们的value
    2. 如果在服务端读不到某个表单控件的值,请检查它是否满足以上规则。

    提交方式:在前面的示例代码中,我为form指定了method="post",这个提交方法就决定了浏览器在提交数据时,通过什么方式来传递它们。
    如果是【post】,那么表单数据将放在请求体中被发送出去。
    如果是【get】,那么表单数据将会追加到查询字符串中,以查询字符串的形式提交到服务端。
    建议:表单通常还是以post方式提交比较好,这样可以不破坏URL,况且URL还有长度限制。

    数据的编码:前面我将浏览器的请求细节用Fiddler做了个截图,从这个图中我们可以看到:控件输入的内容并不是直接发送的, 而是经过一种编码规则来处理的。目前基本上只会只使用二种编码规则:application/x-www-form-urlencoded 和 multipart/form-data , 这二个规则的使用场景简单地说就是:后者在上传文件时使用,其它情形则使用前者(默认)。
    这个规则是在哪里指定的呢? 其实form还有个enctype属性,用它就可以指定编码规则,当我在VS2008写代码时,会有以下提示:

    按照我前面说过的编码规则选择逻辑,application/x-www-form-urlencoded做为默认值,所以,一般情况下我们并不用显式指定。 除非我们要上传文件了,那么此时必须设置enctype="multipart/form-data"

    好了,说了这么一大堆理论,我们再来看一下浏览是如何处理表单数据的。这个过程大致分为4个阶段:
    1. 识别所有的成功控件。
    2. 为所有的成功控件创建一个数据集合,它们包含 control-name/current-value 这样的值对。
    3. 按照form.enctype指定的编码规则对前面准备好的数据进行编码。编码规则将放在请求中,用【Content-Type】指出。
    4. 提交编码后的数据。此时会区分post,get二种情况,提交的地址由form.action属性指定的。

    多提交按钮的表单

    用过Asp.net WebForms框架的人可能都写过这样的页面:一个页面中包含多个服务端按钮。处理方式嘛, 也很简单:在每个按钮的事件处理器写上相应的代码就完事了,根本不用我们想太多。
    不过,对于不理解这背后处理过程的开发人员来说,当他们转到MVC框架下,可能会被卡住:MVC框架中可没有按钮事件! 即使用不用MVC框架,用ashx通用处理器的方式,也会遇到这种问题,怎么办?
    对于这个问题,本文将站在HTML角度给出二个最根本的解决办法。

    方法1:根据【成功控件】定义,我们设置按钮的name,在服务端用name来区分哪个按钮的提交:

    HTML代码

    <form action="Handler1.ashx" method="post">
    <p>客户名称: <input type="text" name="CustomerName" style="width: 300px" /></p>
    <p>客户电话: <input type="text" name="CustomerTel" style="width: 300px" /></p>
    <p><input type="submit" name="btnSave" value="保存" />
        <input type="submit" name="btnQuery" value="查询" />
    </p>
    </form>
    

    服务端处理代码

    // 注意:我们只要判断指定的name是否存在就可以了。        
    if( string.IsNullOrEmpty(context.Request.Form["btnSave"]) == false ) {
        // 保存的处理逻辑
    }
    if( string.IsNullOrEmpty(context.Request.Form["btnQuery"]) == false ) {
        // 查询的处理逻辑
    }
    

    方法2:我将二个按钮的name设置为相同的值(根据前面的成功控件规则,只有被点击的按钮才会提交),在服务端判断value,示例代码如下:

    <form action="Handler1.ashx" method="post">
    <p>客户名称: <input type="text" name="CustomerName" style="width: 300px" /></p>
    <p>客户电话: <input type="text" name="CustomerTel" style="width: 300px" /></p>
    <p><input type="submit" name="submit" value="保存" />
        <input type="submit" name="submit" value="查询" />
    </p>
    </form>
    

    string action = context.Request.Form["submit"];
    if( action == "保存" ) {
        // 保存的处理逻辑
    }
    if( action == "查询" ) {
        // 查询的处理逻辑
    }
    

    当然了,解决这个问题的方法很多,我们还可以在提交前修改form.action属性。 对于MVC来说,可能有些人会选择使用Filter的方式来处理。最终选择哪种方法,可根据各自喜好来选择。
    我可能更喜欢直接使用Ajax提交到一个具体的URL,这样也很直观,在服务端也就不用这些判断了。接着往下看吧。

    上传文件的表单

    前面我说到“数据的编码"提到了form.enctype,这个属性正是上传表单与普通表单的区别,请看以下示例代码:

    <form action="Handler2.ashx" method="post" enctype="multipart/form-data">
    <p><input type="text" name="str" value="一个字符串,别管它" /></p>
    <p>要上传的文件1<input type="file" name="file1"/></p>
    <p>要上传的文件2<input type="file" name="file2"/></p>
    <p><input type="submit" value="提交" /></p>
    </form>
    

    我将上传2个小文件

    我们再来看看当我点击提交按钮时,浏览器发送的请求是个什么样子的:

    注意我用红色边框框出来的部分,以及请求体中的内容。此时请求头Content-Type的值发生了改变, 而且还多了一个叫boundary的参数,它将告诉服务端:请求体的内容以这个标记来分开。 并且,请求体中每个分隔标记会单独占一行,且具体内容为:"--" + boundary, 最后结束的分隔符的内容为:"--" + boundary + "--" 也是独占一行。 从图片中我们还可以发现,在请求体的每段数据前,还有一块描述信息。
    具体这些内容是如何生成的,可以参考本文后面的实现代码。

    再来看看在服务端如何读取上传的文件。

    HttpPostedFile file1 = context.Request.Files["file1"];
    if( file1 != null && string.IsNullOrEmpty(file1.FileName) == false )
        file1.SaveAs(context.Server.MapPath("~/App_Data/") + file1.FileName);
    
    HttpPostedFile file2 = context.Request.Files["file2"];
    if( file2 != null && string.IsNullOrEmpty(file2.FileName) == false )
        file2.SaveAs(context.Server.MapPath("~/App_Data/") + file2.FileName);
    

    或者

    HttpFileCollection files = context.Request.Files;
    foreach( string key in files.AllKeys ) {
        HttpPostedFile file = files[key];
        if( string.IsNullOrEmpty(file.FileName) == false )
            file.SaveAs(context.Server.MapPath("~/App_Data/") + file.FileName);
    }
    

    二种方法都行,前者更能体现控件的name与服务端读取的关系,后者在多文件上传时有更好的扩展性。

    安全问题:注意,上面示例代码中,这样的写法是极不安全的。正确的做法应该是:重新生成一个随机的文件名, 而且最好能对文件内容检查,例如,如果是图片,可以调用.net的一些图形类打开文件,然后"另存"文件。 总之,在安全问题面前只有一个原则:不要相信用户的输入,一定要检查或者转换。

    MVC Controller中多个自定义类型的传入参数

    前面的所有示例代码中都有一个规律:在服务端读取浏览器提交的数据时,都会使用控件的name属性,基本上在Asp.net中就是这样处理。 但是在MVC中,MS为了简化读取表单数据的代码,可以让我们直接在Controller的方法中直接以传入参数的形式指定, 此时框架会自动根据方法的参数名查找对应的输入数据(当然也不止表单数据了)。下面举个简单的例子:

    <form action="/Home/Submit" method="post">
    <p>客户名称: <input type="text" name="Name" style="width: 300px" /></p>
    <p>客户电话: <input type="text" name="Tel" style="width: 300px" /></p>
    <p><input type="submit" value="提交" /></p>
    </form>
    

    Conntroller中的方法的签名:

    public ActionResult Submit(Customer customer)
    {
    }
    
    public ActionResult Submit(string name, string tel)
    {
    }
    

    以上二种方法都是可以的,当然了,前者会比较好,但需要事先定义一个Customer类,代码如下:

    public class Customer
    {
        public string Name { get; set; }
    
        public string Tel { get; set; }
    }
    

    如果表单简单或者业务逻辑简单,我们或许一直也不会遇到什么麻烦,以上代码能很好的工作。 但是,如果哪天我们有了新的业务需要求,需要在这个表单中同时加上一些其它的内容,例如,要把业务员的资料也一起录入进去。 其中业务员的实体类定义如下:

    public class Salesman
    {
        public string Name { get; set; }
    
        public string Tel { get; set; }
    }
    

    Controller的接口需要修改成:

    public ActionResult Submit(Customer customer, Salesman salesman)
    {
    }
    

    这时,HTML表单又该怎么写呢?刚好,这二个类的(部分)属性名称一样,显然,前面表单中的Name,Tel就无法对应了。 此时我们可以将表单写成如下形式:

    <form action="/Home/Submit" method="post">
    <p>客户名称: <input type="text" name="customer.Name" style="width: 300px" /></p>
    <p>客户电话: <input type="text" name="customer.Tel" style="width: 300px" /></p>
    <p>销售员名称: <input type="text" name="salesman.Name" style="width: 300px" /></p>
    <p>销售员电话: <input type="text" name="salesman.Tel" style="width: 300px" /></p>
    <p><input type="submit" value="提交" /></p>
    </form>
    

    注意Controller方法中的参数名与HTML表单中的name是有关系的。

    F5刷新问题并不是WebForms的错

    刚才说到了MVC框架,再来说说WebForms框架。以前时常听到有些人在抱怨用WebForms的表单有F5的刷新重复提交问题。 在此我想为WebForms说句公道话:这个问题并不是WebForms本身的问题,是浏览器的问题, 只是如果您一直使用WebForms的较传统用法,是容易产生这个现象的。那么什么叫做【传统用法】呢?这里我就给个我自己的定义吧: 所谓的WebForms的传统用法是说:您的页面一直使用服务器控件的提交方式(postback),在事件处理后,页面又进入再一次的重现过程, 或者说:当前页面一直在使用POST方式向当前页面提交。

    那么如何避开这个问题呢?办法大致有2种:

    1. PRG模式(Post-Redirect-Get),在事件处理后,调用重定向的操作Response.Redirect(), 而不要在事件处理的后期再去给一些服务器控件绑定数据项了!
    建议:按钮事件只做一些提交数据的处理,将数据绑定的操作放在OnPreRender方法中处理,而不是写在每个事件中(遍地开花)。 不过,这种方式下,可能伟大的ViewState就发挥不了太大的作用了,如果您发现ViewState没用了,在Web.config中全局关掉后, 又发现很多服务器控件的高级事件又不能用了!嗯,杯具有啊。
    这个话题说下去又没完没了,到此为止吧,不过,千万不要以为这种方法是在倒退哦。

    2. 以Ajax方式提交表单,请继续阅读本文。

    以Ajax方式提交整个表单

    前面一直在说”浏览器提交表单",事实上我们也可以用JavaScript提交表单,好处也有很多,比如前面所说的F5刷新问题。 以Ajax方式提交表单的更大好处它是异步的,还可以实现局部刷新,这些特性都是浏览器提交方式没有的。 前面我提到表单在提交时,浏览器要实现的4个步骤,基本上用JS来完成这个操作也是一样的。 但是,前面说的步骤好像很麻烦呢,有没有简单的方法来实现这个过程呢? 嗯,有的,这里我将使用JQuery以及jquery.form.js这个插件来演示这个复杂过程的简单处理方案。

    示例用的HTML表单还是我前面用的代码,完全不需要修改:

    <form action="Handler1.ashx" method="post" >
    <p>客户名称: <input type="text" name="CustomerName" style="width: 300px" /></p>
    <p>客户电话: <input type="text" name="CustomerTel" style="width: 300px" /></p>
    <p><input type="submit" value="提交" /></p>
    </form>
    

    JS代码如下:

    $(function(){
        $('form').ajaxForm({
            success: function(responseText){
                alert(responseText);
            }
        });
    });
    

    是的,就是这么简单,只要调用ajaxForm()就行了。你也可以传入任何$.ajax()能接受的参数。
    它的作用是:修改表单的提交方式,改成Ajax方式提交。最终当用户点击“提交”按钮时,此时不再是浏览器的提交行为了, 而是使用Ajax的方式提交,提交的URL以及提交方法就是在FORM中指定的参数。

    如果您希望要用户点击某个按钮或者链接时,也能提交表单(不经过提交按钮),那么可以使用如下方法:

    $(function(){
        $("#btnId").click(function(){
            $('form').ajaxSubmit({
                success: function(responseText){
                    alert(responseText);
                }
            });
        });
    });
    

    变化很小,只需要将ajaxForm修改成ajaxSubmit就OK了。 与ajaxForm()不同,调用ajaxSubmit()方法将会立即提交表单。

    以Ajax方式提交部分表单

    在前面的示例中,我们看到以Ajax方式提交一个表单是非常容易的,它完全模拟了浏览器的行为。 不过,有时我们可能需要只提交表单的一部分,为的是更好的局部更新,那么又该如何做呢?
    假如我有以下表单的一部分,我只希望在用户某个按钮时将它提交到服务端:

    <div id="divCustomerInfo">
    <p>客户名称: <input type="text" name="CustomerName" style="width: 300px" /></p>
    <p>客户电话: <input type="text" name="CustomerTel" style="width: 300px" /></p>
    </div>
    

    我们可以这样来提交这部分表单的数据:

    $("#btnId").click(function(){
        $.ajax({
            url: "Handler1.ashx", type: "POST",
            data: $('#divCustomerInfo :text').fieldSerialize(),
            success: function(responseText){
                alert(responseText);
            }
        });
        return false;
    });
    

    注意关键的代码行:data: $('#divCustomerInfo :text').fieldSerialize()
    注意:此时将由您指定一个【JQuery选择器】来过滤要提交的控件,而不是使用成功控件的筛选逻辑。

    或者,您也可以使用下面将要介绍的方法,仍然是使用 data: {} 的方式,但需要手工指定数据成员。

    使用JQuery,就不要再拼URL了!

    JQuery越来越流行,以至于在创建MVC项目时,VS IDE会把JQuery也准备好了,可能MS认为开发WEB项目离不开JQuery了。
    的确,JQuery非常方便,尤其是在处理DOM时,不仅如此,在处理AJAX请求时,也非常方便。

    不过,有件事却让我很纳闷:经常看到有人在使用JQuery实现Ajax时,把一堆参数放在URL中传递,当然了, 发送GET请求嘛,这样做不错,但是,让我不解的是:URL是拼接起来的,而且代码又臭又长!

    如果是一个简单的参数:"aaa.aspx?id=" + xxId ,这样也就罢了。但是当一堆参数拼接在一起时,可能一下子还看不清楚到底有几个什么样的参数。 而且经验丰富一些的开发人员会发现这样做有时会有乱码问题,可能网上搜过后,知道还有编码的工作要处理,于是又加了一堆编码方法。 到此为止,这段代码会让人看起来很累!

    如果您平时也是这样做的,那么我今天就告诉您:不要再拼接URL了! $.ajax()的参数不是有个data成员嘛,用它吧。看代码:

    $.ajax({
        url: "Handler1.ashx", type: "POST",
        data: { id: 2, name: "aaa", tel: "~!@#$%^&*()_+-=<>?|", xxxx: "要多少还可以写多少", encoding: "见鬼去吧。?& :)" },
        success: function(responseText) {
            $("#divResult").html(responseText);
        }
    });
    

    你说什么,只能使用GET ? 哦,那就改一下 type 参数吧。

    $.ajax({
        url: "Handler1.ashx", type: "GET",
        data: { id: 2, name: "aaa", tel: "~!@#$%^&*()_+-=<>?|", xxxx: "要多少还可以写多少", encoding: "见鬼去吧。?& :)" },
        success: function(responseText) {
            $("#divResult").html(responseText);
        }
    });
    

    看了这个示例,您还会继续拼URL吗?

    说明:为了排版简单,我将参数放在一行了,建议实际使用时,不要挤在一行。

    id, name 有什么关系

    通常我们在写HTML代码时,会给控件指定一个id属性,这个属性只供JS和CSS使用,在表单提交时,它不起任何作用。

    在上面的示例代码中,可能data {}中的各个value就来源于各个不同的控件,那么为那些控件指定相应的id属性将会方便地找到它们。
    但是如果不需要用JS和CSS控制的控件,或许它们只是用来显示一些数据(只读),那么就没有必要指定id属性, 当然了,name属性也可以不用给出(避免提交无意义的数据)。

    使用C#模拟浏览器提交表单

    浏览器也是一个普通的应用程序,.net framework也提供一些类也能让我们直接发起HTTP请求。 今天我将再次用C#来模拟浏览器的提交请求,同时也可以加深对HTTP请求的理解。

    示例代码分为二段,一段示范了使用application/x-www-form-urlencoded编码方式提交, 另一段则示范了使用multipart/form-data的编码方式。
    为了让大家能再次利用这些代码,我已将关键部分写成独立方法,希望当您有这方面的需求时能马上可以用上。 代码如下:

    1. application/x-www-form-urlencoded

    /// <summary>
    /// 向指定的URL地址发起一个POST请求,同时可以上传一些数据项。
    /// </summary>
    /// <param name="url">要请求的URL地址</param>
    /// <param name="keyvalues">要上传的数据项</param>
    /// <param name="encoding">发送,接收的字符编码方式</param>
    /// <returns>服务器的返回结果</returns>
    static string SendHttpRequestPost(string url, Dictionary<string, string> keyvalues, Encoding encoding)
    {
        if( string.IsNullOrEmpty(url) )
            throw new ArgumentNullException("url");
        
        string postData = null;
        // 将数据项转变成 name1=value1&name2=value2 的形式
        if( keyvalues != null && keyvalues.Count > 0 ) {
            postData = string.Join("&",
                    (from kvp in keyvalues
                     let item = kvp.Key + "=" + HttpUtility.UrlEncode(kvp.Value)
                     select item
                     ).ToArray()
                 );
        }
    
        if( encoding == null )
            encoding = Encoding.UTF8;
    
    
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "POST";            
        request.ContentType = "application/x-www-form-urlencoded; charset=" + encoding.WebName;
        
        if( postData != null ) {
            byte[] buffer = encoding.GetBytes(postData);
    
            Stream stream = request.GetRequestStream();
            stream.Write(buffer, 0, buffer.Length);
            stream.Close();
        }
    
        using( WebResponse response = request.GetResponse() ) {
            using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) {
                return reader.ReadToEnd();
            }
        }
    }
    
    // 调用上面方法的示例代码
    string Test_SendHttpRequestPost()
    {
        string url = "http://localhost:1272/FormWebSite1/Handler1.ashx";
    
        Dictionary<string, string> keyvalues = new Dictionary<string, string>();
        keyvalues.Add("CustomerName", "我是李奇峰,$%@+& ?#^/");
        keyvalues.Add("CustomerTel", "1381723505x");
    
        return SendHttpRequestPost(url, keyvalues, null);
    }
    

    2. multipart/form-data 。注意这部分代码有点复杂,因此我加了很多注释。

    /// <summary>
    /// 向指定的URL地址发起一个POST请求,同时可以上传一些数据项以及上传文件。
    /// </summary>
    /// <param name="url">要请求的URL地址</param>
    /// <param name="keyvalues">要上传的数据项</param>
    /// <param name="fileList">要上传的文件列表</param>
    /// <param name="encoding">发送数据项,接收的字符编码方式</param>
    /// <returns>服务器的返回结果</returns>
    static string SendHttpRequestPost(string url, Dictionary<string, string> keyvalues,
        Dictionary<string, string> fileList, Encoding encoding)
    {
        if( fileList == null )
            return SendHttpRequestPost(url, keyvalues, encoding);
    
        if( string.IsNullOrEmpty(url) )
            throw new ArgumentNullException("url");        
    
        if( encoding == null )
            encoding = Encoding.UTF8;
    
    
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "POST";        // 要上传文件,一定要是POST方法
    
        // 数据块的分隔标记,用于设置请求头,注意:这个地方最好不要使用汉字。
        string boundary = "---------------------------" + Guid.NewGuid().ToString("N");
        // 数据块的分隔标记,用于写入请求体。
        //   注意:前面多了一段: "--" ,而且它们将独占一行。
        byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
    
        // 设置请求头。指示是一个上传表单,以及各数据块的分隔标记。
        request.ContentType = "multipart/form-data; boundary=" + boundary;
    
        
        // 先得到请求流,准备写入数据。
        Stream stream = request.GetRequestStream();
    
    
        if( keyvalues != null && keyvalues.Count > 0 ) {
            // 写入非文件的keyvalues部分
            foreach( KeyValuePair<string, string> kvp in keyvalues ) {
                // 写入数据块的分隔标记
                stream.Write(boundaryBytes, 0, boundaryBytes.Length);
    
                // 写入数据项描述,这里的Value部分可以不用URL编码
                string str = string.Format(
                        "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}",
                        kvp.Key, kvp.Value);
    
                byte[] data = encoding.GetBytes(str);
                stream.Write(data, 0, data.Length);
            }
        }
    
    
        // 写入要上传的文件
        foreach( KeyValuePair<string, string> kvp in fileList ) {
            // 写入数据块的分隔标记
            stream.Write(boundaryBytes, 0, boundaryBytes.Length);
    
            // 写入文件描述,这里设置一个通用的类型描述:application/octet-stream,具体的描述在注册表里有。
            string description = string.Format(
                    "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" +
                    "Content-Type: application/octet-stream\r\n\r\n",
                    kvp.Key, Path.GetFileName(kvp.Value));
    
            // 注意:这里如果不使用UTF-8,对于汉字会有乱码。
            byte[] header = Encoding.UTF8.GetBytes(description);
            stream.Write(header, 0, header.Length);
    
            // 写入文件内容
            byte[] body = File.ReadAllBytes(kvp.Value);
            stream.Write(body, 0, body.Length);
        }
    
    
        // 写入结束标记
        boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
        stream.Write(boundaryBytes, 0, boundaryBytes.Length);
    
        stream.Close();
    
        // 开始发起请求,并获取服务器返回的结果。
        using( WebResponse response = request.GetResponse() ) {
            using( StreamReader reader = new StreamReader(response.GetResponseStream(), encoding) ) {
                return reader.ReadToEnd();
            }
        }
    }
    
    
    // 调用上面方法的示例代码
    string Test_SendHttpRequestPost2()
    {
        string url = "http://localhost:1272/FormWebSite1/Handler2.ashx";
    
        Dictionary<string, string> keyvalues = new Dictionary<string, string>();
        keyvalues.Add("Key1", "本示例代码由 Fish Li 提供");
        keyvalues.Add("Key2", "http://www.cnblogs.com/fish-li");
        keyvalues.Add("Key3", "来几个特殊字符:~!@#$%^&*()-=_+{}[]:;'\"<>?/.,|\\");
        
        Dictionary<string, string> fileList = new Dictionary<string, string>();
        fileList.Add("file1", @"H:\AllTempFiles\ascx中文字.gif");
        fileList.Add("file2", @"H:\AllTempFiles\asax中文字.gif");
    
        return SendHttpRequestPost(url, keyvalues, fileList, Encoding.UTF8);
    }
    

    说明:上面的示例方法中,我并没有对KEY编码,是因为:我想大家选用的KEY应该是不需要编码的(英文字母与数字的组合)。
    而且,我也没加入对Cookie处理的那部分代码,如果您需要在发送请求时,保留Cookie,那么请参考我上一篇博客 【细说Cookie】中的示例代码。

    资源链接

    W3C Forms
    http://www.w3.org/TR/html4/interact/forms.html

    jquery.form.js
    https://github.com/malsup/form

    更多的jquery.form演示
    http://www.cnblogs.com/fish-li/archive/2011/05/02/2034010.html



    展开全文
  • from

    2019-07-27 22:05:59
    Form介绍 ... 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.... ...

    Form介绍 

    我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。

    与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。

    Django form组件就实现了上面所述的功能。

    总结一下,其实form组件的主要功能如下:

    • 生成页面可用的HTML标签
    • 对用户提交的数据进行校验
    • 保留上次输入内容

    普通方式手写注册功能

    views.py

    # 注册
    def register(request):
        error_msg = ""
        if request.method == "POST":
            username = request.POST.get("name")
            pwd = request.POST.get("pwd")
            # 对注册信息做校验
            if len(username) < 6:
                # 用户长度小于6位
                error_msg = "用户名长度不能小于6位"
            else:
                # 将用户名和密码存到数据库
                return HttpResponse("注册成功")
        return render(request, "register.html", {"error_msg": error_msg})

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>注册页面</title>
    </head>
    <body>
    <form action="/reg/" method="post">
        {% csrf_token %}
        <p>
            用户名:
            <input type="text" name="name">
        </p>
        <p>
            密码:
            <input type="password" name="pwd">
        </p>
        <p>
            <input type="submit" value="注册">
            <p style="color: red">{{ error_msg }}</p>
        </p>
    </form>
    </body>
    </html>

    使用form组件实现注册功能

    views.py

    先定义好一个RegForm类:

    from django import forms
    
    # 按照Django form组件的要求自己写一个类
    class RegForm(forms.Form):
        name = forms.CharField(label="用户名")
        pwd = forms.CharField(label="密码")

    在写一个试图函数:

    # 使用form组件实现注册方式
    def register2(request):
        form_obj = RegForm()
        if request.method == "POST":
            # 实例化form对象的时候,把post提交过来的数据直接传进去
            form_obj = RegForm(request.POST)
            # 调用form_obj校验数据的方法
            if form_obj.is_valid():
                return HttpResponse("注册成功")
        return render(request, "register2.html", {"form_obj": form_obj})

    login2.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>注册2</title>
    </head>
    <body>
        <form action="/reg2/" method="post" novalidate autocomplete="off">
            {% csrf_token %}
            <div>
                <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label>
                {{ form_obj.name }} {{ form_obj.name.errors.0 }}
            </div>
            <div>
                <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
                {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }}
            </div>
            <div>
                <input type="submit" class="btn btn-success" value="注册">
            </div>
        </form>
    </body>
    </html>

    看网页效果发现 也验证了form的功能:
    • 前端页面是form类的对象生成的                                      -->生成HTML标签功能
    • 当用户名和密码输入为空或输错之后 页面都会提示        -->用户提交校验功能
    • 当用户输错之后 再次输入 上次的内容还保留在input框   -->保留上次输入内容

    Form那些事儿

    常用字段与插件

    创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

    initial

    初始值,input框里面的初始值。

    class LoginForm(forms.Form):
        username = forms.CharField(
            min_length=8,
            label="用户名",
            initial="张三"  # 设置默认值
        )
        pwd = forms.CharField(min_length=6, label="密码")

    error_messages

    重写错误信息。

    class LoginForm(forms.Form):
        username = forms.CharField(
            min_length=8,
            label="用户名",
            initial="张三",
            error_messages={
                "required": "不能为空",
                "invalid": "格式错误",
                "min_length": "用户名最短8位"
            }
        )
        pwd = forms.CharField(min_length=6, label="密码")

    password

    class LoginForm(forms.Form):
        ...
        pwd = forms.CharField(
            min_length=6,
            label="密码",
            widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
        )

    radioSelect

    单radio值为字符串

    class LoginForm(forms.Form):
        username = forms.CharField(
            min_length=8,
            label="用户名",
            initial="张三",
            error_messages={
                "required": "不能为空",
                "invalid": "格式错误",
                "min_length": "用户名最短8位"
            }
        )
        pwd = forms.CharField(min_length=6, label="密码")
        gender = forms.fields.ChoiceField(
            choices=((1, ""), (2, ""), (3, "保密")),
            label="性别",
            initial=3,
            widget=forms.widgets.RadioSelect()
        )

    单选Select

    class LoginForm(forms.Form):
        ...
        hobby = forms.fields.ChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
            label="爱好",
            initial=3,
            widget=forms.widgets.Select()
        )

    多选select

    class LoginForm(forms.Form):
        ...
        hobby = forms.fields.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.SelectMultiple()
        )

    单选checkbox

    复制代码
    class LoginForm(forms.Form):
        ...
        keep = forms.fields.ChoiceField(
            label="是否记住密码",
            initial="checked",
            widget=forms.widgets.CheckboxInput()
        )
    复制代码

    多选checkbox

    复制代码
    class LoginForm(forms.Form):
        ...
        hobby = forms.fields.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.CheckboxSelectMultiple()
        )
    复制代码

    关于choice的注意事项:

    在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。

    方式一:

    复制代码
    from django.forms import Form
    from django.forms import widgets
    from django.forms import fields
    
     
    class MyForm(Form):
     
        user = fields.ChoiceField(
            # choices=((1, '上海'), (2, '北京'),),
            initial=2,
            widget=widgets.Select
        )
     
        def __init__(self, *args, **kwargs):
            super(MyForm,self).__init__(*args, **kwargs)
            # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
            # 或
            self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
    复制代码

    方式二:

    复制代码
    from django import forms
    from django.forms import fields
    from django.forms import models as form_model
    
     
    class FInfo(forms.Form):
        authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多选
        # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 单选
    复制代码

    Django Form所有内置字段

     Django Form内置字段

    校验

    方式一:

    复制代码
    from django.forms import Form
    from django.forms import widgets
    from django.forms import fields
    from django.core.validators import RegexValidator
     
    class MyForm(Form):
        user = fields.CharField(
            validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
        )
    复制代码

    方式二:

    复制代码
    import re
    from django.forms import Form
    from django.forms import widgets
    from django.forms import fields
    from django.core.exceptions import ValidationError
     
     
    # 自定义验证规则
    def mobile_validate(value):
        mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
        if not mobile_re.match(value):
            raise ValidationError('手机号码格式错误')
     
     
    class PublishForm(Form):
     
     
        title = fields.CharField(max_length=20,
                                min_length=5,
                                error_messages={'required': '标题不能为空',
                                                'min_length': '标题最少为5个字符',
                                                'max_length': '标题最多为20个字符'},
                                widget=widgets.TextInput(attrs={'class': "form-control",
                                                              'placeholder': '标题5-20个字符'}))
     
     
        # 使用自定义验证规则
        phone = fields.CharField(validators=[mobile_validate, ],
                                error_messages={'required': '手机不能为空'},
                                widget=widgets.TextInput(attrs={'class': "form-control",
                                                              'placeholder': u'手机号码'}))
     
        email = fields.EmailField(required=False,
                                error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
                                widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
    复制代码

    补充进阶

    应用Bootstrap样式

     Django form应用Bootstrap样式简单示例

    批量添加样式

    可通过重写form类的init方法来实现。

     批量添加样式

    ModelForm

    form与model的终极结合。

    复制代码
    class BookForm(forms.ModelForm):
    
        class Meta:
            model = models.Book
            fields = "__all__"
            labels = {
                "title": "书名",
                "price": "价格"
            }
            widgets = {
                "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
            }
    复制代码

     class Meta:下常用参数:

    复制代码
    model = models.Student  # 对应的Model中的类
    fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
    exclude = None  # 排除的字段
    labels = None  # 提示信息
    help_texts = None  # 帮助提示信息
    widgets = None  # 自定义插件
    error_messages = None  # 自定义错误信息
    复制代码

    转载于:https://www.cnblogs.com/yzl666/p/11012871.html

    展开全文
  • Python基础学习:关键字from

    千次阅读 2018-03-13 22:00:00
    关键字from一般用于类的导入,其格式为:from 模块名(即类所处的文件名)import 类名 导入的类中的方法要被使用必须采取: 模块名.方法名()...

    关键字from一般用于类的导入,其格式为:

    from 模块名(即类所处的文件名)import 类名

     

    导入的类中的方法要被使用必须采取:

           模块名.方法名()

    展开全文
  • 直到上一篇,我们终于迎来了Python并发编程中,最高级、最重要、当然也是最难的知识点--协程。 当你看到这一篇的时候,请确保你对生成器的知识,有一定的了解。当然不了解,也没有关系,你只要花个几分钟的时间,来...

    直到上一篇,我们终于迎来了Python并发编程中,最高级、最重要、当然也是最难的知识点--协程

    当你看到这一篇的时候,请确保你对生成器的知识,有一定的了解。当然不了解,也没有关系,你只要花个几分钟的时间,来看下我上一篇文章,就能够让你认识生成器,入门协程了。

    友情提醒: 本系列所有的代码均在Python3下编写。Python2中可能有所差异。

    1. 为什么要使用协程

    在上一篇中,我们从生成器的基本认识与使用,成功过渡到了协程。

    但一定有许多人,只知道协程是个什么东西,但并不知道为什么要用协程?换句话来说,并不知道在什么情况下用协程? 它相比多线程来说,有哪些过人之处呢?

    在开始讲yield from 之前,我想先解决一下这个给很多人带来困惑的问题。

    举个例子。 假如我们做一个爬虫。我们要爬取多个网页,这里简单举例两个网页(两个spider函数),获取HTML(耗IO耗时),然后再对HTML对行解析取得我们感兴趣的数据。

    我们的代码结构精简如下:

    def spider_01(url):
        html = get_html(url)
        ...
        data = parse_html(html)
    
    def spider_02(url):
        html = get_html(url)
        ...
        data = parse_html(html)

    我们都知道,get_html()等待返回网页是非常耗IO的,一个网页还好,如果我们爬取的网页数据极其庞大,这个等待时间就非常惊人,是极大的浪费。

    聪明的程序员,当然会想如果能在get_html()这里暂停一下,不用傻乎乎地去等待网页返回,而是去做别的事。等过段时间再回过头来到刚刚暂停的地方,接收返回的html内容,然后还可以接下去解析parse_html(html)

    利用常规的方法,几乎是没办法实现如上我们想要的效果的。所以Python想得很周到,从语言本身给我们实现了这样的功能,这就是yield语法。可以实现在某一函数中暂停的效果。

    试着思考一下,假如没有协程,我们要写一个并发程序。可能有以下问题

    1. 使用最常规的同步编程要实现异步并发效果并不理想,或者难度极高。
    2. 由于GIL锁的存在,多线程的运行需要频繁的加锁解锁,切换线程,这极大地降低了并发性能;

    而协程的出现,刚好可以解决以上的问题。它的特点有

    1. 协程是在单线程里实现任务的切换的
    2. 利用同步的方式去实现异步
    3. 不再需要锁,提高了并发性能

    2. yield from的用法详解

    yield from 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。

    yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。

    2.1 简单应用:拼接可迭代对象

    我们可以用一个使用yield和一个使用yield from的例子来对比看下。

    使用yield

    # 字符串
    astr='ABC'
    # 列表
    alist=[1,2,3]
    # 字典
    adict={"name":"wangbm","age":18}
    # 生成器
    agen=(i for i in range(4,8))
    
    def gen(*args, **kw):
        for item in args:
            for i in item:
                yield i
    
    new_list=gen(astr, alist, adict, agen)
    print(list(new_list))
    # ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

    使用yield from

    # 字符串
    astr='ABC'
    # 列表
    alist=[1,2,3]
    # 字典
    adict={"name":"wangbm","age":18}
    # 生成器
    agen=(i for i in range(4,8))
    
    def gen(*args, **kw):
        for item in args:
            yield from item
    
    new_list=gen(astr, alist, adict, agen)
    print(list(new_list))
    # ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

    由上面两种方式对比,可以看出,yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。

    2.2 复杂应用:生成器的嵌套

    如果你认为只是 yield from 仅仅只有上述的功能的话,那你就太小瞧了它,它的更强大的功能还在后面。

    yield from 后面加上一个生成器后,就实现了生成的嵌套。

    当然实现生成器的嵌套,并不是一定必须要使用yield from,而是使用yield from可以让我们避免让我们自己处理各种料想不到的异常,而让我们专注于业务代码的实现。

    如果自己用yield去实现,那只会加大代码的编写难度,降低开发效率,降低代码的可读性。既然Python已经想得这么周到,我们当然要好好利用起来。

    讲解它之前,首先要知道这个几个概念

    1、调用方:调用委派生成器的客户端(调用方)代码 2、委托生成器:包含yield from表达式的生成器函数 3、子生成器:yield from后面加的生成器函数

    你可能不知道他们都是什么意思,没关系,来看下这个例子。

    这个例子,是实现实时计算平均值的。 比如,第一次传入10,那返回平均数自然是10. 第二次传入20,那返回平均数是(10+20)/2=15 第三次传入30,那返回平均数(10+20+30)/3=20

    # 子生成器
    def average_gen():
        total = 0
        count = 0
        average = 0
        while True:
            new_num = yield average
            count += 1
            total += new_num
            average = total/count
    
    # 委托生成器
    def proxy_gen():
        while True:
            yield from average_gen()
    
    # 调用方
    def main():
        calc_average = proxy_gen()
        next(calc_average)            # 预激下生成器
        print(calc_average.send(10))  # 打印:10.0
        print(calc_average.send(20))  # 打印:15.0
        print(calc_average.send(30))  # 打印:20.0
    
    if __name__ == '__main__':
        main()

    认真阅读以上代码,你应该很容易能理解,调用方、委托生成器、子生成器之间的关系。我就不多说了

    委托生成器的作用是:在调用方与子生成器之间建立一个双向通道

    所谓的双向通道是什么意思呢? 调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。

    你可能会经常看到有些代码,还可以在yield from前面看到可以赋值。这是什么用法?

    你可能会以为,子生成器yield回来的值,被委托生成器给拦截了。你可以亲自写个demo运行试验一下,并不是你想的那样。 因为我们之前说了,委托生成器,只起一个桥梁作用,它建立的是一个双向通道,它并没有权利也没有办法,对子生成器yield回来的内容做拦截。

    为了解释这个用法,我还是用上述的例子,并对其进行了一些改造。添加了一些注释,希望你能看得明白。

    按照惯例,我们还是举个例子。

    # 子生成器
    def average_gen():
        total = 0
        count = 0
        average = 0
        while True:
            new_num = yield average
            if new_num is None:
                break
            count += 1
            total += new_num
            average = total/count
    
        # 每一次return,都意味着当前协程结束。
        return total,count,average
    
    # 委托生成器
    def proxy_gen():
        while True:
            # 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。
            total, count, average = yield from average_gen()
            print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
    
    # 调用方
    def main():
        calc_average = proxy_gen()
        next(calc_average)            # 预激协程
        print(calc_average.send(10))  # 打印:10.0
        print(calc_average.send(20))  # 打印:15.0
        print(calc_average.send(30))  # 打印:20.0
        calc_average.send(None)      # 结束协程
        # 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程
    
    if __name__ == '__main__':
        main()

    运行后,输出

    10.0
    15.0
    20.0
    计算完毕!!
    总共传入 3 个数值, 总和:60,平均数:20.0

    3. 为什么要使用yield from

    学到这里,我相信你肯定要问,既然委托生成器,起到的只是一个双向通道的作用,我还需要委托生成器做什么?我调用方直接调用子生成器不就好啦?

    高能预警~

    下面我们来一起探讨一下,到底yield from 有什么过人之处,让我们非要用它不可。

    3.1 因为它可以帮我们处理异常

    如果我们去掉委托生成器,而直接调用子生成器。那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理。而不像使yield from那样省心。

    # 子生成器
    # 子生成器
    def average_gen():
        total = 0
        count = 0
        average = 0
        while True:
            new_num = yield average
            if new_num is None:
                break
            count += 1
            total += new_num
            average = total/count
        return total,count,average
    
    # 调用方
    def main():
        calc_average = average_gen()
        next(calc_average)            # 预激协程
        print(calc_average.send(10))  # 打印:10.0
        print(calc_average.send(20))  # 打印:15.0
        print(calc_average.send(30))  # 打印:20.0
    
        # ----------------注意-----------------
        try:
            calc_average.send(None)
        except StopIteration as e:
            total, count, average = e.value
            print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
        # ----------------注意-----------------
    
    if __name__ == '__main__':
        main()

    此时的你,可能会说,不就一个StopIteration的异常吗?自己捕获也没什么大不了的。

    你要是知道yield from在背后为我们默默无闻地做了哪些事,你就不会这样说了。

    具体yield from为我们做了哪些事,可以参考如下这段代码。

    #一些说明
    """
    _i:子生成器,同时也是一个迭代器
    _y:子生成器生产的值
    _r:yield from 表达式最终的值
    _s:调用方通过send()发送的值
    _e:异常对象
    """
    
    _i = iter(EXPR)
    
    try:
        _y = next(_i)
    except StopIteration as _e:
        _r = _e.value
    
    else:
        while 1:
            try:
                _s = yield _y
            except GeneratorExit as _e:
                try:
                    _m = _i.close
                except AttributeError:
                    pass
                else:
                    _m()
                raise _e
            except BaseException as _e:
                _x = sys.exc_info()
                try:
                    _m = _i.throw
                except AttributeError:
                    raise _e
                else:
                    try:
                        _y = _m(*_x)
                    except StopIteration as _e:
                        _r = _e.value
                        break
            else:
                try:
                    if _s is None:
                        _y = next(_i)
                    else:
                        _y = _i.send(_s)
                except StopIteration as _e:
                    _r = _e.value
                    break
    RESULT = _r

    以上的代码,稍微有点复杂,有兴趣的同学可以结合以下说明去研究看看。

    1. 迭代器(即可指子生成器)产生的值直接返还给调用者
    2. 任何使用send()方法发给委派生产器(即外部生产器)的值被直接传递给迭代器。如果send值是None,则调用迭代器next()方法;如果不为None,则调用迭代器的send()方法。如果对迭代器的调用产生StopIteration异常,委派生产器恢复继续执行yield from后面的语句;若迭代器产生其他任何异常,则都传递给委派生产器。
    3. 子生成器可能只是一个迭代器,并不是一个作为协程的生成器,所以它不支持.throw()和.close()方法,即可能会产生AttributeError 异常。
    4. 除了GeneratorExit 异常外的其他抛给委派生产器的异常,将会被传递到迭代器的throw()方法。如果迭代器throw()调用产生了StopIteration异常,委派生产器恢复并继续执行,其他异常则传递给委派生产器。
    5. 如果GeneratorExit异常被抛给委派生产器,或者委派生产器的close()方法被调用,如果迭代器有close()的话也将被调用。如果close()调用产生异常,异常将传递给委派生产器。否则,委派生产器将抛出GeneratorExit 异常。
    6. 当迭代器结束并抛出异常时,yield from表达式的值是其StopIteration 异常中的第一个参数。
    7. 一个生成器中的return expr语句将会从生成器退出并抛出 StopIteration(expr)异常。

    没兴趣看的同学,只要知道,yield from帮我们做了很多的异常处理,而且全面,而这些如果我们要自己去实现的话,一个是编写代码难度增加,写出来的代码可读性极差,这些我们就不说了,最主要的是很可能有遗漏,只要哪个异常没考虑到,都有可能导致程序崩溃什么的。

    展开全文
  • Vue中import from的来源:省略后缀与加载文件夹

    万次阅读 多人点赞 2018-11-02 16:44:16
    from ...来导入组件,库,变量等。而from后的来源可以是js,vue,json。这个是在webpack.base.conf.js中设置的: module.exports = { resolve: { extensions: ['.js', '.vue', '.json'], alias: { '@': ...
  • Oracle中的三种 UPDATE FROM 的解决方案

    万次阅读 2016-12-29 14:00:04
    象sql server提供了update的from 子句,可以将要更新的表与其它的数据源连接起来。虽然只能对一个表进行更新,但是通过将要更新的表与其它的数据源连接起来,就可以在update的表达式中引用要更新的表以外的其它数据...
  • 注意import和from import 的区别

    千次阅读 2019-05-29 11:59:04
    注意python中import和from import 的区别 首先明确:尽量不要为了图省事使用from xxx import * python中有两种导入模块的方式,一种是import xxx,另一种是from xxx import yyy,两者的区别在于,第一种仅仅导入一个...
  • DELETE * FROM和DELETE FROM的区别

    万次阅读 2016-12-21 12:04:41
    DELETE * FROM和DELETE FROM的区别
  • 设定: 1.每个standby redo log file 至少要和primary database的redo log 一样大,为了方便管理,Oracle 建议主...SQL> SELECT GROUP#, BYTES/1024/1024 M FROM V$LOG; 2.Standby redo log group 至少要比primary
  • 在MySQL中,写SQL语句的时候 ,可能会遇到You can't specify target table '表名' for update in FROM clause这样的错误,它的意思是说,不能先select出同一表中的某些值,再update这个表(在同一语句中),即不能依据...
  • 函数参数 位置参数:position augment, 默认参数:eg: power(x,y=2), x为位置参数,y为默认参数,y可缺省,调用时可直接:power(5) 可变参数:eg: def calc(*numbers): *numbers是可变参数,接收的是一个tuple, ...
  • 浅浅得理解HTTP

    2020-07-29 17:44:16
    ... TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP...
  • Python的from和import用法

    万次阅读 多人点赞 2019-06-22 11:01:38
    import使一个变量名引用整个模块对象,因此必须通过模块名称来得到该模块的属性,比如我们导入一个数学计算的模块 math: import math print math.pi #导出圆周率的值 >>>3.14159265359 ...
  • 微信分享会根据分享的不同,为原始链接拼接如下参数: 朋友圈 from=timeline&isappinstalled=0 微信群 from=groupmessage&isappinstalled=0 好友分享 from=singlemessage&isappinstalled=0
  • select into from 和 insert into select都是用来复制表,两者的主要区别为: select into from 要求目标表不存在,因为在插入时会自动创建。insert into select from 要求目标表存在 ...
  • Delete from join 用法

    万次阅读 2015-08-19 14:20:49
    delete (别名) from tblA (别名) left join tblb (别名) on。。。用法1、创建使用的表及数据CREATE TABLE YSHA ( code VARCHAR(10), NAME VARCHAR(20) )CREATE TABLE YSHB ( code VARCHAR(10), col VARCHAR(10...
  • 删除表记录(delete from ....where)

    万次阅读 2018-11-04 20:34:46
    delete from [dbo].[userinfo] where ID=2; delete from [dbo].[userinfo] where ID=3 or ID=4;--删除多行 满足条件就删除 delete from [dbo].[userinfo] where ID in(6,7);--删除多行 删除6和7  ...
  • mysql 的delete from 子查询限制

    万次阅读 2016-11-21 11:21:53
    1.使用mysql进行delete from操作时,若子查询的 FROM 字句和更新/删除对象使用同一张表,会出现错误。 mysql> DELETE FROM tab1 WHERE col1 = ( SELECT MAX( col1 ) FROM tab1 ); ERROR 1093 (HY000): You can’...
  • 3.murmur数据导出

    万次阅读 2018-01-28 13:05:47
    select * from acl; select * from bans; select * from channel_info; select * from channel_links; select * from channels; select * from config; select * from users; select * from group_memb
  • 1)Create project from existing sources 2)Import project from external model 2,区别: 1)如果选择Create project from existing sources选项, 则你只能导入使用idea创建的项目源码 2)如果选择Import...
1 2 3 4 5 ... 20
收藏数 5,470,458
精华内容 2,188,183
关键字:

from