Archiwa blogu

Python – funkcje operujące na kolekcjach

2017-04-14

Dziś pokażę, w jaki sposób poprawić czytelność i prostotę kodu poprzez wykorzystanie odpowiednich funkcji Pythona. Naszym zadaniem będzie stworzenie klasy Vector, przyjmującej w konstruktorze listę liczb całkowitych oraz posiadającej kilka metod. Oto wymagania dla tej klasy:

a = Vector([1,2,3])
b = Vector([4,5,6])
a.add(b) #zwraca obiekt: Vector([5,7,9])
a.subtract(b) #zwraca obiekt: Vector([-3,-3,-3])
a.dot(b) #zwraca wynik: 1*4+2*5+3*6 = 32
a.norm() #zwraca wynik: sqrt(1^2+2^2+3^2) = 3.74
a.equals(b) #zwraca wynik: False
a.equals(Vector([1,2,3])) #zwraca wynik: True
str(a) #zwraca łańcuch: "(1,2,3)"
#w przypadku metod add, subtract i dot, gdy a i b mają inną liczbę elementów powinien zostać zgłoszony wyjątek

Skoro znamy wymagania, możemy przejść do implementacji klasy Vector:

from math import sqrt

class Vector():
    def __init__(self, items):
        self.items = items

    def add(self, other):
        if len(self.items) != len(other.items):
            raise Exception('Error')
        a = []
        for i in range(len(self.items)):
            a.append(self.items[i] + other.items[i])
        return Vector(a)

    def subtract(self, other):
        if len(self.items) != len(other.items):
            raise Exception('Error')
        a = []
        for i in range(len(self.items)):
            a.append(self.items[i] - other.items[i])
        return Vector(a)

    def dot(self, other):
        if len(self.items) != len(other.items):
            raise Exception('Error')
        a = 0
        for i in range(len(self.items)):
            a = a + (self.items[i] * other.items[i])
        return a

    def norm(self):
        a = 0
        for i in self.items:
            a = a + (i*i)
        return sqrt(a)

    def equals(self, other):
        if len(self.items) != len(other.items):
            return False
        for i in range(len(self.items)):
            if self.items[i] != other.items[i]:
                return False
        return True

    def __str__(self):
        s = "("
        for i in self.items:
            if len(s) > 1:
                s = s + ","
            s = s + str(i)
        s = s + ")"
        return s

Powyższy kod działa i realizuje wszystkie wymagania. Czy można go jednak uprościć? Jak widać we wszystkich metodach użyte zostały pętle for oraz warunki if. Czy możliwe jest pozbycie się ich wszystkich? Wykorzystując odpowiednie funkcje Pythona nie stanowi to problemu. Oto klasa Vector w nowej wersji:

from operator import add, sub, mul, eq
from functools import reduce
from math import sqrt

class Vector():
    def __init__(self, items):
        self.items = items

    def add(self, other):
        assert(len(self.items) == len(other.items))
        return Vector(map(add, self.items, other.items))

    def subtract(self, other):
        assert(len(self.items) == len(other.items))
        return Vector(map(sub, self.items, other.items))

    def dot(self, other):
        assert(len(self.items) == len(other.items))
        func = (lambda x, y: x + y)
        return reduce(func, map(mul, self.items, other.items))

    def norm(self):
        return sqrt(sum(i * i for i in self.items))

    def equals(self, other):
        eqLen = (len(self.items) == len(other.items))
        return eqLen and all(map(eq, self.items, other.items))

    def __str__(self):
        return "({0})".format(",".join(map(str, self.items)))

