【问题标题】:Python methods from csv来自 csv 的 Python 方法
【发布时间】:2019-08-08 09:03:53
【问题描述】:

我正在执行一项任务,我使用 .csv 中的行创建城市“实例”,然后在方法中使用这些实例来计算距离和人口变化。创建实例工作正常(使用下面的步骤 1-4),直到我尝试调用 printDistance:

##Step 1. Open and read CityPop.csv
with open('CityPop.csv', 'r', newline='') as f:
try:
    reader = csv.DictReader(f)
    ##Step 2. Create "City" class
    class City:
        ##Step 3. Use _init method to assign attribute values
        def __init__(self, row, header):
            self.__dict__ = dict(zip(header, row))

            ##Step 4. Create "Cities" list
            data = list(csv.reader(open('CityPop.csv')))
            instances = [City(i, data[0]) for i in data[1:]]

            ##Step 5. Create printDistance method within "Cities" class  
            def printDistance(self, othercity, instances):
                dist=math.acos((math.sin(math.radians(self.lat)))*(math.sin(math.radians(othercity.lat)))+(math.cos(math.radians(self.lat)))*(math.cos(math.radians(othercity.lat)))*(math.cos(math.radians(self.lon-othercity.lon)))) * 6300 (self.lat, self.lon, othercity.lat, othercity.lon)

当我在 shell 中输入 instances[0].printDistance(instances1) 时,出现错误:

 `NameError: name 'instances' is not defined`

这是缩进问题吗?我应该从代码中调用函数,而不是 shell?

【问题讨论】:

  • def printDistance 真的嵌套在def __init__ 中吗?
  • 类和函数定义一般不应在withtry 内。
  • 不应该将try 缩进到with 里面吗?
  • 你的缩进太乱了,无法分辨出你真正想要做什么。按照您的编写方式,instances__init__ 方法中的一个局部变量。但这没有任何意义,因为它调用的是City(),会导致无限递归。
  • printDistance() 接受 2 个参数,你只用一个参数调用它。但它从不使用instances 参数,那么为什么会有那个参数呢?

标签: python csv methods instance


【解决方案1】:

这与其说是缩进问题,倒不如说是一般的代码结构问题。你嵌套了很多:

  • 所有实际工作都在一条令人难以置信的长线上(有错误)
  • 函数内部(正确)printDistance
  • 在构造函数内部__init__
  • 类定义内部(正确)City
  • try 块内部
  • with 块内部

