Ciągi znaków w Pythonie

Python ma wbudowaną klasę ciągu znaków o nazwie „str” z wieloma przydatnymi funkcjami (jest starszy moduł o nazwie „string”, którego nie należy używać). Literały tekstowe można umieszczać w cudzysłowach podwójnych lub pojedynczych, chociaż częściej używa się cudzysłowów pojedynczych. Znaki ukośnika odwrotnego działają jak zwykle w przypadku literałów ujętych w cudzysłowy pojedynczy i podwójny, np. \n \ \". Literał ciągu ujęty w cudzysłowy może zawierać pojedyncze cudzysłowy bez żadnych cudzysłowów (np. „To mnie nie pomogło”), a ciąg znaków w cudzysłowie może zawierać cudzysłowy podwójne. Literał ciągu znaków może obejmować wiele wierszy, ale znak \ na końcu każdego wiersza musi zawierać ukośnik lewy, który powoduje zmianę znaczenia w nowym wierszu. Literały tekstowe w cudzysłowach potrójnych, """ lub "'" mogą obejmować wiele wierszy tekstu.

Ciągi Pythona są „niezmienne”, co oznacza, że nie można ich zmienić po utworzeniu (ciąg znaków w języku Java również używa tego stałego stylu). Ciągów nie można zmieniać, dlatego budujemy *nowe* ciągi znaków, które reprezentują obliczone wartości. Na przykład wyrażenie „hello” + „there” pobiera 2 ciągi znaków „hello” i „there” i tworzy nowy ciąg „hellothere”.

Dostęp do znaków w ciągu znaków można uzyskać za pomocą standardowej składni [ ]. Podobnie jak w językach Java i C++, Python stosuje indeksowanie oparte na zera, więc jeśli s to „hello” s[1] to „e”. Jeśli indeks jest poza zakresem dla ciągu, Python zgłosi błąd. Styl Pythona (w przeciwieństwie do Perl) jest zatrzymywany, jeśli nie może powiedzieć, co ma robić, zamiast tylko utworzyć wartość domyślną. Praktyczna składnia „wycinka” (poniżej) również pozwala wyodrębnić dowolny podłańcuch z ciągu znaków. Funkcja len(string) zwraca długość ciągu. Składnia [ ] i funkcja len() działają z dowolnym typem sekwencji – ciągami znaków, listami itd. Python stara się, aby jego operacje działały spójnie w przypadku różnych typów. Dla nowicjuszy w Pythonie: nie używaj „len” jako nazwy zmiennej, aby uniknąć zablokowania funkcji len(). Operator „+” może połączyć dwa ciągi znaków. Zwróć uwagę w kodzie poniżej, że zmienne nie są wstępnie zadeklarowane – po prostu przypisz do nich zmienne i gotowe.

  s = 'hi'
  print(s[1])          ## i
  print(len(s))        ## 2
  print(s + ' there')  ## hi there

W przeciwieństwie do Javy znak „+” nie powoduje automatycznej konwersji liczb ani innych typów na ciąg znaków. Funkcja str() konwertuje wartości na postać ciągu znaków, dzięki czemu można je łączyć z innymi ciągami znaków.

  pi = 3.14
  ##text = 'The value of pi is ' + pi      ## NO, does not work
  text = 'The value of pi is '  + str(pi)  ## yes

W przypadku liczb standardowe operatory +, /, * działają w zwykły sposób. Nie ma operatora ++, ale +=, -= itd. działają. Jeśli chcesz podzielić liczbę całkowitą, użyj 2 ukośników, np. 6 // 5 to 1

Funkcja „print” zwykle drukuje jeden lub więcej elementów Pythona, po których następuje nowy wiersz. Literał „surowy” jest poprzedzony literą „r” i przekazuje wszystkie znaki bez specjalnego traktowania ukośnika lewego, więc r'x\nx' zwraca ciąg „x\nx” o długości 4 znaków. „print” może przyjmować kilka argumentów, by zmienić sposób drukowania zawartości (patrz definicja funkcji drukowania w python.org). Na przykład ustawienie parametru „end” na „” powoduje, że nowy wiersz nie jest drukowany po zakończeniu drukowania wszystkich elementów.

  raw = r'this\t\n and that'

  # this\t\n and that
  print(raw)

  multi = """It was the best of times.
  It was the worst of times."""

  # It was the best of times.
  #   It was the worst of times.
  print(multi)

