Konwertuj bajty na ciąg


2302

Używam tego kodu, aby uzyskać standardowe wyjście z zewnętrznego programu:

>>> from subprocess import *
>>> command_stdout = Popen(['ls', '-l'], stdout=PIPE).communicate()[0]

Metoda Communication () zwraca tablicę bajtów:

>>> command_stdout
b'total 0\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file1\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file2\n'

Chciałbym jednak pracować z danymi wyjściowymi jako normalnym ciągiem Python. Żebym mógł wydrukować to w ten sposób:

>>> print(command_stdout)
-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file1
-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file2

Myślałem, że po to jest metoda binascii.b2a_qp () , ale kiedy spróbowałem, znów otrzymałem tę samą tablicę bajtów:

>>> binascii.b2a_qp(command_stdout)
b'total 0\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file1\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file2\n'

Jak przekonwertować wartość bajtów z powrotem na ciąg? Mam na myśli używanie „baterii” zamiast robienia tego ręcznie. I chciałbym, żeby było dobrze w Pythonie 3.


47
dlaczego nie str(text_bytes)działa To wydaje mi się dziwne.
Charlie Parker

12
@CharlieParker Ponieważ str(text_bytes)nie można określić kodowania. W zależności od tego, co jest w text_bytes, text_bytes.decode('cp1250) `może spowodować bardzo inny ciąg znaków do text_bytes.decode('utf-8').
Craig Anderson

6
więc strfunkcja nie przekształca się już w prawdziwy ciąg. Z jakiegoś powodu MUSIMY wyraźnie powiedzieć kodowanie. Jestem leniwy, by przeczytać, dlaczego. Po prostu przekonwertuj go utf-8i sprawdź, czy Twój kod działa. np.var = var.decode('utf-8')
Charlie Parker

@CraigAnderson: unicode_text = str(bytestring, character_encoding)działa zgodnie z oczekiwaniami w Pythonie 3. Chociaż unicode_text = bytestring.decode(character_encoding)bardziej preferowane jest unikanie pomyłek z tym, str(bytes_obj)że tworzy reprezentację tekstową bytes_objzamiast dekodowania jej do tekstu: str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶'istr(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶'
jfs

Odpowiedzi:


3672

Musisz zdekodować obiekt bajtów, aby utworzyć ciąg znaków:

>>> b"abcde"
b'abcde'

# utf-8 is used here because it is a very common encoding, but you
# need to use the encoding your data is actually in.
>>> b"abcde".decode("utf-8") 
'abcde'

57
Używanie również "windows-1252"nie jest niezawodne (np. W przypadku innych wersji językowych systemu Windows), czy nie byłoby najlepiej używać sys.stdout.encoding?
nikow

12
Może to pomoże komuś dalej: Czasami używasz tablicy bajtów do komunikacji ex TCP. Jeśli chcesz przekonwertować tablicę bajtów na ciąg odcinający końcowe znaki „\ x00”, poniższa odpowiedź nie jest wystarczająca. Następnie użyj b'example \ x00 \ x00'.decode ('utf-8'). Strip ('\ x00').
Wookie88

2
Wypełniłem błąd dotyczący dokumentowania go na stronie bugs.python.org/issue17860 - możesz zaproponować łatkę. Jeśli trudno jest wnieść wkład - mile widziane są komentarze, jak to poprawić.
anatoly techtonik

44
W Pythonie 2.7.6 nie obsługuje b"\x80\x02\x03".decode("utf-8")-> UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte.
martineau

9
Jeśli treść ma losowe wartości binarne, utf-8konwersja prawdopodobnie się nie powiedzie. Zamiast tego zobacz odpowiedź @techtonik (poniżej) stackoverflow.com/a/27527728/198536
wallyk

214

Musisz zdekodować ciąg bajtów i przekształcić go w ciąg znaków (Unicode).

W Pythonie 2

encoding = 'utf-8'
'hello'.decode(encoding)

lub

unicode('hello', encoding)

W Pythonie 3

encoding = 'utf-8'
b'hello'.decode(encoding)

lub

str(b'hello', encoding)

2
Co się stanie w Pythonie 3, jeśli ciąg znaków znajduje się w zmiennej?
Alaa M.

1
@AlaaM .: to samo. Jeśli tak variable = b'hello', tounicode_text = variable.decode(character_encoding)
jfs

182

Myślę, że ten sposób jest łatwy:

>>> bytes_data = [112, 52, 52]
>>> "".join(map(chr, bytes_data))
'p44'

6
Dziękuję, twoja metoda zadziałała dla mnie, kiedy nikt inny nie. Miałem niekodowaną tablicę bajtów, której potrzebowałem, zamieniłem w ciąg znaków. Próbowałem znaleźć sposób na ponowne zakodowanie go, aby móc go zdekodować na ciąg znaków. Ta metoda działa idealnie!
leetNightshade

5
@leetNightshade: ale jest to strasznie nieefektywne. Jeśli masz tablicę bajtów, musisz tylko zdekodować.
Martijn Pieters

12
@Martijn Pieters Właśnie wykonałem prosty test porównawczy z tymi innymi odpowiedziami, uruchamiając wiele 10.000 uruchomień stackoverflow.com/a/3646405/353094 I powyższe rozwiązanie było za każdym razem znacznie szybsze. Dla 10.000 uruchomień w Pythonie 2.7.7 zajmuje to 8 ms, w porównaniu z innymi przy 12ms i 18ms. To prawda, że ​​mogą istnieć pewne różnice w zależności od danych wejściowych, wersji Pythona itp. Nie wydaje mi się to zbyt wolne.
leetNightshade

5
@Martijn Pieters Tak. Więc z tym punktem nie jest to najlepsza odpowiedź na treść zadanego pytania. A tytuł wprowadza w błąd, prawda? On / ona chce przekonwertować ciąg bajtów na zwykły ciąg, a nie tablicę bajtów na ciąg. Ta odpowiedź działa dobrze dla tytułu zadanego pytania.
leetNightshade

5
Dla Pythona 3 powinno to być równoważne z bytes([112, 52, 52])- btw bajtów to zła nazwa zmiennej lokalnej właśnie dlatego, że jest to wbudowany p3
Mr_and_Mrs_D

92

Jeśli nie znasz kodowania, to aby wczytać dane binarne do łańcucha w sposób zgodny z Python 3 i Python 2, użyj starożytnego kodowania MS-DOS CP437 :

PY3K = sys.version_info >= (3, 0)

lines = []
for line in stream:
    if not PY3K:
        lines.append(line)
    else:
        lines.append(line.decode('cp437'))

Ponieważ kodowanie jest nieznane, należy oczekiwać, że symbole w języku innym niż angielski cp437zostaną przetłumaczone na znaki (znaki angielskie nie są tłumaczone, ponieważ pasują do większości kodowań jednobajtowych i UTF-8).

Dekodowanie dowolnego wejścia binarnego na UTF-8 jest niebezpieczne, ponieważ możesz otrzymać:

>>> b'\x00\x01\xffsd'.decode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 2: invalid
start byte

To samo dotyczy latin-1, który był popularny (domyślny?) W Pythonie 2. Zobacz brakujące punkty w Układzie strony kodowej - to tam Python dusi się z niesławą ordinal not in range.

AKTUALIZACJA 20150604 : Istnieją pogłoski, że Python 3 ma surrogateescapestrategię błędów do kodowania danych w danych binarnych bez utraty danych i awarii, ale wymaga testów konwersji [binary] -> [str] -> [binary], aby sprawdzić zarówno wydajność, jak i niezawodność.

AKTUALIZACJA 20170116 : Dzięki komentarzowi Nearoo - istnieje również możliwość ukrycia ucieczki wszystkich nieznanych bajtów za pomocą backslashreplaceprocedury obsługi błędów. Działa to tylko w przypadku Python 3, więc nawet z tym obejściem nadal będziesz otrzymywać niespójne dane wyjściowe z różnych wersji Python:

PY3K = sys.version_info >= (3, 0)

lines = []
for line in stream:
    if not PY3K:
        lines.append(line)
    else:
        lines.append(line.decode('utf-8', 'backslashreplace'))

Aby uzyskać szczegółowe informacje, zobacz Obsługa Unicode w języku Python .

AKTUALIZACJA 20170119 : Postanowiłem zaimplementować ukośnik uciekający, który działa zarówno dla Pythona 2, jak i dla Pythona 3. Powinno być wolniejsze niż cp437rozwiązanie, ale powinno dawać identyczne wyniki dla każdej wersji Pythona.

# --- preparation

import codecs

def slashescape(err):
    """ codecs error handler. err is UnicodeDecode instance. return
    a tuple with a replacement for the unencodable part of the input
    and a position where encoding should continue"""
    #print err, dir(err), err.start, err.end, err.object[:err.start]
    thebyte = err.object[err.start:err.end]
    repl = u'\\x'+hex(ord(thebyte))[2:]
    return (repl, err.end)

codecs.register_error('slashescape', slashescape)

# --- processing

stream = [b'\x80abc']

lines = []
for line in stream:
    lines.append(line.decode('utf-8', 'slashescape'))

6
Naprawdę uważam, że Python powinien zapewnić mechanizm zastępujący brakujące symbole i kontynuować.
anatoly techtonik

@techtonik: To nie zadziała na tablicy takiej jak działała w python2.
user2284570,

@ user2284570 masz na myśli listę? I dlaczego powinien działać na tablicach? Zwłaszcza tablice pływaków ..
anatolij techtonik

Możesz również zignorować błędy unicode b'\x00\x01\xffsd'.decode('utf-8', 'ignore')w pythonie 3.
Antonis Kalou,

3
@ anatolytechtonik Istnieje możliwość pozostawienia sekwencji ucieczki w ciągu i przejścia dalej: b'\x80abc'.decode("utf-8", "backslashreplace")spowoduje '\\x80abc'. Informacje te pochodzą ze strony dokumentacji Unicode, która wydaje się być zaktualizowana od czasu napisania tej odpowiedzi.
Nearoo

86

W Pythonie 3 domyślnym kodowaniem jest "utf-8", więc możesz bezpośrednio użyć:

b'hello'.decode()

co jest równoważne z

b'hello'.decode(encoding="utf-8")

Z drugiej strony, w Pythonie 2 , kodowanie jest domyślnie ustawione na domyślne kodowanie ciągu. Dlatego powinieneś użyć:

b'hello'.decode(encoding)

gdzie encodingjest kodowanie, które chcesz.

Uwaga: obsługa argumentów słów kluczowych została dodana w Pythonie 2.7.


41

Myślę, że tak naprawdę chcesz:

>>> from subprocess import *
>>> command_stdout = Popen(['ls', '-l'], stdout=PIPE).communicate()[0]
>>> command_text = command_stdout.decode(encoding='windows-1252')

Odpowiedź Aarona była poprawna, z tym wyjątkiem, że musisz wiedzieć, jakiego kodowania użyć. I wierzę, że Windows używa „Windows-1252”. Będzie to miało znaczenie tylko wtedy, gdy będziesz zawierał nietypowe (spoza ASCII) znaki w treści, ale wtedy coś zmieni.

Nawiasem mówiąc, fakt, że ma to znaczenie, jest powodem, dla którego Python przeszedł na używanie dwóch różnych typów danych binarnych i tekstowych: nie może konwertować magicznie między nimi, ponieważ nie zna kodowania, chyba że to powiesz! Jedyny sposób, w jaki MUSISZ wiedzieć, to przeczytać dokumentację Windows (lub przeczytać tutaj).


3
open()funkcja dla strumieni tekstowych lub Popen()jeśli ją przejdziesz, universal_newlines=Truemagicznie zdecyduj o kodowaniu znaków dla Ciebie ( locale.getpreferredencoding(False)w Python 3.3+).
jfs

2
'latin-1'jest dosłownie kodowaniem ze wszystkimi ustawionymi punktami kodowymi, więc możesz go użyć, aby skutecznie odczytać ciąg bajtów do dowolnego typu ciągu obsługiwanego przez Python (tak dosłownie w Pythonie 2, w Unicode dla Pythona 3).
tripleee

@tripleee: 'latin-1'to dobry sposób na uzyskanie mojibake. Istnieją również magiczne podstawienia w systemie Windows: zaskakująco trudno jest przesyłać dane z jednego procesu do innego niezmodyfikowanego, np . dir: \xb6-> \x14(przykład na końcu mojej odpowiedzi)
jfs

32

Ustaw universal_newlines na True, tj

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]

