Python методы из CSV - PullRequest
       33

Python методы из CSV

0 голосов
/ 18 марта 2019

Я работаю над заданием, в котором я создаю «экземпляры» городов, используя строки в .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)

Когда я ввожу в оболочку экземпляры [0] .printDistance (экземпляры 1 ), я получаю сообщение об ошибке:

 `NameError: name 'instances' is not defined`

Это проблема с отступами? Должен ли я вызывать функцию из кода, а не из оболочки?

Example of how data is stored in csv

Ответы [ 2 ]

0 голосов
/ 18 марта 2019

Вложенные функции не должны содержать self в качестве параметра, поскольку они не являются функциями-членами.Класс не может передать им переменные экземпляра.Вы фактически передаете одно и то же «я» от родительской функции к дочерней.

Также вы не должны вкладывать конструктор, это только для инициации.Действительно, создайте отдельный метод.

И попробуйте создать переменную экземпляра внутри конструктора, и это то, что init для!

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

Также создайте отдельную функцию для создания экземпляров

@classmethod
def getInstance(cls,d1,d2):
    return cls(d1,d2)
0 голосов
/ 18 марта 2019

Это не столько проблема отступа, сколько проблема общей структуры кода.Вы много вложили:

  • Вся фактическая работа на невероятно длинной строке (с ошибками)
  • Внутри функции (правильно) printDistance
  • Внутри конструктора __init__
  • Внутри определения класса (правильно) City
  • Внутри блока try
  • Внутри блока with

Я думаю, это то, что вы пытаетесь сделать:

  • создать класс City, который может выводить расстояние до других городов
  • создать список этих объектов City из .csv, который каким-то образом имеет как расстояния, так и население (вам, вероятно, следует привести пример данных)
  • сделать это отказоустойчивым и чистым способом (следовательно, tryи with)

Причина, по которой ваш instances не работает, заключается в том, что, в отличие от вашего взгляда, он, вероятно, создается неправильно или, по крайней мере, не в правильном контексте.И, конечно, он не будет доступен вам в CLI из-за всей вложенности.

В вашем коде есть несколько явных ошибок:

  • Что такое (self.lat, self.lon, othercity.lat, othercity.lon)в конце последней строки?
  • Почему вы открываете файл для чтения дважды?Вы даже не используете первые reader
  • . Вы прямо назначаете заголовки столбцов из .csv в качестве атрибутов объекта, но неправильно используете их (lat вместо latitude и lonвместо 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, который вызывается в каждом экземпляре City в cities (это то, к чему я переименовал 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 внутри tryblock) и правильный конструктор, который назначает то, что ему нужно, с правильным типом, в то время как читатель решает, куда и куда идти.

Наконец, в качестве бонуса: никто не должен писать вычисления расстояний, подобные этой.Существует множество библиотек, которые справляются с этой задачей гораздо лучше, например 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) для выполнения вычислений, и вероятность математических ошибок значительно сокращается.Если вы должны использовать простую сферу, в библиотеке также есть функции для этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...