Metody z ciągami znaków

Oto kilka najpopularniejszych metod z wykorzystaniem ciągów znaków. Metoda jest jak funkcja, ale działa „na” obiekcie. Jeśli zmienna s jest ciągiem znaków, kod s.lower() uruchamia metodęlower() na tym obiekcie ciągu i zwraca wynik (ta koncepcja metody uruchomionej w obiekcie to jedna z podstawowych koncepcji, które składają się na programowanie zorientowane na obiekty, czyli OOP). Oto kilka najpopularniejszych metod z wykorzystaniem ciągów znaków:

  • s.lower(), s.upper() – zwraca ciąg znaków w wersji pisanej małymi lub wielkimi literami.
  • s.strip() – zwraca ciąg znaków ze spacjami na początku i na końcu,
  • s.isalpha()/s.isdigit()/s.isspace()... – sprawdza, czy wszystkie znaki ciągu znaków znajdują się w różnych klasach znaków.
  • s.startswith('other'), s.endswith('other') – sprawdza, czy ciąg znaków rozpoczyna się czy kończy danym innym ciągiem
  • s.find('other') – wyszukuje określony inny ciąg (nie wyrażenie regularne) w elemencie s i zwraca pierwszy indeks, w którym się zaczyna, lub -1, jeśli nie został znaleziony
  • s.replace('old', 'new') – zwraca ciąg znaków, w którym wszystkie wystąpienia słowa „stare” zostały zastąpione wartością „new”
  • s.split('delim') – zwraca listę podłańcuchów rozdzielonych podanym separatorem. Separator nie jest wyrażeniem regularnym, ale jest zwykłym tekstem. 'aaa,bbb,ccc'.split(',') -> ['aaa', 'bbb', 'ccc']. W szczególnym przypadku funkcja s.split() (bez argumentów) dzieli wszystkie znaki odstępu.
  • s.join(list) – przeciwieństwo metody block() łączy ze sobą elementy na podanej liście przy użyciu ciągu znaków jako separatora, np. '---'.join(['aaa', 'bbb', 'ccc']) -> aaa---bbb---ccc

Wyszukanie w Google „python str” powinno prowadzić do oficjalnych metod ciągu znaków w python.org, które zawierają listę wszystkich metod str.

W Pythonie nie ma oddzielnego typu znaków. Zamiast tego wyrażenie takie jak s[8] zwraca ciąg znaków o długości 1, zawierający ten znak. W przypadku tego ciągu operatory ==, <=, ... działają zgodnie z oczekiwaniami, więc przeważnie nie trzeba wiedzieć, że w Pythonie w języku Python nie ma osobnego skalarnego typu „znaku”.

Wycinki sznurków

Składnia „wycinek” jest przydatna przy odwoływaniu się do podczęści sekwencji – zwykle ciągów tekstowych i list. Wycinek s[start:end] to elementy rozpoczynające się od początku i rozciągające się aż do końca. Załóżmy, że mamy s = "Witaj".

ciąg znaków „hello” z literami w indeksach 0 1 2 3 4

  • s[1:4] to „ell” – znaki zaczynające się od indeksu 1 i rozszerzające się do indeksu 4, ale bez niego
  • s[1:] ma wartość „ello” – pominięcie indeksu domyślnie wskazuje początek lub koniec ciągu.
  • s[:] to „Hello”. Jego pominięcie zawsze daje nam kopię całości (to pitonowy sposób kopiowania sekwencji, np. ciągu znaków lub listy).
  • s[1:100] ma wartość „ello” – zbyt duży indeks jest obcinany do długości ciągu znaków.

Standardowe indeksy liczone od zera zapewniają łatwy dostęp do znaków znajdujących się na początku ciągu. Zamiast tego w Pythonie można użyć liczb ujemnych, aby ułatwić dostęp do znaków na końcu ciągu: s[-1] to ostatni znak „o”, s[-2] to „l” następny znak itd. Ujemne liczby indeksów są odliczane od końca ciągu znaków:

  • s[-1] to „o” – ostatni znak (1 od końca),
  • s[-4] to „e” – 4. od końca
  • s[:-3] to „He” – dochodzi do 3 ostatnich znaków, ale bez uwzględniania.
  • s[-3:] to „llo” – rozpoczyna się od trzeciego znaku od końca i sięga do końca ciągu.

