代码生成器的原理无非就是得到字段相关信息(字段名,字段类型,字段注释等),然后根据模板,其实就是字符串的拼接与替换生成相应代码。

所以第一步我们需要解决如何得到字段的相关信息,有两种方式

  • 通过反射获得程序集类的字段相关信息
  • 读取数据库得到表的字段的相关信息
  1. 新建一个.NET Core控制台项目 取名AbpCodeGenerator

  2. 新建类DocsByReflection

    1. /// <summary>
    2. /// Utility class to provide documentation for various types where available with the assembly
    3. /// </summary>
    4. public class DocsByReflection
    5. {
    6. /// <summary>
    7. /// Provides the documentation comments for a specific method
    8. /// </summary>
    9. /// <param name="methodInfo">The MethodInfo (reflection data ) of the member to find documentation for</param>
    10. /// <returns>The XML fragment describing the method</returns>
    11. public static XmlElement XMLFromMember(MethodInfo methodInfo)
    12. {
    13. // Calculate the parameter string as this is in the member name in the XML
    14. string parametersString = "";
    15. foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
    16. {
    17. if (parametersString.Length > 0)
    18. {
    19. parametersString += ",";
    20. }
    21. parametersString += parameterInfo.ParameterType.FullName;
    22. }
    23. //AL: 15.04.2008 ==> BUG-FIX remove ?)?if parametersString is empty
    24. if (parametersString.Length > 0)
    25. return XMLFromName(methodInfo.DeclaringType, 'M', methodInfo.Name + "(" + parametersString + ")");
    26. else
    27. return XMLFromName(methodInfo.DeclaringType, 'M', methodInfo.Name);
    28. }
    29. /// <summary>
    30. /// Provides the documentation comments for a specific member
    31. /// </summary>
    32. /// <param name="memberInfo">The MemberInfo (reflection data) or the member to find documentation for</param>
    33. /// <returns>The XML fragment describing the member</returns>
    34. public static XmlElement XMLFromMember(MemberInfo memberInfo)
    35. {
    36. // First character [0] of member type is prefix character in the name in the XML
    37. return XMLFromName(memberInfo.DeclaringType, memberInfo.MemberType.ToString()[0], memberInfo.Name);
    38. }
    39. /// <summary>
    40. /// Provides the documentation comments for a specific type
    41. /// </summary>
    42. /// <param name="type">Type to find the documentation for</param>
    43. /// <returns>The XML fragment that describes the type</returns>
    44. public static XmlElement XMLFromType(Type type)
    45. {
    46. // Prefix in type names is T
    47. return XMLFromName(type, 'T', "");
    48. }
    49. /// <summary>
    50. /// Obtains the XML Element that describes a reflection element by searching the
    51. /// members for a member that has a name that describes the element.
    52. /// </summary>
    53. /// <param name="type">The type or parent type, used to fetch the assembly</param>
    54. /// <param name="prefix">The prefix as seen in the name attribute in the documentation XML</param>
    55. /// <param name="name">Where relevant, the full name qualifier for the element</param>
    56. /// <returns>The member that has a name that describes the specified reflection element</returns>
    57. private static XmlElement XMLFromName(Type type, char prefix, string name)
    58. {
    59. string fullName;
    60. if (String.IsNullOrEmpty(name))
    61. {
    62. fullName = prefix + ":" + type.FullName;
    63. }
    64. else
    65. {
    66. fullName = prefix + ":" + type.FullName + "." + name;
    67. }
    68. XmlDocument xmlDocument = XMLFromAssembly(type.Assembly);
    69. XmlElement matchedElement = null;
    70. foreach (XmlElement xmlElement in xmlDocument["doc"]["members"])
    71. {
    72. if (xmlElement.Attributes["name"].Value.Equals(fullName))
    73. {
    74. if (matchedElement != null)
    75. {
    76. throw new DocsByReflectionException("Multiple matches to query", null);
    77. }
    78. matchedElement = xmlElement;
    79. break;
    80. }
    81. }
    82. if (matchedElement == null)
    83. {
    84. throw new DocsByReflectionException("Could not find documentation for specified element", null);
    85. }
    86. return matchedElement;
    87. }
    88. /// <summary>
    89. /// A cache used to remember Xml documentation for assemblies
    90. /// </summary>
    91. static Dictionary<Assembly, XmlDocument> cache = new Dictionary<Assembly, XmlDocument>();
    92. /// <summary>
    93. /// A cache used to store failure exceptions for assembly lookups
    94. /// </summary>
    95. static Dictionary<Assembly, Exception> failCache = new Dictionary<Assembly, Exception>();
    96. /// <summary>
    97. /// Obtains the documentation file for the specified assembly
    98. /// </summary>
    99. /// <param name="assembly">The assembly to find the XML document for</param>
    100. /// <returns>The XML document</returns>
    101. /// <remarks>This version uses a cache to preserve the assemblies, so that
    102. /// the XML file is not loaded and parsed on every single lookup</remarks>
    103. public static XmlDocument XMLFromAssembly(Assembly assembly)
    104. {
    105. if (failCache.ContainsKey(assembly))
    106. {
    107. throw failCache[assembly];
    108. }
    109. try
    110. {
    111. if (!cache.ContainsKey(assembly))
    112. {
    113. // load the docuemnt into the cache
    114. cache[assembly] = XMLFromAssemblyNonCached(assembly);
    115. }
    116. return cache[assembly];
    117. }
    118. catch (Exception exception)
    119. {
    120. failCache[assembly] = exception;
    121. throw exception;
    122. }
    123. }
    124. /// <summary>
    125. /// Loads and parses the documentation file for the specified assembly
    126. /// </summary>
    127. /// <param name="assembly">The assembly to find the XML document for</param>
    128. /// <returns>The XML document</returns>
    129. private static XmlDocument XMLFromAssemblyNonCached(Assembly assembly)
    130. {
    131. string assemblyFilename = assembly.CodeBase;
    132. const string prefix = "file:///";
    133. if (assemblyFilename.StartsWith(prefix))
    134. {
    135. using (StreamReader streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename.Substring(prefix.Length), ".xml")))
    136. {
    137. XmlDocument xmlDocument = new XmlDocument();
    138. xmlDocument.Load(streamReader);
    139. return xmlDocument;
    140. }
    141. }
    142. else
    143. {
    144. throw new DocsByReflectionException("Could not ascertain assembly filename", null);
    145. }
    146. }
    147. }
    148. /// <summary>
    149. /// An exception thrown by the DocsByReflection library
    150. /// </summary>
    151. [Serializable]
    152. class DocsByReflectionException : Exception
    153. {
    154. /// <summary>
    155. /// Initializes a new exception instance with the specified
    156. /// error message and a reference to the inner exception that is the cause of
    157. /// this exception.
    158. /// </summary>
    159. /// <param name="message">The error message that explains the reason for the exception.</param>
    160. /// <param name="innerException">The exception that is the cause of the current exception, or null if none.</param>
    161. public DocsByReflectionException(string message, Exception innerException)
    162. : base(message, innerException)
    163. {
    164. }
    165. }
  3. 新建类MetaTableInfo

    1. public class MetaTableInfo
    2. {
    3. /// <summary>
    4. /// 类的注释
    5. /// </summary>
    6. public string ClassAnnotation { get; set; }
    7. /// <summary>
    8. /// 属性名称
    9. /// </summary>
    10. public string Name { get; set; }
    11. /// <summary>
    12. /// 属性类型
    13. /// </summary>
    14. public string PropertyType { get; set; }
    15. /// <summary>
    16. /// 属性注释
    17. /// </summary>
    18. public string Annotation { get; set; }
    19. /// <summary>
    20. /// 根据类名 反射得到类的信息
    21. /// </summary>
    22. /// <param name="className">类名</param>
    23. /// <returns></returns>
    24. public static List<MetaTableInfo> GetMetaTableInfoListForAssembly(string className)
    25. {
    26. var list = new List<MetaTableInfo>();
    27. //读取的程序集路径 需换成自己项目的下的程序集路径
    28. string sourceAssemblyPath = "D:\\Project\\ZhouDaFu.XinYunFen.Core\\bin\\Debug\\netcoreapp2.0\\ZhouDaFu.XinYunFen.Core.dll";
    29. Type[] types = Assembly.LoadFrom(sourceAssemblyPath).GetTypes();
    30. foreach (var type in types)
    31. {
    32. if (type.Name.Equals(className))
    33. {
    34. var classAnnotation = string.Empty;
    35. try
    36. {
    37. //获取类的注释
    38. XmlElement xmlFromType = DocsByReflection.XMLFromType(type.GetTypeInfo());
    39. classAnnotation = xmlFromType["summary"].InnerText.Trim();
    40. }
    41. catch
    42. {
    43. }
    44. foreach (PropertyInfo properties in type.GetProperties())
    45. {
    46. var metaTableInfo = new MetaTableInfo();
    47. try
    48. {
    49. XmlElement documentation = DocsByReflection.XMLFromMember(type.GetProperty(properties.Name));
    50. metaTableInfo.Annotation = documentation["summary"].InnerText.Trim();
    51. metaTableInfo.ClassAnnotation = classAnnotation;
    52. }
    53. catch
    54. {
    55. metaTableInfo.Annotation = "";
    56. }
    57. metaTableInfo.Name = properties.Name;
    58. if (properties.PropertyType == typeof(int))
    59. {
    60. metaTableInfo.PropertyType = "int";
    61. }
    62. else if (properties.PropertyType == typeof(int?))
    63. {
    64. metaTableInfo.PropertyType = "int?";
    65. }
    66. else if (properties.PropertyType == typeof(long))
    67. {
    68. metaTableInfo.PropertyType = "long";
    69. }
    70. else if (properties.PropertyType == typeof(long?))
    71. {
    72. metaTableInfo.PropertyType = "long?";
    73. }
    74. else if (properties.PropertyType == typeof(DateTime?))
    75. {
    76. metaTableInfo.PropertyType = "DateTime?";
    77. }
    78. else if (properties.PropertyType == typeof(decimal))
    79. {
    80. metaTableInfo.PropertyType = "decimal";
    81. }
    82. else if (properties.PropertyType == typeof(decimal?))
    83. {
    84. metaTableInfo.PropertyType = "decimal?";
    85. }
    86. else if (properties.PropertyType == typeof(string))
    87. {
    88. metaTableInfo.PropertyType = "string";
    89. }
    90. else if (properties.PropertyType == typeof(bool))
    91. {
    92. metaTableInfo.PropertyType = "bool";
    93. }
    94. else
    95. {
    96. metaTableInfo.PropertyType = properties.PropertyType.ToString().Split('.').Last().Replace("]", "");
    97. }
    98. list.Add(metaTableInfo);
    99. }
    100. }
    101. }
    102. return list;
    103. }
    104. }
  4. 在Program类中添加如下代码

    1. //反射程序集的方式生成相应代码
    2. string className = "User";//跟类名保持一致
    3. var metaTableInfoList = MetaTableInfo.GetMetaTableInfoListForAssembly(className);

    启动控制台,获得User相关信息,报如下错误

    这是缺少ABP相关程序集引用,使用NuGet安装如下程序集

  5. 启动控制台得到如下结果,剩下的就是字符串拼接了

  6. 新建一个txt文件模板(直接在代码里面拼接字符串也行,只不过那样修改起来麻烦),取名IndexJsTemplate.txt,添加如下代码

    1. (function () {
    2. $(function () {
    3. var _${{entity_Name_Plural_Here}}Table = $('#MainTable');
    4. var _{{entity_Name_Plural_Here}}Service = abp.services.app.{{entity_Name_Plural_Here}};
    5. var _permissions = {
    6. create: abp.auth.hasPermission('{{Permission_Value_Here}}.Create'),
    7. edit: abp.auth.hasPermission('{{Permission_Value_Here}}.Edit'),
    8. 'delete': abp.auth.hasPermission('{{Permission_Value_Here}}.Delete')
    9. };
    10. var _createOrEditModal = new app.ModalManager({
    11. viewUrl: abp.appPath + '{{App_Area_Name_Here}}/{{Entity_Name_Plural_Here}}/CreateOrEditModal',
    12. scriptUrl: abp.appPath + 'view-resources/Areas/{{App_Area_Name_Here}}/Views/{{Entity_Name_Plural_Here}}/_CreateOrEditModal.js',
    13. modalClass: 'CreateOrEdit{{Entity_Name_Here}}Modal'
    14. });
    15. var dataTable = _${{entity_Name_Plural_Here}}Table.DataTable({
    16. paging: true,
    17. serverSide: true,
    18. processing: true,
    19. listAction: {
    20. ajaxFunction: _{{entity_Name_Plural_Here}}Service.getAll,
    21. inputFilter: function () {
    22. return {
    23. filter: $('#{{Entity_Name_Plural_Here}}TableFilter').val()
    24. };
    25. }
    26. },
    27. columnDefs: [
    28. {
    29. width: 120,
    30. targets: 0,
    31. data: null,
    32. orderable: false,
    33. autoWidth: false,
    34. defaultContent: '',
    35. rowAction: {
    36. cssClass: 'btn btn-brand dropdown-toggle',
    37. text: '<i class="fa fa-cog"></i> ' + app.localize('Actions') + ' <span class="caret"></span>',
    38. items: [
    39. {
    40. text: app.localize('Edit'),
    41. visible: function () {
    42. return _permissions.edit;
    43. },
    44. action: function (data) {
    45. _createOrEditModal.open({ id: data.record.id });
    46. }
    47. }, {
    48. text: app.localize('Delete'),
    49. visible: function () {
    50. return _permissions.delete;
    51. },
    52. action: function (data) {
    53. delete{{Entity_Name_Here}}(data.record);
    54. }
    55. }]
    56. }
    57. }{{Property_Looped_Template_Here}}
    58. ]
    59. });
    60. function get{{Entity_Name_Plural_Here}}() {
    61. dataTable.ajax.reload();
    62. }
    63. function delete{{Entity_Name_Here}}({{entity_Name_Here}}) {
    64. abp.message.confirm(
    65. '',
    66. function (isConfirmed) {
    67. if (isConfirmed) {
    68. _{{entity_Name_Plural_Here}}Service.delete({
    69. id: {{entity_Name_Here}}.id
    70. }).done(function () {
    71. get{{Entity_Name_Plural_Here}}(true);
    72. abp.notify.success(app.localize('SuccessfullyDeleted'));
    73. });
    74. }
    75. }
    76. );
    77. }
    78. $('#Export{{Entity_Name_Here}}ToExcelButton').click(function () {
    79. _{{entity_Name_Here}}Service
    80. .get{{Entity_Name_Here}}ToExcel({})
    81. .done(function (result) {
    82. app.downloadTempFile(result);
    83. });
    84. });
    85. $('#CreateNew{{Entity_Name_Here}}Button').click(function () {
    86. _createOrEditModal.open();
    87. });
    88. abp.event.on('app.createOrEdit{{Entity_Name_Here}}ModalSaved', function () {
    89. get{{Entity_Name_Plural_Here}}();
    90. });
    91. $('#Get{{Entity_Name_Plural_Here}}Button').click(function (e) {
    92. e.preventDefault();
    93. get{{Entity_Name_Plural_Here}}();
    94. });
    95. $(document).keypress(function(e) {
    96. if(e.which === 13) {
    97. get{{Entity_Name_Plural_Here}}();
    98. }
    99. });
    100. });
    101. })();
  7. 首先读取文件模板,然后去替换掉“{{}}” 这种类似占位符的变量,然后再输出一个新的文件,代码如下

    1. static void Main(string[] args)
    2. {
    3. //反射程序集的方式生成相应代码
    4. string className = "User";//跟类名保持一致
    5. var metaTableInfoList = MetaTableInfo.GetMetaTableInfoListForAssembly(className);
    6. SetIndexJsTemplate(className, metaTableInfoList);
    7. }
    8. /// <summary>
    9. /// 生成IndexJsTemplate
    10. /// </summary>
    11. /// <param name="className"></param>
    12. public static void SetIndexJsTemplate(string className, List<MetaTableInfo> metaTableInfoList)
    13. {
    14. var rootPath = (Directory.GetCurrentDirectory().Split(new string[] { "bin" }, StringSplitOptions.RemoveEmptyEntries))[0];
    15. string templateContentDirectory = rootPath + "IndexJsTemplate.txt";
    16. var templateContent = Read(templateContentDirectory);
    17. StringBuilder sb = new StringBuilder();
    18. var i = 1;
    19. foreach (var item in metaTableInfoList)
    20. {
    21. sb.AppendLine(", {");
    22. sb.AppendLine("targets: " + i + ",");
    23. sb.AppendLine("data: \"" + GetFirstToLowerStr(item.Name) + "\"");
    24. sb.AppendLine("}");
    25. i++;
    26. }
    27. var property_Looped_Template_Here = sb.ToString();
    28. templateContent = templateContent
    29. .Replace("{{Entity_Name_Plural_Here}}", className)
    30. .Replace("{{Entity_Name_Here}}", className)
    31. .Replace("{{entity_Name_Here}}", GetFirstToLowerStr(className))
    32. .Replace("{{entity_Name_Plural_Here}}", GetFirstToLowerStr(className))
    33. .Replace("{{App_Area_Name_Here}}", "App_Area_Name")
    34. .Replace("{{Property_Looped_Template_Here}}", property_Looped_Template_Here)
    35. .Replace("{{Permission_Value_Here}}", "Pages.Administration." + className + "")
    36. ;
    37. Write(rootPath, "Index.js", templateContent);
    38. }
    39. #region 文件读取
    40. public static string Read(string path)
    41. {
    42. using (StreamReader sr = new StreamReader(path, Encoding.Default))
    43. {
    44. StringBuilder sb = new StringBuilder();
    45. String line;
    46. while ((line = sr.ReadLine()) != null)
    47. {
    48. sb.AppendLine(line.ToString());
    49. }
    50. return sb.ToString();
    51. }
    52. }
    53. /// <summary>
    54. ///
    55. /// </summary>
    56. /// <param name="filePath">文件保存路径</param>
    57. /// <param name="fileName">文件名</param>
    58. /// <param name="templateContent">模板内容</param>
    59. public static void Write(string filePath, string fileName, string templateContent)
    60. {
    61. if (!Directory.Exists(filePath))
    62. {
    63. Directory.CreateDirectory(filePath);
    64. }
    65. using (FileStream fs = new FileStream(filePath + fileName, FileMode.Create))
    66. {
    67. //获得字节数组
    68. byte[] data = Encoding.Default.GetBytes(templateContent);
    69. //开始写入
    70. fs.Write(data, 0, data.Length);
    71. }
    72. }
    73. /// <summary>
    74. ///
    75. /// </summary>
    76. /// <param name="filePath">文件保存路径</param>
    77. /// <param name="templateContent">模板内容</param>
    78. public static void Write(string filePath, string templateContent)
    79. {
    80. using (FileStream fs = new FileStream(filePath, FileMode.Create))
    81. {
    82. //获得字节数组
    83. byte[] data = Encoding.Default.GetBytes(templateContent);
    84. //开始写入
    85. fs.Write(data, 0, data.Length);
    86. }
    87. }
    88. #endregion
    89. #region 首字母小写
    90. /// <summary>
    91. /// 首字母小写
    92. /// </summary>
    93. /// <param name="s"></param>
    94. /// <returns></returns>
    95. public static string GetFirstToLowerStr(string str)
    96. {
    97. if (!string.IsNullOrEmpty(str))
    98. {
    99. if (str.Length > 1)
    100. {
    101. return char.ToLower(str[0]) + str.Substring(1);
    102. }
    103. return char.ToLower(str[0]).ToString();
    104. }
    105. return null;
    106. }
    107. #endregion
    108. }
  8. 启动控制台,即在当前项目下生成相应代码,这里分享一个我之前写好的基于abp.zero core 5.3.0的

    https://github.com/HisKingdom/CodeGenerator

