Numpy: Pembuatan array objek "terkendali"

Dibuat pada 4 Des 2019  ·  45Komentar  ·  Sumber: numpy/numpy

Pembuatan otomatis array objek baru-baru ini tidak digunakan lagi di numpy. Saya setuju dengan perubahan tersebut, tetapi tampaknya agak sulit untuk menulis jenis kode generik tertentu yang menentukan apakah argumen yang diberikan pengguna dapat dikonversi ke array non-objek.

Contoh kode reproduksi:

Matplotlib berisi cuplikan berikut:

    # <named ("string") colors are handled earlier>
    # tuple color.
    c = np.array(c)
    if not np.can_cast(c.dtype, float, "same_kind") or c.ndim != 1:
        # Test the dtype explicitly as `map(float, ...)`, `np.array(...,
        # float)` and `np.array(...).astype(float)` all convert "0.5" to 0.5.
        # Test dimensionality to reject single floats.
        raise ValueError(f"Invalid RGBA argument: {orig_c!r}")

tetapi terkadang fungsi dipanggil dengan array warna dalam berbagai format (misalnya ["red", (0.5, 0.5, 0.5), "blue"] ) -- kami menangkap ValueError dan mengonversi setiap item satu per satu sebagai gantinya.

Sekarang panggilan ke np.array(c) akan mengeluarkan DeprecationWarning. Bagaimana kita bisa mengatasinya? Bahkan sesuatu seperti np.min_scalar_type(c) mengeluarkan peringatan (yang menurut saya seharusnya tidak?), jadi tidak jelas bagi saya bagaimana cara memeriksa "jika kita mengonversi benda ini menjadi array, apa yang akan menjadi dtype?"

Informasi versi Numpy/Python:


1.19.0.dev0+bd1adc3 3.8.0 (default, 6 November 2019, 21:49:08)
[GCC 7.3.0]

57 - Close?

Komentar yang paling membantu

Bisakah seseorang menunjuk ke contoh operator.mod ?

Adapun operator == , yang saya lihat sedang melakukan sesuatu seperti np.array(vals, dtype=object) == vals mana vals=[1, [2, 3]] (memparafrasekan kode), jadi solusinya adalah secara proaktif membuat array di sebelah kanan samping.

Banyak dari kegagalan scipy tampaknya dalam bentuk np.array([0.25, np.array([0.3])]) , di mana pencampuran skalar dan ndarray dengan shape==(1,) akan bertentangan dengan penemuan dimensi dan membuat array objek. xref gh-15075

Semua 45 komentar

