15-2-第二章 python自定义函数与类 (二)

第五节的一些内容,感觉很抽象,之前没有接触过,还是需要去慢慢理解的。关于if __name__ == "__main__":的介绍,还是要去其他地方多看一下,这本书介绍的不全,也不仔细

4、 从其他脚本调用函数

考虑前面的示例helloworld.py脚本:

def printmessage():
    print("Hello world")

printmessage() 函数可以通过导入 helloworld.py 脚本从另一个脚本调用。例如,脚本 print.py 导入此脚本如下:

import helloworld
helloworld.printmessage()

脚本 print.py 将 helloworld.py 脚本作为模块导入——helloworld。模块名称等于脚本名称减去 .py 扩展名。 函数使用常规语法调用函数,即 module.function

在示例脚本中,helloworld 模块被导入到 print.py 脚本中。 import 语句使 Python 查找名为 helloworld.py 的文件。在 import 语句中不能使用路径,因此识别 Python 查找模块的位置很重要。

首先 Python 查找模块的地方是当前文件夹,即 print.py 脚本所在的文件夹。 当前文件夹可以使用以下代码获取,其中 sys.path() 为系统路径列表

import sys
print(sys.path[0])

当前文件夹也可以通过 os 模块获取,如下:

import os
print(os.getcwd())

接下来,Python 会查看在 Python 本身的安装或后续配置期间设置的所有其他系统路径。路径包含在名为 PYTHONPATH 的环境变量中。请注意,这不是地理处理环境设置,而是 Python 环境的变量。要查看这些路径的完整列表,请使用以下代码:

import sys
for path in sys.path:
    print(path)

sys.path() 函数返回路径列表,迭代使打印输出更易于阅读。在典型情况下,该列表将包括 下图 中所示的路径。
在这里插入图片描述
列表中的第一行是当前脚本的路径,使用 sys.path[0] 返回。其余路径将根据 ArcGIS 的安装方式和使用的环境而有所不同。在路径列表中,您会注意到两种类型(当前文件夹之外)。首先,有与 ArcGIS Pro 核心安装相关的路径,即 \ArcGIS\Pro\bin、\ArcGIS\Pro\Resources\ArcPy 和 \ArcGIS\Pro\Resources\ArcToolbox\Scripts。 这些路径可以执行诸如 import arcpy 之类的操作。其次,有一些路径与正在使用的特定 Python 环境相关联——在本例中为默认环境 arcgispro-py3。 这是是 Python 本身的安装位置,包括任何其他包。如果使用不同的环境,路径列表将具有相同的结构,但 arcgispropy3 将替换为该环境的名称和位置。

如当要导入的模块(即脚本)位于不同的文件夹中——也就是说,不在脚本的当前文件夹或 sys.path 中的任何文件夹中,有两种办法解决,如下所示:

方法1:使用代码添加路径

可以临时添加脚本路径。例如,如果您要调用的脚本位于文件夹 C:\Myscripts 中,则可以在调用函数之前使用以下代码:

import sys
sys.path.append("C:/Myscripts")

sys.path.append() 语句是一种临时解决方案,因此脚本可以在当前会话中调用另一个脚本中的函数。

方法2:使用路径配置文件 path configuration (.pth文件)

您可以通过将路径 configuration file 添加到已经是 sys.path 一部分的文件夹来访问不同文件夹中的模块。通常使用 site-packages 文件夹,例如 C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\Lib\site-packages。路径 configuration file 具有 .pth 扩展名并包含将附加到 sys.path 的路径。 这个文件可以使用基本的文本编辑器创建。作为 ArcGIS Pro 安装的一部分,名为 ArcGISPro.pth 的路径配置文件放置在 Python 的 site-packages 文件夹中。这个文件本身看起来像 下图 中的示例。
在这里插入图片描述
路径配置文件使位于某个特定文件夹中的所有的模块都能被调用。对于默认的.pth文件不要做修改,如果经常使用位于不同文件夹中的脚本,可以自己再新建一个.pth文件。

例如,要导入的模块位于 C:\Myscripts中,可以创建一个.pth文件并将其放置在python站点包文件夹中。arcgis pro的python是无法修改默认环境arcgispro-py3的,因此需要再克隆环境中使用才行。因此,对于许多情况来说,在脚本中添加路径更方便。

注意:第三种方法是直接从操作系统中修改 PYTHONPATH 变量。但是,此选项繁琐且容易出错,因此不推荐使用。