Jest to skrupulatny truizm wycinków, który dla dowolnego indeksu n – s[:n] + s[n:] == s. Działa to nawet w przypadku n ujemnych lub poza granicami. Inną metodą s[:n] i s[n:] jest zawsze partycjonowanie ciągu znaków na dwie części, co pozwala zachować wszystkie znaki. Jak w dalszej części prezentacji okaże się, że wycinki działają z listami.

Formatowanie ciągów znaków

Python może między innymi automatycznie przekonwertować obiekty na ciąg znaków nadający się do wydrukowania. Istnieją 2 wbudowane sposoby, aby to zrobić: sformatowane literały ciągu znaków (nazywane też „ciągami f”) i wywołanie funkcji str.format().

Sformatowane literały ciągu znaków

Sformatowane literały ciągów często można napotkać w takich sytuacjach:

  value = 2.791514
  print(f'approximate value = {value:.2f}')  # approximate value = 2.79

  car = {'tires':4, 'doors':2}
  print(f'car = {car}') # car = {'tires': 4, 'doors': 2}

Sformatowany ciąg literału jest poprzedzony literą „f” (tak jak prefiks „r” używany w przypadku ciągów nieprzetworzonych). Tekst poza nawiasem klamrowym „{}” jest wyświetlany bezpośrednio. Wyrażenia zawarte w znaku „{}” są drukowane zgodnie ze specyfikacją formatu opisaną w specyfikacji formatu.Jest wiele przydatnych funkcji formatowania, takich jak obcinanie i konwertowanie do notacji naukowej oraz wyrównanie do prawej/prawej/środkowej.

Są bardzo przydatne, gdy chcesz wydrukować tabelę obiektów i wyrównać kolumny reprezentujące różne atrybuty obiektów w taki sposób,

  address_book = [{'name':'N.X.', 'addr':'15 Jones St', 'bonus': 70},
      {'name':'J.P.', 'addr':'1005 5th St', 'bonus': 400},
      {'name':'A.A.', 'addr':'200001 Bdwy', 'bonus': 5},]

  for person in address_book:
    print(f'{person["name"]:8} || {person["addr"]:20} || {person["bonus"]:>5}')

  # N.X.     || 15 Jones St          ||    70
  # J.P.     || 1005 5th St          ||   400
  # A.A.     || 200001 Bdwy          ||     5

Ciąg znaków (%)

Python ma też starszą funkcję (podobną do ustawienia „printf()”) do tworzenia ciągu znaków. Operator % pobiera ciąg znaków w formacie printf po lewej stronie (%d int, ciąg %s, liczbę zmiennoprzecinkową %f/%g) oraz pasujące wartości w krocie po prawej stronie (kropka składa się z wartości rozdzielonych przecinkami, zwykle zgrupowanych w nawiasach):

  # % operator
  text = "%d little pigs come out, or I'll %s, and I'll %s, and I'll blow your %s down." % (3, 'huff', 'puff', 'house')

Powyższy wiersz jest dość długi – załóżmy, że chcesz go podzielić na osobne wiersze. Nie można po prostu podzielić wiersza po znaku „%”, tak jak w innych językach, ponieważ Python domyślnie traktuje każdy wiersz jako osobną instrukcję (po stronie znaku plus, dlatego nie trzeba wpisywać średników w każdym wierszu). Aby rozwiązać ten problem, umieść całe wyrażenie w zewnętrznym zestawie nawiasów – wtedy wyrażenie może obejmować wiele wierszy. Ta metoda wykorzystująca różne elementy kodu działa z różnymi strukturami grupującymi opisanymi poniżej: ( ), [ ], { }.

  # Add parentheses to make the long line work:
  text = (
    "%d little pigs come out, or I'll %s, and I'll %s, and I'll blow your %s down."
    % (3, 'huff', 'puff', 'house'))

