Bagaimana cara memeriksa apakah suatu objek adalah daftar atau tupel (tetapi bukan string)?


443

Inilah yang biasanya saya lakukan untuk memastikan bahwa inputnya adalah list/ tuple- tapi bukan a str. Karena berkali-kali saya menemukan bug di mana suatu fungsi melewati suatu strobjek secara tidak sengaja, dan fungsi target tidak for x in lstmengasumsikan bahwa lstitu sebenarnya a listatau tuple.

assert isinstance(lst, (list, tuple))

Pertanyaan saya adalah: adakah cara yang lebih baik untuk mencapai ini?


9
ketik (pertama) adalah daftar?
jackalope

1
bukan isinstance (key, six.string_types)
wyx

Jawaban:


332

Hanya dalam python 2 (bukan python 3):

assert not isinstance(lst, basestring)

Sebenarnya apa yang Anda inginkan, jika tidak Anda akan kehilangan banyak hal yang bertindak seperti daftar, tetapi bukan subclass dari listatau tuple.


91
Ya, ini jawaban yang benar. Di Python 3, basestringhilang, dan Anda hanya memeriksa isinstance(lst, str).
steveha

5
Ada banyak hal yang dapat Anda lakukan seperti daftar, misalnya set, ekspresi generator, iterator. Ada hal-hal eksotis seperti mmap, hal-hal arrayyang kurang eksotis seperti yang bertindak seperti daftar, dan mungkin banyak lagi yang saya sudah lupa.
Nick Craig-Wood

50
Perlu dicatat bahwa ini tidak menjamin itu lstdapat diubah, sedangkan yang asli lakukan (mis. Int akan melewati pemeriksaan ini)
Peter Gibson

11
@PeterGibson - Kombinasi keduanya akan memberikan pemeriksaan yang valid, lebih ketat, dan memastikan 1) pertama bisa diubah, 2) pertama bukan string. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA

4
Nah, solusi ini hanya memeriksa tipe turunan string, tetapi bagaimana dengan bilangan bulat, dobel atau jenis yang tidak dapat diperbaiki lainnya?
Eneko Alonso

171

Ingat bahwa dalam Python kami ingin menggunakan "bebek mengetik". Jadi, apa pun yang bertindak seperti daftar dapat diperlakukan sebagai daftar. Jadi, jangan periksa jenis daftar, lihat saja apakah itu bertindak seperti daftar.

Tetapi string juga berfungsi seperti daftar, dan sering kali bukan itu yang kita inginkan. Ada saat-saat ketika itu bahkan menjadi masalah! Jadi, periksa secara eksplisit untuk string, tetapi kemudian gunakan mengetik bebek.

Berikut adalah fungsi yang saya tulis untuk bersenang-senang. Ini adalah versi khusus repr()yang mencetak urutan dalam kurung sudut ('<', '>').

def srepr(arg):
    if isinstance(arg, basestring): # Python 3: isinstance(arg, str)
        return repr(arg)
    try:
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    except TypeError: # catch when for loop fails
        return repr(arg) # not a sequence so just return repr

Ini bersih dan elegan, secara keseluruhan. Tapi apa yang isinstance()dilakukan cek di sana? Itu semacam peretasan. Tetapi ini penting.

Fungsi ini menyebut dirinya secara rekursif pada apa pun yang bertindak seperti daftar. Jika kami tidak menangani string secara khusus, maka itu akan diperlakukan seperti daftar, dan membagi satu karakter sekaligus. Tetapi kemudian panggilan rekursif akan mencoba memperlakukan setiap karakter sebagai daftar - dan itu akan berhasil! Bahkan string satu karakter berfungsi sebagai daftar! Fungsi akan terus memanggil dirinya secara rekursif sampai stack overflow.

Fungsi seperti ini, yang bergantung pada setiap panggilan rekursif yang memecah pekerjaan yang harus dilakukan, harus memiliki string kasus khusus - karena Anda tidak dapat memecah string di bawah level string satu karakter, dan bahkan satu string -character bertindak seperti daftar.

