2024年2月

ControllerModel类型的Actions属性包含一组描述有效Action方法的ActionModel对象。对于定义在Controller类型中的所有方法,究竟哪些方法才能成为有效的Action方法呢?所以在正式介绍ActionModel类型之前,我们先来聊聊Action方法的选择规则。

一、Action方法的选择
二、ActionModel
三、PropertyModel与ParameterModel
四、SelectorModel
五、实例演示:选择器模型的构建

一、 Action方法的选择

当DefaultApplicationModelProvider对象根据某个具体的Controller类型创建对应ControllerModel对象的时候,它会提取出定义在该类型中的方法,并按照预定义的规则选择出有效的Action方法。有效的Action方法必须满足如下条件:

  • 公共方法:私有(Private)、内部(Internal)和受保护(Protected)方法均为无效Action方法。
  • 非抽象方法:抽象方法为无效的Action方法(这个限制可以忽略,因为Controller不会是抽象类型)。
  • 非泛型方法:Action方法不能定义成泛型方法。
  • 非静态方法:静态方法为无效Action方法。
  • 不是从Object类型上继承的方法:Action方法支持继承,但是从Object类型上继承的方法不能成为Action方法。
  • 不是对IDisposable接口的实现:如果Controller类型实现了IDisposable接口,实现的Dispose方法不是有效的Action方法。

二、ActionModel

如下面的代码片段所示,ActionModel类型实现了ICommonModel、IFilterModel和IApiExplorerModel三个接口。默认注册的DefaultApplicationModelProvider会对ActionModel对象做如下的设置:MemberInfo和ActionMethod属性都将设置为描述当前Action方法的MethodInfo对象。通过标注的特性注册到Action方法上的过滤器会被提取出来,对应的元数据会添加到Filters属性中。ApiExplorer属性返回的ApiExplorerModel对象由标注在Action方法上实现了IApiDescriptionGroupNameProvider和IApiDescriptionVisibilityProvider接口的特性构建而成。

public class ActionModel : ICommonModel, IFilterModel, IApiExplorerModel
{
    public ControllerModel 			Controller { get; set; }

    public IList<IFilterMetadata> 		Filters { get; }
    public ApiExplorerModel 			ApiExplorer { get; set; }
    public IDictionary<object, object> 	Properties { get; }

    public IList<ParameterModel> 		Parameters { get; }
    public IList<SelectorModel> 		Selectors { get; }

    public MethodInfo 				ActionMethod { get; }
    public string 				DisplayName { get; }
    public string 				ActionName { get; set; }
    public IReadOnlyList<object> 		Attributes { get; }

    public IOutboundParameterTransformer 	RouteParameterTransformer { get; set; }
    public IDictionary<string, string> 	RouteValues { get; }

    MemberInfo ICommonModel.MemberInfo { get; }
    string ICommonModel.Name { get; }
}

DefaultApplicationModelProvider会为Action方法的每个参数创建一个ParameterModel对象,并添加到Parameters属性中。应用在Action方法上的用于封装路由信息(特性路由、约束和终结点元数据)的SelectorModel对象会按照上述的方式构建出来,并添加到Selectors属性中。标注在Action方法上的特性会被提取出来并添加到Attributes属性返回的列表中。表示Action名称的ActionName与Name属性具有相同的值,DefaultApplicationModelProvider会默认将它们设置为方法的名称,但是我们可以在方法上通过标注如下这个ActionNameAttribute特性对该属性进行设置。

[AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=true)]
public sealed class ActionNameAttribute : Attribute
{
    public string Name { get; }
    public ActionNameAttribute(string name);
}

我们照例通过一个简单的示例来演示一下DefaultApplicationModelProvider对象针对ActionModel的构建规则。我们依然沿用前面创建的演示实例,这次我们将FoobarController定义成如下的形式。如代码片段所示,我们为Action方法定义了三个参数,并在上面标注了四个特性,其中FoobarAttribute特性是我们应用在该Action方法上的过滤器,ApiExplorerSettingsAttribute特性针对ApiExplorer作了相应设置,HttpGetAttribute特性完成了针对路由和HTTP方法约束的定义,ActionNameAttribute特性则将Action名称设置为“Baz”。

public class FoobarController
{
    [Foobar]
    [ApiExplorerSettings(GroupName ="test")]
    [HttpGet("/foobar/baz")]
    [ActionName("Baz")]
    public void Index(string foo, string bar, string baz) => throw new NotImplementedException();
}

我们需要修改定义在HomeController中的Action方法Index。如下面的代码片段所示,在利用通过参数注入的ApplicationModelProducer对象根据提供的FoobarController类型创建出对应ApplicationModel对象之后,我们提取出描述Action方法Index的ActionModel对象,并将其作为Model呈现在默认的View中。

public class HomeController : Controller
{
    [HttpGet("/")]
    public IActionResult Index([FromServices]ApplicationModelProducer producer)
    {
        var applicationModel = producer.Create(typeof(FoobarController));
        return View(applicationModel.Controllers.Single().Actions.Single());
    }
}

我们将按照如下的形式重新定义了Action方法Index对应的View。如下面的代码片段所示,这是一个Model类型为ActionModel的强类型View,,它将ActionModel承载的元数据呈现在一个表格中。

@model Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel
@{
    var filters 	= Model.Filters;
    var routeValues 	= Model.RouteValues.ToArray();
    var parameters 	= Model.Parameters;
    var attributes 	= Model.Attributes;
}
<html>
<head>
    <title>Action</title>
</head>
<body>
    <table border="1" cellpadding="0" cellspacing="0">
        <tr><td>Method</td><td>@Model.ActionMethod.Name</td></tr>
        <tr><td>ActionName</td><td>@Model.ActionName</td></tr>
        <tr><td>DisplayName</td><td>@Model.DisplayName</td></tr>
        <tr>
            <td rowspan="@parameters.Count">Parameters</td>
            <td>@parameters[0].Name</td>
        </tr>
        @for (int index = 1; index < parameters.Count; index++)
        {
            <tr><td>@parameters[index].Name</td></tr>
        }
        <tr>
            <td rowspan="@filters.Count">Filters</td>
            <td>@filters[0].GetType().Name</td>
        </tr>
        @for (int index = 1; index < filters.Count; index++)
        {
            <tr><td>@filters[index].GetType().Name</td></tr>
        }
        <tr>
            <td rowspan="@attributes.Count">Attributes</td>
            <td>@attributes[0].GetType().Name</td>
        </tr>
        @for (int index = 1; index < attributes.Count; index++)
        {
            <tr><td>@attributes[index].GetType().Name</td></tr>
        }
        @if (routeValues.Length == 0)
        {
            <tr><td>RouteValues</td><td>N/A</td></tr>
        }
        else
        {
            <tr>
                <td rowspan="@routeValues.Length">RouteValues</td>
                <td>@routeValues[0].Key = @routeValues[0].Value</td>
            </tr>
        }
        @for (int index = 1; index < routeValues.Length; index++)
        {
            <tr><td>@routeValues[index].Key = @routeValues[index].Value</td></tr>
        }
        <tr>
            <td rowspan="2">ApiExplorer</td>
            <td>IsVisible = @Model.ApiExplorer.IsVisible </td>
        </tr>
        <tr>
            <td>GroupName = @Model.ApiExplorer.GroupName </td>
        </tr>
    </table>
</body>
</html>

改动后的演示程序启动后,我们利用浏览器访问应用的主页,可以得到如图1所示的输出结果。我们从图中可以看出,Action名称来源于标注在方法上的ActionNameAttribute特性。DefaultApplicationModelProvider会为方法的每个参数创建一个ParameterModel对象并添加到ActionModel对象的Properties属性中。通过特性标注注册到Action方法上的FoobarAttribute过滤器被添加到ActionModel对象的Filters属性中。Action方法标注的四个特性全部被添加到ActionModel对象的Attributes属性中。ActionModel对象的ApiExplorer属性返回的ApiExplorerModel对象是由标注在方法上的ApiExplorerSettingsAttribute特性构建的。值得一提的是,Controller和Action的名称此时并没有作为路由参数添加到RouteValues属性中。

clip_image002