手把手教你实现自己的abp代码生成器的更多相关文章

  1. 手把手教你用动软.NET代码生成器实例教程

    动软实战攻略 手把手教你用动软 文档编号:20110421 版权所有 © 2004-2011 动软 在线帮助:http://help.maticsoft.com 目录   一.        产品介绍 ...

  2. 手把手教你使用FineUI+动软代码生成器开发一个b/s结构的取送货管理信息系统(附源码)之开篇

    一 本系列随笔概览及产生的背景 近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座, ...

  3. 手把手教你使用FineUI开发一个b/s结构的取送货管理信息系统系列博文索引

    近阶段接到一些b/s类型的软件项目,但是团队成员之前大部分没有这方面的开发经验,于是自己选择了一套目前网上比较容易上手的开发框架(FineUI),计划录制一套视频讲座,来讲解如何利用FineUI快速开 ...

  4. 手把手教你做个人 app

    我们都知道,开发一个app很大程度依赖服务端:服务端提供接口数据,然后我们展示:另外,开发一个app,还需要美工协助切图.没了接口,没了美工,app似乎只能做成单机版或工具类app,真的是这样的吗?先 ...

  5. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(四)-使用Travis自动部署Hexo(2)

    前言 前面一篇文章介绍了Travis自动部署Hexo的常规使用教程,也是个人比较推荐的方法. 前文最后也提到了在Windows系统中可能会有一些小问题,为了在Windows系统中也可以实现使用Trav ...

  6. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(三)-使用Travis自动部署Hexo(1)

    前言 前面两篇文章介绍了在github上使用hexo搭建博客的基本环境和hexo相关参数设置等. 基于目前,博客基本上是可以完美运行了. 但是,有一点是不太好,就是源码同步问题,如果在不同的电脑上写文 ...

  7. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(二)-Hexo参数设置

    前言 前文手把手教从零开始在GitHub上使用Hexo搭建博客教程(一)-附GitHub注册及配置介绍了github注册.git相关设置以及hexo基本操作. 本文主要介绍一下hexo的常用参数设置. ...

  8. 手把手教从零开始在GitHub上使用Hexo搭建博客教程(一)-附GitHub注册及配置

    前言 有朋友问了我关于博客系统搭建相关的问题,由于是做开发相关的工作,我给他推荐的是使用github的gh-pages服务搭建个人博客. 推荐理由: 免费:github提供gh-pages服务是免费的 ...

  9. UWP Jenkins + NuGet + MSBuild 手把手教你做自动UWP Build 和 App store包

    背景 项目上需要做UWP的自动安装包,在以前的公司接触的是TFS来做自动build. 公司要求用Jenkins来做,别笑话我,之前还真不晓得这个东西. 会的同学请看一下指出错误,不会的同学请先自行脑补 ...