Zgłaszanie wyjątków zostało zastąpione funkcją assert, która robi to automatycznie gdy przekazany warunek nie zostanie spełniony. Z kolei wszystkie pętle stały się zbędne po wykorzystaniu funkcji map, reduce, sum oraz all. Oto opis tych i kilku podobnych funkcji Pythona:

  • add(a, b) – to samo co a + b
  • sub(a, b) – to samo co a – b
  • mul(a, b) – to samo co a * b
  • eq(a, b) – to samo co a == b
  • sum(iterable) – zwraca sumę przekazanych elementów
  • max(iterable) – zwraca największy z przekazanych elementów
  • min(iterable) – zwraca najmniejszy z przekazanych elementów
  • any(iterable) – zwraca True jeżeli dla któregokolwiek z przekazanych elementów bool(x) == True
  • all(iterable) – zwraca True jeżeli dla wszystkich przekazanych elementów bool(x) == True
  • map(function, *iterables) – zwraca iterator, którego elementy są wynikami przekazanej funkcji, która z kolei jako argumenty przyjmuje kolejne elementy przekazanych kolekcji. Liczba wynikowych elementów odpowiada liczbie elementów najkrótszej z przekazanych kolekcji. Np. list(map(add, [1,2,3], [4,5,6])) = [5,7,9] lub list(map(eq, [1,2,3], [1,5,3])) = [True, False, True]
  • reduce(function, sequence[, initial]) – redukuje przekazaną kolekcję do pojedynczej wartości poprzez wykonanie przekazanej dwuargumentowej funkcji na każdym elemencie kolekcji. W każdym kroku do funkcji przekazywany jest bieżący wynik operacji (poprzedni wynik funkcji) wraz z kolejnym elementem kolekcji. Jeżeli nie zostanie podana opcjonalna wartość początkowa, będzie nią pierwszy element kolekcji.
  • filter(function, iterable) – zwraca iterator zawierający te elementy przekazanej kolekcji, dla których przekazana funkcja zwróciła wartość True. Jeżeli nie zostanie przekazana funkcja (None), zwrócone zostaną elementy, dla których bool(x) == True

Wykorzystując powyższe funkcje – dodatkowo łącząc je z listami składanymi – możemy w prosty i przejrzysty sposób wykonać wiele operacji, które w innych językach programowania wymagałyby zastosowania skomplikowanych i mało czytelnych pętli. Właśnie dlatego tak lubię Pythona. 🙂

Reklamy

Python – odczyt oraz modyfikacja dokumentów XML

2017-03-30

W dzisiejszym wpisie pokażę, w jaki sposób odczytywać oraz modyfikować dokumenty XML w języku Python.

Poniżej znajduje się przykładowy dokument XML zawierający informacje o książkach:

<?xml version='1.0' encoding='UTF-8'?>
<books>
  <book>
    <title>Krótka historia czasu</title>
    <author>Stephen Hawking</author>
    <publisher>Zysk i S-ka</publisher>
    <publication_date>2015</publication_date>
    <chapters>
      <chapter number="1">
        <title>Nasz obraz Wszechświata</title>
        <page>13</page>
      </chapter>
      <chapter number="2">
        <title>Czas i przestrzeń</title>
        <page>25</page>
      </chapter>
    </chapters>
  </book>
  <book>
    <title>Filozofia kosmologii</title>
    <author>Michał Heller</author>
    <publisher>Copernicus Center Press</publisher>
    <publication_date>2013</publication_date>
    <chapters>
      <chapter number="1">
        <title>Kosmologia przed Einsteinem</title>
        <page>13</page>
      </chapter>
      <chapter number="2">
        <title>Kosmologia 1917-1965</title>
        <page>39</page>
      </chapter>
    </chapters>
  </book>
</books>

A oto klasy, które reprezentują obiekty książki i rozdziału z powyższego pliku:

class Book():
    def __init__(self, title):
        self.title = title
        self.author = None
        self.publisher = None
        self.publicationDate = None
        self.chapters = list()

class Chapter():
    def __init__(self, book, title, page):
        self.book = book
        self.title = title
        self.page = page
        self.number = None

Mając dokument XML oraz klasy odpowiednich obiektów, możemy przejść do operacji odczytu oraz modyfikacji dokumentu.

Odczyt danych z pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def ReadBooks():
    file = r"D:\App\!Python\Test\books.xml"
    books = list()
    doc = parse(file)
    root = doc.getroot()
    for bookElement in root.iterfind("book"):
        title = bookElement.findtext("title")
        book = Book(title)
        book.author = bookElement.findtext("author")
        book.publisher = bookElement.findtext("publisher")
        book.publicationDate = bookElement.findtext("publication_date")
        for chapterElement in bookElement.iterfind("chapters/chapter"):
            title = chapterElement.findtext("title")
            page = chapterElement.findtext("page")
            chapter = Chapter(book, title, page)
            chapter.number = chapterElement.get("number")
            book.chapters.append(chapter)
        book.chapters.sort(key = lambda c : int(c.page))
        books.append(book)
    return books