Salah satu pilihannya adalah
```python
mencoba:
# maju dari permainan dan promosikan penghentian ke kesalahan yang akan menggantikannya
dengan warnings.catch_warnings():
warnings.filterwarnings('naikkan', DeprecationWarning, pesan="...")
c_arr = np.asarray(c)
kecuali (DeprecationWarning, ValueError):
# apa pun yang Anda lakukan saat ini untuk ValueError

Saya kira ini, dan tes gagal yang disebutkan dalam gh-15045 adalah contoh di mana memancarkan DeprecationWarning selama beberapa tahun alih-alih secara langsung memancarkan ValueError menyebabkan lebih banyak kode churn daripada yang dibutuhkan.

Perhatikan bahwa warnings.catch_warnings bukan threadsafe. Itu membuat solusi sedikit rentan terhadap masalah tindak lanjut di telepon.

Saya pikir kode-churn sepadan dengan periode penghentian.

Matplotlib menjalankan test suite-nya dengan warning-as-failures untuk menangkap perubahan semacam ini lebih awal sehingga sepertinya sistem ini bekerja untuk saya :).

Tetapi AFAICT bahkan tidak ada perbaikan yang cukup mudah (seperti yang ditunjukkan di atas, perbaikan yang diusulkan bukan threadsafe) untuk itu:/

Saya pikir saya melihat poin @anntzer di sini. Kami berada dalam kekacauan di mana perpustakaan hilir ingin cepat gagal sehingga mereka dapat mencoba sesuatu yang lain, sementara pengguna harus diperlihatkan pesan yang lebih lembut.

Masalahnya adalah hari ini tidak ada cara bagi penulis perpustakaan untuk bertanya "apakah ini akan mengeluarkan peringatan" tanpa benar-benar... memancarkan peringatan, dan menekannya bukanlah threadsafe.

Mengenai keamanan utas peringatan: https://bugs.python.org/issue37604

AFAIK, penghentian ada di cabang rilis. Apakah kita ingin mengembalikannya? Jika tidak, perbaikan akan membutuhkan backport. Saya masih tidak jelas mengapa peringatan tidak dimunculkan di roda cabang rilis dan tidak muncul di build malam sampai dua build terakhir. Saya tidak mengubah apa pun setelah cabang dan tidak ada yang terlihat sangat mencurigakan di komit sejak saat itu di cabang master kecuali, mungkin, #15040.

IMHO (dan sesuai dengan poin @mattip di atas) itu adalah jenis perubahan yang akan lebih mudah ditangani di hilir jika peralihan ke peningkatan terjadi tanpa periode penghentian. Tidak yakin itu pilihan:/

Atau mungkin multibuild memperlakukan cabang berbeda dari master.

FWIW Saya selalu setidaknya -1 pada perubahan ini, terutama sebagai pengguna yang tajam dari struktur data yang kasar, tetapi bagaimanapun sekarang saya perlu mencari tahu apa yang harus dilakukan tentang ratusan kegagalan pengujian untuk persiapan SciPy 1.4.0rc2 di https://github.com/scipy/scipy/pull/11161

sekarang saya perlu mencari tahu apa yang harus dilakukan tentang ratusan kegagalan pengujian

Pilihan yang mudah adalah:

  • Tekan peringatan di konfigurasi pytest Anda
  • Buka masalah untuk memperbaikinya nanti

Inti dari kami menggunakan DeprecationWarning alih-alih ValueError adalah untuk memberi proyek hilir dan pengguna masa tenggang untuk melakukan hal itu.

AFAIK, penghentian ada di cabang rilis. Apakah kita ingin mengembalikannya?

Saya pikir kita lakukan, itu masalah hujan. Kami sekarang memiliki daftar apa yang rusak di Pandas, Matplotlib, SciPy, di dalam numpy.testing dan NumPy ufuncs, == , dll. Saya pikir kita harus mengembalikan perubahan sekarang dan menilai/memperbaiki semua itu hal-hal, kemudian memperkenalkan kembali penghentian tersebut.

Bisakah kita berkompromi pada peringatan penghentian yang tertunda?

Dengan begitu, proyek hilir dapat menambahkannya ke daftar abaikan mereka, dan ketika kami beralih kembali ke DeprecationWarning, mereka dapat membuat keputusan lagi.

Kami tampaknya telah menyimpang dari masalah asli, yang tampaknya "diberi urutan nilai, bagaimana matplotlib dapat menentukan apakah itu satu warna atau daftar warna". Saya pikir harus ada solusi yang tidak memerlukan casting nilai ke ndarray, dan memeriksa dtype dari array itu. Beberapa jenis fungsi is_a_color() rekursif mungkin merupakan solusi yang lebih baik.

Saya telah mengembalikan perubahan untuk 1.18.x di #15053.

Sentimennya adalah melanggar scipy dan pandas CI cukup mengganggu untuk sementara mengembalikannya ke master juga. Saya ingin itu kembali pada dasarnya dijadwalkan (katakanlah dalam sebulan). Kita mungkin perlu menemukan solusi sekalipun. Juga perbaikan yang dilakukan panda sedikit mengkhawatirkan saya, karena mereka menggunakan catch_warnings .

Jika benar-benar tidak ada cara, dan kami membutuhkan penekanan peringatan thread-safe. np.seterr mungkin bisa menampung slot untuk itu :/.

Kami tampaknya telah menyimpang dari masalah asli, yang tampaknya "diberi urutan nilai, bagaimana matplotlib dapat menentukan apakah itu satu warna atau daftar warna".

Saya pikir masalah yang diangkat @anntzer lebih umum. Ini tentang menulis fungsi yang membutuhkan banyak jenis input, dengan logika seperti:

  • buat ndarray(flexible_input)
  • if `new_ndarray.dtype.kind == 'O': tangani ini
  • lain: use_the_array

karena seseorang tidak dapat menambahkan dtype=object ke kode tersebut, apa yang harus dilakukan?

Juga perbaikan yang dilakukan panda sedikit mengkhawatirkan saya, karena mereka menggunakan catch_warnings .