之前用于计算折线的弯曲度指数的示例被重新审视,以说明如何从其他脚本调用自定义函数。这个包含自定义函数的脚本称为rivers.py,如下所示:

import math
def sinuosity(shape):
    channel = shape.length
    deltaX = shape.firstPoint.X - shape.lastPoint.X
    deltaY = shape.firstPoint.Y - shape.lastPoint.Y
    valley = math.sqrt(pow(deltaX, 2) + pow(deltaY, 2))
    return channel / valley

这个脚本必须导入math模块,因为math模块在脚本中被使用。 但是 不需要导入 ArcPy,因为这是在使用搜索光标创建几何对象的其他脚本中完成的。rivers.py 脚本不包含任何硬编码,因此代码无需修改即可重用。

什么是硬编码将可变变量用一个固定数值表示,这种方式在编码的过程中会导致变量很难修改。因此通常采用的方式都是软编码的方式,也即通过一个标记取代变量名称*,而这个标记的值是可以不断变化,变量的名称却不变,例如在我使用的R语言进行地理探测器的过程中,将excel导入过后的数据都命名为data,不论是导入哪一组数据,变量名均为data,不用总是修改变量名,也不用在乎原先的数据文件名是什么,只需要将数据导入即可。*

这个脚本调用自定义函数的脚本名称为:river_calculations.py

import arcpy
#导入上面建立的rivers模块
import rivers
arcpy.env.workspace = "C:/Data/Hydro.gdb"
fc = "streams"
with arcpy.da.SearchCursor(fc, ["OID@", "SHAPE@"]) as cursor:
    for row in cursor:
        oid = row[0]
        shape = row[1]
        si = round(rivers.sinuosity(shape), 3)
        print(f"Stream ID {oid} has a sinuosity index of {si}")

脚本必须导入 ArcPy 才能使用搜索光标创建几何对象。它还必须使用 import rivers 将rivers.py 脚本作为模块导入。导入模块使自定义函数可用于当前脚本。调用自定义函数时,必须包含该模块名——rivers.sinuosity() 而不仅仅是 sinuosity()。
通过在单独的脚本中编写自定义函数,创建了一个可供其他脚本使用的模块,这显着提高了代码的可用性。

5、将代码组织成模块

通过创建自定义函数的脚本,您将脚本用作模块。事实上,所有 Python 脚本 文件 都是模块。 这就是为什么可以通过首先导入脚本(模块),然后使用诸如 module.function 之类的语句来调用该函数。回想一下这个例子:

import random
random_number = random.randrange(1, 100)
print(random_number)

random模块是random.py构成,位于python自动识别的文件夹中:C:\Program
Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\Lib。这个脚本文件包含了几个函数,包括randrange函数。

上一节中的 rivers.py 脚本代表了另一个示例。使用 import rivers 导入模块后,可以使用rivers.sinuosity() 调用自定义函数。尽管示例代码只使用了一个函数,但它可以很容易地扩展为包含与河流有关的其他相关函数。

这个方法可以轻松地在脚本中创建新函数并从另一个脚本调用它们。但是,它也引入了一个复杂的问题:如何区分自己运行脚本和从另一个脚本调用它?所需要的是提供对脚本执行的控制的结构。如果脚本自己运行,则执行该函数。如果模块被导入到另一个脚本中,该函数在被专门调用之前不会被执行。
考虑示例 hello.py 脚本,其中包含一个函数以及一些测试代码以确保该函数有效:

def printmessage():
    print("Hello world")
printmessage()

这个测试类型是合理的,因为当自己运行脚本时,它证明该功能有效。如果没有最后一条打印消息,您将无法看到该功能是否正常工作。但是,当你导入这个模块时使用的功能如下:

import hello

脚本将会立马打印消息:"hello world "
将脚本文件作为模块导入时,不希望测试代码自动运行,但仅在您调用 特定的 函数时才运行。因此希望能够区分单独运行脚本和将其作为模块导入另一个脚本。 这是变量 __name__ 的来源(每边有两个下划线)。对于任何脚本,此变量的值为“__main__”。对于导入的模块,变量设置为模块的名称。在包含函数的脚本中使用 if 语句可以区分脚本和模块,如下所示:

def printmessage():
    print("Hello world")
if __name__ == "__main__":
    printmessage()

