Bagaimana saya bisa memeriksa versi Python dalam program yang menggunakan fitur bahasa baru?


239

Jika saya memiliki skrip Python yang membutuhkan setidaknya versi Python tertentu, apa cara yang benar untuk gagal dengan anggun ketika versi Python sebelumnya digunakan untuk meluncurkan skrip?

Bagaimana cara mendapatkan kontrol cukup awal untuk mengeluarkan pesan kesalahan dan keluar?

Sebagai contoh, saya memiliki program yang menggunakan operator ternery (baru dalam 2,5) dan "dengan" blok (baru dalam 2,6). Saya menulis rutin kecil pemeriksa versi interpreter yang merupakan hal pertama yang akan dipanggil skrip ... kecuali itu tidak sampai sejauh itu. Sebagai gantinya, skrip gagal selama kompilasi python, bahkan sebelum rutinitas saya dipanggil. Dengan demikian pengguna skrip melihat beberapa tracebacks kesalahan synax sangat jelas - yang cukup banyak membutuhkan ahli untuk menyimpulkan bahwa itu hanya kasus menjalankan versi yang salah dari Python.

Saya tahu cara memeriksa versi Python. Masalahnya adalah bahwa beberapa sintaks ilegal di versi Python yang lebih lama. Pertimbangkan program ini:

import sys
if sys.version_info < (2, 4):
    raise "must use python 2.5 or greater"
else:
    # syntax error in 2.4, ok in 2.5
    x = 1 if True else 2
    print x

Ketika dijalankan di bawah 2.4, saya ingin hasil ini

$ ~/bin/python2.4 tern.py 
must use python 2.5 or greater

dan bukan hasil ini:

$ ~/bin/python2.4 tern.py 
  File "tern.py", line 5
    x = 1 if True else 2
           ^
SyntaxError: invalid syntax

(Menyalurkan untuk rekan kerja.)


3
"periksa versi python. Masalahnya adalah bahwa beberapa sintaks ilegal di versi python yang lebih lama." Saya tidak mengerti bagaimana ini menjadi masalah. Jika Anda dapat memeriksa versi, Anda dapat menghindari kesalahan sintaksis. Bagaimana pemeriksaan versi tidak berlaku untuk sintaks? Bisakah Anda mengklarifikasi pertanyaan Anda?
S.Lott

4
@ S.Lott Tidak, Anda tidak salah, hanya saja kesulitannya adalah memasukkan kode di suatu tempat yang juga tidak akan dibaca (diurai) dan tidak dieksekusi - ini tidak segera terlihat seperti yang ditunjukkan oleh jawaban.
Brendan

7
S.Lott, Anda tidak dapat menjalankan pengujian dalam versi python lama karena tidak dikompilasi. Sebaliknya, Anda mendapatkan kesalahan sintaksis generik. Cobalah kode contoh dengan juru bahasa 2.4 dan Anda akan melihat bahwa Anda tidak dapat mengikuti tes versi.
Mark Harrison

7
@ S.Lott Yah itu tergantung pada apa yang Anda anggap sepele - secara pribadi saya tidak akan mempertimbangkan membuat file terpisah untuk berbagai versi Python atau menelurkan proses ekstra sepele. Saya akan mengatakan pertanyaan ini sangat berharga, terutama ketika Anda menganggap Python penuh trik yang rapi dan sering mengejutkan - Saya datang ke sini dari Google ingin tahu apakah ada jawaban yang rapi
Brendan

7
Saya pikir kami telah mencapai akhir dari diskusi ini. Saya mengajukan pertanyaan tentang sesuatu yang saya tidak tahu bagaimana melakukannya, dan mendapat jawaban yang memberi tahu saya bagaimana melakukannya. Saya tidak mengusulkan apa pun, saya hanya menerima jawaban orip yang bekerja sangat baik untuk saya (sebenarnya rekan kerja yang saya salurkan). Viva Le Stack Overflow!
Mark Harrison

Jawaban:


111

Anda dapat menguji menggunakan eval:

try:
  eval("1 if True else 2")
except SyntaxError:
  # doesn't have ternary

Juga, with adalah tersedia dalam Python 2.5, hanya menambahkan from __future__ import with_statement.