Catatan: the try/ exceptadalah cara terbersih untuk mengekspresikan niat kami. Tetapi jika kode ini entah bagaimana kritis terhadap waktu, kita mungkin ingin menggantinya dengan semacam tes untuk melihat apakah argada urutan. Daripada menguji jenisnya, kita mungkin harus menguji perilaku. Jika memiliki .strip()metode, itu adalah string, jadi jangan menganggapnya sebagai urutan; jika tidak, jika itu dapat diindeks atau diubah, itu adalah urutan:

def is_sequence(arg):
    return (not hasattr(arg, "strip") and
            hasattr(arg, "__getitem__") or
            hasattr(arg, "__iter__"))

def srepr(arg):
    if is_sequence(arg):
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    return repr(arg)

EDIT: Saya awalnya menulis di atas dengan cek untuk __getslice__()tetapi saya perhatikan bahwa dalam collectionsdokumentasi modul, metode yang menarik adalah __getitem__(); ini masuk akal, itulah cara Anda mengindeks objek. Tampaknya lebih mendasar daripada __getslice__()itu saya mengubah di atas.


2
@stantonk, terima kasih sudah mengatakannya, tapi saya pikir sudah ada jawaban yang diterima ketika saya menulis ini dan saya tidak benar-benar berharap jawaban yang diterima diubah.
steveha

@steveha: srepradalah ide yang sangat menarik. Tetapi saya memiliki pendapat yang berbeda dari Anda tentang perlunya kasus khusus str. Ya, strsejauh ini merupakan hal yang paling jelas dan umum yang akan menyebabkan rekursi tak terbatas srepr. Tapi saya bisa dengan mudah membayangkan iterables yang ditentukan pengguna yang berperilaku dengan cara yang sama (dengan atau tanpa alasan yang baik). Daripada kasus khusus str, kita harus mengakui bahwa pendekatan ini dapat mengalami rekursi tak terbatas, dan menyetujui beberapa cara untuk menghadapinya. Saya akan mengirim saran saya dalam jawaban.
maks

1
Saya pikir ini pasti jalan yang benar. Namun, untuk menangani kasus khusus (string dalam skenario ini), saya pikir kita lebih baik mengajukan pertanyaan "bagaimana manusia akan membedakannya?" Misalnya, pertimbangkan argumen fungsi yang bisa berupa daftar alamat email atau alamat email tunggal (dengan mengingat bahwa string hanyalah daftar karakter). Berikan variabel ini ke manusia. Bagaimana bisa tahu yang mana itu? Cara termudah yang dapat saya pikirkan adalah untuk melihat berapa banyak karakter dalam setiap item dalam daftar. Jika lebih besar dari 1, argumennya tentu bukan daftar karakter.
Josh

1
Saya sudah memikirkan hal ini sedikit, dan mendiskusikannya dengan beberapa orang lain, dan saya pikir srepr()tidak apa-apa. Kita membutuhkan fungsi rekursif untuk menangani hal-hal seperti daftar yang bersarang di dalam daftar lain; tetapi untuk string kami lebih suka mencetaknya "foo"daripada sebagai <'f', 'o', 'o'>. Jadi pemeriksaan eksplisit untuk string masuk akal di sini. Juga, sebenarnya tidak ada contoh tipe data lain di mana iterasi selalu mengembalikan iterable dan rekursi akan selalu menyebabkan stack overflow, jadi kita tidak perlu properti khusus untuk menguji hal ini ("Praktisitas mengalahkan kemurnian").
steveha

1
Ini tidak berfungsi di Python 3, karena string memiliki __iter__()metode dalam Python 3, tetapi tidak dalam Python 2. Anda tidak memiliki tanda kurung di is_sequence()dalamnya, seharusnya berbunyi:return (not hasattr(arg, "strip") and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")))
MiniQuark

124
H = "Hello"

if type(H) is list or type(H) is tuple:
    ## Do Something.
else
    ## Do Something.

11
Kecuali itu tidak menggunakan idiom Python dari mengetik bebek seperti komentator lain telah menunjukkan (meskipun itu menjawab pertanyaan secara langsung dan bersih).
Soren Bjornstad

7
Jawaban ini kurang dapat diterima daripada yang lain karena tidak memungkinkan untuk mengetik bebek dan juga gagal dalam kasus sederhana subkelas (contoh khas adalah kelas namedTuple).
Philippe Gauthier

11
"Tidak diperbolehkan untuk mengetik bebek" tidak membuat jawaban menjadi kurang dapat diterima, terutama mengingat bahwa jawaban ini benar-benar menjawab pertanyaan.
Petri

4
Saya telah mengangkat jawaban ini, tetapi if isinstance( H, (list, tuple) ): ...lebih pendek dan lebih jelas.
shahar_m

2
Sintaksis alternatif:if type(H) in [list, tuple]:
Štefan Schindler

77

Untuk Python 3:

import collections.abc

if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str):
    print("obj is a sequence (list, tuple, etc) but not a string")

Diubah dalam versi 3.3: Pindah Koleksi Kelas Dasar Abstrak ke modul collections.abc. Untuk kompatibilitas mundur, mereka akan terus terlihat dalam modul ini juga sampai versi 3.8 di mana ia akan berhenti bekerja.

Untuk Python 2:

import collections

if isinstance(obj, collections.Sequence) and not isinstance(obj, basestring):
    print "obj is a sequence (list, tuple, etc) but not a string or unicode"

5
Wow! Ini bekerja dengan sangat baik, dan jauh lebih ringkas daripada jawaban yang benar lainnya. Saya tidak tahu bahwa tipe bawaan mewarisi dari collections.Sequencetetapi saya mengujinya dan saya melihat mereka melakukannya. Begitu juga xrange. Bahkan lebih baik, tes ini tidak termasuk dict, yang memiliki keduanya __getitem__dan __iter__.
Neil Mayhew

Adakah yang tahu mengapa hasil inspect.getmro(list)tidak termasuk Sequence? Bagaimana kita mencari tahu apa yang bisa kita lakukan isinstanceketika getmrotidak menunjukkan semuanya?
Steve Jorgensen

Susunan Resolusi Metode @SteveJorgensen menentukan jalur pencarian kelas yang digunakan oleh Python untuk mencari metode yang tepat untuk digunakan di kelas. Sequenceadalah kelas abstrak.
suzanshakya

3
Di Python3, Anda bisa mengganti isinstance (obj, basestring) dengan isinstance (obj, str), dan itu akan berhasil.
Adrian Keister

2
dalam Python 3 yang Anda butuhkan dan bukan isinstance (obj, bytes) ... jika Anda ingin daftar hal-hal, dan tidak hanya untuk menyebutkan byte ...
Erik Aronesty

35

Python dengan rasa PHP:

def is_array(var):
    return isinstance(var, (list, tuple))

6
Python adalah bahasa yang diketik bebek, jadi Anda benar-benar harus memeriksa apakah var memiliki atribut __getitem__. Juga namanya menyesatkan, karena ada juga modul array. Dan var juga bisa menjadi numpy.ndarray atau jenis lainnya, yang memiliki __getitem__. Lihat stackoverflow.com/a/1835259/470560 untuk jawaban yang benar.
peterhil

9
@peterhil strjuga __getitem__karena itu cek Anda tidak mengecualikanstr
erikbwork

9
Demikian juga dikt. Memeriksa __getitem__adalah saran yang buruk di sini.
Petri

10

Secara umum, fakta bahwa suatu fungsi yang berulang pada suatu objek bekerja pada string dan juga tupel dan daftar adalah lebih banyak fitur daripada bug. Anda tentu bisa menggunakan isinstanceatau mengetik bebek untuk memeriksa argumen, tetapi mengapa Anda harus?

Itu terdengar seperti pertanyaan retoris, tetapi ternyata tidak. Jawaban untuk "mengapa saya harus memeriksa tipe argumen?" mungkin akan menyarankan solusi untuk masalah nyata, bukan masalah yang dirasakan. Mengapa bug ketika string dilewatkan ke fungsi? Juga: jika itu bug ketika string dilewatkan ke fungsi ini, apakah itu juga bug jika beberapa non-list / tuple iterable diteruskan ke sana? Mengapa atau mengapa tidak?

Saya pikir jawaban yang paling umum untuk pertanyaan tersebut adalah pengembang yang menulis f("abc")mengharapkan fungsi berperilaku seolah-olah mereka menulis f(["abc"]). Mungkin ada keadaan di mana lebih masuk akal untuk melindungi pengembang dari diri mereka sendiri daripada mendukung kasus penggunaan pengulangan di seluruh karakter dalam sebuah string. Tapi saya harus berpikir panjang dan keras dulu.


16
"Tapi aku akan berpikir panjang dan keras tentang itu dulu." Saya tidak akan. Jika fungsi tersebut seharusnya merupakan fungsi list-y, maka ya, itu harus memperlakukan mereka sama (yaitu, diberi daftar, keluarkan ke belakang, hal-hal seperti itu). Namun, jika itu adalah fungsi di mana salah satu argumen dapat berupa string atau daftar string (yang merupakan kebutuhan yang cukup umum) maka memaksa pengembang menggunakan fungsi itu untuk selalu memasukkan parameter mereka di dalam sebuah array sepertinya sedikit banyak . Juga, pikirkan bagaimana Anda akan menangani, katakanlah, input JSON. Anda pasti ingin memperlakukan daftar objek yang berbeda dari string.
Jordan Reiter

8

Coba ini untuk keterbacaan dan praktik terbaik:

Python2

import types
if isinstance(lst, types.ListType) or isinstance(lst, types.TupleType):
    # Do something

Python3

import typing
if isinstance(lst, typing.List) or isinstance(lst, typing.Tuple):
    # Do something

Semoga ini bisa membantu.


Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType'
Juha Untinen