Dodanie elementu do pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def AddBook(book):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()                    
    bookElement = Element("book")
    SubElement(bookElement, "title").text = book.title
    SubElement(bookElement, "author").text = book.author
    SubElement(bookElement, "publisher").text = book.publisher
    SubElement(bookElement, "publication_date").text = book.publicationDate
    chaptersElement = SubElement(bookElement, "chapters")
    for chapter in book.chapters:
        chapterElement = Element("chapter")
        chapterElement.set("number", chapter.number)
        SubElement(chapterElement, "title").text = chapter.title
        SubElement(chapterElement, "page").text = chapter.page
        chaptersElement.append(chapterElement)
    root.append(bookElement)
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

Modyfikacja elementu w pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def EditBook(oldBook, newBook):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()
    bookElement = [b for b in root.iterfind("book") if b.findtext("title") == oldBook.title][0]
    bookElement.find("title").text = newBook.title
    bookElement.find("author").text = newBook.author
    bookElement.find("publisher").text = newBook.publisher
    bookElement.find("publication_date").text = newBook.publicationDate
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

def EditChapter(oldChapter, newChapter):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()
    bookElement = [b for b in root.iterfind("book") if b.findtext("title") == oldChapter.book.title][0]
    chapterElement = [c for c in bookElement.iterfind("chapters/chapter") if c.findtext("title") == oldChapter.title][0]
    chapterElement.set("number", newChapter.number)
    chapterElement.find("title").text = newChapter.title
    chapterElement.find("page").text = newChapter.page
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

Usunięcie elementu z pliku XML:

from xml.etree.ElementTree import parse, Element, SubElement

def DeleteBook(book):
    file = r"D:\App\!Python\Test\books.xml"
    doc = parse(file)
    root = doc.getroot()
    bookElement = [b for b in root.iterfind("book") if b.findtext("title") == book.title][0]
    root.remove(bookElement)
    doc.write(file, encoding = "UTF-8", xml_declaration = True)

Astronomy Picture of the Day jako tapeta

2017-02-10

Astronomy Picture of the Day (APOD) to strona udostępniona przez NASA, na której codziennie pojawia się nowe zdjęcie astronomiczne. Oprócz aktualnego zdjęcia, mamy również dostęp do fotografii archiwalnych z każdego dnia od 1995 roku. Adres strony to https://apod.nasa.gov. Bardzo często zdjęcia z tej strony ustawiałem jako tło pulpitu w systemie, wymagało to jednak wejścia na stronę, ręcznego pobrania obrazu i ustawienia go jako tapeta. Jakiś czas temu, na początku mojej nauki Pythona, postanowiłem napisać skrypt robiący to automatycznie, dodatkowo z możliwością pobierania zdjęć archiwalnych:

import datetime
import urllib.request
import ctypes

date = input("Fotografia z dnia (format RRMMDD, domyślnie dzisiejsza data): ")

if not date:
    date = datetime.datetime.now().strftime("%y%m%d")

print("Data: " + date)

apod = "http://apod.nasa.gov/apod/"
url = "{0}ap{1}.html".format(apod, date)

print("Url: " + url)

with urllib.request.urlopen(url) as response:
    for line in response:
        decodedLine = line.decode('utf-8')
        startIndex = decodedLine.find('<a href="image/')
        if startIndex != -1:
            urlLine = decodedLine.strip()
            break

endIndex = urlLine.find('">')
imgUrl = apod + urlLine[startIndex + 9 : endIndex]
imgExt = imgUrl[-3 :]

print("Link do obrazu: " + urlLine)
print("Url obrazu: " + imgUrl)

filePath = r"D:\Apod\apod." + imgExt
urllib.request.urlretrieve(imgUrl, filePath)

print("Plik zapisany w: " + filePath)