5
Korzystałem z tej metody i działa. Chociaż to tylko zgadywanie kodowania na podstawie preferencji użytkownika w systemie, więc nie jest ono tak niezawodne, jak niektóre inne opcje. Oto, co robi, odwołując się do docs.python.org/3.4/library/subprocess.html: „Jeśli universal_newlines jest Prawdą, [stdin, stdout i stderr] zostaną otwarte jako strumienie tekstu w trybie uniwersalnych nowych linii przy użyciu kodowania zwróconego przez ustawienia regionalne .getpreferredencoding (False). ”
twasbrillig

W wersji 3.7 możesz (i powinieneś) zrobić text=Truezamiast universal_newlines=True.
Boris

23

Podczas gdy odpowiedź @Aaron Maenpaa po prostu działa, użytkownik niedawno zapytał :

Czy jest jakiś prostszy sposób? „fhand.read (). decode („ ASCII ”)” [...] Jest tak długo!

Możesz użyć:

command_stdout.decode()

decode()ma standardowy argument :

codecs.decode(obj, encoding='utf-8', errors='strict')


.decode()takie użycie 'utf-8'może się nie powieść (dane wyjściowe polecenia mogą używać innego kodowania znaków lub nawet zwrócić niezdefiniowaną sekwencję bajtów). Chociaż jeśli wejście to ascii (podzbiór utf-8), to .decode()działa.
jfs