@seberg bukankah suppress_warnings lebih baik untuk ini?

@rgommers tidak, suppress_warnings memecahkan masalah penekanan peringatan menjadi permanen padahal seharusnya tidak. Itu telah diperbaiki pada versi python yang lebih baru, sehingga kami tidak benar-benar membutuhkannya lagi (memiliki properti yang lebih baik, karena mendukung bersarang, tetapi tidak mendukung keamanan utas. Saya tidak yakin itu mungkin di luar python, dan bahkan jika ya, itu mungkin tidak diinginkan)

Tidak sepenuhnya yakin apakah kasus bermasalah bertentangan dengan niat awal (https://numpy.org/neps/nep-0034.html) dari mereka yang tidak kami antisipasi.

Bagaimanapun, jalan keluarnya adalah dengan secara eksplisit mengaktifkan perilaku lama di sepanjang baris "menghargai perhatian Anda, tetapi kami secara eksplisit menginginkan objek yang bergantung pada konteks dtype dan akan menangani sendiri input yang bermasalah". Sesuatu seperti salah satu dari

~~~
np.array(data, dtype='allow_object')

np.array(data, allow_object_dtype=True)

dengan np.array_create_allow_object_dtype():
np.array(data)
~~~

semua tidak terlalu cantik dan penamaan pasti akan ditingkatkan. Tetapi ini memberikan jalan keluar yang bersih untuk perpustakaan yang mengandalkan perilaku dan ingin menyimpannya (setidaknya untuk saat ini).

Bukankah kasus matplotlib sebenarnya:

with np.forbid_ragged_arrays_immediately():
    np.array(data)

karena Anda benar-benar ingin menangkap kesalahan, daripada mendapatkan objek dtype?

Tidak ada pengembalian penghentian yang saat ini tertunda untuk master. Saya tidak berpikir itu harus dikembalikan secara grosir seperti di 1.18 karena itu juga menghapus perbaikan, yang menurut saya ingin kita pertahankan. @mattip Pengembalian yang lebih bertarget akan dihargai sampai kami memutuskan apa yang harus dilakukan dalam jangka panjang.

FWIW Saya pikir sebagian besar tempat di mpl yang terkena ini dapat diperbaiki (dengan restrukturisasi lebih atau kurang - dalam satu kasus ternyata kode jika jauh lebih cepat setelah ...).
Saya pikir API yang diusulkan @timhoffm akan lebih bagus daripada with np.forbid_ragged_arrays_immediately: karena yang terakhir dapat dengan mudah ditulis dalam bentuk yang pertama (naikkan jika np.array(..., allow_object=True).dtype == object ) sedangkan sebaliknya ( try: with np.forbid: ... except ValueError: ... ) akan kurang efisien jika kita tetap ingin membuat array objek. Tetapi CM (hanya "bergerak secara lokal melewati periode penghentian") akan lebih baik daripada tidak sama sekali.

(Sekali lagi, saya pikir perubahannya bagus, hanya masalah bagaimana itu dieksekusi.)

Ya, kita hanya perlu mencari tahu seperti apa seharusnya API itu. Seperti yang ditunjukkan oleh banyak orang, saat ini ada dua masalah utama:

  1. Membingungkan object dan "allow ragged" . Jika objek memiliki tipe yang masuk akal (katakanlah Decimal ) Anda sebenarnya ingin mendapatkan peringatan/kesalahan tetapi juga mungkin perlu meneruskan dtype=object
  2. Tidak ada cara untuk ikut serta dalam perilaku baru atau tetap menggunakan yang lama (tanpa peringatan). Tampaknya setidaknya Opt-In kemungkinan diperlukan untuk penggunaan internal, jika kami tidak menyediakannya, pada dasarnya kami berasumsi bahwa (mungkin secara tidak langsung) hanya pengguna akhir yang mengalami kasus ini?

Akhirnya, kita harus mencari cara untuk menjejalkannya ke dalam kode kita :). ndmin mungkin menjadi target lain untuk menjejalkan setidaknya flag yang mengendalikan perilaku kasar.

Tidak ada pengembalian penghentian yang saat ini tertunda untuk master. Saya tidak berpikir itu harus dikembalikan secara grosir seperti di 1.18 karena itu juga menghapus perbaikan, yang menurut saya ingin kita pertahankan. @mattip Pengembalian yang lebih bertarget akan dihargai sampai kami memutuskan apa yang harus dilakukan dalam jangka panjang.

