`
arganzheng
  • 浏览: 105301 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Python语言学习笔记

阅读更多

Python语言学习笔记

2010-09-23 星期四 寒流+雨

中秋有三天假,感觉老长似的,专门跑出枫林晚借了本《Learning Python》的书,准备假期看看。厄,现在是假期的中间,书确实是看完了,700页左右的英文书,前后花了3个小时左右,当然只是看,没有编码。

发现自己现在看语言方面的书真是太快了,因为语言的东西确实相差无几。特别是语法方面的细节,在看书的时候我几乎就是一扫而过。比如,如何定义一个类,它的if-else,for循环语法,etc。因为这些只是实现上的差异(语法定义上的差异),跟这个语言的功能与编程范式无关——当然,我承认,语法很影响一个人对一门语言的选择与偏爱,它就像人的相貌一样。

但是,一门的语言最重要的差别不在于语法,而在于其它几个重要的方面。这里直接给出vczh同学的结论,因为我同意他的观点:

计算机语言作为一个计算的定义,在我们开发脚本引擎之前需要先进行了解。对于目前流行的若干种语言,我们可以抽象出一组正交属性来描述他们。

一、命令式与描述式

一门语言是命令式或者描述式取决于这门语言是用来告诉计算机怎样做还是做什么的。举个例子,SQL和Prolog是描述式语言,而C++、C#等则是命令式语言。我们在使用SQL的时候告诉服务器的是我们需要满足什么条件的数据项,而不是告诉服务器我们需要通过什么计算来获得自己所需要的数据项。描述式的语言的优点在于其可读性好。C# 3.0为数据查询加入了LINQ让我们可以在C#中书写类似SQL的代码查询数据。

另一个比较模糊的例子则是Haskell。Haskell很难区分是命令式语言还是描述式语言。因为从形式上来说我们告诉编译器的是我们想做什么而不是我们想怎么做,但是Haskell给我们的工具的粒度太细以至于我们为了告诉编译器做什么的同时仍然需要考虑一个问题是如何被解决的。

二、按值计算与惰性计算

惰性计算的语言很少出现以至于可能很多人都不知道"原来语言可以是这个样子的"。惰性计算的精神是不去执行没用的代码。什么是没用的代码呢?只要是这段代码的值不对外界产生任何影响,譬如没有往屏幕、硬盘或者是其他什么地方写点什么数据,就是没有用的。当然,至于这段代码中间做了些什么事情那是不管的。

举一个比较简单的例子,假设现在有如下代码:

function PrintAndReturn(Message,Result)
{
    Print(Message);

    return Result;
}

function DoSomething(BoolA,BoolB)
{
    If(BoolA || BoolB) Print("!");
}

DoSomething(PrintAndReturn("Hello",true),PrintAndReturn("World",false));

DoSomething函数传入两个参数,都是布尔类型的。如果这两个参数其中有一个是true的话那么就往屏幕上打出一个感叹号。PrintAndReturn函数接受两个参数,往屏幕上打出第一个参数,函数返回第二个参数。

对于一门按值计算的语言,也就是我们平常见到的那种,执行的结果是"HelloWorld!"。因为为了调用DoSomething我们需要首先获得两个布尔值。

对于一门惰性计算的语言,执行的结果是"Hello!"。因为DoSomething在对BoolA || BoolB进行求值的时候计算了BoolA,发现是true,于是BoolB这个参数就没有用了,因此PrintAndReturn("World",false)也就不会执行了,导致"World"不会显示在屏幕上。

当然,对于上面举的这个例子来说,这种语言有着惰性计算的属性并不合理。一门语言为了不具有二义性,在存在惰性计算的同时必须对自己的类型系统进行改造。关于这方面的资料可以查阅Haskell语言中Monad的原理。Haskell作为一门惰性计算的语言,在不关心求值顺序的同时,仍然保证结果的一致性。上面这个例子,如果程序对||的求值是从右操作数开始的话,那么输出的结果就变成"HelloWorld!"了。惰性计算的好处在于可以在逻辑上表达无穷大的对象,而在实际的计算过程中并不需要将这个无穷大的对象一次性计算出来,而是需要哪里算到哪里。举个例子:

function MakeArray(Index)
{
    return [Index]++MakeArray(Index+1);
}

function Sum(Array,Count)
{
    Result=0;

    for i=0 to Count-1

        Result+=Array[i];

   return Result;
}

Print(Sum(MakeArray(1),10));

在这个例子中,Index代表一个只有一个元素的数组,其内容是Index,而++操作符将两个数组接起来。于是MakeArray(1)就产生了一个无穷长的数组,其内容是1,2,3,4,...。Sum计算数组的前若干个数字的和。对于一门惰性计算的语言,这个例子将输出55,因为我们需要的仅仅是前10个数字,因此MakeArray只需要递归10次就自动挺下来了。而对于一门按值计算的语言来说,将发生死循环而出现不可停机现象。

三、强类型、弱类型与无类型

一门语言是无类型当且仅当一个固定的符号的类型可以在运行时改变。譬如如下代码:

TheVariable=1;

TheVariable="I am a string!";

第一行创建了一个int类型的TheVariable变量,而第二行则将TheVariable修改成了字符串类型。一门无类型语言的对象类型可以是数值、字符串、数组、类、闭包、函数指针等等的东西。

只要不是无类型的,那必然就是强类型或者弱类型的了。强类型与弱类型的分界线比较明显。只要存在隐式类型转换的语言则是弱类型的,譬如C语言能将int隐式转换为double。不存在隐式转换的语言也是存在的,譬如Haskell。在Haskell里面不能创建一个实数类型的名字但是绑定一个整数的值上去。因为整数跟实数的类型是不同的,而且不存在隐式转换。

四、函数与闭包

凡是支持闭包的语言必然是支持函数的,但是并不是所有支持函数的语言都支持闭包,而且也并不是所有的语言都有函数。Windows的批处理文件所能理解的语言就是不支持函数的语言的一个例子。

至于什么是闭包呢?闭包就是可以保持函数执行的上下文的一种强大的函数指针。举个例子:

function Add(a)
    {
        return function(b)
        {
            Return a+b;
        }
    }

    Inc=Add(1);

    Inc10=Add(10);

    Print(Inc(5));

    Print(Inc10(5));

这个例子将输出6和15。执行Inc=Add(1);的时候,Add函数返回了一个新的函数,这个函数接受参数b并返回参数a和b相加的结果。返回的这个函数将参数a记了下来。所以Inc和Inc10在执行的时候,虽然执行的是同一个函数,但是这个函数所看到的a确是不同的。a的值的不同代表着Inc和Inc10执行函数的不同。这也就是闭包是可以保持函数执行的上下文的由来了。当然,一门不支持闭包的语言是不能允许上面这种写法的。

这四种属性是区分语言特征的重要属性。至于一门语言是否支持面向对象的写法或者支持元编程或者泛型之类的东西,并不是十分重要的特性,虽然我们使用起来的感觉非常不同。

不过话说回来,虽然说语言的编程范式(如面向对象或者面向过程),跟语言本身不是特别有关系(用面向过程的语言,一样可以编写面向对象的代码),但是这是非常大的语法糖,所以也是一个非常重要的考虑方面。异常处理和泛型编程也是一样的。

另外需要补充几点的是:1. 语言本身的内存管理也是一个非常重要的考虑方面,即是否支持垃圾回收和引用计数。2. 语言的动态程度,这个与vczh的第三点有点重叠,但是支持多大程度的元编程确实是个问题。3. 参数和返回值的传递方式(按值传递还是按引用传递,etc.)。4. 作用域规则

貌似有点扯远了。。回到主题。学习Python,其实最主要是掌握它的对象视图。下面是笔者的总结。

在Python中,所有的变量都是引用(指针,地址,名字,标签),不包含任何类型信息,这就是前面vczh同学说的无类型,指的就是变量(Variable)是无类型的。但是基本上目前任何一门语言都是有类型的(整型,字符型,etc.)。Python也不例外。它的类型信息是跟对象(Object)挂钩的。变量指向创建的对象。在Python中,一切都是对象,包括函数,甚至“类型”本身就是一个对象。它是一门基于对象的语言(all values are objects),同时支持面向对象的编程范式。

Figure 9-3. Python’s major built-in object types, organized by categories. Everything is a type of object in Python, even the type of an object! The type of any object is an object of type “type.”

In fact, even types themselves are an object type in Python: a call to the built-in function type(X) returns the type object of object X. Type objects can be used for manual type comparisons in Python if statements. 

One note on type names: as of Python 2.2, each core type has a new built-in name added to support type customization through object-oriented subclassing: dict, list, str, tuple, int, long, float, complex, unicode, type, and file (file is a synonym for open). Calls to these names are really object constructor calls, not simply conversion functions, though you can treat them as simple functions for basic usage.

另外,由于Python中变量是无类型的,对象是有类型的,所以它采用了将“变量赋值”作为“变量声明”的做法。因为赋值操作其实就是创建(可能会复用)一个对象,然后将这个对象的地址信息赋值给这个变量,这样就可以通过该变量引用这个对象了。所以:

>>> a=3

Python编译器将做如下事情:

1. Create an object to represent the value 3.(这里python会推断出3其实是一个整数,所以其类型信息是Int。)

2. Create the variable a, if it does not yet exist.

3. Link the variable a to the new object 3.

Each object also has two standard header fields: a type designator used to mark the type of the object, and a reference counter used to determine when it’s OK to reclaim the object. 

根据值来推断变量类型,其实是很有道理的,在静态类型语言中如C,对复制语句做类型是否一致检查,依据的也是这样的。所以是有道理的。只不过将赋值语句作为声明,混淆了赋值语句的含义,所以当你真正需要是修改一个变量值(使其指向另一个对象),而不是重新定义一个新变量时,需要一些特殊的标识来告诉编译器。如:

X = 88             # Global X

def func():
    global X       # Global X: outside def
    X = 99        

func()
print X            # Prints 99
 

如果没有global X这个显示作用域声明,那么在func中X=99,其实是声明了一个local作用域的新变量X,函数外面的X其实没有变化。

 
这其实是Python的一个设计败笔,因为Python的作用域原则上采用的是Lexical Scoping(文法作用域)。亦即:
The scope of a variable (where it can be used) is always determinged by where it is assigned in your source code, and has nothing to do with which functions call which. If a variabl is assigned inside a def, it is local to that function; if assigned outside a def, it is global to the entire file. We call this lexical scoping because variable scopes are determined entired by the locations of the variables in the source code of your program files, not by function calls.
 
在Ruby中,为了避免这种奇怪的问题,采用了是显示的作用域声明机制(通过变量名约定的形式)。

Punctuation characters may appear at the start and end of Ruby identifiers. They have the following meanings:

$ Global variables are prefixed with a dollar sign. Following Perl's example, Ruby defines a number of global variables that include other punctuation characters, such as $_ and $-K.
@ Instance variables are prefixed with a single at sign, and class variables are prefixed with two at signs.
? As a helpful convention, methods that return Boolean values often have names that end with a question mark.
! Method names may end with an exclamation point to indicate that they should be used cautiously. This naming convention is often to distinguish mutator methods that alter the object on which they are invoked from variants that return a modified copy of the original object.
= Methods whose names end with an equals sign can be invoked by placing the method name, without the equals sign, on the left side of an assignment operator.

Here are some example identifiers that contain leading or trailing punctuation characters:

$files          # A global variable
@data           # An instance variable
@@counter       # A class variable
empty?          # A Boolean-valued method or predicate
sort!           # An in-place alternative to the regular sort method
timeout=        # A method invoked by assignment

A number of Ruby's operators are implemented as methods, so that classes can redefine them for their own purposes. It is therefore possible to use certain operators as method names as well. In this context, the punctuation character or characters of the operator are treated as identifiers rather than operators. See Section 4.6 for more about Ruby's operators.

there are four kinds of variables in Ruby, and lexical rules govern their names. Variables that begin with $ are global variables, visible throughout a Ruby program. Variables that begin with @ and @@ are instance variables and class variables, used in object-oriented programming and explained in Chapter 7. And variables whose names begin with an underscore or a lowercase letter are local variables, defined only within the current method or block. (See Section 5.4.3 for more about the scope of local variables.)

常量也是通过变量名约定的。 

4.5.1. Assigning to Variables
When we think of assignment, we usually think of variables, and indeed, these are the most common lvalues in assignment expressions. Recall that Ruby has four kinds of variables: local variables, global variables, instance variables, and class variables. These are distinguished from each other by the first character in the variable name. Assignment works the same for all four kinds of variables, so we do not need to distinguish between the types of variables here. 
Keep in mind that the instance variables of Ruby's objects are never visible outside of the object, and variable names are never qualified with an object name. Consider this assignment: 
point.x, point.y = 1, 2

The lvalues in this expression are not variables; they are attributes, and are explained shortly. 
Assignment to a variable works as you would expect: the variable is simply set to the specified value. The only wrinkle has to do with variable declaration and an ambiguity between local variable names and method names. Ruby has no syntax to explicitly declare a variable: variables simply come into existence when they are assigned. Also, local variable names and method names look the same—there is no prefix like $ to distinguish them. Thus, a simple expression such as x could refer to a local variable named x or a method of self named x. To resolve this ambiguity, Ruby treats an identifier as a local variable if it has seen any previous assignment to the variable. It does this even if that assignment was never executed. The following code demonstrates: 
Code View:

class Ambiguous
  def x; 1; end # A method named "x". Always returns 1
  def test
    puts x      # No variable has been seen; refers to method above: prints 1
    # The line below is never evaluated, because of the "if false" clause. But
    # the parser sees it and treats x as a variable for the rest of the method.
    x = 0 if false
    puts x    # x is a variable, but has never been assigned to: prints nil
    x = 2     # This assignment does get evaluated
    puts x    # So now this line prints 2
  end
end

Groovy就更进一步了,采用的是显式的变量定义——使用def关键字。这样就将定义与赋值分离开来。从而避免了作用域混淆情况。
因为对象与类型信息挂钩(包括操作),所以在这些脚本语言中,支持这样的操作:3.times { print "Ruby! " },9.downto(1) {|n| print n }  ,etc.
 

分享到:
评论

相关推荐

    YOLOv8-PyQt5-GUI-pred-insects-mytwu-995识别和分类昆虫种类-检测生态研究和害虫防治+数据集+训练好的模型.zip

    YOLOv8-PyQt5-GUI-pred-insects-mytwu_995识别和分类昆虫种类-检测生态研究和害虫防治+数据集+训练好的模型包含pyqt可视化界面,有使用教程 1. 内部包含标注好的目标检测数据集,分别有yolo格式(txt文件)和voc格式标签(xml文件), 共995张图像, 已划分好数据集train,val, test,并附有data.yaml文件可直接用于yolov5,v8,v9,v10,v11,v12等算法的训练; 2. yolo目标检测数据集类别名:insects(昆虫),包括 army_worm(行军虫)、legume_blister_beetle(豆类起泡叶甲)、red_spider(红蜘蛛)、rice_gall_midge(水稻瘿蚊)、rice_leaf_roller(水稻卷叶螟)、rice_leafhopper(水稻飞虱)、rice_water_weevil(水稻水象甲)、wheat_phloeothrips(小麦皮层蓟马)、white_backed_plant_hopper(白背飞虱)、yellow_rice_borer(水稻二化螟)等 3. yolo项目用途:识别和分类昆虫种类,用于生态研究和害虫防治 4. 可视化参考链接:https://blog.csdn.net/weixin_51154380/article/details/126395695?spm=1001.2014.3001.5502

    在Matlab_Simulink中使用3D动画的帆船模型仿真.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    MATLAB函数曲线图像数据提取与重绘:快速高效的数据可视化解决方案

    内容概要:本文详细介绍了如何利用MATLAB进行函数曲线图像的数据提取、复现重绘及后处理的方法。主要内容涵盖数据准备与导入、数据提取与预处理、图像复现与重绘、图像修改与后处理四个主要阶段。文中强调了程序编写的清晰性和注释的详尽性,确保即使是不熟悉MATLAB编程的用户也能快速理解和修改代码。此外,本文特别指出该服务不含曲线拟合、公式提取及FFT频谱分析等高级分析功能,但能在10分钟内快速出图,提供实惠的价格和服务质量。 适合人群:科研人员、工程师及其他需要进行数据可视化工作的专业人士,尤其是那些希望快速掌握MATLAB绘图技巧的新手。 使用场景及目标:适用于需要将原始图像数据转换为高质量函数曲线图像的各种应用场景,如实验数据分析、工程报告制作等。目标是在短时间内获得精确、美观的图表,同时提高工作效率。 其他说明:本文提供的方法和技术可以帮助用户更好地理解和应用MATLAB的基础绘图功能,为进一步深入学习打下坚实基础。虽然目前不涉及复杂的数学建模或信号处理技术,但对于大多数日常的数据可视化任务来说已经足够强大。

    移动通信复习资料全部答案.docx

    移动通信复习资料全部答案.docx

    自动驾驶领域Lanelet2高精地图解析与全局路径规划实战指南

    内容概要:本文详细介绍了Lanelet2高精地图的解析方法及其在全球路径规划中的应用。首先解释了Lanelet2作为当前主流高精度地图格式之一的特点,特别是它对车道、交通规则等元素的数据结构化表示方式。接着展示了如何利用Python和C++代码片段来加载和处理Lanelet2格式的地图,包括正确设置投影参数、遍历车道以及处理特定类型的车道(如左转车道)。然后深入探讨了全局路径规划的具体实现步骤,强调了选择合适搜索半径的重要性,并分享了一些实际项目中遇到的问题及解决方案,如坐标系转换错误导致的地图偏移、断头路引发的死循环等问题。最后提醒开发者注意地图制作过程中的常见陷阱,建议使用手动可视化工具进行细致检查。 适用人群:从事自动驾驶相关领域的工程师和技术爱好者,尤其是那些需要理解和使用Lanelet2高精地图进行开发的人群。 使用场景及目标:帮助读者掌握Lanelet2高精地图的基本概念和关键技术点,能够独立完成从地图加载到全局路径规划的一系列任务,避免常见的开发误区。 其他说明:文中提供的实例代码和实践经验对于初学者来说非常有价值,有助于快速入门并应用于实际项目中。

    第3章客户关系管理软件系统.ppt

    第3章客户关系管理软件系统.ppt

    GD32F303CCT6-LED点灯

    使用GD32F303芯片作为主控MCU,通过PB3输出1秒周期的高低电平点亮LED灯

    电子商务网站建设试题二及答案.docx

    电子商务网站建设试题二及答案.docx

    数组信号参数最大似然估计方差模拟 matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    行星齿轮系统非线性动力学模型与求解策略研究

    内容概要:本文探讨了行星齿轮系统的非线性求解及其与齿轮动力学和非线性动力学的关系。首先介绍了齿轮动力学的基础概念,包括基本方程和各参数的意义。接着详细解释了行星齿轮系统中存在的非线性因素,特别是时变啮合刚度这一重要特征,并通过Python代码进行了模拟展示。最后,文章展示了如何应用Runge-Kutta方法和其他数值解法来解决含有非线性因素的行星齿轮动力学问题,强调了这类研究对于机械系统优化设计的重要意义。 适合人群:从事机械工程领域的研究人员和技术人员,尤其是关注行星齿轮系统动态特性的专业人士。 使用场景及目标:适用于需要深入了解行星齿轮非线性行为的研究项目或工程项目,旨在提高对行星齿轮系统复杂动力学特性的认识,从而改进设计和提升可靠性。 其他说明:文中提供了具体的Python代码实例用于演示关键概念,有助于读者更直观地理解理论知识并应用于实际工作中。

    基于MATLAB的教育桥梁,连接机器人领域的理论.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    Python网络爬虫Scrapy框架详解:架构、组件与实战应用

    内容概要:本文全面解析了Python网络爬虫框架Scrapy。首先介绍了Scrapy的基本概念、特点及其与Requests+BeautifulSoup的区别,强调Scrapy更适合生产级别的爬虫项目,特别是需要高并发、分布式、持久化存储的场景。接着详细阐述了Scrapy的架构组件,包括Scrapy Engine、Scheduler、Downloader、Spiders、Item Pipeline等,并解释了各组件的功能及相互关系。随后讲解了Scrapy的安装与项目创建过程,包括环境准备、项目结构、创建和运行第一个Spider。文章还深入探讨了核心组件如Spider、Item、Item Pipeline、Downloader Middleware和Spider Middleware的具体实现与应用场景。此外,介绍了选择器(CSS和XPath)的使用技巧以及数据清洗方法。高级技巧部分涵盖了登录处理、JavaScript渲染页面的解决方案、分布式爬虫的实现以及性能优化策略。最后,通过实战案例展示了如何构建电商网站商品爬虫和新闻网站爬虫,同时提供了Scrapy的部署与监控方法,包括使用ScrapyD、Prometheus+Grafana等工具。; 适合人群:具备一定Python编程基础,对网络爬虫感兴趣的开发者,尤其是希望构建高效、稳定、可扩展爬虫系统的工程师。; 使用场景及目标:①理解Scrapy框架的工作原理及其优势;②掌握Scrapy的安装配置、项目创建及各组件的具体使用;③学习如何处理复杂的网页结构和动态加载的内容;④实现高并发、分布式爬虫并进行有效的性能优化;⑤部署和监控爬虫系统,确保其稳定运行。; 其他说明:本文不仅提供了理论知识,还包含了大量的代码示例和实战经验分享,帮助读者快速上手Scrapy并应用于实际项目中。此外,文中还提及了Scrapy的最佳实践、常见问题解决方法以及未来发展方向,为深入学习和研究提供了丰富的资源。

    PLC在电网备用电源自动投入中的双电源切换组态应用及其实现 全面版

    内容概要:本文介绍了PLC(Programmable Logic Controller)在电网备用电源自动投入系统中的应用,重点讨论了双电源切换组态的具体实现方法。文章首先阐述了双电源切换组态的基本原理,然后详细解析了梯形图接线图原理图、IO分配和组态画面的设计。最后,通过实际应用中的代码分析,展示了如何通过合理的程序设计来确保系统的稳定性和可靠性。 适合人群:从事电力系统自动化领域的工程师和技术人员,尤其是对PLC编程和双电源切换感兴趣的读者。 使用场景及目标:适用于希望深入了解PLC在电力系统中具体应用的技术人员,帮助他们掌握双电源切换组态的设计和实现方法,提升系统的稳定性和可靠性。 其他说明:文中提供了详细的梯形图接线图原理图、IO分配和组态画面展示,有助于读者更好地理解和实践相关技术。

    这种方法处理基于Gompertz分布函数的各向异性扩散滤波。matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    含多规格接触对的电连接器贮存寿命评估.zip

    含多规格接触对的电连接器贮存寿命评估.zip

    基于STM32F407的认字图形游戏

    基于STM32F407的认字图形游戏

    基于S7-200 PLC和组态王的空调控制系统设计与实现

    内容概要:本文详细介绍了基于S7-200 PLC和组态王的空调控制系统的设计与实现。首先阐述了系统架构,涵盖输入模块(接收温度、湿度等环境参数)、输出模块(控制空调设备的操作)和报警模块(检测异常并报警)。其次,展示了详细的接线图和原理图,确保PLC与空调设备之间的连接正确无误。接着,说明了合理的IO分配,使PLC能准确接收和处理信号。最后,介绍了多种组态画面,提供灵活的用户选择,界面设计简洁易用。 适合人群:从事工业自动化领域的工程师和技术人员,特别是对PLC编程和空调控制系统感兴趣的读者。 使用场景及目标:适用于需要设计和实施智能建筑空调控制系统的项目。目标是帮助读者掌握基于S7-200 PLC和组态王的技术细节,从而实现高效的空调控制系统。 其他说明:本文不仅提供了理论知识,还附有详细的图纸和实例,便于读者理解和实践。

    移动电子商务局限.pptx

    移动电子商务局限.pptx

Global site tag (gtag.js) - Google Analytics