图1 Action模型默认的构建规则

三、PropertyModel与ParameterModel

默认注册的DefaultApplicationModelProvider会将定义在Controller类型的公共属性(包括从基类继承的属性)提取创建,然后创建相应的PropertyModel对象并添加到ControllerModel对象的ControllerProperties属性中。描述属性的PropertyModel对象和描述参数的ParameterModel对象都是为了提供模型绑定的元数据,所以它们具有相同的基类ParameterModelBase。

public abstract class ParameterModelBase : IBindingModel
{
    public string 				Name { get; protected set; }
    public Type 				ParameterType { get; }
    public IReadOnlyList<object> 		Attributes { get; }
    public BindingInfo 		 	        BindingInfo { get; set; }
    public IDictionary<object, object> 	Properties { get; }
}

如上面的代码片段所示,抽象类ParameterModelBase实现了IBindingModel,所以它需要利用实现的BindingInfo书信提供模型绑定信息。ParameterModelBase并没有实现IPropertyModel接口,但是其自身提供了一个Properties属性。ParameterModelBase的Name和ParameterType属性分别表示对应参数/属性的名称和类型。标注到属性上的特性会添加到Attributes属性中。如下所示的是描述Controller属性的PropertyModel类型,和描述Action方法参数的ParameterModel类型的定义。

public class PropertyModel : ParameterModelBase, ICommonModel, IBindingModel
{
    public ControllerModel 			Controller { get; set; }
    public PropertyInfo 			PropertyInfo { get; }
    public string 				PropertyName { get; set; }
    public IReadOnlyList<object> 		Attributes { get; }
    public IDictionary<object, object> 	Properties { get; }
    MemberInfo ICommonModel.MemberInfo { get; }
}

public class ParameterModel : ParameterModelBase, ICommonModel
{
    public ActionModel 			        Action { get; set; }
    public ParameterInfo 			ParameterInfo { get; }
    public string 				ParameterName { get; set; }
    public string 				DisplayName { get; }
    public IReadOnlyList<object> 		Attributes { get; }
    public IDictionary<object, object> 	Properties { get; }
    MemberInfo ICommonModel.MemberInfo { get; }
}

四、SelectorModel

SelectorModel类型是对Action选择器(Selector)的描述,这里的选择器旨在解决如何为请求选择匹配Action的问题,所以它承载的其实针对路由的原始定义。如下面的代码片段所示,SelectorModel类型通过AttributeRouteModel、ActionConstraints和EndpointMetadata分别存储了特性路由信息、约束和终结点元数据。

public class SelectorModel
{
    public AttributeRouteModel 		        AttributeRouteModel { get; set; }
    public IList<IActionConstraintMetadata> 	ActionConstraints { get; }
    public IList<object> 			EndpointMetadata { get; }
}

public class AttributeRouteModel
{
    public IRouteTemplateProvider 	Attribute { get; }
    public string 			Template { get; set; }
    public int? 			Order { get; set; }
    public string 			Name { get; set; }
    public bool 			SuppressLinkGeneration { get; set; }
    public bool 			SuppressPathMatching { get; set; }
    public bool 			IsAbsoluteTemplate { get; }
}

由于路由可以通过标注到Controller类型或者Action方法上相应的特性来定义,所以描述Controller类型和Action方法的ControllerModel和ActionModel类型都具有一个Selectors属性来保存各自的选择器,DefaultApplicationModelProvider针对它们的解析方式也是一致的。这里用来定义路由的特性实现了如下所示的IRouteTemplateProvider特性。

public interface IRouteTemplateProvider
{
    string 	Template { get; }
    int? 	Order { get; }
    string 	Name { get; }
}

顾名思义,实现IRouteTemplateProvider接口的特性旨在定义一个针对指定模板的路由。除此之外,针对SelectorModel的构建还涉及另一个名为IActionHttpMethodProvider的接口,实现该接口的特性为目标Action定义针对HTTP方法的约束。

public interface IActionHttpMethodProvider
{
    IEnumerable<string> HttpMethods { get; }
}

对于目前提供的实现了IActionHttpMethodProvider接口的特性来说,它们无一例外都同时实现了IRouteTemplateProvider接口。比如下面这个AcceptVerbsAttribute特性可以标注到Action方法上指定一组支持的HTTP方法,同时也可以利用三个属性(Route、Name和Order)对路由作相应的定义(显式实现的Template和Order属性与自身Route和Order属性具有相同的值)。顺便提一下,我们在AcceptVerbsAttribute特性构造函数以字符串指定的HTTP方法名称会一律转换成大写形式。