在这种情况下,模块的测试(即 printmessage())只有在脚本自己运行时才会运行。如果将模块导入另一个脚本,则在调用该函数之前不会运行任何代码。
这个结构不限于测试。在某些地理处理脚本中,几乎整个脚本都包含一个或多个函数,并且如果脚本确实是自己运行的,那么只有最后几行代码会调用该函数。 结构如下:

import arcpy
<import other modules as needed>
def mycooltool(<arguments>):
    <lines of code>
    ...
if __name__ == "__main__":
    mycooltool(<arguments>)

这个 结构提供了对运行脚本的控制,并可以以两种不同的方式使用相同的脚本——单独运行它或从另一个脚本调用它。
考虑与第 1 章中讨论的 Illuminated Contours 工具相关的脚本。该脚本首先导入几个模块,然后是一个名为IlluminatedContours() 的函数。
在这里插入图片描述
整个脚本实际上是作为函数编写的,而脚本的最后几行调用函数,如图所示。
在这里插入图片描述
if __name__ == ''__main__"之后的代码块:仅当脚本自行运行时才会执行。脚本通常在 ArcGIS Pro 中运行 Illuminated Contours 工具时运行。在 ArcGIS Pro 中运行脚本不会被视为来自另一个脚本的调用,因为该脚本不是作为模块导入的,而是通过在 ArcGIS Pro 中运行工具来调用的。结果,代码块执行。代码使用 arcpy.GetParameterAsText() 函数从工具接收参数,第 3 章对此进行了更详细的说明。最后的代码行是对函数IlluminatedContours() 的调用,以执行手头的任务。

这种结构的好处是该功能可以以其他方式使用。例如,可以编写一个将 IllumContours.py 脚本作为模块导入的脚本,然后可以调用IlluminatedContours() 函数,而无需对脚本进行任何更改或使用工具对话框。许多包含自定义函数的脚本仅提供支持作用,通常不会自行运行。再次考虑计算折线弯曲度指数的rivers.py脚本:

import math
def sinuosity(shape):
    channel = shape.length
    deltaX = shape.firstPoint.X - shape.lastPoint.X
    deltaY = shape.firstPoint.Y - shape.lastPoint.Y
    valley = math.sqrt(pow(deltaX, 2) + pow(deltaY, 2))
    return channel / valley

没有折线作为输入,就没有什么可计算的。运行脚本不会产生错误,但它也不会做任何事情,因为从未调用过该函数。为了清楚地表明脚本需要不属于脚本的特定输入,可以将以下代码添加到脚本中:

if __name__ == "__main__":
    print("This script requires geometry objects as inputs.")

在这种情况下,当脚本自行运行时,会打印消息,并且很明显,如果没有特定的输入,脚本不会执行任何计算。

6、新建类

在前面的部分中,看到了如何创建自己的自定义函数并将代码组织到模块中。 该方法大大提高了代码的可重用性,因为可以编写一段代码并通过从同一个脚本或另一个脚本中调用它来多次使用它。
但是,这些功能和模块有其局限性。主要限制是函数不像变量那样存储信息。每次运行一个函数时,它都是从头开始的。

在某些情况下,函数和变量密切相关。例如,考虑一个具有多个属性的地块,例如土地利用类型、总评估价值和总面积。地块也可能有与之相关的程序,例如如何根据土地使用类型和总评估价值估算财产税。这些函数需要属性的值。 值可以作为变量传递给函数。如果一个函数必须改变变量怎么办?值可以由函数返回。但是,变量的传递和返回可能会变得很麻烦。

更好的解决方案是使用。类提供了一种将密切相关的函数和变量组合在一起的方法,以便它们可以相互交互。一个类还可以处理相同类型的多个对象。例如,每个地块可能具有相同的属性。 将与一种数据类型相关的函数和变量组合在一起的概念是面向对象编程 (OOP) 的一个基本方面。

类是这些相关函数和变量的容器。类可以创建由这些函数和变量定义的对象**。属于类的函数称为方法,属于类的变量称为属性**。下面的示例将更清楚地解释方法和属性的使用。

ArcPy 包含许多类,例如 env 类,它可以访问和设置环境设置,以及 Result 类,它取消地理处理工具返回的 Result 对象的属性和方法。
然而,在 Python 中创建自己的自定义类会带来许多新的可能性。
使用class关键词来定义类:

class <classname>(object):

对括号中的对象的引用意味着正在创建的自定义类基于 Python 中的通用类。由于引用是隐式的,所以可以省略,如下:class <classname>():。虽然前面的代码是正确的,但最好包含对象以确保与 Python 2 的兼容性。