Saya tidak melihat masalah dengan pengembalian penuh dan kemudian memperkenalkan kembali bagian apa pun yang masuk akal sekarang. Sekali lagi, mengembalikan sesuatu bukanlah penilaian nilai tentang apa yang baik atau buruk, itu hanya cara pragmatis untuk membongkar banyak hal yang baru saja kita pecahkan dengan menekan tombol gabungkan. Jelas ada dampak dan masalah yang belum terpecahkan yang tidak diramalkan dalam NEP, jadi mengembalikan terlebih dahulu adalah hal yang benar untuk dilakukan.

Argumen untuk belum kembali - saat perubahan sedang dalam master, kita dapat memanfaatkan proses CI hilir untuk mencoba dan mencari tahu seperti apa solusi mereka

CI hilir berwarna merah, itu _sangat_ tidak membantu. Kami sekarang memiliki daftar kegagalan mereka, kami tidak perlu menyimpan CI mereka merah untuk membuat hidup kami sedikit lebih mudah di sini.

Dan setidaknya CI Matplotlib berjalan melawan pip install --pre bukan cabang utama

Dan setidaknya CI Matplotlib berjalan melawan pip install --pre bukan cabang utama

Itu menarik dari roda malam sepertinya. Perubahan sudah dikembalikan untuk 1.18.0rc1, jadi Anda tidak akan melihatnya jika Anda akan menginstal dengan --pre dari PyPI.

Beberapa komentar di atas sama dengan memikirkan kembali perubahan yang diusulkan dalam NEP 34. Saya tidak yakin apakah utas ini adalah tempat yang tepat untuk melanjutkan diskusi ini, tapi begini. (Tidak ada salahnya jika itu harus didiskusikan di tempat lain--menyalin dan menempel komentar itu mudah. ​​:smile: Juga, beberapa dari Anda telah melihat variasi komentar ini dalam diskusi tentang slack.)

Setelah memikirkan hal ini baru-baru ini, saya berakhir dengan ide yang sama dengan saran pertama @timhoffm (dan ide itu mungkin telah diusulkan di lain waktu dalam beberapa bulan terakhir): tentukan objek string atau singleton tertentu yang, ketika diberikan sebagai argumen dtype ke array , memungkinkan fungsi untuk menangani input berbentuk compang-camping dengan membuat larik objek 1-d. Akibatnya, ini memungkinkan perilaku pra-NEP-34 dari dtype=None di mana input berbentuk kasar secara otomatis dikonversi ke array objek. Jika ada nilai lain untuk dtype diberikan (termasuk None atau object ), peringatan penghentian diberikan jika input berbentuk tidak rata. Dalam versi NumPy yang akan datang, peringatan itu akan diubah menjadi kesalahan.

Saya pikir sudah jelas sekarang bahwa menggunakan dtype=object untuk mengaktifkan penanganan input berbentuk compang-camping bukanlah solusi yang baik untuk masalah tersebut. Idealnya, kita akan memisahkan pengertian "array objek" dari "array compang-camping". Tetapi kita tidak dapat sepenuhnya memisahkan mereka, karena ketika kita ingin menangani array yang tidak rata, satu-satunya pilihan yang kita miliki adalah membuat array objek. Di sisi lain, terkadang kita menginginkan sebuah array objek, tetapi kita tidak menginginkan konversi otomatis dari input berbentuk compang-camping ke array objek dari urutan.

Misalnya (lih. item 1 dalam komentar terakhir @seberg ), misalkan f1 , f2 , f3 dan f4 adalah Fraction objek, dan saya bekerja dengan array objek Fraction s. Saya tidak tertarik untuk membuat array yang compang-camping. Jika saya tidak sengaja menulis a = np.array([f1, f2, [f3, f4]], dtype=object) , saya _ingin_ itu menghasilkan kesalahan, karena semua alasan yang kami miliki NEP 34. Namun, dengan NEP 34, itu akan membuat larik 1-d dengan panjang 3.