22

Aby zinterpretować sekwencję bajtów jako tekst, musisz znać odpowiednie kodowanie znaków:

unicode_text = bytestring.decode(character_encoding)

Przykład:

>>> b'\xc2\xb5'.decode('utf-8')
'µ'

lspolecenie może generować dane wyjściowe, których nie można interpretować jako tekstu. Nazwy plików w Uniksie mogą być dowolną sekwencją bajtów oprócz ukośnika b'/'i zera b'\0':

>>> open(bytes(range(0x100)).translate(None, b'\0/'), 'w').close()

Próba odkodowania takiej bajtowej zupy przy użyciu kodowania utf-8 podnosi UnicodeDecodeError.

Może być gorzej. Dekodowanie może się nie powieść po cichu i spowodować mojibake, jeśli użyjesz niewłaściwego niekompatybilnego kodowania:

>>> '—'.encode('utf-8').decode('cp1252')
'—'

Dane są uszkodzone, ale Twój program nie jest świadomy wystąpienia awarii.

Ogólnie, jakie kodowanie znaków do użycia nie jest osadzone w samej sekwencji bajtów. Musisz przekazać te informacje poza pasmem. Niektóre wyniki są bardziej prawdopodobne niż inne, dlatego chardetistnieje moduł, który może odgadnąć kodowanie znaków. Pojedynczy skrypt w języku Python może wykorzystywać kodowanie wielu znaków w różnych miejscach.


