Zamiana kierunku wiatru wyrażonego w stopniach, na opisanego ‚ładniej’ -w nieco nawigacyjny sposób, mogłaby być w Pythonie wykonana następująco:
def wind_direction2descr_old(wind_dir): """ po prostu zmieniam wartość kątową na opisową kierunku wiatru :param wind_dir: liczbowa wartość w stopniach :return: unistring 1 lub dwie literki """ wind_dir_descr = u'' if wind_dir is not None and isinstance(wind_dir, (int, long, float)): wind_dir = round(wind_dir % 360, 0) if wind_dir < 22.5 or wind_dir >= (360 - 22.5): wind_dir_descr = u'N' dir_wokolo = 45 if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5): wind_dir_descr = u'NE' dir_wokolo = 90 if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5): wind_dir_descr = u'E' dir_wokolo = 135 if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5): wind_dir_descr = u'SE' dir_wokolo = 180 if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5): wind_dir_descr = u'S' dir_wokolo = 225 if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5): wind_dir_descr = u'SW' dir_wokolo = 270 if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5): wind_dir_descr = u'W' dir_wokolo = 315 if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5): wind_dir_descr = u'NW' return wind_dir_descr
Czyli okrąg-tort – 360 stopni umownie podzielony na 8 równych kawałków, ten na górze N – jak północ, na dole S – południe, po bokach E i W oraz pomiędzy nimi: NE SE SW NW. Osiem kawałków razem, licząc od góry – zgodnie ze wskazówkami zegara da nam: ‚N’ ‚NE’ ‚E’ ‚SE’ ‚S’ ‚SW’ ‚W’ ‚NW’.
Gdy 360 stopni na 8 otrzymamy 45, tak więc dla 0 stopni ( +/- połowa 45 = 22.5 stopnia ), czyli od -22.5 do +22.5 daje N , 45 (+/- 22.5) czyli 22.5 do 67.5 daje NE itd.
Funkcja powyżej w ten właśnie sposób zamienia wartość kąta na jego nawigacyjny opis kierunku.
Wcześniej sprawdzamy, czy wejściowa dana istnieje i jest liczbą – aby uniknąć błędów, jeśli jest poza zakresem 0 – 360 stopni to przywracamy ją do tego zakresu, zaokrąglamy i porównujemy aby określić opis słowny.
Można to też zrobić tak:
def wind_direction2descr(wind_dir): """ po prostu zmieniam wartość kątową na opisową kierunku wiatru :param wind_dir: liczbowa wartość w stopniach :return: unistring 1 lub dwie literki """ wind_dir_descr = u'' if wind_dir is not None and isinstance(wind_dir, (int, long, float)): index = int(((float(wind_dir) + 22.5) % 360) / 45) % 8 wind_dir_descr = [u'N', u'NE', u'E', u'SE', u'S', u'SW', u'W', u'NW', ][index] return wind_dir_descr
…czyli przeliczyć wind_dir na liczbę 0…7, która będzie indexem tablicy oznaczeń kierunków i da nam oczekiwany wynik.
Zaraz, zaraz, jak sprawdzić, czy wynik będzie oczekiwany?
Może przy pomocy testu?
wind_dir = 0 wind_dir_descr = wind_direction2descr(wind_dir) print wind_dir, '-->', wind_dir_descr
…można to sprawdzać na piechotę oczywiście, można też napisać ładny Python’owy test.
Na piechotę:
Test:
# -*- coding: utf-8 -*- # TODO reload utf-8 import sys reload(sys) sys.setdefaultencoding("utf-8") from datetime import datetime import describe_weather try: import unittest2 as unittest except ImportError: import unittest class DescribeWeatherTest(unittest.TestCase): def test_wind_direction2descr(self): wind_dir_test_list = [ [0, u'N'], [360, u'N'], [22.4, u'N'], [337.6, u'N'], [45 - 22.4, u'NE'], [45 + 22.4, u'NE'], [90 - 22.4, u'E'], [90 + 22.4, u'E'], [135 - 22.4, u'SE'], [135 + 22.4, u'SE'], [180 - 22.4, u'S'], [180 + 22.4, u'S'], [225 - 22.4, u'SW'], [225 + 22.4, u'SW'], [270 - 22.4, u'W'], [270 + 22.4, u'W'], [315 - 22.4, u'NW'], [315 + 22.4, u'NW'], [None, u''], ] for test_case in wind_dir_test_list: wind_dir = test_case[0] expected = test_case[1] res = describe_weather.wind_direction2descr(wind_dir) self.assertIsInstance(res, unicode, u'nie jest UNICODE') self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected) + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir)) return None
Od początku:
# -*- coding: utf-8 -*-
– sposób kodowania znaków użyty tutaj, oraz
import sys reload(sys) sys.setdefaultencoding("utf-8")
– sposób kodowania użyty w wytworzonym pliku wynikowym testu,
import describe_weather
– pobieram plik, w którym zawarta jest opisana wyżej funkcja,
import unittest
– pobieram pliki wykonujące tworzone testy, tzw testy jednostkowe lub ‚unit-testy’
Na bazie pobranych unit-testów tworzę klasę:
class DescribeWeatherTest(unittest.TestCase):
a w niej metodę / funkcję wykonującą test na opisanej powyżej mojej funkcji zamiany stopni na opis kierunku wiatru:
def test_wind_direction2descr(self):
nazwa klasy tworzona jest w sposób CamelCasing, czyli wielbłądzi – ma garby (wielkie litery) jako rozdzielenie wyrazów i Test w nazwie (np. ‚OpiszMojTest’ albo ‚OpiszJakisDziwnyNieModTest’…), nazwa metody – małymi literami podkreślnik jako rozdzielenie i na końcu ‚_test’.
Taka konwencja pozwala łatwiej zrozumieć …niezrozumiałe.
Co wewnątrz testu?
Mam do przetestowania funkcje, chcę jej zadawać wartości kąta i sprawdzać, czy otrzymany wynik jest zgodny z oczekiwaniami. Np jeśli zadam 0 (zero), chciałbym uzyskać ‚N’.
Zapiszmy to jako jeden przypadeczek testowy:
[0, u’N’]
(literka u oznacza kodowanie jako unicode, choć tutaj nie ma większego znaczenia…)
jeśli zebrać przypadeczki razem i ułożyć z nich listę otrzymamy:
wind_dir_test_list = [ [0, u'N'], [360, u'N'], [22.4, u'N'], [337.6, u'N'], [45 - 22.4, u'NE'], [45 + 22.4, u'NE'], [90 - 22.4, u'E'], [90 + 22.4, u'E'], [135 - 22.4, u'SE'], [135 + 22.4, u'SE'], [180 - 22.4, u'S'], [180 + 22.4, u'S'], [225 - 22.4, u'SW'], [225 + 22.4, u'SW'], [270 - 22.4, u'W'], [270 + 22.4, u'W'], [315 - 22.4, u'NW'], [315 + 22.4, u'NW'], [None, u''], ]
…gdzie pierwszy element to zadawana wartość, drugi – oczekiwana odpowiedź.
Jak testujemy?
for test_case in wind_dir_test_list: wind_dir = test_case[0] expected = test_case[1] res = describe_weather.wind_direction2descr(wind_dir) self.assertIsInstance(res, unicode, u'nie jest UNICODE') self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected) + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir)) return None
Pętla pobierająca jeden przypadeczek do ‚test_case’, gdzie ‚test_case[0]’ to zadawana wartość, a test_case[1] – oczekiwany wynik,
… wykonanie funkcji (dwa człony nazwy – pierwszy to nazwa zaimportowanego pliku ‚describe_weather’, drugi – ‚wind_direction2descr’ -nazwa funkcji oraz w nawiasach dana wejściowa )
wynik zwracany do zmiennej ‚res’ – jak resource (zasoby) oraz porównywana -to już narzędzia unit-testów:
assertIsInstance – czy jest typu unicode (bo tylko taki akceptujemy zgodnie z założeniami),
oraz:
assertEqual – czy to co uzyskano ‚res’ jest identyczne (equal -> równe) z oczekiwamym ‚expected’.
W obu ‚assert`ach’ sprawdzających, ostatnią wartością jest komunikat tekstowy, który pojawia się gdy wynik jest inny niż oczekiwano.
Na końcu, aby wykonać wszystkie testy:
if __name__ == '__main__': # --- jeden sposób: # unittest.main() # --- drugi sposób (chyba ładniejszy no i dla Jenkinsa): import xmlrunner unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))
…i wykonanie (poprzez run dla PyCharm).
Narzędzie Unittest wygeneruje raport:
Running tests... ---------------------------------------------------------------------- .......................... ---------------------------------------------------------------------- Ran 26 tests in 0.003s OK Generating XML reports...
…także do pliku:
<?xml version="1.0" ?> <testsuite errors="0" failures="0" name="DescribeWeatherTest-20180423140707" tests="26" time="0.003"> <testcase classname="DescribeWeatherTest" name="test_describe_weather_teraz" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_for_effect_id_find_hours_ranges" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_integer_to_effect_description" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie_doby" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_doby" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_prognozy" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_prognoze_doby" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_doby" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_prognozy" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_doby" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_prognoze" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_wilgotnosc_popoludnia" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_doby" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_popoludnia" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_max" time="0.001"/> <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_najblizsza" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_semi_zachmurzenie_percent2_octascale" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_special" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_temperatura_ze_znakiem" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_wind_boufort2descr_pl" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_wind_direction2descr" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_wind_speed_mps_2boufort" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_zachmurzenie_percent2_octascale" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_zamien_tylko_interesujace_efekty_atmosferyczne_na_opis" time="0.000"/> <testcase classname="DescribeWeatherTest" name="test_zapamietaj_okres_od_do" time="0.000"/> <system-out> <![CDATA[]]> </system-out> <system-err> <![CDATA[]]> </system-err> </testsuite>
Opis unittestów można znaleźć tutaj: https://docs.python.org/2/library/unittest.html
Co do wiatru, mamy jeszcze jego prędkość, można ją wyrażać w stopniach Bouforta, opisowo a także zwyczajnie – w metrach na sekundę…
Zamiana prędkości na stopnie Bouforta – w zależności od zakresu – bułka z masłem,
zaś stopnie Bouforta (z pewnością pamiętacie że zapisujemy inaczej niż stopnie Celsjusza – mianowicie bez znaczka stopni, tylko samo B) na opis, to już kiełbaska na tym maśle 🙂 :
def wind_boufort2descr_pl(wind_boufort): """ zamień stopnie bouforta na opis :param wind_boufort: liczba całkowita 0...12 :return: unistring-opis """ wind_descr = u'' l_w_d = [u'cisza', u'powiew', u'słaby wiatr', u'łagodny wiatr', u'umiarkowany wiatr', u'dość silny wiatr', u'silny wiatr', u'bardzo silny wiatr', u'wicher', u'silny sztorm', u'bardzo silny sztorm', u'gwałtowny sztorm', u'huragan'] if wind_boufort is not None and isinstance(wind_boufort, (int, long, float)) and 0 <= wind_boufort <= 12: wind_descr = l_w_d[wind_boufort] return wind_descr
…oczywiście jakiś test do tej bułki.
Radości!
WikS