我认为这就是你想要做的:

  • 创建一个City类,可以打印自己到其他城市的距离
  • 从一个包含距离和人口的 .csv 文件生成这些城市对象的列表(您可能应该提供一个数据示例)
  • 以容错和干净的方式执行此操作(因此有 trywith

您的instances 不起作用的原因是,与您想象的不同,它可能没有正确创建,或者至少没有在正确的上下文中创建。由于所有的嵌套,您肯定无法在 CLI 上使用它。

您的代码中有许多明显的错误:

  • 最后一行末尾的(self.lat, self.lon, othercity.lat, othercity.lon) 是什么?
  • 为什么要打开文件进行两次读取?你甚至没有使用第一个reader
  • 您直截了当地将 .csv 中的列标题指定为对象属性,但拼写错误(lat 而不是 latitudelon 而不是 longitude

看起来有点像在不同地方找到的大量代码被粘贴到一个块中 - 这是清理后的样子:

import csv
import math


class City:
    def print_distance(self, other_city):
        print(f'{self.city} to {other_city.city}')
        # what a mess...
        print(math.acos(
            (math.sin(math.radians(float(self.latitude)))) * (math.sin(math.radians(float(other_city.latitude)))) + (
                math.cos(math.radians(float(self.latitude)))) * (math.cos(math.radians(float(other_city.latitude)))) * (
                math.cos(math.radians(float(self.longitude) - float(other_city.longitude))))) * 6300)

    def __init__(self, values, attribute_names):
        # this is *nasty* - much better to add the attributes explicitly, but left as original
        # also, note that you're reading strings and floats here, but they are all stored as str
        self.__dict__ = dict(zip(attribute_names, values))


with open('CityPop.csv', 'r', newline='') as f:
    try:
        reader = csv.reader(f)
        header = next(reader)
        cities = [City(row, header) for row in reader]

        for city_1 in cities:
            for city_2 in cities:
                city_1.print_distance(city_2)
    except Exception as e:
        print(f'Apparently were doing something with this error: {e}')

注意print_distance 现在是City 的一个方法,它在cities 中的每个City 实例上调用(我将instances 重命名为)。

现在,如果你真的在尝试,这更有意义:

import csv
import math


class City:
    def print_distance(self, other_city):
        print(f'{self.name} to {other_city.name}')
        # not a lot better, but some at least
        print(
            math.acos(
                math.sin(math.radians(self.lat)) *
                math.sin(math.radians(other_city.lat))
                +
                math.cos(math.radians(self.lat)) *
                math.cos(math.radians(other_city.lat)) *
                math.cos(math.radians(self.lon - other_city.lon))
            ) * 6300
        )

    def __init__(self, lat, lon, name):
        self.lat = float(lat)
        self.lon = float(lon)
        self.name = str(name)


try:
    with open('CityPop.csv', 'r', newline='') as f:
        reader = csv.reader(f)
        header = next(reader)
        cities = [City(lat=row[1], lon=row[2], name=row[4]) for row in reader]

        for city_1 in cities:
            for city_2 in cities:
                city_1.print_distance(city_2)
except FileNotFoundError:
    print(f'Could not find the input file.')

注意清理后的计算,捕获可能发生的错误(with 位于 try 块内)和正确的构造函数,它使用正确的类型分配所需的内容,而读者决定哪些字段去哪里。

最后,作为奖励:没有人应该编写这样的距离计算。有很多库在这方面做得更好,比如 GeoPy。您需要做的就是pip install geopy 获取它,然后您就可以使用它了:

import csv
import geopy.distance


class City:
    def calc_distance(self, other_city):
        return geopy.distance.geodesic(
            (self.lat, self.lon), 
            (other_city.lat, other_city.lon)
        ).km

    def __init__(self, lat, lon, name):
        self.lat = float(lat)
        self.lon = float(lon)
        self.name = str(name)


try:
    with open('CityPop.csv', 'r', newline='') as f:
        reader = csv.reader(f)
        header = next(reader)
        cities = [City(lat=row[1], lon=row[2], name=row[4]) for row in reader]

        for city_1 in cities:
            for city_2 in cities:
                print(city_1.calc_distance(city_2))
except FileNotFoundError:
    print(f'Could not find the input file.')

请注意,我也将print 移出方法,因为在对象中计算并在其外部打印更有意义。所有这一切的好处是计算现在使用适当的测地线 (WGS-84) 进行计算,并且数学错误的几率大大降低。如果您必须使用简单的球体,那么库也有相应的功能。

【讨论】:

  • 是的,这就是我想要的。我在原始帖子中添加了一个数据示例。我也会尝试重新处理这些实例。谢谢!
【解决方案2】:

嵌套函数不能包含 self 作为参数,因为它们不是成员函数。类不能将实例变量传递给它们。您实际上是在将相同的 self 从父函数传递给子函数。

您也不能嵌套构造函数,这仅用于启动目的。确实创建一个单独的方法。

并尝试在构造函数中创建实例变量,这就是 init 的作用!

self.instances = [self.getInstance(i, data[0]) for i in data[1:]]

还为实例化创建单独的函数

@classmethod
def getInstance(cls,d1,d2):
    return cls(d1,d2)

【讨论】:

  • 如果可能,请使用 pandas 并将数据读入数据框并应用计算,这样会更快,并且需要更少的代码行。此外,您的 csv 分隔符将是 \n 而不是''
猜你喜欢
  • 1970-01-01
  • 2012-06-24
  • 2021-07-02
  • 1970-01-01
  • 2016-09-12
  • 1970-01-01
  • 2018-09-02
  • 2020-05-21
  • 2018-06-12
相关资源
最近更新 更多