Membaca pesan error ini
Ketika Anda menjalankan kode Python yang menggunakan async with dan mendapati program berhenti secara tiba-tiba, kemungkinan besar Anda sedang berhadapan dengan salah satu error yang cukup umum di kalangan pengembang yang bekerja dengan asynchronous programming. Pesan error yang muncul di terminal Anda biasanya terlihat seperti berikut:
SyntaxError: 'async with' outside async functionAtau pada beberapa versi Python yang lebih lama, pesannya bisa sedikit berbeda:
SyntaxError: asynchronous context manager 'async with' outside async functionPesan ini secara harfiah memberi tahu Anda bahwa sintaksis async with yang Anda tulis berada di luar fungsi asinkron. Dalam bahasa Indonesia sederhananya: Anda mencoba menggunakan perintah yang dirancang khusus untuk lingkungan asinkron, tetapi perintah tersebut diletakkan di tempat yang bukan merupakan lingkungan asinkron. Ini seperti mencoba menyalakan mesin jet di dalam garasi biasa — mesinnya memang bisa jalan, tapi konteksnya salah total.
Error ini muncul karena Python membedakan secara ketat antara dunia sinkronus dan asinkronus. Pernyataan async with adalah sebuah asynchronous context manager yang membutuhkan fungsi pembungkusnya bertipe async def. Ketika interpreter Python mem-parsing kode Anda dan menemukan async with di dalam fungsi biasa (yang didefinisikan dengan def biasa) atau di level modul secara langsung, interpreter akan segera menolaknya karena melanggar aturan sintaksis bahasa Python.
Perhatikan contoh kode berikut yang akan memicu error ini:
import aiohttp
def fetch_data():
async with aiohttp.ClientSession() as session:
response = await session.get('https://api.example.com/data')
return await response.json()Ketika Anda menjalankan file tersebut dengan perintah python app.py, output yang akan muncul adalah:
File "app.py", line 4
async with aiohttp.ClientSession() as session:
^
SyntaxError: 'async with' outside async functionTanda panah menunjuk tepat ke baris di mana async with ditulis. Interpreter Python sudah mendeteksi masalah ini bahkan sebelum kode dijalankan — error ini merupakan SyntaxError, yang berarti terjadi pada saat parsing, bukan pada saat eksekusi. Ini penting untuk dipahami karena artinya kode Anda tidak akan pernah berjalan sama sekali selama kesalahan sintaksis ini masih ada, tidak peduli bagaimana pun Anda mencoba menangkapnya dengan try-except.
Penyebab umum
Ada beberapa akar masalah yang paling sering menjadi biang kerok di balik error ini. Urutan berikut disusun dari yang paling sering ditemui di lapangan berdasarkan pengalaman komunitas pengembang Python:
Mendefinisikan fungsi dengan
defbiasa, bukanasync def. Ini adalah penyebab paling dominan. Banyak pengembang, terutama yang baru beralih ke asynchronous programming, terbiasa menulisdefdan lupa bahwa setiap kali mereka ingin menggunakanasync withatauawaitdi dalam fungsi, fungsi tersebut harus didefinisikan denganasync def. Kesalahan ini sering terjadi saat refactoring kode sinkronus menjadi asinkronus — bagianasync withsudah diubah, tapi deklarasi fungsinya tertinggal.Menempatkan
async withdi level modul (top-level). Beberapa pengembang mencoba menulisasync withlangsung di badan skrip tanpa membungkusnya dalam fungsi asinkron apapun. Python tidak mengizinkan pernyataanasync withdi level modul karena modul dieksekusi secara sinkronus saat diimpor. Satu-satunya pengecualian adalah penggunaanasyncio.run()yang membungkus pemanggilan fungsi asinkron.Salah indentasi yang menyebabkan
async withberada di luar scope fungsi asinkron. Ketika indentasi tidak tepat,async withbisa saja secara tidak sengaja "keluar" dari blok fungsiasync def. Ini terutama sering terjadi pada kode yang sudah banyak di-edit atau di-copy-paste dari berbagai sumber. Di Python, indentasi adalah bagian dari sintaksis, sehingga satu spasi pun bisa membuat perbedaan besar.Mendelegasikan pemanggilan asinkron ke fungsi helper yang bukan asinkron. Pola ini muncul ketika pengembang memisahkan logika utama (asinkron) ke dalam helper function, tetapi lupa menandai helper tersebut sebagai
async def. Misalnya, fungsimain()ditulis sebagaiasync def, tetapi helperprocess_request()yang memanggilasync withtetap ditulis sebagaidefbiasa.IDE atau linter yang tidak dikonfigurasi dengan benar sehingga tidak menampilkan peringatan. Tanpa bantuan static analysis yang tepat, kesalahan ini baru terdeteksi saat runtime, padahal seharusnya bisa dicegah sejak awal penulisan.
Untuk memastikan penyebab mana yang sedang Anda alami, periksa hal berikut secara berurutan. Pertama, lihat baris yang ditunjuk oleh error — apakah async with tersebut berada di dalam fungsi yang diawali async def? Kedua, periksa indentasi — apakah async with benar-benar berada di dalam blok fungsi tersebut, bukan di luar karena salah indent? Ketiga, apakah async with ditulis langsung di level modul tanpa fungsi pembungkus? Dengan tiga pertanyaan sederhana ini, Anda bisa mengidentifikasi akar masalah dengan cepat.
Cara memperbaiki
Perbaikan untuk error ini bersifat langsung dan mekanis. Berikut adalah langkah-langkah yang dapat Anda ikuti sesuai dengan penyebab yang telah diidentifikasikan.
Langkah 1: Ubah deklarasi fungsi menjadi async def
Jika async with berada di dalam fungsi yang didefinisikan dengan def biasa, ubah menjadi async def:
# SEBELUM (error)
import aiohttp
def fetch_data(url):
async with aiohttp.ClientSession() as session:
response = await session.get(url)
return await response.json()
# SESUDAH (fixed)
import aiohttp
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
response = await session.get(url)
return await response.json()Setelah dijalankan dengan python app.py, jika tidak ada error lain, Anda tidak akan lagi melihat pesan SyntaxError. Namun, ingat bahwa fungsi asinkron harus dipanggil dengan await atau melalui asyncio.run().
Langkah 2: Bungkus async with yang berada di level modul
Jika async with ditulis langsung di level modul, bungkuslah dalam fungsi asinkron dan panggil via asyncio.run():
# SEBELUM (error)
import asyncio
import aiohttp
async with aiohttp.ClientSession() as session:
response = await session.get('https://api.example.com/data')
data = await response.json()
print(data)
# SESUDAH (fixed)
import asyncio
import aiohttp
async def main():
async with aiohttp.ClientSession() as session:
response = await session.get('https://api.example.com/data')
data = await response.json()
print(data)
asyncio.run(main())Yang akan kamu lihat ketika dijalankan:
{'key': 'value', 'status': 'ok'}Langkah 3: Perbaiki indentasi
# SEBELUM (error - async with keluar dari scope async def)
async def process():
pass
async with aiohttp.ClientSession() as session:
await session.get('https://api.example.com')
# SESUDAH (fixed - async with berada di dalam async def)
async def process():
async with aiohttp.ClientSession() as session:
await session.get('https://api.example.com')Langkah 4: Ubah helper function menjadi asinkron
# SEBELUM (error)
async def main():
result = process_request() # Memanggil helper yang tidak async
return result
def process_request():
async with aiohttp.ClientSession() as session:
return await session.get('https://api.example.com')
# SESUDAH (fixed)
async def main():
result = await process_request() # Panggil dengan await
return result
async def process_request():
async with aiohttp.ClientSession() as session:
return await session.get('https://api.example.com')Verifikasi solusi berhasil:
Untuk memastikan perbaikan Anda benar-benar bekerja, jalankan perintah berikut di terminal:
python -c "import py_compile; py_compile.compile('app.py', doraise=True)"Jika tidak ada output apapun, berarti file Anda lolos pemeriksaan sintaksis. Selain itu, Anda juga bisa menggunakan mypy untuk pemeriksaan tipe yang lebih mendalam:
mypy app.pyHasil yang muncul:
Success: no issues found in 1 source fileSolusi langsungnya adalah: setiap kali Anda menulis async with, pastikan ia berada di dalam fungsi async def. Ini adalah aturan mutlak di Python yang tidak bisa dihindari atau dilewati dengan trik apapun. Perbaikan ini bersifat permanen selama Anda konsisten menerapkan konvensi penamaan async def untuk setiap fungsi yang mengandung operasi asinkron.
Supaya tidak terulang
Mencegah error ini muncul kembali membutuhkan kombinasi antara konfigurasi tool yang tepat dan penerapan Praktik terbaik dalam penulisan kode. Berikut adalah pendekatan menyeluruh yang bisa Anda terapkan.
Konfigurasi linter dan type checker
Langkah paling efektif untuk mencegah error ini terulang adalah memasang tool yang mendeteksi masalah sebelum kode dijalankan. Ruff, sebuah linter Python modern yang sangat cepat, mampu mendeteksi penggunaan async with di luar fungsi asinkron dengan aturan ASYNC-nya. Konfigurasikan Ruff di file pyproject.toml Anda:
[tool.ruff]
select = ["ASYNC", "E", "F"]
[tool.ruff.lint]
extend-select = ["ASYNC"]Jalankan Ruff secara berkala:
ruff check app.pyOutput jika ada masalah:
app.py:4:5: ASYNC100 `async with` used outside async functionSelain Ruff, mypy dengan flags ketat juga membantu:
mypy --strict app.pyPre-commit hook
Agar pemeriksaan berjalan otomatis setiap kali Anda ingin melakukan commit, pasang pre-commit hook:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
args: [--fix]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypyPasang dengan:
pre-commit installMulai sekarang, setiap kali Anda menjalankan git commit, Ruff dan mypy akan memeriksa kode Anda secara otomatis. Jika ada async with di luar fungsi asinkron, commit akan ditolak.
Praktik terbaik penulisan kode asinkron
Terapkan beberapa konvensi berikut dalam tim Anda. Pertama, gunakan prefiks async_ pada nama fungsi asinkron untuk memudahkan identifikasi, misalnya async_fetch_data() alih-alih sekadar fetch_data(). Kedua, selalu tulis dekorator atau docstring yang menjelaskan bahwa fungsi bersifat asinkron. Ketiga, buat aturan bahwa setiap modul yang mengimpor library asinkron seperti aiohttp, aioredis, atau asyncpg wajib mendefinisikan fungsi utamanya sebagai async def. Keempat, gunakan tipe hint secara konsisten — fungsi asinkron yang mengembalikan str seharusnya ditandai dengan async def func() -> str, bukan def func() -> Coroutine[Any, Any, str].
Perbedaan antar sistem operasi
Secara umum, error ini tidak memiliki perbedaan perilaku antar OS karena merupakan SyntaxError level bahasa Python. Namun, ada nuansa penting terkait event loop. Di Windows, asyncio.run() menggunakan ProactorEventLoop secara default sejak Python 3.8, yang memiliki karakteristik berbeda dari SelectorEventLoop di Linux dan macOS. Ini tidak mempengaruhi munculnya SyntaxError, tetapi mempengaruhi bagaimana kode asinkron Anda berjalan setelah error diperbaiki. Jika Anda menggunakan subprocess asinkron di Windows, Anda mungkin perlu menambahkan konfigurasi tambahan:
import asyncio
import sys
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
async def main():
async with aiohttp.ClientSession() as session:
pass
asyncio.run(main())Di Linux dan macOS, konfigurasi di atas tidak diperlukan karena SelectorEventLoop sudah menjadi default.
Diagnosis lebih dalam
Jika Anda sudah menerapkan semua solusi di atas namun error masih muncul, atau muncul error baru yang berkaitan, maka diperlukan pendekatan diagnosis yang lebih mendalam. Ada beberapa skenario di mana solusi standar tidak langsung bekerja.
Skenario: error muncul akibat circular import atau dynamic code evaluation
Jika kode Anda menggunakan exec(), eval(), atau memuat modul secara dinamis, pesan error bisa muncul di tempat yang tidak terduga. Dalam kasus ini, traceback Python mungkin menunjuk ke file <string> atau <eval> alih-alih file sumber Anda yang sebenarnya. Untuk mendiagnosisnya, jalankan Python dengan flag verbose:
python -v app.py 2>&1 | grep -i "import\|async"Flag -v akan mencatat setiap impor modul yang dilakukan Python, sehingga Anda bisa melihat urutan pemuatan modul dan mengidentifikasi apakah ada modul yang memuat kode asinkron secara tidak tepat.
Membaca log dengan teliti
Ketika SyntaxError muncul, Python selalu menyertakan nomor baris dan posisi kolom. Perhatikan baik-baik informasi ini. Jika nomor baris yang ditunjuk tidak mengandung async with, kemungkinan ada masalah di baris sebelumnya yang membuat parser Python bingung — misalnya, tanda kurung yang tidak tertutup atau string yang tidak diakhiri. Gunakan perintah berikut untuk melihat konteks di sekitar baris error:
sed -n '1,10p' app.py | cat -nAtau gunakan Python untuk mengecek sintaksis secara granular:
import ast
import sys
try:
with open('app.py', 'r') as f:
ast.parse(f.read())
print("Tidak ada syntax error")
except SyntaxError as e:
print(f"Error di baris {e.lineno}, kolom {e.offset}: {e.msg}")
print(f"Text: {e.text}")Hasil yang muncul:
Error di baris 4, kolom 5: 'async with' outside async function
Text: async with aiohttp.ClientSession() as session:Kasus khusus: fixture testing dan decorator
Di framework seperti pytest, penggunaan decorator @pytest.fixture dengan fungsi asinkron sering membingungkan. Jika Anda menggunakan pytest-asyncio, fixture asinkron harus didefinisikan dengan async def dan ditandai dengan decorator yang tepat:
# SEBELUM (berpotensi error bila konfigurasi salah)
@pytest.fixture
def db_session():
async with aiosqlite.connect('test.db') as db:
yield db
# SESUDAH (benar)
@pytest.fixture
async def db_session():
async with aiosqlite.connect('test.db') as db:
yield dbTanpa konfigurasi asyncio_mode = "auto" di pytest.ini, pytest mungkin tidak mengenali fixture asinkron dengan benar, yang bisa memicu error tidak langsung.
Bacaan resmi terkait
Untuk pemahaman yang lebih mendalam tentang mekanisme asynchronous context manager di Python, dokumentasi resmi menyediakan penjelasan lengkap. Halaman Asynchronous Context Managers di Python Language Reference menjelaskan bagaimana protokol __aenter__ dan __aexit__ bekerja di balik layar. Halaman Coroutine dan Task di dokumentasi asyncio memberikan panduan komprehensif tentang cara menulis, menjadwalkan, dan mengelola coroutine. Selain itu, PEP 492 yang memperkenalkan async dan await ke dalam Python menjelaskan rasional desain di balik aturan bahwa async with hanya boleh digunakan di dalam fungsi asinkron — sebuah pembatasan yang sengaja dibuat untuk menjaga kejelasan dan determinisme eksekusi pada level bahasa.
Jika Anda ingin memahami kenapa Python membuat keputusan desain ini, PEP 492 adalah bacaan wajib. Inti filosofinya adalah bahwa kode asinkron dan sinkron harus dipisahkan secara eksplisit agar programmer tidak secara tidak sengaja mencampur keduanya, yang bisa menyebabkan bug yang sangat sulit dilacak. SyntaxError ini, meskipun terasa mengganggu, sebenarnya adalah fitur keamanan yang melindungi Anda dari kesalahan yang lebih besar di kemudian hari.