EDIT: untuk mendapatkan kontrol cukup awal, Anda dapat membaginya menjadi .pyfile yang berbeda dan memeriksa kompatibilitas di file utama sebelum mengimpor (misalnya dalam __init__.pydalam paket):

# __init__.py

# Check compatibility
try:
  eval("1 if True else 2")
except SyntaxError:
  raise ImportError("requires ternary support")

# import from another module
from impl import *

10
ini jawaban yang fantastis. masalah utama dari pertanyaan yang perlu diatasi adalah bahwa suatu program harus benar secara sintaksis untuk versi python itu untuk bahkan mulai mengeksekusi, jadi menggunakan sintaks baru menghalangi program untuk memulai dari versi interpreter yang lebih lama. eval bekerja di sekitar itu
Autoplectic

7
Jika paket sedang diinstal oleh setuptools, byte-kompilasi file sumber akan gagal. Juga, semua contortions untuk menghasilkan pesan kesalahan run-time tampak sedikit sia-sia - mengapa tidak mendokumentasikan persyaratan dan membiarkannya saja?
John Machin

2
Perhatikan bahwa jika mencoba memeriksa ekspresi, alih-alih pernyataan sederhana, Anda harus menggunakan execalih-alih eval. Saya punya ini muncul ketika mencoba untuk menulis fungsi yang akan mencetak ke stderr di kedua py2k dan py3k.
Xiong Chiamiov

2
Saya pikir versi yang lebih bersih dari solusi ini adalah untuk meletakkan "cek" Anda dalam modul terpisah dan impor itu (bungkus importstasiun di coba / kecuali). Perhatikan bahwa Anda mungkin perlu memeriksa hal-hal lain selain SyntaxErrorjuga (mis. Fungsi builtin atau penambahan ke perpustakaan standar)
Steven

103

Miliki pembungkus di sekitar program Anda yang melakukan hal berikut.

import sys

req_version = (2,5)
cur_version = sys.version_info

if cur_version >= req_version:
   import myApp
   myApp.run()
else:
   print "Your Python interpreter is too old. Please consider upgrading."

Anda juga dapat mempertimbangkan untuk menggunakan sys.version(), jika Anda berencana untuk bertemu orang-orang yang menggunakan interpreter Python pra-2.0, tetapi kemudian Anda memiliki beberapa ekspresi reguler untuk dilakukan.

Dan mungkin ada cara yang lebih elegan untuk melakukan ini.


7
FYI, "cur_version> = req_version" harus berfungsi sebagai kondisional.
orip

4
sys.version_infobukan fungsi.
nh2

3
Menempatkan kode di dalam kondisi sukses seperti itu adalah praktik yang sangat buruk karena merupakan lekukan yang tidak perlu dan penambahan logika. Lakukan saja: if sys.version_info [: 2] <req_version: print "old"; sys.exit () - dan jika tidak, lanjutkan seperti biasa.
timss

1
Seperti yang dikatakan Tim Peters dalam "The Zen of Python": "Flat lebih baik daripada bersarang." (Anda dapat melihat ini dengan mengetikkan "impor ini" dengan python)
Christopher Shroba

1
@ChristopherShroba Terima kasih atas import this. Pengalihan yang indah.
Samuel Harmer

32

Mencoba

platform impor
platform.python_version ()

Seharusnya memberi Anda string seperti "2.3.1". Jika ini tidak persis seperti yang Anda inginkan, ada serangkaian data yang tersedia melalui build-in "platform". Apa yang Anda inginkan harus ada di suatu tempat.


4
-1: Ini tidak berfungsi, seperti yang dijelaskan dalam pertanyaan yang diperbarui. Jika Anda menggunakan sintaks dari versi Python yang lebih baru maka file Anda tidak akan dikompilasi, dan jika tidak dikompilasi, tidak dapat berjalan dan memeriksa versinya!
Scott Griffiths

1
@ScottGriffiths Jalankan print(platform.python_version())bukannya platform.python_version()!
Suriyaa

@ScottGriffiths Juga periksa jawaban saya: stackoverflow.com/a/40633458/5157221 .
Suriyaa

22

Mungkin cara terbaik untuk melakukan perbandingan versi ini adalah dengan menggunakan sys.hexversion. Ini penting karena membandingkan versi tupel tidak akan memberi Anda hasil yang diinginkan di semua versi python.