lsdane wyjściowe można przekonwertować na ciąg znaków w języku Python przy użyciu os.fsdecode() funkcji, która działa nawet w przypadku niezdefiniowanych nazw plików (używa sys.getfilesystemencoding()i surrogateescapeobsługi błędów w systemie Unix):

import os
import subprocess

output = os.fsdecode(subprocess.check_output('ls'))

Aby uzyskać oryginalne bajty, możesz użyć os.fsencode().

Jeśli podasz universal_newlines=Trueparametr, a następnie subprocessużyje go locale.getpreferredencoding(False)do zdekodowania bajtów, np. Może to być cp1252system Windows.

Do dekodowania strumienia bajtów w locie io.TextIOWrapper() można użyć: przykład .

Różne polecenia mogą używać różnych kodowań znaków dla swoich danych wyjściowych, np. dirPolecenie wewnętrzne ( cmd) może używać cp437. Aby zdekodować jego dane wyjściowe, możesz przekazać kodowanie jawnie (Python 3.6+):

output = subprocess.check_output('dir', shell=True, encoding='cp437')

Nazwy plików mogą różnić się od os.listdir()(który używa Windows Unicode API), np. '\xb6'Można je zastąpić '\x14'mapami koderów-cp437 firmy Python w b'\x14'celu sterowania znakiem U + 0014 zamiast U + 00B6 (¶). Aby obsługiwać nazwy plików z dowolnymi znakami Unicode, zobacz Dekodowanie danych wyjściowych PowerShell, które mogą zawierać znaki Unicode inne niż ASCII do ciągu znaków w języku Python


16

Ponieważ to pytanie faktycznie dotyczy subprocessdanych wyjściowych, dostępne jest bardziej bezpośrednie podejście, ponieważ Popenakceptuje słowo kluczowe kodujące (w Python 3.6+):

>>> from subprocess import Popen, PIPE
>>> text = Popen(['ls', '-l'], stdout=PIPE, encoding='utf-8').communicate()[0]
>>> type(text)
str
>>> print(text)
total 0
-rw-r--r-- 1 wim badger 0 May 31 12:45 some_file.txt

Ogólna odpowiedź dla innych użytkowników to dekodowanie bajtów na tekst:

>>> b'abcde'.decode()
'abcde'

Bez argumentu sys.getdefaultencoding()zostaną użyte. Jeśli Twoje dane nie są sys.getdefaultencoding(), musisz jawnie określić kodowanie w decodewywołaniu:

>>> b'caf\xe9'.decode('cp1250')
'café'

3
Lub w Pythonie 3.7 możesz przejść text=Truedo dekodowania stdin, stdout i stderr przy użyciu danego kodowania (jeśli jest ustawione) lub domyślnego systemu w innym przypadku. Popen(['ls', '-l'], stdout=PIPE, text=True).
Boris

Dekodowanie lsdanych wyjściowych przy użyciu utf-8kodowania może się nie powieść (patrz przykład w mojej odpowiedzi z 2016 r .).
jfs

1
@ Boris: jeśli encodingparametr jest podany, wówczas textparametr jest ignorowany.
jfs

11

Jeśli powinieneś uzyskać następujące informacje, próbując decode():

AttributeError: obiekt „str” nie ma atrybutu „dekodowania”

Możesz także określić typ kodowania bezpośrednio w obsadzie:

>>> my_byte_str
b'Hello World'

>>> str(my_byte_str, 'utf-8')
'Hello World'

6

Podczas pracy z danymi z systemów Windows (z \r\nzakończeniami linii) moja odpowiedź brzmi

String = Bytes.decode("utf-8").replace("\r\n", "\n")

Dlaczego? Wypróbuj to z multiline Input.txt:

Bytes = open("Input.txt", "rb").read()
String = Bytes.decode("utf-8")
open("Output.txt", "w").write(String)

Wszystkie zakończenia linii zostaną podwojone (do \r\r\n), co spowoduje dodatkowe puste linie. Funkcje odczytywania tekstu w Pythonie zwykle normalizują zakończenia linii, dzięki czemu używane są tylko łańcuchy \n. Jeśli otrzymujesz dane binarne z systemu Windows, Python nie ma na to szans. A zatem,

Bytes = open("Input.txt", "rb").read()
String = Bytes.decode("utf-8").replace("\r\n", "\n")
open("Output.txt", "w").write(String)

skopiuje twój oryginalny plik.


