What is Pythonic?

วันนี้ลองมาอธิบายคำว่า Pythonic กันดีกว่า พอดีมีคนถามที่Codenone แล้วตอนนั้นก็ตอบแบบสั้นๆ ช่วงนี้พอมีเวลาเลยลองมาศึกษาให้ถ่องแท้ดีกว่า ผมเองก็ไม่รู้จะอธิบายยังไงเหมือนกัน คล้ายกับว่า Pythonic เป็นปรัชญาชนิดหนึ่ง เรียบง่าย ตรงไปตรงมา เข้าใจอย่างไรก็เป็นแบบนั้น ไม่มีความซับซ้อน บางคนก็บอกว่ามันคือ pseudocode ที่ทำงานได้จริงๆ ผมเคยลองเขียน pseudocode ด้วย Python มันก็ดูสะอาดดี แต่ก็มีหลายคนไม่ชอบ เพราะไม่มีปีกกา {} หรือ begin end แล้วไม่สบายใจ เหมือนชีวิตขาดอะไรไป โชคดีที่ผมไม่ใช่คนแรกที่สงสัย มีคนพยายามตอบหลายครั้ง ผมชอบคำอธิบายที่ Python Secret Weblog มากที่สุด เพราะมีตัวอย่างชัดเจน อย่างน้อยก็เห็นแล้วร้อง อ๋อ ไม่ต้องเสียเวลาจินตนาการ

ยกตัวอย่างแรกสุด เอาแบบง่ายๆ พื้นๆ

for (i=0; i) {
    do_something(mylist[i]);
}

เวลาแปลงเป็น Python ตรงๆ จะได้เป็น

i = 0
while i < mylist_length:
    do_something(mylist[i])
    i += 1

แต่จริงๆ แล้วถ้าจะเขียนแบบ Python ให้มากขึ้นซักนิด ก็ควรจะใช้ for กับ range() แทน

for i in range(mylist_length):
    do_something(mylist[i])

อย่างไรก็ตาม for ไม่ควรใช้ index แต่ควรทำกับข้อมูลนั้นตรงๆ

for element in mylist:
    do_something(element)

จะเห็นว่าแบบสุดท้ายกระชับและเข้าใจง่ายที่สุด โอกาสผิดพลาดต่ำมาก ไม่ต้องกลัวว่า i จะอยู่นอกสโคปของ mylist ให้เปลืองสมอง ไม่ต้องใช้ตัวแปรหลายตัว

ลองมาดูตัวอย่างถัดไป ในภาษาทั่วไปเวลาเราต้องส่งค่ากลับมาหลายค่าก็ต้องส่งทางพารามิเตอร์

void foo(int *a,float *b) {
    *a = 3;
    *b = 5.5;
}

เวลาใช้ก็แบบนี้

int alpha;
int beta;
 
foo(&alpha,&beta);

ถ้าเขียนแบบนี้เป๊ะๆ ใน Python จะได้แบบนี้

def foo(a,b):
    a[0] = 3
    b[0] = 5.5
 
alpha = [0]
beta = [0]
foo(alpha,beta)
alpha = alpha[0]
beta = beta[0]

ที่ต้องทำแบบนี้เพราะว่า alpha กับ beta เป็นตัวแปรที่เก็บค่าล้วนๆ เวลาส่งเป็นพารามิเตอร์จะส่งไปเฉพาะค่า ไม่มีพอยเตอร์ซะด้วย ถ้าอยากจะทำตามนั้นจริงๆ ก็ต้องใช้ลิสต์เข้ามาช่วยจำลองพอยเตอร์ จากโค้ดข้างบนจะเห็นว่ามันเลวร้ายมาก ไม่มีความ Pythonic เอาซะเลย ลองมาดูแบบนี้บ้าง

def foo():
    return 3,5.5
 
alpha,beta = foo()

เรียบง่าย ชัดเจน ไม่ได้หมายความว่าภาษาอื่นทำแบบนี้ไม่ได้ Ruby Perl PHP ทำแบบนี้ได้หมด ก็อย่างที่บอกตั้งแต่ต้น มันเป็นปรัชญา Python ไม่ได้สนับสนุนให้ทำแบบนี้ แต่มันทำได้ และนอกจากนี้ทำแล้วดีด้วย เขียน Python มากๆ แล้วจะรู้สึกว่าวิธีคิดเปลี่ยนไป กลายเป็นคิดแบบ Python คนพวกนี้จะกลายเป็น Pythonian ทุกภาษามีวิถีของตนเอง นี่คือวิถีของ Python

ลองมาดูตัวอย่างถัดไป ภาษา OOP อื่นๆ มักสนับสนุนให้ใช้ accessor เพื่อสร้าง setter getter สำหรับการอ่านเขียนค่าที่เก็บในอ๊อบเจ็ก แต่เรื่องพวกนี้ไม่จำเป็นใน Python

class A:
    def __init__(self,a,b):
        self.a = a
        self.b = b
 
o = A('1',2)
print o.a,o.b
o.b = 3
print o.a,o.b

หลายคนสงสัยว่าแล้วอย่างนี้จะปลอดภัยรึเปล่า เรื่องความปลอดภัยและความถูกต้องเป็นเรื่องของผู้พัฒนา ถ้าเขียนโปรแกรมแล้วไม่รู้ว่าควรใส่ค่าอะไรลงในตัวแปรไหน ก็คงไม่ดีนัก พูดง่ายๆ ก็คือเขียนโปรแกรมต้องมีสติ ถ้าเขียนโปรแกรมแล้วต้องพึ่ง IDE เพื่อตรวจสอบทุกอย่าง ก็หมายความว่าโปรแกรมที่ได้จะถูกบังคับด้วยความสามารถของ IDE อะไรที่ IDE ทำไม่ได้ โปรแกรมเราก็ทำไม่ได้ กลายเป็นข้อจำกัดที่ไม่สามารถแก้ได้ ในขณะที่ Python อยากให้ผู้พัฒนารู้ว่ากำลังทำอะไร และทำให้ดีแม้ในส่วนเล็กน้อยด้วย ตามหลักการของ bottom-up เราสามารถนำชิ้นส่วนเล็กๆ มาประกอบเป็นชิ้นส่วนใหญ่ๆ ได้ ถ้าชิ้นส่วนเล็กๆ ไม่มีปัญหา การเอามาประกอบเป็นชิ้นที่ใหญ่ขึ้นก็ไม่ควรก่อปัญหา ถ้าชิ้นส่วนใหญ่ไม่มีปัญหาชิ้นส่วนที่ใหญ่กว่าก็ไม่ควรมีปัญหา ขอเพียงทำตามนี้ไม่ว่าโปรแกรมจะใหญ่แค่ไหนก็สามารถเขียนได้โดยที่ไม่ต้องพึ่ง IDE

ทิ้งท้ายเล็กน้อย ถ้าอยากใช้ getter setter ใน Python จริงๆ ก็สามารถทำได้ในแบบ Python

class A:
    def __init__(self,a,b):
        self.a = a
        self.b = b
 
    def get_a(self):
        return self.a
    def set_a(self,v):
        self.a = v
    a = property(get_a,set_a)
 
    def get_b(self):
        return self.b
    def set_b(self,v):
        self.b = v
    b = property(,get_b,set_b)
 
a = A('1',2)
a.a = '2'
a.b = 3
print a.a,a.b

ตอนใช้เหมือนปกติ เปลี่ยนแค่ตอนเขียนเท่านั้น

Tags: , ,

เอาไปใส่ใ

เอาไปใส่ใน book ด้วยก็น่าจะมีประโยชน์ดี

Post new comment