import sys
if sys.hexversion < 0x02060000:
    print "yep!"
else:
    print "oops!"

Saya pikir ini yang paling elegan, tapi mungkin bukan yang termudah untuk dipahami oleh pengembang lainnya.
Nick Bolton

5
Bisakah Anda menjelaskan dalam keadaan apa membandingkan tupel versi tidak akan memberikan hasil yang diinginkan?
SpoonMeiser

tuple versi dapat berisi nilai alfanumerik juga.
Sorin

8
-1: Ini tidak berfungsi, seperti yang dijelaskan dalam pertanyaan yang diperbarui. Jika Anda menggunakan sintaks dari versi Python yang lebih baru maka file Anda tidak akan dikompilasi, dan jika tidak dikompilasi, tidak dapat berjalan dan memeriksa versinya!
Scott Griffiths

Pada versi / platform mana ini gagal?
sorin

15
import sys    
# prints whether python is version 3 or not
python_version = sys.version_info.major
if python_version == 3:
    print("is python 3")
else:
    print("not python 3")

7
Sadarilah bahwa dengan Python 2.6 dan bawah, sys.version_infoadalah tidak tupel bernama. Anda harus menggunakan sys.version_info[0]nomor versi utama, dan sys.version_info[1]untuk minor.
coredumperror

9

Jawaban dari Nykakin di AskUbuntu :

Anda juga dapat memeriksa versi Python dari kode itu sendiri menggunakan platformmodul dari pustaka standar.

Ada dua fungsi:

  • platform.python_version() (mengembalikan string).
  • platform.python_version_tuple() (mengembalikan tuple).

Kode Python

Buat file misalnya version.py:)

Metode mudah untuk memeriksa versi:

import platform

print(platform.python_version())
print(platform.python_version_tuple())

Anda juga dapat menggunakan evalmetode ini:

try:
  eval("1 if True else 2")
except SyntaxError:
  raise ImportError("requires ternary support")

Jalankan file Python di baris perintah:

$ python version.py 
2.7.11
('2', '7', '11')

Output Python dengan CGI melalui Server WAMP pada Windows 10:

Screenshot 2016-11-16 14.39.01 oleh Suriyaa Kudo


Sumber daya yang bermanfaat


7

Set menjadi bagian dari bahasa inti dalam Python 2.4, agar tetap kompatibel. Saya melakukan ini saat itu, yang akan bekerja untuk Anda juga:

if sys.version_info < (2, 4):
    from sets import Set as set

3
lebih baik memeriksa fitur daripada versi, bukan? try: set except NameError: from sets import Set as set
orip

@orip: Mengapa? Jika Anda tahu di versi mana fitur telah diperkenalkan, seperti set di sini, cukup gunakan kode di atas. Tidak ada yang salah dengan itu.
André

7

Meskipun pertanyaannya adalah: Bagaimana cara mendapatkan kontrol cukup awal untuk mengeluarkan pesan kesalahan dan keluar ?

Pertanyaan yang saya jawab adalah: Bagaimana saya mendapatkan kontrol cukup awal untuk mengeluarkan pesan kesalahan sebelum memulai aplikasi ?

Saya bisa menjawabnya dengan sangat berbeda dari posting lainnya. Tampaknya jawaban sejauh ini mencoba menyelesaikan pertanyaan Anda dari dalam Python.

Saya katakan, lakukan pengecekan versi sebelum meluncurkan Python. Saya melihat jalur Anda adalah Linux atau unix. Namun saya hanya bisa menawarkan Anda skrip Windows. Saya gambar mengadaptasikannya ke sintaks scripting linux tidak akan terlalu sulit.

Berikut ini skrip DOS dengan versi 2.7:

@ECHO OFF
REM see http://ss64.com/nt/for_f.html
FOR /F "tokens=1,2" %%G IN ('"python.exe -V 2>&1"') DO ECHO %%H | find "2.7" > Nul
IF NOT ErrorLevel 1 GOTO Python27
ECHO must use python2.7 or greater
GOTO EOF
:Python27
python.exe tern.py
GOTO EOF
:EOF