[AttributeUsage( AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
public sealed class AcceptVerbsAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
    public IEnumerable<string> 	HttpMethods { get; }
    public string 			Route { get; set; }
    public int 			        Order { get; set; }
    public string 			Name { get; set; }

    string IRouteTemplateProvider.Template { get; }
    int? IRouteTemplateProvider.Order { get; }

    public AcceptVerbsAttribute(string method);
    public AcceptVerbsAttribute(params string[] methods);
}

虽然AcceptVerbsAttribute特性可以为我们指定多个支持的HTTP方法,但是我们似乎更倾向于使用针对具有某种HTTP方法的特性,比如HttpGetAttribute、HttpPostAttribute、HttpPutAttribute、HttpDeleteAttribute、HttpHeadAttribute、HttpPatchAttribute和HttpOptionsAttribute特性,它们都派生于如下这个抽象的HttpMethodAttribute特性类型。

[AttributeUsage( AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
public abstract class HttpMethodAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider
{
    public IEnumerable<string> 	HttpMethods { get; }
    public string 			Template { get; }
    public int 			        Order { get; set; }
    public string 			Name { get; set; }

    int? IRouteTemplateProvider.Order { get; }

    public HttpMethodAttribute(IEnumerable<string> httpMethods);
    public HttpMethodAttribute(IEnumerable<string> httpMethods, string template);
}

IRouteTemplateProvider接口除了上述这些实现类型之外,如下这个专门用来定义路由的RouteAttribute特性单独实现了该接口。从提供的代码片段可以看出,RouteAttribute特性可以标注在Controller类型或者具体的Action方法上。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
public class RouteAttribute : Attribute, IRouteTemplateProvider
{
    public string 		Template { get; }
    public int 		        Order { get; set; }
    public string 		Name { get; set; }

    public RouteAttribute(string template);
    int? IRouteTemplateProvider.Order { get; }
}

DefaultApplicationModelProvider针对SelectorModel对象的构建逻辑分为两种情况。如果标注在当前Controller类型或者Action方法上的特性没有提供任何路由信息,这种情况由分为两种场景:其一,根本没有标注任何实现了IRouteTemplateProvider接口的特性;其二,所有的特性并没有对定义在该接口的三个属性(Template、Name和Order)做任何设置,比如我们经常在Action方法上标注一个没有提供任何参数的HttpGetAttribute特性,其目的只是限制支持的HTTP方法,而不是定义路由。

在这种情况下,DefaultApplicationModelProvider会创建一个唯一的SelectorModel对象。由于没有任何路由被定义,所以该对象的AttributeRouteModel属性会返回Null。标注在Controller类型或者Action方法上实现了IActionConstraintMetadata接口的特性会作为约束添加到ActionConstraints属性中。如果标注了实现IActionHttpMethodProvider接口的特性对HTTP方法做了限制,一个对应的HttpMethodActionConstraint对象会额外创建出来并添加到ActionConstraints属性中。与此同时,一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来并作为终结点元数据被添加到EndpointMetadata属性中。除此之外,所有特性都会作为终结点元数据添加到EndpointMetadata属性中。

如果当前Controller类型或者Action方法上标注了实现IRouteTemplateProvider接口的特性,并且作了具体的路由设置,DefaultApplicationModelProvider会为每个IRouteTemplateProvider对象创建一个SelectorModel对象。对于每一个针对IRouteTemplateProvider对象创建的SelectorModel对象来说,设置的路由信息会被提取出来用于创建对应的AttributeRouteModel对象。如果当前特性是一个IActionHttpMethodProvider对象,一个对应的HttpMethodActionConstraint对象会额外创建出来并添加到ActionConstraints属性中。与此同时一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来,当前特性和这个对象都将作为终结点元数据被添加到EndpointMetadata属性中。

如果当前IRouteTemplateProvider对象类型为RouteAttribute,那些没有提供路由信息的实现了IActionHttpMethodProvider接口的特性(即只定义了HTTP方法约束的特性)会被提取出来,一个根据它们提供的HTTP方法列表创建的HttpMethodActionConstraint对象并添加到ActionConstraints属性中。与此同时,一个针对HTTP方法列表的HttpMethodMetada对象会被创建出来并作为终结点元数据被添加到EndpointMetadata属性中。EndpointMetadata属性最终包含的终结点元数据还包括当前RouteAttribute特性和这些单纯定义约束的特性。

五、实例演示:选择器模型的构建

对于DefaultApplicationModelProvider为Controller类型或者Action方法构建SelectorModel的逻辑,我想针对具体的场景会更好理解一点,为此我们来演示几个简单的实例。我们依然沿用前面的演示程序,并将FoobarController类型改写成如下的形式。FoobarController类型中定义了三个Action方法,接下来我们看看DefaultApplicationModelProvider对象会为它们创建出怎样的选择器。

public class FoobarController
{
    [HttpGet]
    [HttpPost]
    public void Foo()=>throw new NotImplementedException();

    [HttpGet("bar")]
    [HttpPost("bar")]
    public void Bar() => throw new NotImplementedException();

    [HttpGet()]
    [HttpPost("bar")]
    [Route("bar")]
    public void Baz() => throw new NotImplementedException();
}

我们对定义在HomeController中的Action方法Index作如下的修改。如代码片段所示,在该方法中,我们利用通过参数注入的ApplicationModelProducer对象根据FoobarController类型创建出对应的ApplicationModel对象,然后利用查询字符串绑定的actionName参数提取出描述对应Action的ActionModel对象。我们将ActionModel对象的Selectors属性提取的选择器列表作为Model呈现在View中。

public class HomeController : Controller
{
    [HttpGet("/")]
    public IActionResult Index([FromServices]ApplicationModelProducer producer, [FromQuery]string actionName)
    {
        var applicationModel = producer.Create(typeof(FoobarController));
        var actionModel = applicationModel.Controllers.Single().Actions.Single(it=>string.Compare(it.ActionName, actionName, true) == 0);
        return View(actionModel.Selectors);
    }
}

如下所示的是修改后的View。如代码片段所示,这个是一个Model类型为IList<SelectorModel>的强类型View,我们将提供的用于描述选择器的每个SelectorModel对象的元数据信息呈现在表格中。

@using Microsoft.AspNetCore.Mvc.ApplicationModels;
@model IList<SelectorModel>
<html>
<head>
    <title>Selectors</title>
</head>
<body>
    <table border="1" cellpadding="0" cellspacing="0">
        @for (int i = 0; i < Model.Count; i++)
        {
            var selector = Model[i];
            var constraints = selector.ActionConstraints;
            var metadata = selector.EndpointMetadata;
            <tr><td colspan="2">Selector @(i+1)</td></tr>
            <tr>
                <td>AttributeRouteModel</td>
                <td>@selector.AttributeRouteModel?.Template</td>
            </tr>
            <tr>
                <td rowspan="@constraints.Count">ActionConstraints</td>
                <td>@constraints[0].GetType().Name</td>
            </tr>
            @for (int j = 1; j < constraints.Count; j++)
            {
                <tr><td>@constraints[j].GetType().Name</td></tr>
            }
            <tr>
                <td rowspan="@metadata.Count">EndpointMetadata</td>
                <td>@metadata[0].GetType().Name</td>
            </tr>
            @for (int j = 1; j < metadata.Count; j++)
            {
                <tr><td>@metadata[j].GetType().Name</td></tr>
            }
        }
    </table>
</body>
</html>

由于第一个Action方法Foo上的两个IActionHttpMethodProvider特性并没有提供任何的路由信息,所以它只具有一个AttributeRouteModel属性为Null的SelectorModel对象。这两个特性提供的针对HTTP方法(GET和POST)的约束会转换成一个HttpMethodActionConstraint对象并添加到SelectorModel对象的ActionConstraints属性中。除此之外,这两个特性会直接作为终结点元数据被添加到SelectorModel对象的EndpointMetadata属性中,该属性还会包含一个针对HTTP方法约束的HttpMethodMetada对象。图2所示的就是演示应用返回的针对Action方法Foo的选择器信息。

clip_image004

图2 Action方法Foo的选择器

第二个Action方法Bar上的两个特性均指定了路由模板,所以DefaultApplicationModelProvider会为它创建两个针对性的SelectorModel对象。DefaultApplicationModelProvider会根据特性(HttpGetAttribute和HttpPostAttribute)提供的路由信息来创建对应的AttributeRouteModel对象。SelectorModel对象ActionConstraints属性会包含根据各自提供的HTTP方法约束创建的HttpMethodActionConstraint对象。EndpointMetadata属性将会包含两个终结点元数据对象,分别是当前的特性和根据HTTP方法约束创建的HttpMethodMetada对象。图3所示的就是演示应用返回的针对Action方法Bar的选择器列表。

clip_image006

图3  Action方法Bar的选择器

第三个Action方法方法上标注了三个特性,但是其中只有两个特性提供了路由信息,所以DefaultApplicationModelProvider最终会根据标注的HttpPostAttribute和RouteAttribute特性创建出两个对应的SelectorModel对象。根据标注的HttpPostAttribute特性针对SelectorModel对象的创建与上面一致,所以我们现在只关注针对RouteAttribute特性创建的SelectorModel对象。该对象提供的AttributeRouteModel对象自然由RouteAttribute特性提供的路由信息来创建。

该方法上没有提供路由信息的HttpGetAttribute特性将被用来提供当前路由的约束,所以这个SelectorModel对象的ActionConstraints属性中会包含一个根据这个特性创建的HttpMethodActionConstraint对象。这个SelectorModel对象的EndpointMetadata属性中最终会包含三个终结点元数据,分别是标注的RouteAttribute和HttpGetAttribute特性,以及根据HTTP方法约束创建的HttpMethodMetada对象。图4所示的就是演示应用返回的针对Action方法Baz的选择器列表。

clip_image008

图4  Action方法Bar的选择器

ASP.NET Core MVC应用模型的构建[1]: 应用的蓝图
ASP.NET Core MVC应用模型的构建[2]: 应用模型
ASP.NET Core MVC应用模型的构建[3]: Controller模型
ASP.NET Core MVC应用模型的构建[4]: Action模型

本文分享自华为云社区《
Python数学模块深度解析与实战应用
》,作者: 柠檬味拥抱。

在Python中,
math

decimal
模块是处理数学运算的重要工具。
math
提供了一系列常见的数学函数,而
decimal
则专注于高精度的浮点数运算。本文将深入探讨这两个模块的基础知识,并通过实际的代码示例演示它们的用法。

1. math模块的基础

1.1 常用数学函数

math
模块包含了许多常见的数学函数,比如
sin

cos

tan

sqrt
等。让我们看一个简单的例子,计算正弦函数的值:

import math

angle
= math.radians(30) # 将角度转换为弧度
sin_value
=math.sin(angle)

print(f
"sin(30°) 的值为:{sin_value}")

在这个例子中,我们使用了
radians
函数将角度转换为弧度,然后计算了正弦函数的值。

1.2 随机数生成

math
模块还提供了生成随机数的函数,比如
random
。以下是一个简单的随机数生成示例:

import math

random_number
=math.random()
print(f
"随机数:{random_number}")

1.3 数学常数

math
模块还包含了一些重要的数学常数,比如圆周率 π 和自然对数的底数 e。示例代码如下:

import math

pi_value
=math.pi
e_value
=math.e

print(f
"π 的值为:{pi_value}")
print(f
"e 的值为:{e_value}")

2. decimal模块的高精度计算

2.1 初始化Decimal对象

decimal
模块中的
Decimal
类支持高精度的浮点数运算。首先,我们需要初始化一个
Decimal
对象:

from decimalimport Decimal

decimal_number
= Decimal('3.1415926535')
print(f
"Decimal 对象的值:{decimal_number}")

2.2 高精度计算示例

decimal
模块允许我们进行高精度的计算,避免了浮点数运算中的精度损失。以下是一个简单的高精度加法示例:

from decimalimport Decimal, getcontext

# 设置精度上下文
getcontext().prec
= 10# 高精度加法
result
= Decimal('1.23456789') + Decimal('2.34567890')

print(f
"高精度加法的结果:{result}")

3. 代码解析

以上代码中,我们首先导入了相应的模块和类,然后展示了
math
模块中常见数学函数的使用,以及
decimal
模块中高精度计算的例子。在高精度计算示例中,通过设置精度上下文,我们确保了计算的准确性。

4. 实战:数学模块的综合运用

为了更好地展示
math

decimal
模块的综合应用,我们将通过一个实际问题来演示它们的强大之处。考虑到一个金融应用场景,我们需要计算复利的最终本息和。

4.1 使用math模块进行复利计算

import math

def compound_interest(principal, rate, time):
# 复利计算公式:A
= P * (1 + r/n)^(nt)
n
= 12# 假设每年复利12次
compound_amount
= principal * math.pow((1 + rate/n), n*time)returncompound_amount

# 示例:计算1000元本金,年利率5
%,存款5年后的本息和
principal_amount
= 1000annual_interest_rate= 0.05years= 5result=compound_interest(principal_amount, annual_interest_rate, years)
print(f
"复利计算结果:{result:.2f} 元")

4.2 使用decimal模块进行高精度复利计算

from decimalimport Decimal, getcontext

def decimal_compound_interest(principal, rate, time):
# 复利计算公式:A
= P * (1 + r/n)^(nt)
n
= 12# 假设每年复利12次

# 初始化Decimal对象
principal_decimal
=Decimal(str(principal))
rate_decimal
= Decimal(str(rate/n))
time_decimal
= Decimal(str(n*time))

# 高精度复利计算
compound_amount
= principal_decimal * (1 + rate_decimal)**time_decimalreturncompound_amount

# 设置精度上下文
getcontext().prec
= 15# 示例:计算1000元本金,年利率5%,存款5年后的本息和(高精度计算)
result_decimal
=decimal_compound_interest(principal_amount, annual_interest_rate, years)
print(f
"高精度复利计算结果:{result_decimal:.15f} 元")

5. 代码解析

在这个实战示例中,我们使用
math
模块和
decimal
模块分别进行了复利计算。通过
math
模块,我们可以进行常规的浮点数计算,而通过
decimal
模块,我们实现了高精度的复利计算,避免了浮点数运算中的精度问题。

值得注意的是,在
decimal
模块中,我们通过初始化
Decimal
对象,并使用该对象进行高精度计算。同时,通过设置精度上下文,我们确保了计算的准确性。

综合而言,深入理解并灵活运用
math

decimal
模块,可以为数学计算提供更精确和可靠的工具,特别是在需要处理金融或其他对精度要求较高的领域。

6. 拓展应用:图形绘制与数据可视化

为了更全面地展示数学模块的实际应用,我们将通过绘制正弦函数图形,结合
math
模块进行数据可视化。同时,使用
matplotlib
库来实现图形的绘制。

6.1 绘制正弦函数图形

import math
import matplotlib.pyplot
asplt
import numpy
asnp

# 生成正弦函数的数据
x
= np.linspace(0, 2 * math.pi, 100) # 在0到2π之间生成100个点
y
=np.sin(x)

# 绘制正弦函数图形
plt.plot(x, y, label
='sin(x)')
plt.title(
'Sin Function')
plt.xlabel(
'x')
plt.ylabel(
'sin(x)')
plt.legend()
plt.grid(True)
plt.show()

6.2 结合decimal模块的高精度绘制

在上述示例中,我们使用了
numpy
库生成正弦函数的数据,并通过
matplotlib
库将图形绘制出来。如果需要更高的精度,我们可以结合
decimal
模块进行计算和绘制。

from decimalimport Decimal, getcontext

# 设置精度上下文
getcontext().prec
= 30# 使用Decimal进行高精度计算
x_decimal
= [Decimal(str(val)) for val inx]
y_decimal
= [Decimal(str(math.sin(val))) for val inx_decimal]

# 绘制高精度正弦函数图形
plt.plot(x_decimal, y_decimal, label
='sin(x) (High Precision)')
plt.title(
'High Precision Sin Function')
plt.xlabel(
'x')
plt.ylabel(
'sin(x)')
plt.legend()
plt.grid(True)
plt.show()

7. 代码解析

在这个拓展应用示例中,我们通过
matplotlib
库实现了正弦函数图形的绘制,并且结合了
decimal
模块进行高精度计算和绘制。通过这样的方式,我们能够更准确地呈现数学函数的图形,特别是在对精度要求较高的情况下。

这个实例展示了如何将数学计算与数据可视化结合起来,为开发者提供了更全面的工具,使其能够更好地理解和展示数学概念。

总的来说,深入了解数学模块,并结合其他强大的库进行实际应用,可以使开发者更好地处理数学问题,从而更高效地解决实际工程中的挑战。

8. 高级应用:数学模块在科学计算中的角色

数学模块在科学计算领域中扮演着重要的角色,尤其是在处理复杂的数学问题和算法时。让我们通过一个实例,使用
math
模块和
numpy
库来解决线性代数中的矩阵运算问题。

8.1 解线性方程组

考虑一个简单的线性方程组:

[ 2x + y = 5 ]

[ 4x - 3y = 2 ]

我们可以使用
numpy
库中的
linalg.solve
函数来解决这个方程组。

import numpy asnp

# 系数矩阵
coefficients
= np.array([[2, 1], [4, -3]])

# 右侧常数向量
constants
= np.array([5, 2])

# 解线性方程组
solution
=np.linalg.solve(coefficients, constants)

print(f
"线性方程组的解为:x = {solution[0]}, y = {solution[1]}")

8.2 数学模块的辅助计算

在解线性方程组的过程中,我们可以使用
math
模块中的一些函数进行计算。例如,可以使用
math.sqrt
函数计算*方根。

import math

# 计算*方根
square_root_result
= math.sqrt(9)

print(f
"*方根的计算结果:{square_root_result}")

9. 代码解析

在这个高级应用示例中,我们展示了数学模块在科学计算中的角色,通过
numpy
库解决了一个线性方程组的问题。同时,我们使用了
math
模块的函数进行辅助计算。

科学计算中,数学模块的功能不仅仅限于提供数学函数,还包括支持更复杂的计算、算法和科学研究。通过结合不同的库和模块,开发者能够更轻松地处理各种数学和科学计算任务。

总的来说,深入学习和灵活运用数学模块,结合其他领域的库,将为科学计算提供强大的工具和技术支持。这种综合应用的能力对于在科学、工程等领域从事数学建模和计算的开发者来说是至关重要的。

10. 性能优化:数学模块在算法中的应用

除了提供丰富的数学函数和科学计算支持外,数学模块还在算法优化方面发挥着关键作用。让我们通过一个例子,使用
math
模块中的函数来优化一个简单的算法。

考虑计算一个整数的*方根的问题,我们可以使用二分查找算法,结合
math
模块的
sqrt
函数来提高计算效率。

import math

def binary_search_sqrt(num, epsilon
=1e-6):if num < 0:
raise ValueError(
"负数没有实数*方根")if num == 0 or num == 1:returnnum

low, high
= 0, num
mid
= (low + high) / 2 while abs(mid**2 - num) >epsilon:if mid**2 <num:
low
=midelse:
high
=mid

mid
= (low + high) / 2 returnmid

# 示例:计算
16的*方根
result
= binary_search_sqrt(16)
print(f
"通过二分查找计算的*方根:{result}")

# 对比使用math模块的sqrt函数
math_result
= math.sqrt(16)
print(f
"math模块的sqrt函数计算的*方根:{math_result}")

11. 代码解析与总结

在这个例子中,我们通过二分查找算法计算整数的*方根,并结合
math
模块的
sqrt
函数进行性能优化。这种方式比直接使用循环逐步逼**方根更加高效,特别是对于大整数。

性能优化是数学模块在算法和计算任务中的一项重要职责。开发者可以根据具体情况选择合适的数学模块中的函数,结合算法进行优化,以提高程序的执行效率。

总体来说,数学模块不仅提供了数学计算的基础功能,还在算法优化和性能提升方面发挥着重要的作用,使得开发者能够更好地解决实际问题。

12. 实践案例:金融计算中的数学模块应用

在金融领域,数学模块的应用尤为广泛。我们将通过一个简单的实践案例,使用
math
模块和
numpy
库来进行简单的贷款计算。

考虑一个贷款情景,借款金额为 ( P ) 元,年利率为 ( r ),贷款期限为 ( n ) 年。我们希望计算每月还款金额。

import numpy asnp
import math

def loan_payment(principal, annual_interest_rate, loan_term):
# 将年利率转换为月利率
monthly_interest_rate
= annual_interest_rate / 12 / 100# 计算每月还款金额
monthly_payment
= (principal * monthly_interest_rate) / (1 - math.pow(1 + monthly_interest_rate, -loan_term))returnmonthly_payment

# 示例:借款金额
10000 元,年利率 5%,贷款期限 3
loan_amount
= 10000annual_rate= 5loan_period= 3monthly_payment_result=loan_payment(loan_amount, annual_rate, loan_period)
print(f
"每月还款金额:{monthly_payment_result:.2f} 元")

13. 代码解析与总结

在这个实践案例中,我们使用
math
模块和
numpy
库计算了每月的还款金额,应用了贷款计算中的数学公式。

金融计算通常涉及复杂的数学模型和公式,而数学模块的强大功能使得处理这些计算变得更加简单和高效。通过合理运用数学模块,开发者能够轻松解决金融领域中的各种计算和建模问题。

总的来说,数学模块在实际应用中扮演着重要的角色,为各个领域的开发者提供了丰富的工具和技术支持。

14. 结合机器学习:数学模块在数据科学中的应用

数学模块在数据科学和机器学习领域中起到至关重要的作用。让我们通过一个简单的线性回归的例子,结合
numpy

math
模块,来演示数学模块在机器学习中的应用。

import numpy asnp
import math
import matplotlib.pyplot
asplt

# 生成示例数据
np.random.seed(
42)
X
= 2 * np.random.rand(100, 1)
y
= 4 + 3 * X + np.random.randn(100, 1)

# 使用numpy进行线性回归
X_b
= np.c_[np.ones((100, 1)), X]
theta_best
=np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)

# 打印线性回归参数
print(
"线性回归参数:", theta_best)

# 绘制原始数据和回归线
plt.scatter(X, y, color
='blue', label='原始数据')
plt.plot(X, X_b.dot(theta_best), color
='red', label='线性回归')
plt.title(
'线性回归示例')
plt.xlabel(
'X')
plt.ylabel(
'y')
plt.legend()
plt.show()

15. 代码解析与总结

在这个机器学习的例子中,我们使用了
numpy
库进行矩阵计算,结合
math
模块中的数学函数,实现了简单的线性回归模型。

在数据科学和机器学习中,数学模块的功能得到了广泛应用,包括线性代数运算、概率分布计算等。通过合理运用数学模块,数据科学家和机器学习工程师能够更好地理解和应用各种算法和模型,从而更准确地分析和预测数据。

总体而言,数学模块为数据科学和机器学习提供了强大的计算基础,使得开发者能够更高效地进行建模和分析工作。

总结

本文深入探讨了Python中的数学模块
math

decimal
的基础知识,并通过多个实际的代码示例展示了它们在不同领域的应用。首先,我们学习了
math
模块提供的常用数学函数、随机数生成和数学常数等功能,然后通过实战演示了高精度计算的应用。接着,通过绘制正弦函数图形,展示了数学模块在数据可视化中的应用。进一步,我们探讨了数学模块在科学计算中的角色,解决了线性方程组和复杂算法的问题。随后,通过性能优化的例子,展示了数学模块在算法中的应用,提高了计算效率。在实践案例中,我们应用数学模块进行简单的贷款计算,展示了它在金融领域的实际应用。最后,通过线性回归的例子,展示了数学模块在机器学习中的应用。

总体而言,数学模块在Python中扮演着多重角色,包括提供基础数学功能、支持高精度计算、辅助科学计算、优化算法性能以及应用于数据科学和机器学习等。深入理解和灵活应用这些数学模块,能够使开发者更好地处理各种数学问题,从而在不同领域取得更高效、精确的计算和分析结果。

点击关注,第一时间了解华为云新鲜技术~

My-PaddleOCR

介绍

如何在 C++ 项目中,通过源码使用 PaddlePaddle 实现 OCR 功能。
本项目的所有源码:
gitee: paddleocr

目前,官方提供使用 PaddleOcr 的方案有:

  • 在 Python 项目中,调用
    paddlepaddle
    +
    paddleocr
    包。

  • 在 C++项目中,调用一个可执行文件。(由编译
    PaddleOCR
    中的
    deploy/cpp_infer
    下的代码形成)

    Paddle OCR 提供了一个通过编译
    deploy/cpp_infer
    下的代码为
    ppocr.exe
    ,然后通过命令行调用获取 OCR 的结果。
    具体过程见:
    服务器端 C++预测

其它方法:

  • 使用 Python 写一个 RESTful 服务,然后让 C++项目调用这个服务功能。

这里主要介绍一个更加直接的方法:


  • deploy/cpp_infer
    的源码引入到我们的 C++项目。
  • 做适当修改后,我们就可以直接使用这些 API。
  • 当然,如果你愿意的话,也可以将这些源码形成一个新的项目,编译成一个 dll。(这里并不介绍)
    说明:
  • 下面的方法只在 release 版中有效。可能是因为
    paddle_inference.dll
    只支持 release 版。
    如果要支持 debug 版,可能需要重新编译 PaddlePaddle。

Paddle OCR C++ 源码

Paddle OCR 的仓库,在
github: PaddleOCR
或者
gitee: PaddleOCR
C++ 相关的代码在目录
deploy/cpp_infer
里。

如何引入 Paddle OCR C++ 源码

需要安装的组件

  • opencv
    我在 opencv 4.6 版本上测试通过。
    注意:opencv 4.5 版本存在一些问题,会导致功能异常。
    设置环境变量:
@REM 设置opencv目录环境变量
setx OPENCV_ROOT "C:\3rd\opencv4.6\build"
  • paddle_inference
    我用的是 2.6 版。
    设置环境变量:
@REM 设置paddle_inference目录环境变量
setx PADDLE_ROOT "C:\3rd\paddle_inference"
  • cuda (optional)
    你应该懂得。
  • cudnn (optional)
    你应该懂得。

创建 C++项目

  • 创建一个空的 C++ 项目。

  • 在项目中创建文件:
    OpenCVDebug.props

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup />
  <ItemDefinitionGroup>
    <ClCompile>
      <AdditionalIncludeDirectories>$(OPENCV_ROOT)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
      <AdditionalLibraryDirectories>$(OPENCV_ROOT)\x64\vc15\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>opencv_world460d.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup />
</Project>
  • 在项目中创建文件:
    OpenCVRelease.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup />
  <ItemDefinitionGroup>
    <ClCompile>
      <AdditionalIncludeDirectories>$(OPENCV_ROOT)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
      <AdditionalLibraryDirectories>$(OPENCV_ROOT)\x64\vc15\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>opencv_world460.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup />
</Project>
  • 在项目中创建文件:
    PaddleDebug.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup />
  <ItemDefinitionGroup>
    <ClCompile>
      <AdditionalIncludeDirectories>./;$(PADDLE_ROOT)/paddle/include;$(PADDLE_ROOT)/third_party/install/zlib/include;$(PADDLE_ROOT)/third_party/boost;$(PADDLE_ROOT)/third_party/eigen3;$(PADDLE_ROOT)/third_party/install/mklml/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
      <AdditionalLibraryDirectories>$(PADDLE_ROOT)/third_party/install/zlib/lib;$(PADDLE_ROOT)/third_party/install/mklml/lib;$(PADDLE_ROOT)/paddle/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>paddle_inference.lib;mklml.lib;libiomp5md.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup />
</Project>
  • 在项目中创建文件:
    PaddleRelease.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup />
  <ItemDefinitionGroup>
    <ClCompile>
      <AdditionalIncludeDirectories>./;$(PADDLE_ROOT)/paddle/include;$(PADDLE_ROOT)/third_party/install/zlib/include;$(PADDLE_ROOT)/third_party/boost;$(PADDLE_ROOT)/third_party/eigen3;$(PADDLE_ROOT)/third_party/install/mklml/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
      <AdditionalLibraryDirectories>$(PADDLE_ROOT)/third_party/install/zlib/lib;$(PADDLE_ROOT)/third_party/install/mklml/lib;$(PADDLE_ROOT)/paddle/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>paddle_inference.lib;mklml.lib;libiomp5md.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup />
</Project>
  • 修改 C++项目文件:
    my-paddleocr.vcxproj
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
    <Import Project=".\OpenCVDebug.props" />
    <Import Project=".\PaddleDebug.props" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
    <Import Project=".\OpenCVRelease.props" />
    <Import Project=".\PaddleRelease.props" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
    <Import Project=".\OpenCVDebug.props" />
    <Import Project=".\PaddleDebug.props" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
    <Import Project=".\OpenCVRelease.props" />
    <Import Project=".\PaddleRelease.props" />
  </ImportGroup>

引入 Paddle OCR C++ 源码

  • 克隆 Paddle OCR 仓库到本地。
  • 切换到分支
    release/2.6
git clone https://gitee.com/paddlepaddle/PaddleOCR.git
cd PaddleOCR
git checkout release/2.6

  • deploy/cpp_infer
    目录下的
    include

    src
    两个目录的内容复制到我们的 C++ 项目中。
  • 修改新的
    src
    目录名称为
    ocr
  • 删除
    ocr/main.cpp

  • include
    目录下创建
    ocr_flags.h
    文件,内容如下:

这个文件是为了替换 google 的 gflags 库的使用。

#pragma once

// common args
#include <string>
using std::string;

#define DECLARE_bool(name) extern bool FLAGS_##name;
#define DECLARE_int32(name) extern int FLAGS_##name;
#define DECLARE_string(name) extern string FLAGS_##name;
#define DECLARE_double(name) extern double FLAGS_##name;

#define DEFINE_VARIABLE(type, name, value, help)                               \
    static const type FLAGS_nono##name = value;                                \
    type FLAGS_##name = FLAGS_nono##name;                                      \
    static type FLAGS_no##name = FLAGS_nono##name;

#define DEFINE_bool(name, val, txt) DEFINE_VARIABLE(bool, name, val, txt)
#define DEFINE_int32(name, val, txt) DEFINE_VARIABLE(int, name, val, txt)
#define DEFINE_string(name, val, txt) DEFINE_VARIABLE(string, name, val, txt)
#define DEFINE_double(name, val, txt) DEFINE_VARIABLE(double, name, val, txt)
  • 修改
    include/args.h
    文件,内容如下:

不使用
gflags/gflags.h
注释行
// #include <gflags/gflags.h>
增加行
#include "ocr_flags.h"

...
// #include <gflags/gflags.h>
#include "ocr_flags.h"
...
  • 修改
    ocr/args.cpp
    文件,内容如下:

不使用
gflags/gflags.h
注释行
// #include <gflags/gflags.h>
增加行
#include "include/ocr_flags.h"

...
// #include <gflags/gflags.h>
#include "include/ocr_flags.h"
...
  • 修改
    include/paddleocr.cpp
    文件,内容如下:

不使用
auto_log/autolog.h
注释行
// #include "auto_log/autolog.h"
注释方法
PPOCR::benchmark_log
的内容。

// #include "auto_log/autolog.h"
...
void PPOCR::benchmark_log(int img_num) {
  // if (this->time_info_det[0] + this->time_info_det[1] +
  // this->time_info_det[2] >
  //     0) {
  //   AutoLogger autolog_det("ocr_det", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                          FLAGS_enable_mkldnn, FLAGS_cpu_threads, 1,
  //                          "dynamic", FLAGS_precision, this->time_info_det,
  //                          img_num);
  //   autolog_det.report();
  // }
  // if (this->time_info_rec[0] + this->time_info_rec[1] +
  // this->time_info_rec[2] >
  //     0) {
  //   AutoLogger autolog_rec("ocr_rec", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                          FLAGS_enable_mkldnn, FLAGS_cpu_threads,
  //                          FLAGS_rec_batch_num, "dynamic", FLAGS_precision,
  //                          this->time_info_rec, img_num);
  //   autolog_rec.report();
  // }
  // if (this->time_info_cls[0] + this->time_info_cls[1] +
  // this->time_info_cls[2] >
  //     0) {
  //   AutoLogger autolog_cls("ocr_cls", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                          FLAGS_enable_mkldnn, FLAGS_cpu_threads,
  //                          FLAGS_cls_batch_num, "dynamic", FLAGS_precision,
  //                          this->time_info_cls, img_num);
  //   autolog_cls.report();
  // }
}
...
  • 修改
    include/paddlestructure.cpp
    文件,内容如下:

不使用
auto_log/autolog.h
注释行
// #include "auto_log/autolog.h"
注释方法
PPOCR::benchmark_log
的内容。

// #include "auto_log/autolog.h"
...
void PaddleStructure::benchmark_log(int img_num) {
  // if (this->time_info_det[0] + this->time_info_det[1] +
  // this->time_info_det[2] >
  //     0) {
  //   AutoLogger autolog_det("ocr_det", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                          FLAGS_enable_mkldnn, FLAGS_cpu_threads, 1,
  //                          "dynamic", FLAGS_precision, this->time_info_det,
  //                          img_num);
  //   autolog_det.report();
  // }
  // if (this->time_info_rec[0] + this->time_info_rec[1] +
  // this->time_info_rec[2] >
  //     0) {
  //   AutoLogger autolog_rec("ocr_rec", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                          FLAGS_enable_mkldnn, FLAGS_cpu_threads,
  //                          FLAGS_rec_batch_num, "dynamic", FLAGS_precision,
  //                          this->time_info_rec, img_num);
  //   autolog_rec.report();
  // }
  // if (this->time_info_cls[0] + this->time_info_cls[1] +
  // this->time_info_cls[2] >
  //     0) {
  //   AutoLogger autolog_cls("ocr_cls", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                          FLAGS_enable_mkldnn, FLAGS_cpu_threads,
  //                          FLAGS_cls_batch_num, "dynamic", FLAGS_precision,
  //                          this->time_info_cls, img_num);
  //   autolog_cls.report();
  // }
  // if (this->time_info_table[0] + this->time_info_table[1] +
  //         this->time_info_table[2] >
  //     0) {
  //   AutoLogger autolog_table("table", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                            FLAGS_enable_mkldnn, FLAGS_cpu_threads,
  //                            FLAGS_cls_batch_num, "dynamic", FLAGS_precision,
  //                            this->time_info_table, img_num);
  //   autolog_table.report();
  // }
  // if (this->time_info_layout[0] + this->time_info_layout[1] +
  //         this->time_info_layout[2] >
  //     0) {
  //   AutoLogger autolog_layout("layout", FLAGS_use_gpu, FLAGS_use_tensorrt,
  //                             FLAGS_enable_mkldnn, FLAGS_cpu_threads,
  //                             FLAGS_cls_batch_num, "dynamic",
  //                             FLAGS_precision, this->time_info_layout,
  //                             img_num);
  //   autolog_layout.report();
  // }
}
...
  • 修改
    include/utility.cpp
    文件,内容如下:

不使用
dirent.h
注释行
// #include <dirent.h>
注释方法
Utility::GetAllFiles
的内容。

// #include <dirent.h>
...
// list all files under a directory
void Utility::GetAllFiles(const char *dir_name,
                          std::vector<std::string> &all_inputs) {
  // if (NULL == dir_name) {
  //   std::cout << " dir_name is null ! " << std::endl;
  //   return;
  // }
  // struct stat s;
  // stat(dir_name, &s);
  // if (!S_ISDIR(s.st_mode)) {
  //   std::cout << "dir_name is not a valid directory !" << std::endl;
  //   all_inputs.push_back(dir_name);
  //   return;
  // } else {
  //   struct dirent *filename; // return value for readdir()
  //   DIR *dir;                // return value for opendir()
  //   dir = opendir(dir_name);
  //   if (NULL == dir) {
  //     std::cout << "Can not open dir " << dir_name << std::endl;
  //     return;
  //   }
  //   std::cout << "Successfully opened the dir !" << std::endl;
  //   while ((filename = readdir(dir)) != NULL) {
  //     if (strcmp(filename->d_name, ".") == 0 ||
  //         strcmp(filename->d_name, "..") == 0)
  //       continue;
  //     // img_dir + std::string("/") + all_inputs[0];
  //     all_inputs.push_back(dir_name + std::string("/") +
  //                          std::string(filename->d_name));
  //   }
  // }
}
...

编写调用程序

  • 在项目中创建文件:
    paddle_util.h
    内容如下:
#pragma once
using namespace std;

#include <include/paddleocr.h>

class PaddleUtil {
public:
  PaddleUtil();

public:
  static PaddleUtil &get();
  static void init();
  void rec_image(const string &imageFile);

private:
  PaddleOCR::PPOCR ocr;
};
  • 在项目中创建文件:
    paddle_util.cpp
    内容如下:
#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <vector>

#include "paddle_util.h"
#include <include/args.h>

using namespace PaddleOCR;

PaddleUtil::PaddleUtil() {}

PaddleUtil &PaddleUtil::get() {
  static PaddleUtil self;
  return self;
}

void PaddleUtil::init() {
  FLAGS_det = true;
  FLAGS_rec = true;
  FLAGS_cls = false;
  FLAGS_use_angle_cls = false;
  FLAGS_det_model_dir = "model/whl/det/en/en_PP-OCRv3_det_infer";
  FLAGS_rec_model_dir = "model/whl/rec/en/en_PP-OCRv4_rec_infer";
  FLAGS_rec_char_dict_path = "model/en_dict.txt";
}

void PaddleUtil::rec_image(const string &imageFile) {
  if (FLAGS_benchmark) {
    ocr.reset_timer();
  }

  cv::Mat img = cv::imread(imageFile, cv::IMREAD_COLOR);
  if (!img.data) {
    std::cerr << "[ERROR] image read failed! image path: " << imageFile
              << std::endl;
    return;
  }

  std::vector<OCRPredictResult> ocr_result =
      ocr.ocr(img, FLAGS_det, FLAGS_rec, FLAGS_cls);

  Utility::print_result(ocr_result);
  if (FLAGS_visualize && FLAGS_det) {
    std::string file_name = Utility::basename(imageFile);
    Utility::VisualizeBboxes(img, ocr_result, FLAGS_output + "/" + file_name);
  }
}
  • 在项目中创建主程序文件:
    my-paddleocr.cpp
    内容如下:
#include <iostream>

#include "paddle_util.h"

int main() {

  PaddleUtil::init();
  PaddleUtil::get().rec_image("model/254.jpg");
  std::cout << "Done!\n";
}

编译运行

  • 在 Visual Studio 中使用 Release 版本编译。
  • 运行
    copy_paddle_dll.bat
    ,复制
    paddle_inference.dll
    到输出目录。
@REM copy dll to Release
copy /Y %PADDLE_ROOT%\paddle\lib\paddle_inference.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\mkldnn\lib\mkldnn.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\mklml\lib\mklml.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\mklml\lib\libiomp5md.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\paddle2onnx\lib\paddle2onnx.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\onnxruntime\lib\onnxruntime.dll .\x64\Release\

前言:

在本篇 Taurus.MVC WebMVC 入门开发教程的第五篇文章中,我们将学习如何处理表单提交和进行数据验证。

这是 Web 开发中非常重要的一部分,因为它涉及到用户输入数据的处理和有效性验证。

我们将继续使用 Taurus.Mvc 命名空间,并探讨如何在控制器中接收表单数据、进行数据验证并给出相应的响应。

步骤1:创建视图

首先,我们需要创建一个包含表单的视图。

在视图文件(例如 c
reate.html
)中,我们可以使用 HTML 表单元素来接收用户输入的数据。

<!DOCTYPE html>
<html>
<head>
    <title>创建用户</title>
</head>
<body>
    <h1>创建用户</h1>
    <form method="post">
        <label for="name">姓名:</label>
        <input type="text" name="name" id="name" required>
        <br>
        <label for="age">年龄:</label>
        <input type="number" name="age" id="age" required>
        <br>
        <input type="submit" value="提交" name="btnAddUser">
<div id="printMsg"></div> </form> </body> </html>

在上述代码中,我们创建了一个简单的表单,包含一个文本输入框和一个数字输入框,用于接收用户的姓名和年龄。

表单的
method
属性被设置为
post
,默认将数据提交到
当前页面。

同时给按钮起了个名称"
btnAddUser
",事件按钮,对应控制器的方法。

步骤2:更新控制器

接下来,我们需要在控制器中创建一个方法来处理表单提交。

我们继续沿用上文使用到的 HomeController.cs 文件,并在其中添加一个名为
Create
的方法,用来呈现显示页面内容。

同时添加 BtnAddUser 方法【
和按钮同名,不区分大小写
】来处理按钮事件。

public classHomeController : Taurus.Mvc.Controller
{
public voidIndex()
{
...
}public voidCreate() { }public void BtnAddUser(string name, intage)
{
View.Set(
"printMsg", name + "-" +age);
}
}

在 HomeController 类中,定义和 Create.html 同名的方法,用来显示该表单界面。

同时添加 BtnAddUser 方法,来处理按钮点击事件。

上述代码中的 BtnAddUser 方法可以指定接收 表单 对象作为参数,其中包含了表单提交的数据。

我们也可以通过 Query<string>("
name")
和 Query<int>(
"age")
来获取相应的用户输入值。

上述示例代码中,最后通过:View.Set 方法,可以将数据呈现在指定(ID=“printMsg”)的 Html 标签中。

步骤3:进行数据验证

在表单提交后,我们通常需要对用户输入的数据进行验证,以确保其有效性。

在上述代码的中,我们将参数表单的接收参数,定义到方法参数中,同时指定了数据类型。

对于表单参数较少时:

将表单参数放到方法参数中,可以简化代码,同时利用框架的的数据类型转换,进行有效转换。

对于参数较多时:

您可以通过定义实体Model,并将它定义到方法参数中,例如:

    public voidBtnAddUser(User user)
{
View.Set(
"printMsg", user.name + "-" +user.age);
}

当然我们也可以不定义任何方法参数,通过 Query<T>(Name
)
方法来获取相应的用户输入值。

    public voidBtnAddUser()
{
View.Set(
"printMsg", Query<String>("name") + "-" + Query<int>("age"));
}

接下来,您需要自行添加数据验证的逻辑,以保证程序的稳定输出。

例如,您可以使用条件语句、正则表达式或其他验证方式来检查用户输入的数据是否符合要求。

如果数据验证失败,您可以返回一个视图,向用户显示错误信息并要求重新输入。

如果数据验证成功,您可以执行其他操作(例如保存到数据库)并重定向用户到另一个页面。

步骤4:运行应用程序

最后,运行应用程序并访问
/Home/Create
路由,您将看到一个表单页面。

尝试提交表单并查看控制台输出,您将看到用户输入的姓名和年龄被成功获取。

总结

通过本篇教程,我们学习了如何在 Taurus.MVC WebMVC 中处理表单提交和进行数据验证。

我们创建了一个包含表单的视图,并在控制器中接收表单数据,并进行数据验证和其他逻辑操作。

本系列的目录大纲为:

Taurus.MVC WebMVC 入门开发教程1:框架下载环境配置与运行

Taurus.MVC WebMVC 入门开发教程2:一个简单的页面呈现

Taurus.MVC WebMVC 入门开发教程3:数据绑定Model

Taurus.MVC WebMVC 入门开发教程4:数据列表绑定List<Model>

Taurus.MVC WebMVC 入门开发教程5:表单提交与数据验证

Taurus.MVC WebMVC 入门开发教程6:路由配置与路由映射

Taurus.MVC WebMVC 入门开发教程7:部分视图和页面片段

本文考虑OFDM系统在多径信道下的误码性能

代码

clc;close all;clear

%% Seting parameters
EbN0_list = 20:2:40;
Q_order_list = 2:2:10;
loopNumber = 10000;
fprintf('Qm\t EbN0 \t \t EsN0 \t \t SNR_Cal \t \t ser \t\t ser_theory\t\t\t ber\t\t nloop \t\t \n');
for iQorder = 1 : length(Q_order_list)
for iEbN0 = 1 : length(EbN0_list)

%% Frame structure
N_Frame = 1;
N_Symbol = 1;
N_RB = 106;
N_SC_perRB = 12;
N_SC = N_RB * N_SC_perRB;
N_Ant = 1;
N_fft_order = floor(log2(N_RB * N_SC_perRB));
N_fft = 2^(N_fft_order+1);
N_cp = N_fft/8;
EbN0 = EbN0_list(iEbN0);

%% Modulation
Q_order = Q_order_list(iQorder);
Qm = 2^Q_order;
N_bit = N_Frame * N_Symbol * N_RB * N_SC_perRB * Q_order;

%% Noise Calculation
SNR =  EbN0 + 10 * log10(Q_order);

%% Loop
for iloop = 1 :loopNumber
data_bit_in = randi([0 1], 1, N_bit);
dataSymbolsIn = bi2de(reshape(data_bit_in, Q_order, N_bit/Q_order).', 'left-msb'); 
dataMod = qammod(dataSymbolsIn, Qm,'UnitAveragePower', true); 

%% Show Constellation
%scatterplotme(dataMod)

%% Resource Mapping
RE_Grid = zeros(N_RB * N_SC_perRB,N_Symbol * N_Frame);
dataMod_tmp = reshape(dataMod,N_RB * N_SC_perRB,[]); %only data
Power_Scale = 1;
RE_Grid_all = Power_Scale * dataMod_tmp;

%% IFFT add CP
frame_mod_shift = ifftshift(RE_Grid_all); 
ifft_data = ifft(frame_mod_shift,N_fft)*sqrt(N_fft); 
%ifft_data = ifft(frame_mod_shift)*sqrt(1272); 
Tx_cd = [ifft_data(N_fft-N_cp+1:end,:);ifft_data];
time_signal = reshape(Tx_cd,[],1);

%% Channel
power_RE = sum(sum(abs(RE_Grid_all).^2)) / N_RB / N_SC_perRB / N_Symbol / N_Frame;
power_tp = sum(sum(abs(ifft_data).^2)) / N_RB / N_SC_perRB / N_Symbol / N_Frame;  %IFFT zero padding averages the true RE Power
N0 = power_RE .* 10.^(-SNR / 10);
white_noise_starand = 1/sqrt(2)*(randn(size(time_signal)) + 1j * randn(size(time_signal)));
nTap = 2;
taps = RayleighChanTaps(nTap);
% taps = [0.9,0.1];
time_signal_path = Multipath_channel(time_signal,taps);
TransmittedSignal = time_signal_path + sqrt(N0) * white_noise_starand;

%% Receive and Sys
ReceivedSignal = TransmittedSignal;
hF = fftshift(fft(taps,N_fft));

%% FFT and Frame   
frame_recieved_parallel = reshape(ReceivedSignal, N_fft + N_cp, []);
frame_Received = frame_recieved_parallel(N_cp + 1:end,:);    
frame_Grid_fft = fft(frame_Received,N_fft) / sqrt(N_fft);
RE_Grid_all_fftshift = fftshift(frame_Grid_fft);
RE_Grid_all_fftshift_eq = fftshift(diag(1./hF)*RE_Grid_all_fftshift);
RE_Grid_all_Received = fftshift(RE_Grid_all_fftshift_eq(1:N_SC,:));
% figure(1)
% plot(abs(RE_Grid_all_fftshift(:,1)))
% figure(2)
% plot(abs(RE_Grid_all_fftshift_eq(:,1)))
% figure(3)
% plot(abs(abs(hF)))
% figure(4)
% plot(abs(abs(1./hF)))

%% Demodulation
RE_PreDeMod = reshape(RE_Grid_all_Received,[],1);
dataSymbolsOut = qamdemod(RE_PreDeMod, Qm,'UnitAveragePower', true); 
data_bit_out = reshape((de2bi(dataSymbolsOut, 'left-msb')).',1,[]); 
power_RE_receid = sum(sum(abs(RE_PreDeMod).^2)) / N_RB / N_SC_perRB / N_Symbol / N_Frame;
snr_all(iQorder,iEbN0,iloop) = 10*log10(power_RE/(power_RE_receid - power_RE));

%% Result: Ser and Ber
%Ser
sym_err = length(find(dataSymbolsOut - dataSymbolsIn));
ser_all(iQorder,iEbN0,iloop) = sym_err / length(dataSymbolsOut);
%Ber
bit_error = sum(abs(data_bit_out - data_bit_in));
ber_all(iQorder,iEbN0,iloop) = bit_error / length(data_bit_out);
end
sers = mean(ser_all,3);
snrs = mean(snr_all,3);
bers = mean(ber_all,3);
sers_theory(iQorder,iEbN0) = QAM_SER_Theory(Qm,EbN0);

    fprintf('%dQAM\t%f\t %f\t %f\t %e\t\t%e\t\t%e\t\t%d\t\n', Qm, EbN0, SNR,snrs(iQorder,iEbN0),sers(iQorder,iEbN0),sers_theory(iQorder,iEbN0),bers(iQorder,iEbN0),loopNumber);
    end
end

figure(1)
semilogy(EbN0_list, bers(1,:), 'k--+');
hold on 
grid on
semilogy(EbN0_list, bers(2,:), 'r--o');
semilogy(EbN0_list, bers(3,:), 'b--x');
semilogy(EbN0_list, bers(4,:), 'g--s');
xlabel('Eb/N0,dB');
ylabel('BER');
title('BER VERS SNR');
legend('QPSK','16QAM','256QAM','1024QAM');


figure(2)
semilogy(EbN0_list, sers(1,:), 'k--+');
hold on 
grid on
semilogy(EbN0_list, sers(2,:), 'r--o');
semilogy(EbN0_list, sers(3,:), 'b--x');
semilogy(EbN0_list, sers(4,:), 'g--s');
xlabel('Eb/N0,dB');
ylabel('SER');
title('SER VERS SNR');
%SML =  simulation, THR = theory
legend('QPSK','16QAM','256QAML','1024QAM');

用到的信道与过信道代码

function taps = RayleighChanTaps(nTap)
  taps= 1/sqrt(2)*1/sqrt(nTap)*(randn(nTap,1) + 1j*randn(nTap,1));
  taps = taps./sum(abs(taps));
end

function taps = ExponentialChanTaps(SampRateMHz, delaySprdNsec)
sampTimeNsec = 1000 / SampRateMHz;

if delaySprdNsec == 0
    Kmax = 0;
    vark = 1;
else
    Kmax = ceil(10 * delaySprdNsec/sampTimeNsec);
    var0 = 1 - exp(- sampTimeNsec /delaySprdNsec);
    k = (0:Kmax)';
    vark = var0 * exp( -k *sampTimeNsec/delaySprdNsec);
end
    stdDevReOrIm = sqrt(vark/2);
    taps = stdDevReOrIm .*(randn(Kmax +1,1) + 1j*randn(Kmax+1,1));
end

function yt = Multipath_channel(xt,taps)
    ht = taps;
    xht = conv(ht,xt);
    %yt = xht(end - length(xt)+1:end);
    yt = xht(1:length(xt));
end

仿真结果

瑞利信道下的仿真结果,设置抽头系数为2,仿真次数设置1000次曲线才会平滑。

在瑞利信道和白噪声下的仿真结果对比

结果略

一个感兴趣的点是固定信噪比时误码性能随多径的数量是如何变化的,先保证最大时延扩展没有超过CP的长度。

结论分析

瑞利信道下的误码率曲线近似为直线,(很奇怪,难以理解)

反思