有一个语句末尾的冒号,这意味着类语句后面的代码与任何代码块的缩进相同。这个 是缩进的代码块是 definition 类。一个类通常由一个或多个函数组成,这意味着一个类的一般结构如下:

class <class>(object):
    def <function1>(<arguments>):
         <code>
    def <function2>(<arguments>):
        <code>

一个简单的例子如下所示:

class Person(object):
    def setname(self, name):
        self.name = name
    def greeting(self):
        print("My name is (0).".format(self.name))

class 关键字用于创建一个名为 Person 的 Python 类。 该类包含两个方法定义——它们本质上是函数定义,只是它们写在类语句中,因此 被称为“方法”。 self 参数是指对象本身。您可以随意称呼它,但按照惯例,它几乎总是被称为“self”。

Python 代码样式指南建议对类名使用 CapitalizedWords 或 CapWords 约定,例如 MyClass。相比之下,变量、函数和脚本的推荐样式都是小写。但是,这个样式不是必需的,许多开发人员遵循不同的约定。例如,ArcPy 中的函数名称不遵循样式指南。

一个类可以被认为是一个蓝图。它描述了如何制作某些东西,可以从此蓝图创建许多实例。从一个类创建的每个对象都称为该类的一个实例。创建类的实例有时称为实例化类。

关于如何使用类,如下所示:首先创建一个对象

me = Person()

使用赋值语句创建 Person 类的实例。创建这个实例看起来像调用一个函数,但是正在创建一个 Person 类型的对象。一旦创建了实例,就可以使用类的属性和方法,如下所示:

me.setname("Abraham Lincoln")
me.greeting()

运行代码将打印如下内容:

My name is Abraham Lincoln.

这个示例相对简单,但它说明了一些关键概念。首先,使用 class 关键字创建一个类。其次,类的变量称为属性,它可以像所有变量一样存储值。创建类的实例时,可以传递这些属性的值。第三,类的函数称为方法,可以像所有函数一样执行任务。创建类的实例时,可以将函数作为类的方法调用。一个类可以包含许多属性和方法。

现在回到一块土地的例子。您想要创建一个名为 Parcel 的类,该类具有两个属性(土地使用类型和总评估价值)和一个与该地块相关联的方法(计算税金)。就本例而言,假设房产税计算如下:对于单户住宅,税款 = 0.05 * 价值;对于多户住宅,税收 = 0.04 * 价值;对于所有其他土地用途,税收 = 0.02 * 价值。换句话说,税收计算是基于土地使用类型。创建一个Parcel类的代码如下:

class Parcel(object):
    def __init__(self, landuse, value):
        self.landuse = landuse
        self.value = value
    def assessment(self):
        if self.landuse == "SFR":
            rate = 0.05
        elif self.landuse == "MFR":
            rate = 0.04
        else:
            rate = 0.02
        assessment = self.value * rate
        return assessment

该 类称为 Parcel ,是使用 class 关键字创建的。 类包含两个方法:init() 和assessment()。 init() 方法是为初始化对象保留的特殊方法,这个方法除了 self 之外必须至少有一个参数。在示例中,此方法具有三个参数:self、landuse 和 value。但是,当使用类创建对象时,不使用 第一个参数 (self),因为它表示对象本身,并且是通过使用类隐式提供的。 init() 方法用于通过给属性一个值来初始化(或指定)具有其初始属性的对象,该类现在可用于创建 Parcel 对象,这些对象具有称为土地利用和价值的属性。

接下来看看如何使用这个类。下面的代码创建了一个类的实例:

myparcel = Parcel("SFR", 200000)

这个代码创建单个 Parcel 对象,并为这两个属性分配值。现在可以使用这些属性。例如,以下代码打印两个属性的值:

print(myparcel.landuse)
print(myparcel.value)

结果如下:

SFR
200000

这部分代码仅用于确认 Parcel 对象已创建并且属性具有值。您还可以检查对象的类型,如下所示:

print(type(myparcel))

结果是:

<class '__main__.Parcel'>

这个检查确认对象的类型是 Parcel。 main 部分表示类定义驻留在当前脚本中。

到目前为止,该代码仅用于创建 Parcel 类的实例和 conArm 对象的属性。assessment() 方法是进行实际计算的地方。创建 Parcel 对象后,您可以使用属性和方法,如下所示:

print("Land use: ", myparcel.landuse)
mytax = myparcel.assessment()
print("Tax: ", mytax)

结果是:

Land use: SFR
Tax: 10000.0

assessment() 方法用于计算土地用途为单户住宅 (SFR) 且价值为 200,000 美元的这一宗地的税款。在示例中,对象属性的值被硬编码到脚本中,但在实际场景中,这些值将驻留在数据库中。您可以为数据库中的每个地块运行财产税计算,为每个地块创建一个新实例来执行计算。

在许多情况下,可能希望在多个脚本中使用该类。这可以通过将类放在一个模块中来完成——也就是说,使用类的 定义 创建一个单独的脚本,然后可以从另一个脚本中调用它。 这个方法类似于为函数创建单独的脚本,可以从其他脚本调用,如本章前面所述。

在此示例中,Parcel 类的现有代码被复制到名为 parcelclass.py 的单独脚本中,其编码如下:

class Parcel(object):
    def __init__(self, landuse, value):
        self.landuse = landuse
        self.value = value
    def assessment(self):
        if self.landuse == "SFR":
           rate = 0.05
        elif self.landuse == "MFR":
           rate = 0.04
       else:
           rate = 0.02
        assessment = self.value * rate
        return assessment

在此示例中,使用该类的脚本称为 parceltax.py,其编码如下:

import parcelclass
myparcel = parcelclass.Parcel("SFR", 200000)
print("Land use: ", myparcel.landuse)
mytax = myparcel.assessment()
print(mytax)

一些笔记是有序的。 名为 parcelclass 的模块被导入以使使用 Parcel 类成为可能。这个方法仅在 parcelclass.py 脚本与 parceltax.py 脚本位于同一文件夹中或 Python 查找模块的知名位置之一时才有效。 Parcel 对象是使用 . 结构创建的,即 parcelclass.Parcel。这个示例仅创建一个 Parcel 对象,但可以对数据库表中的任意数量的 Parcel 重复此过程。

在单独的脚本中创建一个类可以更好地组织代码,并可以在许多不同的脚本中重用该类。
类也可用于处理几何对象。回想一下前面部分中名为 sinuosity() 的自定义函数,它使用几何对象。您可以使用自定义类来创建对象,而不是使用自定义函数,并且该函数成为该类的方法。

创建 River 类的代码如下:

import math
class River(object):
    def __init__(self, shape):
        self.shape = shape
    def sinuosity(self):
        channel = self.shape.length
        deltaX = self.shape.firstPoint.X -
    self.shape.lastPoint.X
        deltaY = self.shape.firstPoint.Y -
    self.shape.lastPoint.Y
        valley = math.sqrt(pow(deltaX, 2) + pow(deltaY, 2))
        return channel / valley

River 类的唯一属性是几何对象,尽管可以使用其他属性。sinuosity() 方法与前面部分中使用的自定义函数 sinuosity() 相同,但现在引用的是 River 对象而不是几何对象。 该方法的参数是对象(即 self),而自定义函数的参数是传递给函数的几何对象(即 shape),该方法的代码块使用 self.shape的形状。
如图所示,通常在代码块周围添加空行以提高可读性。
在这里插入图片描述
使用类计算弯曲度指数的代码如下:

import arcpy
import rivers
arcpy.env.workspace = "C:/Data/Hydro.gdb"
fc = "streams"
with arcpy.da.SearchCursor(fc, ["OID@", "SHAPE@"]) as cursor:
    for row in cursor:
        oid = row[0]
        shape = row[1]
        segment = rivers.River(row[1])
        si = round(segment.sinuosity(), 3)
        print(f"Stream ID {oid} has a sinuosity index of {si}")

因为该类是在单独的脚本(即rivers.py)中创建的,所以该脚本必须作为模块导入。在 for 循环中,使用rivers.River() 为输入要素类中的每一行创建一个新的River 对象,其中rivers 是模块,River 是类。该对象被分配给一个变量(即,segment),然后可以使用 segment.sinusosity() 调用计算 sinusosity index 的函数作为该对象的方法。

River 类示例仅使用一个方法,在这种情况下,该类不提供与自定义函数相关的附加功能。

但是,可以使用其他属性和方法来扩展该类,这些属性和方法可以有效地将多个函数以有意义的方式组合在一起。例如,您可以通过使用高程数据或与渠道属性或row的方向或其他水文相关信息相关的方法来获得与河段坡度相关的方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值