Ini tidak menjalankan bagian mana pun dari aplikasi Anda dan karenanya tidak akan meningkatkan Pengecualian Python. Itu tidak membuat file temp atau menambahkan variabel lingkungan OS. Dan itu tidak mengakhiri aplikasi Anda ke pengecualian karena aturan sintaksis versi yang berbeda. Itu tiga titik keamanan akses yang kurang memungkinkan.

The FOR /Fline adalah kuncinya.

FOR /F "tokens=1,2" %%G IN ('"python.exe -V 2>&1"') DO ECHO %%H | find "2.7" > Nul

Untuk beberapa versi python, periksa url: http://www.fpschultze.de/modules/smartfaq/faq.php?faqid=17

Dan versi retas saya:

[Skrip MS; Versi Python memeriksa prelaunch modul Python] http://pastebin.com/aAuJ91FQ


Untuk suara turun itu, jangan takut untuk menjelaskan alasannya.
DevPlayer

Persis yang saya cari. Terima kasih! Apa %% H?
Clocker

@Clocker python.exe -V akan mengembalikan string "Python 2.7". Konsol menempatkan string "Python" ke %% G dan string "2,7" di os var %% H yang dibuat secara otomatis (huruf berikutnya setelah G). Gema %% H | temukan "2.7" pipes "2.7" ke dalam perintah DOS find "2.7" yang mengatur tingkat kesalahan ke 1 jika %% H ditemukan di "2.7". Level kesalahan itu, menghasilkan 1, dari menggunakan perintah DOS find, akan memungkinkan kita untuk bercabang ke label batch DOS: Python27
DevPlayer

3
import sys
sys.version

akan mendapat jawaban seperti ini

'2.7.6 (default, 26 Okt 2016, 20:30:19) \ n [GCC 4.8.4]'

di sini 2.7.6 adalah versi


2

Seperti disebutkan di atas, kesalahan sintaksis terjadi pada waktu kompilasi, bukan pada saat dijalankan. Sementara Python adalah "bahasa yang ditafsirkan", kode Python sebenarnya tidak langsung ditafsirkan; itu dikompilasi ke kode byte, yang kemudian ditafsirkan. Ada langkah kompilasi yang terjadi ketika modul diimpor (jika tidak ada versi yang sudah dikompilasi tersedia dalam bentuk file .pyc atau .pyd) dan saat itulah Anda mendapatkan kesalahan Anda, tidak (tepatnya) ketika kode Anda sedang berjalan.

Anda dapat menunda langkah kompilasi dan mewujudkannya pada saat dijalankan untuk satu baris kode, jika Anda ingin, dengan menggunakan eval, seperti yang disebutkan di atas, tetapi saya pribadi lebih suka menghindari melakukan itu, karena hal itu menyebabkan Python melakukan berpotensi kompilasi run-time yang tidak perlu, untuk satu hal, dan untuk yang lain, itu menciptakan apa yang bagi saya terasa seperti kekacauan kode. (Jika Anda mau, Anda dapat membuat kode yang menghasilkan kode yang menghasilkan kode - dan memiliki waktu yang sangat luar biasa memodifikasi dan men-debug itu dalam 6 bulan dari sekarang.) Jadi yang saya sarankan adalah sesuatu yang lebih seperti ini:

import sys
if sys.hexversion < 0x02060000:
    from my_module_2_5 import thisFunc, thatFunc, theOtherFunc
else:
    from my_module import thisFunc, thatFunc, theOtherFunc

.. yang akan saya lakukan bahkan jika saya hanya memiliki satu fungsi yang menggunakan sintaks yang lebih baru dan itu sangat singkat. (Sebenarnya saya akan mengambil setiap tindakan yang masuk akal untuk meminimalkan jumlah dan ukuran fungsi tersebut. Saya bahkan mungkin menulis fungsi seperti ifTrueAElseB (cond, a, b) dengan satu baris sintaks di dalamnya.)

Hal lain yang mungkin perlu ditunjukkan (bahwa saya sedikit kagum belum ada yang menunjukkan) adalah bahwa sementara versi Python sebelumnya tidak mendukung kode seperti

value = 'yes' if MyVarIsTrue else 'no'

..itu memang mendukung kode suka

value = MyVarIsTrue and 'yes' or 'no'

Itu adalah cara lama menulis ekspresi ternary. Saya belum menginstal Python 3, tetapi sejauh yang saya tahu, cara "lama" itu masih berfungsi hingga hari ini, jadi Anda dapat memutuskan sendiri apakah layak menggunakan kondisional baru dengan syarat, jika Anda perlu untuk mendukung penggunaan versi Python yang lebih lama.