SPI_SETDESKWALLPAPER = 20
SPIF_UPDATEINIFILE = 1
SPIF_SENDWININICHANGE = 2

ctypes.windll.user32.SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, filePath, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE)

input("Gotowe...")

Na początku skryptu wczytywana jest data dla pobieranego zdjęcia (domyślnie jest to dzisiejszy dzień). Następnie budowany jest adres odpowiedniej strony i pobierana jej zawartość. Po odszukaniu adresu obrazu, jest on pobierany i zapisywany w określonej lokalizacji na dysku. Na końcu pobrany obraz ustawiany jest jako tapeta.

Python – wstęp

2016-10-17

Tym wpisem chciałbym rozpocząć cykl tematów poświęconych językowi Python. Jednak zanim przejdę do konkretnych przykładów kodu, dziś pokażę jak rozpocząć pracę z tym językiem.

Python jest językiem interpretowanym, co oznacza konieczność instalacji interpretera. W tym celu wchodzimy na stronę www.python.org, przechodzimy do działu Download, pobieramy najnowszą wersję dla systemu Windows i instalujemy. Domyślnie Python instalowany jest w katalogu C:\Program Files (x86)\Python35-32 (w zależności od wersji). Warto dodać ten folder do zmiennej środowiskowej Path. Po zakończeniu instalacji możemy sprawdzić czy wszystko działa. Z poziomu wiersza poleceń uruchamiamy interpreter (python.exe) i naszym oczom powinna pokazać się konsola Python. Tutaj możemy już pisać kod:

pythonconsole

Jeżeli chcemy uruchomić kod umieszczony w pliku, to wprowadzamy polecenie „python.exe my_python_code.py” lub po prostu ustawiamy w systemie uruchamianie plików *.py przez aplikację python.exe.

Wiemy już w jaki sposób uruchamiać kod Pythona, teraz opiszę kilka darmowych środowisk ułatwiających jego pisanie. Co prawda do pisania kodu wystarczy notatnik (polecam Notepad2), jednak sposób ten sprawdzi się jedynie przy niewielkich skryptach. W przypadku większych projektów warto użyć bardziej zaawansowanego środowiska.

 

IDLE

idle

IDLE to środowisko programistyczne dołączone do standardowej instalacji Pythona. Co ciekawe, napisane jest całkowicie w Pythonie. Osobiście nie przepadam za nim, gdyż na tle innych narzędzi prezentuje się po prostu archaicznie (zarówno pod względem wyglądu jak i funkcjonalności).

 

Visual Studio Code

pythonvscode

Visual Studio Code to lekki, szybki i całkowicie darmowy edytor programistyczny o sporych możliwościach. Dzięki mechanizmowi rozszerzeń dostępne są wtyczki praktycznie dla wszystkich popularnych języków. Aby wykorzystać go do pisania w Pythonie wystarczy kilka kroków:

1) Instalujemy rozszerzenie dla Pythona: Python – Don Jayamanne

2) Przechodzimy do File -> Preferences -> User Settings i wpisujemy ścieżkę do interpretera Python:

// Place your settings in this file to overwrite the default settings
{
    "python.pythonPath": "C:/Program Files (x86)/Python35-32/python.exe"
}

3) W celu uruchomienia tworzonej aplikacji używamy skrótu Ctrl+Shift+B, następnie (tylko przy pierwszym uruchomieniu) klikamy Configure Task Runner, wybieramy Others i wprowadzamy ustawienia:

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "0.1.0",
    "command": "python",
    "isShellCommand": true,
    "args": ["${file}"],
    "showOutput": "always"
}

Po tych krokach mamy w pełni skonfigurowane środowisko z takimi funkcjami jak kolorowanie składni, IntelliSense, refactoring czy debugowanie.

 

Visual Studio Community

pythonvs

Darmowa wersja środowiska Visual Studio w pełni wspiera tworzenie aplikacji w Pythonie. Musimy jedynie podczas instalacji zaznaczyć opcję Python Tools for Visual Studio. Dzięki temu otrzymujemy kolorowanie składni, IntelliSense, refactoring, debugowanie, interaktywne okno czy zarządzanie zainstalowanymi pakietami.