随机推荐

  1. unigui的编译部署

    unigui的编译部署 unigui既可以EXE形态部署,也可以IIS的ISAPI的形态部署.关键在工程文件.dpr里面的编译开关. {$define UNIGUI_VCL} // 注释此编译开关将使 ...

  2. unigui结合JS方法

    在js中界面上所有组件都当成html里来控制 1.控制按钮事件 document.getElementById(MainForm.UniButton4.getId()).click(); 这个方法让J ...

  3. FastReport套打 和连续打印

    FastReport套打,纸张是连续的带锯齿的已经印刷好的,类似于通信公司发票这里设计的是客户销售记录.客户有两个要求:1.因为打印纸张是印刷的,明细记录只有8行,所以,如果明细记录如果不到8行,就将 ...

  4. ICCV2013、CVPR2013、ECCV2013目标检测相关论文

    CVPapers 网址: http://www.cvpapers.com/   ICCV2013 Papers about Object Detection: 1. Regionlets for Ge ...

  5. JS学习笔记8_错误处理

    1.错误处理语法:(js没有异常exception一说,都是错误error) try{ //throw new Error(msg); //throw '错误'; //throw false; //t ...

  6. 【hyperscan】示例解读 pcapscan

    示例位置: <hyperscan source>/examples/pcapscan.cc参考:http://01org.github.io/hyperscan/dev-reference ...

  7. 【AWK】:常用总结

    单机文本数据处理,常用AWK,总结一下AWK最常用的要点,备忘备查. 1.What is AWK(1)Aho.Weinberger.Kernighan三位发明者名字首字母:(2)一个行文本处理工具: ...

  8. 一步步Cobol 400 上手自学入门教程01 - 基础概念

    先学习基础概念 1.COBOL字符:包含: User-defined words 用户定义字符 ŸSystem-names ŸReserved words 关键字 2.用户定义字符User-defin ...

  9. python with用法举例

    我们知道在操作文件对象的时候可以这么写 with open('a.txt') as f: '代码块' 上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明_ ...

  10. 第四章 PCA降维

    目录 1. PCA降维 PCA:主成分分析(Principe conponents Analysis) 2. 维度的概念 一般认为时间的一维,而空间的维度,众说纷纭.霍金认为空间是10维的. 3. 为 ...