2
Serius? Gandakan kode Anda hanya agar Anda dapat mengubah beberapa struktur kecil? Yuck. Yuck sangat. Dan a and b or calih - alih b if a else c, itu tidak setara; jika bpalsu itu akan gagal, menghasilkan abukan b.
Chris Morgan

1
Saya tidak menyarankan kode duplikat, saya menyarankan untuk membuat fungsi wrapper untuk kode versi spesifik, yang tanda tangannya tidak berubah di seluruh versi, dan menempatkan fungsi-fungsi tersebut dalam modul versi khusus. Saya berbicara tentang fungsi yang panjangnya mungkin 1 hingga 5 baris. Memang benar bahwa a dan b atau c tidak sama dengan b jika a lain c dalam kasus di mana b dapat dievaluasi salah. Jadi saya kira ifAThenBElseC (a, b, c) di common_ops_2_4.py, harus sepanjang 2 atau 3 baris, bukan 1. Metode ini sebenarnya mengurangi seluruh kode Anda dengan merangkum idiom umum menjadi fungsi.
Shavais

2

Letakkan yang berikut di bagian paling atas file Anda:

import sys

if float(sys.version.split()[0][:3]) < 2.7:
    print "Python 2.7 or higher required to run this code, " + sys.version.split()[0] + " detected, exiting."
    exit(1)

Kemudian lanjutkan dengan kode Python normal:

import ...
import ...
other code...

1
lebih suka menggunakansys.version_info < (2, 7)
Antti Haapala

@AnttiHaapala ini sangat cocok untuk saya, dapatkah Anda menjelaskan mengapa membandingkan sys.version_infojenis ini dengan tuple berfungsi?
jjj

@ jjj sys.version_info dulu tuple; misalnya (2, 4, 6, 'final', 0); hanya dalam Python 3 dan 2,7 itu diubah menjadi tipe yang terpisah, yang bagaimanapun sebanding dengan tupel.
Antti Haapala

@AnttiHaapala Saya suka pendekatan itu lebih baik daripada saya ... terima kasih!
jml

1

Saya pikir cara terbaik adalah menguji fungsionalitas daripada versi. Dalam beberapa kasus, ini sepele, tidak demikian dalam kasus lain.

misalnya:

try :
    # Do stuff
except : # Features weren't found.
    # Do stuff for older versions.

Selama Anda cukup spesifik dalam menggunakan blok coba / kecuali, Anda dapat mencakup sebagian besar pangkalan Anda.


2
Kamu benar. Itulah yang dia tanyakan bagaimana caranya - terkadang menguji fitur di versi Y bahkan tidak bisa dikompilasi menjadi bytecode di versi X, jadi itu tidak bisa dilakukan secara langsung.
orip

1

Saya baru saja menemukan pertanyaan ini setelah pencarian cepat sambil mencoba menyelesaikan masalah sendiri dan saya telah menghasilkan hibrida berdasarkan beberapa saran di atas.

Saya suka ide DevPlayer menggunakan skrip pembungkus, tetapi downside adalah bahwa Anda akhirnya mempertahankan beberapa pembungkus untuk OS yang berbeda, jadi saya memutuskan untuk menulis pembungkus dengan python, tetapi menggunakan dasar yang sama "ambil versi dengan menjalankan exe" logika dan datang dengan ini.

Saya pikir itu harus bekerja untuk 2,5 dan seterusnya. Saya sudah mengujinya pada 2.66, 2.7.0 dan 3.1.2 di Linux dan 2.6.1 pada OS X sejauh ini.

import sys, subprocess
args = [sys.executable,"--version"]

output, error = subprocess.Popen(args ,stdout = subprocess.PIPE, stderr = subprocess.PIPE).communicate()
print("The version is: '%s'"  %error.decode(sys.stdout.encoding).strip("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLMNBVCXZ,.+ \n") )

Ya, saya tahu decode / strip akhir mengerikan, tetapi saya hanya ingin cepat mengambil nomor versi. Aku akan memperbaikinya.

Ini bekerja cukup baik untuk saya untuk saat ini, tetapi jika ada yang bisa memperbaikinya (atau memberi tahu saya mengapa itu ide yang buruk) itu akan keren juga.