Alternatif yang menambahkan argumen kata kunci baru, seperti saran kedua @timhoffm , tampaknya lebih rumit daripada yang diperlukan. Masalah yang kami coba pecahkan adalah "foot gun" di mana input kasar secara otomatis dikonversi ke array objek 1-d. Masalah hanya muncul ketika dtype=None diteruskan ke array . Mengharuskan pengguna untuk mengganti dtype=None dengan dtype=<special-value-that-enables-ragged-handling> untuk mempertahankan perilaku lama yang merepotkan adalah perubahan sederhana pada API yang mudah dijelaskan. Apakah kita benar-benar membutuhkan lebih dari itu?

Saya pikir sudah jelas sekarang bahwa menggunakan dtype=object untuk mengaktifkan penanganan input berbentuk compang-camping bukanlah solusi yang baik untuk masalah tersebut. Idealnya, kita akan memisahkan pengertian "array objek" dari "array compang-camping".

Kedengarannya masuk akal, mungkin. Juga baik untuk menunjukkan bahwa tidak ada konsep "array compang-camping" yang nyata di NumPy . Ini adalah sesuatu yang pada dasarnya tidak kami dukung (cari "kasar" di dokumen, pada pelacak masalah atau milis untuk mengonfirmasi jika Anda mau), itu adalah sesuatu yang didukung DyND dan XND, dan kami baru mulai membicarakannya untuk memiliki frase untuk membahas "kami ingin menghapus perilaku np.array([1, [2, 3]]) yang membuat pengguna tersandung". Oleh karena itu memanggang dalam "array compang-camping" sebagai hal API baru harus dilakukan dengan sangat hati-hati, itu sama sekali bukan sesuatu yang ingin kami promosikan. Jadi akan lebih baik untuk memperjelasnya dalam penamaan dtype=some_workaround apa pun yang mungkin kita tambahkan.

Tampaknya pendapat umum menyatukan solusi untuk memperpanjang penghentian (mungkin tanpa batas) dengan mengizinkan np.array(vals, dtype=special) yang akan berperilaku seperti sebelum NEP 34. Saya lebih suka singleton daripada string, karena itu berarti penggunaan perpustakaan dapat melakukan special = getattr(np.special, None) dan kodenya akan berfungsi di seluruh versi.

Sekarang kita perlu memutuskan nama dan di mana itu harus diekspos. Mungkin never_fail atau guess_dimensions ? Adapun di mana untuk mengeksposnya, saya lebih suka untuk tidak menggantungnya np daripada beberapa modul internal lainnya, mungkin dengan _ untuk menunjukkan itu benar-benar antarmuka pribadi.

Saya pikir jalan ke depan adalah untuk mengubah NEP 34, kemudian mengekspos diskusi di milis.

Perhatikan bahwa ada beberapa laporan juga masalah dengan menggunakan operator ( == dan operator.mod setidaknya). Apakah Anda mengusulkan untuk mengabaikannya, atau entah bagaimana menyimpan status itu di array?

Dalam hampir semua kasus, mungkin diketahui bahwa salah satu operan adalah array numpy. Jadi mungkin untuk mendapatkan perilaku yang terdefinisi dengan baik dengan mengonversi secara manual ke array numpy.

Bisakah seseorang menunjuk ke contoh operator.mod ?

Adapun operator == , yang saya lihat sedang melakukan sesuatu seperti np.array(vals, dtype=object) == vals mana vals=[1, [2, 3]] (memparafrasekan kode), jadi solusinya adalah secara proaktif membuat array di sebelah kanan samping.

Banyak dari kegagalan scipy tampaknya dalam bentuk np.array([0.25, np.array([0.3])]) , di mana pencampuran skalar dan ndarray dengan shape==(1,) akan bertentangan dengan penemuan dimensi dan membuat array objek. xref gh-15075

Bisakah seseorang menunjuk ke contoh operator.mod ?

Melihat itu di PR Pandas @jbrockmendel , tapi saya pikir itu sudah berubah (tidak melihat operator.mod eksplisit lagi di komentar).

Adapun operator == , yang saya lihat sedang melakukan sesuatu seperti np.array(vals, dtype=object) == vals mana vals=[1, [2, 3]] (memparafrasekan kode), jadi solusinya adalah secara proaktif membuat array di sebelah kanan samping.

Pada saat itu menjadi np.array(vals, dtype=object) == np.array(vals, dtype=object) , jadi lebih baik hapus saja tesnya :)

