Bagaimana saya bisa memahami klausa `lain` dari loop Python?


191

Banyak programmer Python mungkin tidak menyadari bahwa sintaks dari whileloop dan forloop menyertakan else:klausa opsional :

for val in iterable:
    do_something(val)
else:
    clean_up()

Tubuh elseklausa adalah tempat yang baik untuk beberapa jenis tindakan pembersihan, dan dijalankan pada terminasi loop normal: Yaitu, keluar dari loop dengan returnatau breakmelewatkan elseklausa; keluar setelah continuemenjalankannya. Aku tahu ini hanya karena saya hanya melihat ke atas (lagi), karena saya tidak pernah bisa mengingat saat ini elseklausul dijalankan.

Selalu? Pada "kegagalan" dari loop, seperti namanya? Pemutusan hubungan kerja reguler? Bahkan jika loop keluar dengan return? Saya tidak pernah bisa sepenuhnya yakin tanpa mencarinya.

Saya menyalahkan ketidakpastian yang terus-menerus saya pada pilihan kata kunci: Saya menemukan elsesangat tidak kesalahan untuk semantik ini. Pertanyaan saya bukanlah "mengapa kata kunci ini digunakan untuk tujuan ini" (yang mungkin akan saya pilih untuk ditutup, meskipun hanya setelah membaca jawaban dan komentar), tetapi bagaimana saya bisa memikirkan elsekata kunci sehingga semantiknya masuk akal, dan saya dapatkah karena itu mengingatnya?

Saya yakin ada cukup banyak diskusi tentang ini, dan saya bisa membayangkan bahwa pilihan dibuat untuk konsistensi dengan klausa trypernyataan else:(yang saya juga harus melihat ke atas), dan dengan tujuan tidak menambah daftar Kata-kata Python cadangan. Mungkin alasan untuk memilih elseakan memperjelas fungsinya dan membuatnya lebih mudah diingat, tapi saya setelah menghubungkan nama dengan fungsi, bukan setelah penjelasan historis semata.

Jawaban atas pertanyaan ini , yang secara singkat ditutup oleh pertanyaan saya sebagai duplikat, berisi banyak cerita belakang yang menarik. Pertanyaan saya memiliki fokus yang berbeda (bagaimana menghubungkan semantik spesifik elsedengan pilihan kata kunci), tetapi saya merasa harus ada tautan ke pertanyaan ini di suatu tempat.


23
Bagaimana dengan "jika ada sesuatu yang tersisa untuk beralih ... lain"
OneCricketeer

4
Saya pikir Anda dapat mengingatnya sekarang setelah menulis pertanyaan ini :)
Jasper

11
yang elseberarti pada dasarnya, "jika kondisi kelanjutan gagal". Dalam perulangan tradisional, kondisi kelanjutan biasanya i < 42, dalam hal ini, Anda dapat melihat bagian itu sebagaiif i < 42; execute the loop body; else; do that other thing
njzk2

1
Ini semua benar, dan saya terutama menyukai jawaban drawoc, tetapi hal lain yang perlu dipertimbangkan adalah bahwa kata kunci lain yang tersedia juga masuk akal secara sintaksis. Anda mungkin tahu mencoba / kecuali dan mungkin mencoba / kecuali / akhirnya, tetapi juga memiliki lain - menjalankan kode ini jika tidak terkecuali terjadi. Btw, bukan hal yang sama dengan mendorong kode ini di bawah klausa coba - penanganan pengecualian paling baik digunakan ketika ditargetkan secara sempit. Jadi, meskipun masuk akal secara konseptual - sesuai sejumlah jawaban di sini - saya pikir kata kunci juga digunakan kembali - jalankan ini dalam kondisi tertentu .
JL Peyret

1
@Falanwe, ada perbedaan ketika kode keluar oleh break. Case use kanonik adalah ketika loop mencari sesuatu, dan rusak ketika menemukannya. Ini elsedieksekusi hanya jika tidak ada yang ditemukan.
alexis

Jawaban:


212

(Ini terinspirasi oleh jawaban @ Mark Tolonen.)

Sebuah ifpernyataan menjalankan nya elseklausul jika kondisinya false. Secara identik, whileloop menjalankan klausa lain jika kondisinya bernilai false.

Aturan ini cocok dengan perilaku yang Anda jelaskan:

  • Dalam eksekusi normal, loop sementara berulang kali berjalan hingga kondisi bernilai false, dan karenanya keluar secara normal loop menjalankan klausa lain.
  • Saat Anda menjalankan breakpernyataan, Anda keluar dari loop tanpa mengevaluasi kondisinya, sehingga kondisinya tidak dapat mengevaluasi false dan Anda tidak pernah menjalankan klausa yang lain.
  • Ketika Anda menjalankan continuepernyataan, Anda mengevaluasi kondisi lagi, dan melakukan apa yang biasanya Anda lakukan di awal perulangan lingkaran. Jadi, jika kondisinya benar, Anda terus mengulang, tetapi jika itu salah Anda menjalankan klausa lain.
  • Metode lain untuk keluar dari loop, seperti return, tidak mengevaluasi kondisi dan karena itu tidak menjalankan klausa lain.

forloop berperilaku dengan cara yang sama. Anggap saja kondisinya benar jika iterator memiliki lebih banyak elemen, atau salah jika tidak.


8
Ini adalah jawaban yang paling bagus. Perlakukan loop Anda seperti serangkaian pernyataan elif dan perilaku yang lain akan mengekspos logika alaminya.
Nomenator

1
Saya juga suka jawaban ini, tetapi tidak menggambar analogi dengan serangkaian elifpernyataan. Ada jawaban yang sesuai, dan memiliki satu upvote bersih.
alexis

2
baik tidak persis, loop sementara bisa memiliki kondisi memenuhi False tepat sebelum itu break, dalam hal elseini tidak akan berjalan tetapi kondisinya False. Demikian pula dengan forloop dapat breakpada elemen terakhir.
Tadhg McDonald-Jensen

36

Lebih baik memikirkannya seperti ini: elseBlok akan selalu dieksekusi jika semuanya berjalan dengan baik di forblok sebelumnya sehingga mencapai kelelahan.

Tepat dalam konteks ini berarti tidak exception, tidak break, tidak return. Pernyataan apa pun yang membajak kendali forakan menyebabkan elseblok dilewati.


Kasus penggunaan umum ditemukan ketika mencari item dalam iterable, yang pencariannya dimatikan ketika item ditemukan atau "not found"bendera dinaikkan / dicetak melalui elseblok berikut :

for items in basket:
    if isinstance(item, Egg):
        break
else:
    print("No eggs in basket")  

A continuetidak membajak kontrol dari for, jadi kontrol akan dilanjutkan ke elsesetelah forhabis.


20
Kedengarannya sangat bagus ... tapi kemudian Anda mengharapkan elseklausa akan dieksekusi ketika segalanya tidak berjalan dengan benar, bukan? Saya sudah bingung lagi ...
alexis

Saya harus tidak setuju dengan Anda pada "Secara teknis, Ini tidak [semantik mirip dengan yang lain else]", karena elsedijalankan ketika tidak ada kondisi di loop untuk mengevaluasi ke True, seperti yang saya tunjukkan dalam jawaban saya
Tadhg McDonald- Jensen

@ TadhgMcDonald-Jensen Anda juga dapat memutus perulangan pada a False. Jadi pertanyaan tentang bagaimana foryang rusak tergantung pada kasus penggunaan.
Moses Koledoye

Itu benar, saya meminta cara untuk entah bagaimana menghubungkan apa yang terjadi dengan makna bahasa Inggris "lain" (yang memang tercermin dalam penggunaan lain elsedalam python). Anda memberikan ringkasan intuitif yang bagus tentang apa yang elsedilakukan, @Moses, tetapi tidak tentang bagaimana kami dapat mengaitkan perilaku ini dengan "lain". Jika kata kunci yang berbeda digunakan (misalnya, nobreakseperti yang disebutkan dalam jawaban untuk pertanyaan terkait), akan lebih mudah untuk dimengerti.
alexis

1
Ini benar-benar tidak ada hubungannya dengan "segalanya berjalan dengan benar". Yang lain dieksekusi murni ketika if/ whilekondisi menilai false atau forkehabisan item. breakada loop berisi (setelah else). continuekembali dan mengevaluasi kondisi loop lagi.
Mark Tolonen

31

Kapan suatu ifeksekusi else? Ketika kondisinya salah. Persis sama untuk while/ else. Jadi Anda dapat menganggap while/ elsehanya sebagai ifyang terus menjalankan kondisi sebenarnya hingga bernilai salah. A breaktidak mengubah itu. Itu hanya melompat keluar dari loop yang berisi tanpa evaluasi. The elsehanya dijalankan jika mengevaluasi yang if/ whilekondisi adalah palsu.

The formirip, kecuali kondisi yang palsu melelahkan iterator nya.

continuedan breakjangan dieksekusi else. Itu bukan fungsi mereka. The breakkeluar loop mengandung. The continuekembali ke atas loop yang mengandung, di mana kondisi loop dievaluasi. Ini adalah tindakan mengevaluasi if/ whileke false (atau fortidak memiliki item lagi) yang dijalankan elsedan tidak ada cara lain.


1
Apa yang Anda katakan terdengar sangat masuk akal, tetapi menggabungkan ketiga kondisi penghentian bersama-sama, "sampai [kondisi] False atau break / continue", salah: Yang terpenting, elseklausa dieksekusi jika loop keluar dengan continue(atau biasanya), tetapi tidak jika kita keluar dengan break. Kehalusan ini adalah alasan mengapa saya mencoba untuk benar-benar elsemenangkap apa yang ditangkap dan apa yang tidak.
alexis

4
@ Alex ya saya perlu mengklarifikasi di sana. Diedit. melanjutkan tidak mengeksekusi yang lain, tetapi kembali ke atas loop yang kemudian dapat dinilai salah.
Mark Tolonen

24

Inilah arti dasarnya:

for/while ...:
    if ...:
        break
if there was a break:
    pass
else:
    ...

Ini adalah cara yang lebih baik untuk menulis pola umum ini:

found = False
for/while ...:
    if ...:
        found = True
        break
if not found:
    ...

The elseklausul tidak akan dieksekusi jika ada returnkarena returndaun fungsi, seperti yang dimaksudkan untuk. Satu-satunya pengecualian untuk apa yang mungkin Anda pikirkan adalah finally, yang tujuannya adalah untuk memastikan bahwa itu selalu dieksekusi.

continuetidak ada hubungannya dengan masalah ini. Ini menyebabkan iterasi saat ini dari loop ke ujung yang mungkin terjadi untuk mengakhiri seluruh loop, dan jelas dalam kasus itu loop tidak berakhir dengan a break.

try/else serupa:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...

20

Jika Anda menganggap loop Anda sebagai struktur yang mirip dengan ini (kode pseudo-agak):

loop:
if condition then

   ... //execute body
   goto loop
else
   ...

mungkin lebih masuk akal. Loop pada dasarnya hanyalah ifpernyataan yang diulang sampai kondisinya false. Dan ini adalah poin penting. Loop memeriksa kondisinya dan melihat bahwa itu false, sehingga menjalankanelse (seperti normal if/else) dan kemudian loop dilakukan.

Jadi perhatikan bahwa else hanya eksekusi yang dieksekusi ketika kondisi diperiksa . Itu berarti bahwa jika Anda keluar dari tubuh loop di tengah eksekusi dengan misalnya a returnatau a break, karena kondisi tidak dicentang lagi,else kasing tidak akan dieksekusi.

A continuedi sisi lain menghentikan eksekusi saat ini dan kemudian melompat kembali untuk memeriksa kondisi loop lagi, itulah sebabnya elsedapat dicapai dalam skenario ini.


Saya sangat suka jawaban ini, tetapi Anda dapat menyederhanakan: Abaikan endlabel dan letakkan saja bagian goto loopdalam iftubuhnya. Mungkin bahkan ketinggalan jaman dengan meletakkannya ifpada garis yang sama dengan labelnya, dan tiba-tiba terlihat seperti orignal.
Bergi

@Bergi Ya, saya pikir itu membuatnya sedikit lebih jelas, terima kasih.
Keiwan

15

Momen getcha saya dengan elseklausa loop adalah ketika saya menonton ceramah oleh Raymond Hettinger , yang menceritakan sebuah kisah tentang bagaimana dia pikir itu seharusnya dipanggil nobreak. Lihatlah kode berikut, menurut Anda apa yang akan dilakukan?

for i in range(10):
    if test(i):
        break
    # ... work with i
nobreak:
    print('Loop completed')

Apa yang akan Anda duga? Nah, bagian yang mengatakan nobreakhanya akan dieksekusi jika sebuah breakpernyataan tidak mengenai loop.


8

Biasanya saya cenderung memikirkan struktur loop seperti ini:

for item in my_sequence:
    if logic(item):
        do_something(item)
        break

Menjadi sangat mirip sejumlah variabel if/elifpernyataan:

if logic(my_seq[0]):
    do_something(my_seq[0])
elif logic(my_seq[1]):
    do_something(my_seq[1])
elif logic(my_seq[2]):
    do_something(my_seq[2])
....
elif logic(my_seq[-1]):
    do_something(my_seq[-1])

Dalam hal ini elsepernyataan pada for loop bekerja persis seperti elsepernyataan pada rantai elifs, ia hanya mengeksekusi jika tidak ada kondisi sebelum mengevaluasi ke True. (atau hentikan eksekusi dengan returnatau pengecualian) Jika loop saya tidak sesuai dengan spesifikasi ini biasanya saya memilih untuk tidak menggunakan for: elsekarena alasan yang tepat Anda memposting pertanyaan ini: itu tidak intuitif.


Baik. Tetapi satu loop berjalan beberapa kali, jadi sedikit tidak jelas bagaimana Anda bermaksud menerapkan ini pada for-loop. Bisakah Anda mengklarifikasi?
alexis

@alexis Saya telah mengulang jawaban saya, saya pikir ini jauh lebih jelas sekarang.
Tadhg McDonald-Jensen

7

Yang lain sudah menjelaskan mekanika while/for...else, dan referensi bahasa Python 3 memiliki definisi otoritatif (lihat sementara dan untuk ), tetapi ini adalah mnemonik pribadi saya, FWIW. Saya kira kuncinya bagi saya adalah untuk memecah ini menjadi dua bagian: satu untuk memahami arti darielse dalam kaitannya dengan loop kondisional, dan satu untuk memahami kontrol loop.

Saya merasa paling mudah untuk memulai dengan memahami while...else:

whileAnda memiliki lebih banyak item, lakukan hal-hal, elsejika Anda kehabisan, lakukan ini

The for...elsemnemonic pada dasarnya sama:

forsetiap item, lakukan hal-hal, tetapi elsejika Anda kehabisan, lakukan ini

Dalam kedua kasus, elsebagian hanya tercapai setelah tidak ada lagi item untuk diproses, dan item terakhir telah diproses secara teratur (yaitu tidak ada breakatau return). A continuehanya kembali dan melihat apakah ada item lagi. Mnemonik saya untuk aturan ini berlaku untuk keduanya whiledan for:

ketika breaking atau returning, tidak ada yang elseharus dilakukan,
dan ketika saya mengatakan continue, itu "kembali ke awal untuk memulai" untuk Anda

- dengan "loop back to start" yang berarti, jelas, awal dari loop di mana kami memeriksa apakah ada lebih banyak item di iterable, sehingga sejauh elsemenyangkut, continuebenar-benar tidak memainkan peran sama sekali.


4
Saya menyarankan agar itu dapat ditingkatkan dengan mengatakan bahwa tujuan umum dari loop untuk / selain itu adalah untuk memeriksa item sampai Anda menemukan apa yang Anda cari dan ingin hentikan , atau Anda kehabisan item. Bagian "lain" ada untuk menangani bagian "Anda kehabisan item (tanpa menemukan apa yang Anda cari)".
supercat

@ supercat: Bisa jadi, tapi saya tidak tahu apa kegunaan yang paling umum di luar sana. The elsejuga dapat digunakan untuk melakukan sesuatu ketika Anda hanya selesai dengan semua item. Contohnya termasuk menulis entri log, memperbarui antarmuka pengguna, atau memberi sinyal beberapa proses lain yang telah Anda lakukan. Apa saja, sungguh. Juga, beberapa bagian kode memiliki case "berhasil" dengan bagian breakdalam loop, dan elsedigunakan untuk menangani case "error" di mana Anda tidak menemukan item yang cocok selama iterasi (mungkin itu yang Anda pikirkan dari?).
Fabian Fagerholm

1
Kasus Saya berpikir itu tepatnya terjadi di mana kasus ini sukses berakhir dengan "istirahat", dan "lain" menangani kurangnya keberhasilan. Jika tidak ada "istirahat" dalam satu lingkaran, kode "lain" mungkin juga cukup mengikuti loop sebagai bagian dari blok penutup.
supercat

Kecuali jika Anda perlu membedakan antara kasus di mana loop melewati semua item yang dapat diulang tanpa gangguan (dan itu adalah kasus yang berhasil) dan kasus di mana tidak. Maka Anda harus meletakkan kode "menyelesaikan" di elseblok loop , atau melacak hasilnya menggunakan cara lain. Saya pada dasarnya setuju, saya hanya mengatakan saya tidak tahu bagaimana orang menggunakan fitur ini dan oleh karena itu saya ingin menghindari membuat asumsi apakah elseskenario " menangani kasus yang berhasil" atau skenario " elsemenangani kasus yang tidak berhasil" lebih umum. Tapi Anda punya poin bagus, jadi komentarnya terunggul!
Fabian Fagerholm

7

Dalam pengembangan Test-driven (TDD), saat menggunakan Transformation Priority Premise paradigma , Anda memperlakukan loop sebagai generalisasi pernyataan bersyarat.

Pendekatan ini menggabungkan dengan baik dengan sintaks ini, jika Anda hanya mempertimbangkan pernyataan sederhana if/else(tidak elif):

if cond:
    # 1
else:
    # 2

menggeneralisasi ke:

while cond:  # <-- generalization
    # 1
else:
    # 2

baik.

Dalam bahasa lain, langkah-langkah TDD dari satu kasus ke kasus dengan koleksi membutuhkan lebih banyak refactoring.


Ini adalah contoh dari blog 8thlight :

Dalam artikel yang ditautkan di blog 8thlight, kata Bungkus Kata dianggap: menambahkan jeda baris ke string ( svariabel dalam cuplikan di bawah) untuk membuatnya cocok dengan lebar yang diberikan ( lengthvariabel dalam cuplikan di bawah). Pada satu titik implementasi terlihat sebagai berikut (Jawa):

String result = "";
if (s.length() > length) {
    result = s.substring(0, length) + "\n" + s.substring(length);
} else {
    result = s;
}
return result;

dan tes berikutnya, yang saat ini gagal adalah:

@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
    assertThat(wrap("verylongword", 4), is("very\nlong\nword"));
    }

Jadi kami memiliki kode yang berfungsi secara kondisional: ketika suatu kondisi tertentu terpenuhi, baris baru ditambahkan. Kami ingin meningkatkan kode untuk menangani beberapa jeda baris. Solusi yang disajikan dalam artikel ini mengusulkan untuk menerapkan transformasi (jika-> sementara) , namun penulis membuat komentar bahwa:

Sementara loop tidak dapat memiliki elseklausa, jadi kita perlu menghilangkan elsejalan dengan melakukan lebih sedikit di ifjalan. Sekali lagi, ini adalah refactoring.

yang memaksa untuk melakukan lebih banyak perubahan pada kode dalam konteks satu tes gagal:

String result = "";
while (s.length() > length) {
    result += s.substring(0, length) + "\n";
    s = s.substring(length);
}
result += s;

Dalam TDD kami ingin menulis kode sesedikit mungkin untuk membuat tes lulus. Berkat sintaksis Python, transformasi berikut dimungkinkan:

dari:

result = ""
if len(s) > length:
    result = s[0:length] + "\n"
    s = s[length:]
else:
    result += s

untuk:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s

6

Cara saya melihatnya, else: menyala ketika Anda beralih melewati ujung loop.

Jika Anda breakatau returnatau raiseAnda tidak iterate masa akhir loop, Anda berhenti immeadiately, dan dengan demikian else:blok tidak akan berjalan. Jika Anda continuemasih mengulangi melewati akhir loop, karena terus hanya melompat ke iterasi berikutnya. Itu tidak menghentikan loop.


1
Saya suka itu, saya pikir Anda sedang melakukan sesuatu. Ini sedikit terkait dengan bagaimana perulangan digunakan untuk diterapkan di masa lalu yang buruk sebelum kata kunci loop. (Yaitu: cek ditempatkan di bagian bawah loop, dengan gotobagian atas pada kesuksesan.) Tapi itu versi yang lebih pendek dari jawaban terpilih ...
Alex

@ Alex, subyektif, tapi saya menemukan cara mengekspresikannya lebih mudah untuk dipikirkan.
Winston Ewert

sebenarnya saya setuju. Kalau saja karena itu pithier.
Alex

4

Pikirkan elseklausa sebagai bagian dari konstruksi loop; breakkeluar sama sekali dari konstruk loop, dan dengan demikian melompati elseklausa.

Tapi sungguh, pemetaan mental saya hanyalah bahwa itu adalah versi 'terstruktur' dari pola C / C ++ pola:

  for (...) {
    ...
    if (test) { goto done; }
    ...
  }
  ...
done:
  ...

Jadi ketika saya menemukan for...elseatau menulis sendiri, daripada memahaminya secara langsung , saya secara mental menerjemahkannya ke dalam pemahaman pola di atas dan kemudian mencari tahu bagian mana dari sintaksis python yang dipetakan ke bagian mana dari pola tersebut.

(Saya menempatkan 'terstruktur' dalam kutipan menakut-nakuti karena perbedaannya bukan apakah kode terstruktur atau tidak terstruktur, tetapi hanya apakah ada kata kunci dan tata bahasa yang didedikasikan untuk struktur tertentu)


1
Dimana else? Jika Anda bermaksud done:label untuk berdiri proksi atau else:, saya yakin Anda memilikinya persis mundur.
alexis

@alexis The 'lain' kode akan mengisi '...' segera sebelum itu done:label. Korespondensi keseluruhan, mungkin, paling baik dikatakan sebagai berikut: Python memiliki elsekonstruksi -on-loop sehingga Anda dapat mengekspresikan pola aliran kontrol ini tanpa goto.
zwol

Ada cara lain untuk menjalankan pola aliran kontrol ini, misalnya dengan menetapkan bendera. Itu yang elsemenghindari.
alexis

2

Jika Anda memasangkan elsedengan for, itu bisa membingungkan. Saya tidak berpikir kata kunci elseadalah pilihan yang bagus untuk sintaks ini, tetapi jika Anda memasangkan elsedengan ifyang berisi break, Anda dapat melihatnya benar-benar masuk akal. elsehampir tidak berguna jika tidak ada ifpernyataan sebelumnya dan saya percaya ini sebabnya desainer sintaks memilih kata kunci.

Biarkan saya menunjukkannya dalam bahasa manusia.

forsetiap orang dalam kelompok tersangka ifsiapa pun adalah penjahat breakinvestigasi. elsemelaporkan kegagalan.


1

Cara saya berpikir tentang hal itu, kuncinya adalah mempertimbangkan makna continuedaripada else.

Kata kunci lain yang Anda sebutkan keluar dari loop (keluar secara tidak normal) sementara continuetidak, itu hanya melompati sisa blok kode di dalam loop. Fakta bahwa ia dapat mendahului terminasi loop adalah insidental: terminasi ini sebenarnya dilakukan dengan cara normal dengan mengevaluasi ekspresi kondisional loop.

Maka Anda hanya perlu mengingat bahwa elseklausa dieksekusi setelah terminasi loop normal.


0
# tested in Python 3.6.4
def buy_fruit(fruits):
    '''I translate the 'else' below into 'if no break' from for loop '''
    for fruit in fruits:
        if 'rotten' in fruit:
            print(f'do not want to buy {fruit}')
            break
    else:  #if no break
        print(f'ready to buy {fruits}')


if __name__ == '__main__':
    a_bag_of_apples = ['golden delicious', 'honeycrisp', 'rotten mcintosh']
    b_bag_of_apples = ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
    buy_fruit(a_bag_of_apples)
    buy_fruit(b_bag_of_apples)

'''
do not want to buy rotten mcintosh
ready to buy ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
'''
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.