1
Dalam Python 3, itu adalah: from typing import List-> isinstance([1, 2, 3], List= True, dan isinstance("asd", List)= False
Juha Untinen

5

The strobjek tidak memiliki __iter__atribut

>>> hasattr('', '__iter__')
False 

jadi kamu bisa melakukan cek

assert hasattr(x, '__iter__')

dan ini juga akan menghasilkan objek yang bagus AssertionErroruntuk objek yang tidak dapat diubah.

Sunting: Seperti yang disebutkan Tim dalam komentar, ini hanya akan bekerja di python 2.x, bukan 3.x


8
Hati-hati: Dalam Python 3 hasattr('','__iter__')kembali True. Dan tentu saja itu masuk akal karena Anda dapat beralih menggunakan string.
Tim Pietzcker

1
Betulkah? Saya tidak tahu itu. Saya selalu berpikir ini adalah solusi elegan untuk masalah ini, oh well.
Moe

1
Tes ini tidak berfungsi pada pyodbc.Row. Ia tidak memiliki iter __ () tetapi lebih atau kurang berperilaku seperti daftar (bahkan mendefinisikan "__setitem "). Anda dapat mengulangi elemen-elemennya dengan baik. Fungsi len () berfungsi dan Anda bisa mengindeks elemen-elemennya. Saya berjuang untuk menemukan kombinasi yang tepat yang menangkap semua jenis daftar, tetapi mengecualikan string. Saya pikir saya akan menerima cek pada " getitem " dan " len " sementara secara eksplisit tidak termasuk basestring.
haridsv

5

Ini tidak dimaksudkan untuk langsung menjawab OP, tetapi saya ingin berbagi beberapa ide terkait.

Saya sangat tertarik dengan jawaban @steveha di atas, yang sepertinya memberikan contoh di mana ketikan bebek tampaknya pecah. Namun, setelah dipikir-pikir, teladannya menunjukkan bahwa mengetik bebek sulit dilakukan, tetapi tidak menyarankan bahwa strlayak untuk penanganan khusus.

Setelah semua, non- strtipe (misalnya, tipe yang ditentukan pengguna yang mempertahankan beberapa struktur rekursif yang rumit) dapat menyebabkan sreprfungsi @steveha menyebabkan rekursi tak terbatas. Meskipun ini memang agak tidak mungkin, kami tidak bisa mengabaikan kemungkinan ini. Oleh karena itu, daripada khusus casing strdi srepr, kita harus menjelaskan apa yang ingin kita sreprlakukan ketika hasil rekursi tak terbatas.

Tampaknya satu pendekatan yang masuk akal adalah dengan mematahkan rekursi pada sreprsaat itu list(arg) == [arg]. Ini, pada kenyataannya, akan sepenuhnya menyelesaikan masalah dengan str, tanpa ada isinstance.

Namun, struktur rekursif yang sangat rumit dapat menyebabkan loop tak terbatas di mana list(arg) == [arg]tidak pernah terjadi. Karena itu, walaupun pemeriksaan di atas bermanfaat, itu tidak cukup. Kami membutuhkan sesuatu seperti batas keras pada kedalaman rekursi.

Maksud saya adalah bahwa jika Anda berencana untuk menangani jenis argumen sewenang-wenang, penanganan strmelalui pengetikan bebek jauh, jauh lebih mudah daripada menangani jenis yang lebih umum yang mungkin (secara teoritis) Anda temui. Jadi, jika Anda merasa perlu untuk mengecualikan strinstance, Anda harus menuntut bahwa argumen adalah instance dari salah satu dari beberapa jenis yang Anda tentukan secara eksplisit.


1
Hmm, saya suka cara Anda berpikir. Saya pikir Anda tidak dapat berdebat bahwa kode saya praktis: persis ada satu kasus umum str,, yang ditangani oleh kode kasus khusus. Tetapi mungkin harus ada properti standar baru yang dapat diperiksa kode, .__atomic__katakanlah, yang menandakan bahwa sesuatu tidak dapat dipecah lebih jauh. Mungkin sudah terlambat untuk menambahkan fungsi builtin lain atomic()ke Python, tapi mungkin kita bisa menambahkan from collections import atomicatau sesuatu.
steveha

5

Saya menemukan fungsi bernama is_afterence dalam tensorflow .

def is_sequence(seq):
  """Returns a true if its input is a collections.Sequence (except strings).
  Args:
    seq: an input sequence.
  Returns:
    True if the sequence is a not a string and is a collections.Sequence.
  """
  return (isinstance(seq, collections.Sequence)
and not isinstance(seq, six.string_types))

Dan saya telah memverifikasi bahwa itu memenuhi kebutuhan Anda.


2

Saya melakukan ini di testcases saya.

def assertIsIterable(self, item):
    #add types here you don't want to mistake as iterables
    if isinstance(item, basestring): 
        raise AssertionError("type %s is not iterable" % type(item))

    #Fake an iteration.
    try:
        for x in item:
            break;
    except TypeError:
        raise AssertionError("type %s is not iterable" % type(item))

Tidak diuji pada generator, saya pikir Anda dibiarkan pada 'hasil' berikutnya jika dilewatkan dalam generator, yang dapat mengacaukan segalanya di hilir. Tapi sekali lagi, ini adalah 'yang tak terhitung'


2

Dengan cara "mengetik bebek", bagaimana

try:
    lst = lst + []
except TypeError:
    #it's not a list

atau

try:
    lst = lst + ()
except TypeError:
    #it's not a tuple

masing-masing. Ini menghindari hal-hal isinstance/ hasattrintrospeksi.

Anda juga dapat memeriksa sebaliknya:

try:
    lst = lst + ''
except TypeError:
    #it's not (base)string

Semua varian sebenarnya tidak mengubah konten variabel, tetapi menyiratkan penugasan kembali. Saya tidak yakin apakah ini mungkin tidak diinginkan dalam beberapa keadaan.

Menariknya, dengan penugasan "di tempat" +=tidak ada yang TypeErrorakan dinaikkan dalam hal apa pun jika lstmerupakan daftar (bukan tupel ). Itu sebabnya tugas dilakukan dengan cara ini. Mungkin seseorang bisa menjelaskan mengapa itu terjadi.


1

cara paling sederhana ... menggunakan anydanisinstance

>>> console_routers = 'x'
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
False
>>>
>>> console_routers = ('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True
>>> console_routers = list('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True

1

Versi lain dari mengetik bebek untuk membantu membedakan objek seperti string dari objek seperti urutan lainnya.

Representasi string dari objek mirip string adalah string itu sendiri, sehingga Anda dapat memeriksa apakah Anda mendapatkan objek yang sama kembali dari strkonstruktor:

# If a string was passed, convert it to a single-element sequence
if var == str(var):
    my_list = [var]

# All other iterables
else: 
    my_list = list(var)

Ini harus bekerja untuk semua objek yang kompatibel dengan strdan untuk semua jenis objek yang dapat diubah.


0

Python 3 memiliki ini:

from typing import List

def isit(value):
    return isinstance(value, List)

isit([1, 2, 3])  # True
isit("test")  # False
isit({"Hello": "Mars"})  # False
isit((1, 2))  # False

Jadi untuk memeriksa Daftar dan Tuple, itu akan menjadi:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)

0
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"

2
apakah ini cara yang baik untuk melakukan ini?
ersh

1
Selamat datang di SO. Penjelasan mengapa kode ini menjawab pertanyaan akan sangat membantu.
Nick

Ya tentu saja, saya menggunakan metode yang mirip dengan ini karena pipa diperlakukan sebagai atau jadi Anda menyatakan bahwa jenisnya harus daftar atau jenis tuple yang menghasilkan pesan kesalahan khusus untuk penanganan kesalahan. Saya percaya ini menjawab pertanyaan, tetapi saya ingin tahu seolah-olah ini adalah cara yang efektif untuk melakukan ini karena saya masih mencoba untuk memahami penulisan kode yang paling optimal. Namun saya tidak yakin apakah kode ini melewatkan hal-hal yang dapat bertindak seperti daftar / tupel tetapi bukan merupakan subkelas dari keduanya, karena bagaimana jawaban yang diterima membahas kemungkinan itu. Terima kasih!
ersh

-1

Lakukan saja ini

if type(lst) in (list, tuple):
    # Do stuff

5
isinstance (lst, (list, tuple))
Davi Lima

@ DavidLima OK, itu cara lain. Tetapi type () direkomendasikan untuk tipe in-built dan status untuk Kelas.
ATOzTOA

-1

dalam python> 3.6

import collections
isinstance(set(),collections.abc.Container)
True
isinstance([],collections.abc.Container)
True
isinstance({},collections.abc.Container)
True
isinstance((),collections.abc.Container)
True
isinstance(str,collections.abc.Container)
False

2
Pada pemeriksaan terakhir Anda menggunakan tipe str, bukan string. Coba isinstance('my_string', collections.abc.Container)dan Anda akan melihat bahwa itu akan kembali True. Ini karena abc.Containerpersediaan __contains__metode, dan string memilikinya, tentu saja.
Georgy

-6

Saya cenderung melakukan ini (jika saya benar-benar harus):

for i in some_var:
   if type(i) == type(list()):
       #do something with a list
   elif type(i) == type(tuple()):
       #do something with a tuple
   elif type(i) == type(str()):
       #here's your string

5
Anda hampir tidak pernah melakukan ini. Apa yang terjadi jika saya some_varadalah turunan dari kelas yang merupakan subkelas dari list()? Kode Anda tidak akan tahu apa yang harus dilakukan dengan kode itu, meskipun itu akan bekerja dengan sempurna di bawah kode "melakukan sesuatu dengan daftar". Dan Anda jarang perlu peduli tentang perbedaan antara daftar dan tupel. Maaf, -1.
steveha

1
Tidak perlu menulis type(tuple())- tupleakan dilakukan. Sama untuk daftar. Juga, keduanya strdan unicodeextended basestring, yang merupakan tipe string asli, jadi Anda ingin memeriksanya.
perlakukan mod Anda dengan baik

@DrBloodmoney: Downvote tidak disengaja. Harap (sepele) edit jawaban Anda untuk memungkinkan saya menghapus downvote.
SabreWolfy

Bagi saya, kesetaraan sepertinya bukan perbandingan yang berarti untuk jenis. Aku akan menguji identitas sebagai gantinya: type(i) is list. Juga, type(list())itu listsendiri ... Akhirnya, ini tidak bekerja dengan anggun dengan subclass. Jika isebenarnya dan OrderedDict, atau semacam namesuple, kode ini akan memperlakukan sebagai string.
bukzor
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.