1

Untuk skrip python mandiri , trik mendokumentasikan modul berikut untuk menegakkan versi python (di sini v2.7.x) berfungsi (diuji pada * nix).

#!/bin/sh
''''python -V 2>&1 | grep -q 2.7 && exec python -u -- "$0" ${1+"$@"}; echo "python 2.7.x missing"; exit 1 # '''

import sys
[...]

Ini harus menangani executable python yang hilang juga tetapi memiliki ketergantungan pada grep. Lihat di sini untuk latar belakang.


0

Anda dapat memeriksa dengan sys.hexversionatau sys.version_info.

sys.hexversiontidak terlalu ramah manusia karena merupakan angka heksadesimal. sys.version_infoadalah tuple, jadi lebih ramah manusia.

Periksa Python 3.6 atau yang lebih baru dengan sys.hexversion:

import sys, time
if sys.hexversion < 0x30600F0:
    print("You need Python 3.6 or greater.")
    for _ in range(1, 5): time.sleep(1)
    exit()

Periksa Python 3.6 atau yang lebih baru dengan sys.version_info:

import sys, time
if sys.version_info[0] < 3 and sys.version_info[1] < 6:
    print("You need Python 3.6 or greater.")
    for _ in range(1, 5): time.sleep(1)
    exit()

sys.version_infolebih ramah manusia, tetapi membutuhkan lebih banyak karakter. Saya akan merekomendasikansys.hexversion , meskipun kurang ramah manusia.

Saya harap ini membantu Anda!


0

Saya memperluas jawaban yang sangat baik dari akhan, yang mencetak pesan yang membantu sebelum skrip Python dikompilasi.

Jika Anda ingin memastikan bahwa skrip sedang dijalankan dengan Python 3.6 atau yang lebih baru, tambahkan dua baris ini di bagian atas skrip Python Anda:

#!/bin/sh
''''python3 -c 'import sys; sys.exit(sys.version_info < (3, 6))' && exec python3 -u -- "$0" ${1+"$@"}; echo 'This script requires Python 3.6 or newer.'; exit 1 # '''

(Catatan: Baris kedua dimulai dengan empat tanda kutip tunggal dan berakhir dengan tiga tanda kutip tunggal. Ini mungkin terlihat aneh, tetapi ini bukan kesalahan ketik.)

Keuntungan dari solusi ini adalah bahwa kode seperti print(f'Hello, {name}!')tidak akan menyebabkan SyntaxErrorjika versi Python yang lebih lama dari 3,6 digunakan. Anda akan melihat pesan bermanfaat ini sebagai gantinya:

This script requires Python 3.6 or newer.

Tentu saja, solusi ini hanya bekerja pada shell mirip Unix, dan hanya ketika skrip dipanggil secara langsung (seperti ./script.py:), dan dengan bit izin eXecute yang tepat ditetapkan.


-2

Bagaimana dengan ini:

import sys

def testPyVer(reqver):
  if float(sys.version[:3]) >= reqver:
    return 1
  else:
    return 0

#blah blah blah, more code

if testPyVer(3.0) == 1:
  #do stuff
else:
  #print python requirement, exit statement

5
-1: Ini tidak berfungsi, seperti yang dijelaskan dalam pertanyaan yang diperbarui. Jika Anda menggunakan sintaks dari versi Python yang lebih baru maka file Anda tidak akan dikompilasi, dan jika tidak dikompilasi, tidak dapat berjalan dan memeriksa versinya!
Scott Griffiths

-3

Masalahnya cukup sederhana. Anda memeriksa apakah versinya kurang dari 2.4, tidak kurang dari atau sama dengan . Jadi jika versi Python adalah 2.4, itu tidak kurang dari 2.4. Apa yang seharusnya Anda miliki adalah:

    if sys.version_info **<=** (2, 4):

tidak

    if sys.version_info < (2, 4):

4
baca paragraf 3, dan pembaruannya. Anda tidak langsung mengeksekusi kode ini karena kode Anda tidak dapat dikompilasi pada 2.4 jika Anda menggunakan konstruksi bahasa baru.
Mark Harrison

Kurang dari itu baik-baik saja, hanya hilang eval.
Craig
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.