.replace("\r\n", "\n")Tak długo szukałem dodatku. To jest odpowiedź, jeśli chcesz poprawnie renderować HTML.
mhlavacka

5

Zrobiłem funkcję czyszczenia listy

def cleanLists(self, lista):
    lista = [x.strip() for x in lista]
    lista = [x.replace('\n', '') for x in lista]
    lista = [x.replace('\b', '') for x in lista]
    lista = [x.encode('utf8') for x in lista]
    lista = [x.decode('utf8') for x in lista]

    return lista

6
Można rzeczywiście łańcucha wszystkich .strip, .replace, .encodeitp połączeń w jednym listowego i tylko iteracyjne nad listą raz zamiast iteracji po nim pięć razy.
Taylor Edmiston,

1
@TaylorEdmiston Być może oszczędza na alokacji, ale liczba operacji pozostanie taka sama.
JulienD

5

Dla Python 3, jest to o wiele bezpieczniejsze i pythonowy podejście przekonwertować z bytedo string:

def byte_to_str(bytes_or_str):
    if isinstance(bytes_or_str, bytes): # Check if it's in bytes
        print(bytes_or_str.decode('utf-8'))
    else:
        print("Object not of byte type")

byte_to_str(b'total 0\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file1\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file2\n')

Wynik:

total 0
-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file1
-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file2

5
1) Jak powiedział @ bodangly, sprawdzanie typu wcale nie jest pytoniczne. 2) Napisana funkcja nosi nazwę „ byte_to_str”, co oznacza, że ​​zwróci ciąg, ale drukuje tylko skonwertowaną wartość i drukuje komunikat o błędzie, jeśli się nie powiedzie (ale nie zgłosi wyjątku). Takie podejście jest również pozbawione mitów i zaciemnia bytes.decodepodane rozwiązanie.
cosmicFluke

3

Z sys - parametry i funkcje specyficzne dla systemu :

Aby zapisać lub odczytać dane binarne ze / do standardowych strumieni, użyj bazowego bufora binarnego. Na przykład, aby zapisać bajty na standardowe wyjście, użyj sys.stdout.buffer.write(b'abc').


3
Potok do podprocesu jest już buforem binarnym. Twoja odpowiedź nie zawiera odpowiedzi na pytanie, jak uzyskać wartość ciągu z byteswartości wynikowej .
Martijn Pieters

1
def toString(string):    
    try:
        return v.decode("utf-8")
    except ValueError:
        return string

b = b'97.080.500'
s = '97.080.500'
print(toString(b))
print(toString(s))

1
Chociaż ten kod może odpowiedzieć na pytanie, zapewnienie dodatkowego kontekstu dotyczącego tego, jak i / lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi. Pamiętaj, że odpowiadasz na pytanie dla czytelników w przyszłości, nie tylko dla osoby zadającej teraz pytanie! Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia. Nie zaszkodzi też wspomnieć, dlaczego ta odpowiedź jest bardziej odpowiednia niż inne.
Dev-iL

Wytłumaczenie byłoby w porządku.
Peter Mortensen

1

W konkretnym przypadku „uruchom polecenie powłoki i uzyskaj jego wynik jako tekst zamiast bajtów”, w Pythonie 3.7 powinieneś użyć subprocess.runi przekazać text=True(oraz capture_output=Trueprzechwycić dane wyjściowe)

command_result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
command_result.stdout  # is a `str` containing your program's stdout

textbył wywoływany universal_newlinesi był zmieniany (no cóż, alias) w Pythonie 3.7. Jeśli chcesz obsługiwać wersje Python starsze niż 3.7, podaj universal_newlines=Truezamiasttext=True


0

Jeśli chcesz przekonwertować dowolne bajty, nie tylko ciąg znaków przekonwertowany na bajty:

with open("bytesfile", "rb") as infile:
    str = base64.b85encode(imageFile.read())

with open("bytesfile", "rb") as infile:
    str2 = json.dumps(list(infile.read()))

Nie jest to jednak zbyt wydajne. Zmieni obraz o wielkości 2 MB na 9 MB.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.