Tak jest lepiej, ale kolejka jest jeszcze trochę długa. Python umożliwia pocięcie wiersza na fragmenty, które są następnie automatycznie łączone. Aby ten wiersz był jeszcze krótszy, możemy to zrobić w następujący sposób:

  # Split the line into chunks, which are concatenated automatically by Python
  text = (
    "%d little pigs come out, "
    "or I'll %s, and I'll %s, "
    "and I'll blow your %s down."
    % (3, 'huff', 'puff', 'house'))

Ciągi znaków (Unicode vs. bajty)

Zwykłe ciągi znaków Pythona to ciągi Unicode.

Python obsługuje też ciągi tekstowe składające się ze zwykłych bajtów (oznaczone prefiksem „b” przed literałem ciągu), na przykład:

> byte_string = b'A byte string'
> byte_string
  b'A byte string'

Ciąg Unicode to inny typ obiektu niż ciąg bajtów, ale różne biblioteki, takie jak wyrażenia regularne, działają prawidłowo, jeśli są przekazywane w obu tych formatach.

Aby przekonwertować zwykły ciąg Pythona na bajty, wywołaj w nim metodę encode(). W przeciwnym razie metoda decode() ciągu bajtowego konwertuje zakodowane bajty zwykłe na ciąg Unicode:

> ustring = 'A unicode \u018e string \xf1'
> b = ustring.encode('utf-8')
> b
b'A unicode \xc6\x8e string \xc3\xb1'  ## bytes of utf-8 encoding. Note the b-prefix.
> t = b.decode('utf-8')                ## Convert bytes back to a unicode string
> t == ustring                         ## It's the same as the original, yay!

True

W sekcji Odczytywanie plików znajduje się przykład pokazujący, jak otworzyć plik tekstowy z kodowaniem i odczytać ciągi Unicode.

Instrukcja if

Python nie używa { } do umieszczania bloków kodu na potrzeby if/loops/function itp. Zamiast nich w Pythonie używa się dwukropka (:) i wcięć/białych znaków do grupowania instrukcji. Test wartości logicznej „if” nie musi być umieszczony w nawiasach (duża różnica w stosunku do C++/Java) i może zawierać klauzule *elif* i *else* (mnemotechnika: słowo „elif” ma taką samą długość jak słowo „else”).

Jako testu if-test możesz użyć dowolnej wartości. Wszystkie wartości „zero” są liczone jako „fałsz”: brak, 0, pusty ciąg, pusta lista, pusty słownik. Dostępny jest też typ wartości logicznej z 2 wartościami: True i False (przekształcona na liczbę całkowitą, czyli 1 i 0). W Pythonie występują zwykłe operacje porównania: ==, !=, <, <=, >, >=. W przeciwieństwie do Javy i C znak == jest przeciążony, aby poprawnie działać z ciągami znaków. Operatory logiczne to zapisane słowa *and*, *or*, *not* (Python nie używa znaków C && || !). Tak może wyglądać kod aplikacji medycznej, która udostępnia zalecenia dotyczące napojów w ciągu dnia – zwróć uwagę, jak poszczególne bloki instrukcji potem/else zaczynają się od znaku „:”, a wyrażenia są pogrupowane według wcięcia:

  if time_hour >= 0 and time_hour <= 24:
    print('Suggesting a drink option...')
    if mood == 'sleepy' and time_hour < 10:
      print('coffee')
    elif mood == 'thirsty' or time_hour < 2:
      print('lemonade')
    else:
      print('water')

Widzę, że pomijanie „:” to najczęstszy błąd składni podczas pisania w powyższym kodzie. Prawdopodobnie dlatego, że trzeba to robić w dodatkowy sposób, a nie w moich nawykach C++/Java. Poza tym testu z wartościami logicznymi nie umieszczaj w nawiasach – jest to nawyk w języku C/Java. Jeżeli kod jest krótki, możesz umieścić go w tym samym wierszu po znaku „:” (dotyczy to również funkcji, pętli itp.), chociaż niektórzy uważają, że rozmieszczenie elementów w osobnych wierszach jest łatwiejsze.

  if time_hour < 10: print('coffee')
  else: print('water')

Ćwiczenie: string1.py

Aby przećwiczyć materiał z tej sekcji, spróbuj wykonać ćwiczenie string1.py w sekcji Podstawowe ćwiczenia.