@mattip menulis:

Saya lebih suka singleton daripada string, karena itu berarti penggunaan perpustakaan dapat melakukan special = getattr(np.special, None) dan kode mereka akan berfungsi di seluruh versi.

Kedengarannya OK untuk saya.

Sekarang kita perlu memutuskan nama dan di mana itu harus diekspos. Mungkin never_fail atau guess_dimensions ? Adapun di mana untuk mengeksposnya, saya lebih suka untuk tidak menggantungnya np daripada beberapa modul internal lainnya, mungkin dengan _ untuk menunjukkan itu benar-benar antarmuka pribadi.

Nama kerja saya saat ini untuk ini adalah legacy_auto_dtype , tetapi mungkin ada banyak nama lain yang tidak akan saya keluhkan.

Saya tidak yakin nama itu harus pribadi. Dengan definisi praktis dari _private_ dan _public_, ini akan menjadi objek _public_. Ini memberi pengguna sarana untuk melestarikan perilaku warisan, misalnya, array(data) dengan menulis ulang itu sebagai array(data, dtype=legacy_auto_dtype) . Saya membayangkan NEP yang diperbarui akan menjelaskan bahwa ini adalah bagaimana kode harus dimodifikasi untuk mempertahankan perilaku warisan (bagi mereka yang harus melakukannya). Jika demikian, objek tersebut jelas bukan milik pribadi. Faktanya, sepertinya itu adalah objek publik yang akan tetap ada di NumPy tanpa batas. Tapi mungkin pemahaman saya tentang bagaimana NEP 34 yang dimodifikasi akan dimainkan salah.

Setuju dengan deskripsi @WarrenWeckesser tentang publik/swasta; baik itu publik, atau tidak boleh digunakan oleh siapa pun di luar NumPy.

Re name: silakan pilih nama yang menjelaskan fungsionalitasnya. Hal-hal seperti "warisan" hampir tidak pernah merupakan ide yang baik.

silakan pilih nama yang menjelaskan fungsi tersebut.

auto_object , auto_dtype , auto ?

Berpikir keras sebentar...

Apa yang dilakukan objek ini?

Saat ini, ketika NumPy diberikan objek Python yang berisi suburutan yang panjangnya tidak konsisten dengan larik nd biasa, NumPy akan membuat larik dengan tipe data object , dengan objek di tingkat pertama tempat terjadinya inkonsistensi bentuk kiri sebagai objek Python. Misalnya, array([[1, 2], [1, 2, 3]]) memiliki bentuk (2,) , np.array([[1, 2], [3, [99]]]) memiliki bentuk (2, 2) , dll. Dengan NEP 34, kami menghentikan perilaku itu, jadi cobalah untuk membuat array dengan input "kasar" pada akhirnya akan menghasilkan kesalahan, kecuali jika diaktifkan secara eksplisit. Nilai khusus yang sedang kita bicarakan memungkinkan perilaku lama.

Apa nama yang bagus untuk itu ? ragged_as_object ? inconsistent_shapes_as_object ?

Pada saat itu menjadi np.array(vals, dtype=object) == np.array(vals, dtype=object) , jadi lebih baik hapus saja tesnya :)

Yah, aku parafrase. Tes yang sebenarnya lebih seperti my_func(vals) == vals harus menjadi my_func(vals) == np.array(vals, dtype=object)

Saya akan mengusulkan perpanjangan ke NEP 34 untuk memungkinkan nilai khusus untuk dtype.

Perhatikan bahwa tampaknya scipy tidak memerlukan penjaga ini untuk lulus tes dengan scipy/scipy#11310 dan scipy/scipy#11308

gh-15119 digabung, yang mengimplementasikan kembali NEP. Jika tidak dikembalikan, kami dapat menutup masalah ini

Saya akan menutup ini, karena kami tidak menindaklanjutinya sebelum rilis 1.19. Dan saya setidaknya berharap alasannya adalah karena diskusi telah mereda karena semua proyek besar dapat menemukan solusi yang masuk akal untuk masalah yang ditimbulkannya.
Harap perbaiki saya jika saya salah, terutama jika ini masih rentan terhadap masalah dengan panda, matplotlib, dll. Tapi saya berasumsi kita akan mendengarnya selama siklus kandidat rilis 1.19.x.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat