จะใช้ OOP ใน Python อย่างไรครับ

  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.
  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.

มันมีทั้ง massage, inheritance, polymorphism, encapsulation ใน Python ใช้อย่างไรเหรอครับ
(มีตัวอย่างโต้ดโปรแกรมด้วยจะขอบคุณมากครับ ไม่ใช่งานส่งอ.นะ แค่อกยากรู้จริง)

sugree's picture

เรื่องมันยาวครับ oo ใน Python ไม่ค่อยจะสมบูรณ์ ผมก็ไม่เห็นความจำเป็นต้องสมบูรณ์ เรื่องนี้มันขึ้นกับความเชื่อ

ถ้าเราเขียน oo ก็จะได้ oo แต่ถ้าไม่เขียน oo ก็ไม่ได้ oo

อาจฟังดูแปลกๆ ยกตัวอย่างง่ายๆ oo ของ Java บังคับให้ทุกอย่างเป็น oo แล้วหาทางแก้ด้วย public static void main ซึ่งบางคนไม่เข้าใจ oo ผลออกมาเลยกลายเป็น ทุกเมธทอดเป็น public static กันหมด ซึ่งก็ไม่ใช่ oo ซะแล้ว ใน Python เลยไม่ได้บังคับ ไม่มีคำว่า public protected private มีแต่หลักการซึ่งขึ้นกับความเชื่ออยู่ดี

Inheritance

class Human:
    def __init__(self,name,gender):
        self.gender = gender
        self.name = name
 
class Man(Human):
    def __init__(self,name):
        Human.__init__(self,name,'male')
 
class Female(Human):
    def __init__(self,name):
        Human.__init__(self,name,'female')

เอ นี่ผมยกตัวอย่างถูกรึเปล่าเนี่ย เช้านี้หัวตื้อจัง

Polymorphism

ถ้าเปลี่ยน Human ข้างบนเป็นแบบนี้แล้ว Male กับ Female ยังเหมือนเดิม ก็ยังทำงานได้อยู่ดี นี่เป็น polymorphism แบบ Python

class Human:
    def __init__(self,name,gender,address=None,telno=None):
        self.name = name
        self.gender = gender
        self.address = address
        self.telno = telno

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

me = Human('johnny thomson','male',telno='66-2-111-1111')

ถ้าอยากให้ยืดหยุ่นสุดๆ ก็ใช้แบบนี้

class Human:
    def __init__(self,name,gender,*args,**kw):
        self.name = name
        self.gender = gender
        self.address = kw.get('address',None)
        self.telno = kw.get('telno',None)

แปลว่าอะไรก็ตามที่ตามหลัง gender และไม่ได้ระบุชื่อจะส่งมาเป็น list อยู่ใน args ส่วนที่ระบุชื่อทั้งหมดจะอยู่ใน kw เป็น dict

Encapsulation

ตัวแปรในตัวอย่างข้างบนทั้งหมดถือเป็น public จริงๆ ก็มีแต่ public น่ะแหละ ใน Python จะใช้วิธีตั้งชื่อแทน โดยที่ถ้าชื่อขึ้นต้นด้วย _ แปลว่า protected และ __ คือ privateเช่น

class Human:
    def __init__(self,name,gender,address=None,telno=None):
        self.name = name
        self._gender = gender
        self.__address = address
        self.__telno = telno

แต่ในความเป็นจริงไม่มีการปิดกั้นใดๆ ทั้งสิ้น บางคนก็ชอบใช้เพื่อบอกให้รู้ว่า __ อาจจะมีการเปลี่ยนแปลงได้ในอนาคต ส่วน _ จะเปลี่ยนน้อยหน่อย นอกนั้นจะไม่เปลี่ยนหรือนานมากๆ ซึ่งก็เป็นแนวคิดของ Encapsulation อยู่แล้ว แต่ถ้าใครตะขิดตะขวงใจว่าไม่มี setter getter ก็มีทางออกให้ด้วย property()

class Human:
    def __init__(self,name,gender,address=None,telno=None):
        self._name = name
        self._gender = gender
        self.__address = address
        self.__telno = telno
 
    def get_name(self):
        return self._name
    name = property(get_name)
 
    def get_gender(self):
        return self._gender
    def set_gender(self,v):
        assert v.lower() not in ['male','female']
        self._gender = v
    gender = property(get_gender,set_gender)
 
    def get_address(self):
        return self.__address
    def set_address(self,v):
        self.__address = v
    address = property(get_address,set_address)
 
    def get_telno(self):
        return self.__telno
    def set_telno(self,v):
        self.__telno = v
    telno = property(get_telno,set_telno)

จากโค้ดข้างบน

  • name แก้ไม่ได้
  • gender ถูกเช็คก่อนแก้
  • address กับ telno เหมือนปกติ

ผมไม่แม่นเรื่อง oo ซักเท่าไหร่ message นี่คืออะไรหว่า ลองอธิบายซักนิดซิครับ ผมจะได้รู้เรื่องกับเค้าบ้าง

รู้สึกว่า Polymorphism จะหมายถึงอย่างอื่นน่ะครับ คือ มันจะหมายถึงว่าการที่ class ลูก สามารถเปลี่ยนพฤติกรรมไปจาก class แม่ได้

ใน Python แนวคิดนี้ไม่ค่อยมีผลเท่าไหร่ เพราะว่า ตัวแปรไม่ได้มี static type ติดตัวไปกับมัน ทำให้เวลาเรียกใช้ method ที่เรา def เอาไว้ มันก็เป็นไปตามตัวแปร

เพื่อให้เห็นภาพ ยกตัวอย่างจาก java แล้วกันครับ สมมติผมมีคลาส Par แล้วก็มีคลาสที่ inherit มาชื่อ Chd ทีนี้ ถ้าใน par มี method m1 แล้วก็ใน chd เขียน method m1 ใหม่ นะครับ ทีนี้ ถ้าผมสั่งประมาณนี้

Par p = new Par();
Chd c = new Chd();
p.m1(); //ผลลัพธ์เป็น m1 ของ Par
p = c;
p.m1(); //ผลลัพธ์เป็น m1 ของ Chd

สังเกตว่าในรอบหลัง จะเรียก method m1 ของ Chd ถึงแม้ว่า static type ของ p จะเป็น Par

ทีนี้ ผมเข้าใจว่าใน Python มันไม่มี static type ของตัวแปรอยู่แล้ว (type จะเหมือนว่าติดไปกับ "ค่า" ของมัน) เวลาเรียก method อะไรมันก็ไปหาเอาจากที่เราประกาศ

ซึ่งมันจะเปิดกว้างกว่าใน Java ซึ่งในกรณีของ Java หรือ C++ เราจะทำได้ถ้า Chd inherit มาจาก Par แต่ใน Python จะเป็นยังไงก็ได้ ไม่จำเป็นต้องมีความสัมพันธ์อะไรกันเลยก็ได้ ลักษณะเช่นนี้จะคล้าย ๆ กับภาษาพวก Smalltalk เสียมากกว่าน่ะครับ

veer's picture

ถ้าต้องเป็นพ่อแม่ลูกกัน เรียกว่า Subtyping polymorphism (แบบ Java ที่อ.jittat กรุณายกตัวอย่างให้ดู)

type polymorphism ทั่วไป ผมเข้าใจว่า method ชื่อเดียวกัน ของต่าง object กันมี definition ต่างกัน แต่ว่าเราเขียนโปรแกรมส่ง message ได้เหมือนเดิม ไม่ว่า method นั้นจะมี definition อย่างไร

class TicketBoy:
  def travel(from, to):
    if from == "kaset" and to == "the mall":
      return "14 bath"
    else:
      raise RuntimeError
 
class Me
  def travel(from, to):
     print "Who cares?"
     sys.exit(1)
 
 
def yourfunc(person):
   person.travel("kaset", "the mall")
 
me = Me()
ticket_boy = TicketBoy()
 
yourfunc(me)
yourfunc(ticket_boy)

จากตัวอย่าง object ชื่อ ticket_boy กับ me มี method ชื่อ travel เหมือนกัน แต่ definition ไม่เหมือนกัน

แต่ว่า yourfunc ส่ง message "travel" (พร้อม argument from:"kaset" to:"the mall") ไปให้ทั้ง ticket_boy กับ me ได้เหมือนกัน โดยไม่ต้องไปเขียน yourfunc_ticketboy หรือ yourfunc_me แยกกันต่่างหาก

แบบ Ruby และ Python นี่เรียก Duck typing ไม่สนใจว่าเป็น พ่อ แม่ ลูก กันหรือเปล่า(แบบ subtyping) ประมาณว่าส่ง message ไปก่อน เดี๋ยว receiver ก็ส่งกลับมาบอกเอง ถ้า receiver ไม่รู้จัก message นั้น มันก็ raise exception มั้ง

sugree's picture

เอ้าเหรอ ผมนึกว่า Polymorphism คือการที่ทำให้ method รับพารามิเตอร์ได้หลายแบบ อย่างใน Java ก็มี Constructor ได้หลายตัว เพื่อรับหลายแบบ ส่วน Python ทำแบบนั้นไม่ได้ต้องใช้วิธี keyword argument แทน

อันนั้นเรียก overloading นะ คิดว่า

sugree's picture

จาก wikipedia บอกว่า overloading ก็เป็นหนึ่งใน polymorphism พูดง่ายๆ ก็คือเรื่องที่เราพูดๆ กันมามันเป็น polymorphism กันหมด มันเป็นแค่เทคนิคย่อยๆ อืมลึกซึ้งจริงๆ สอบผ่านมาได้ไงหว่า

อืม กว้่างจริงๆ
ถ้าแปลตามตัวคงแปลว่า หลายรูปแบบ
คือ ชื่อเดียว แต่หลายรูปแบบ.... อืม อืม อืม

veer's picture

polymorphism ภายใน object เดียวกัน :-O

ตอนเรียนผมก็ไม่ได้หลับนะครับ ทำไมไม่เห็นรู้เลยอะ lol
(อาจจะเรียนไม่เหมือนกัน)

omni_kh's picture

ตามความเข้าใจของผมนั้น message นั้นคือการคุยกันของ object ต่างๆ นะครับ อาจจะเป็นการเรียก method, การ instantiate, ฯลฯ

พอไปแอบดูใน wikipedia แล้วก็ได้มาอย่างนี้:

In the terminology of some object-oriented languages, a message is the single means to pass control to an object. If the object 'responds' to the message, it has a method for that message.

ที่มา: http://en.wikipedia.org/wiki/Message_%28computer_science%29

pittaya's picture

ในโลกของ OO เค้าไม่เรียกว่าการ call function น่ะครับ แต่จะมองว่า object ตัวนึงส่ง message ไปหา object อีกตัวนึง แล้ว object ตัวนั้นแปลความหมายของ message นั้นออกมาได้ว่าอย่างไร

sugree's picture

อย่างนี้นี่เอง สงสัยตอนเรียนเรื่องนี้จะหลับ ขอบคุณมากครับ

veer's picture

function call นี่ส่วนใหญ่มันก็ต้องมี function นั้นอยู่จริงๆ ใช่เปล่า?

แต่เวลา receiver ได้ message ไป receiver ก็ไม่จำเป็นต้องมี method หรือ function อยู่จริงๆ ก็ได้ แล้วแต่ว่า receiver นั้นอยากจะ respond กับ message นั้นอย่างไร

ลองเล่น Gorm แล้วอาจจะเห็นภาพเรื่องพวกนี้มากขึ้นก็ได้

เรื่องพวกนี้ผมลืมไปเกือบหมดแล้วครับตอนนี้ แหะ ๆ น่าเสียดายเหมือนกัน ได้มาอ่านอีกรอบรู้สึกดีครับ ได้ฟื้นความรู้เก่า ๆ

ผมขอตั้งข้อสังเกตครับว่า คนที่ใช้ภาษา script จนชินจะไม่ค่อยแน่นทฤษฏี OOP มากนัก ผมเนี่ยตัวดีเลย ซึ่งจริง ๆ ก็ไม่ดีหรอกครับ แต่มันก็มีเหตุผลของมันอยู่

ทฤษฏี OOP ต่าง ๆ ในปัจจุบันมักตั้งอยู่บนพื้นฐานของ Java (C++, C# ก็กล้อมแกล้มไปกันได้ ไม่ต่างกันมาก) คุณลักษณะอย่างหนึ่งของ Java คือเป็น Static Type ดังนั้นเลยต้องมีเทคนิคต่าง ๆ มากมายเกิดขึ้น เพื่อให้การทำงานยืดหยุ่นขึ้น

ผมเคยซื้อหนังสือมาอ่านสองเล่ม Software Architecture กับอีกเล่ม รู้จักแต่ชื่อภาษาเยอรมันครับ Entwurt Muster ประมาณว่าการออกแบบพิมพ์เขียวโปรแกรม ทั้งสองเล่นเป็น Java Oriented อ่านผ่าน ๆ จนจบ สรุปใจความได้ว่า จะออกแบบโปรแกรมยังไง ให้ต่อยอดได้โดยไม่มีปัญหา โดยสามารถ reuse โค้ดได้ และยืดหยุ่น ซึ่งสองอย่างนี้มักมาไม่พร้อมกัน ใน Software Architecture เขาเขียนถึงไว้หน่อยนึงครับว่า ภาษาพวก dynamic type มักไม่จำเป็นต้องใช้ทฤษฏีในหนังสือ สิ่งที่สำคัญกว่าคือ Specification, ตัวโครงสร้าง, Customization และ Optimization ซึ่งในภาษา static type ก็มีความสำคัญไม่น้อยไปกว่ากัน แต่หากไม่รู้วิธีเขียนที่ดีพออาจพบทางตันได้ง่ายกว่า

ดังนั้นคนที่เคยชินกับ static type OOP ต้องเปลี่ยนวิธีคิดเยอะพอสมควรครับ

sugree's picture

ใช่แล้วๆ ผมเข้าใจว่าจะเป็นพวก Design Patterns

ข้อเสียที่ควรหลีกเลี่ยง มันทำให้ผมปวดหัวมาก คือ multiple inheritance

ปัญหาคือ สืบทอดได้หลายคลาส แล้วถ้าเกิด class A กับ class B มี method ชื่อซ้ำกันเวลาที่ class C(A,B) เนี่ย มันจะใช้ method ของ class ไหน หรือต้องมาระบุชื่อ class นำหน้า method นั้นก่อนหรือเปล่า

ขนาดภาษา D ที่เป็นรุ่นต่อไปของ C++ ยังตัด Multiple Inheritance ออกไปเลย ดูเปรียบเทียบได้ที่นี่ครับ

เขาถึงได้ว่า python มี OOP ที่ยังไม่สมบูรณ์นัก เพราะอิงกับ C++ ซะเยอะไปหน่อย

work4best's picture

การมี multiple inheritance ไม่ได้ทำให้ ความเป็น OOP ไม่สมบูรณ์นะครับ

อย่างในภาษา Eiffel (http://en.wikipedia.org/wiki/Eiffel_programming_language) ซึ่งเป็น ISO-standardized object-oriented programming language นั้น นอกเหนือจากเรื่อง Design by contract ที่โด่งดังแล้ว เรื่อง multiple inheritance นั้นเป็นหนึ่งในจุดแข็งของภาษาเลยทีเดียว

sugree's picture

ใน Python จะใช้ multiple inheritance สำหรับทำส่วนประกอบแล้วเอามาผสมพันธุ์เพื่อสร้างสัตว์ประหลาดที่มีความสามารถหลากหลาย เช่น เขียนเซิร์ฟเวอร์ธรรมดาขึ้นมา ถ้าอยากทำเป็นเทรดก็แค่ inherit เพิ่มเข้าไปก็จบ บางทีก็เรียก MixIn เรื่องชื่อ method ซ้ำกัน ถ้าเราไม่ทำแบบนั้นก็ไม่มีใครว่านี่ครับ เลี่ยงได้ก็เลี่ยง แต่บางทีผมก็อยากทำ โดยเฉพาะถ้า class แม่เป็นของคนอื่น ก็ต้องจำใจทำเป็นอย่างยิ่ง

work4best's picture

ผมมองว่า python โดยความตั้งใจแล้ว ไม่ได้จะพยายามเป็นภาษา OOP อย่างสมบูรณ์แบบมาตั้งแต่แรกแล้ว

ลองดู feature ของภาษาใหม่ๆที่ออกมาใน version หลังๆ มีความพยายามที่จะปรับปรุงเรื่อง OOP บ้างแต่ก็ไม่มาก feature ใหม่ส่วนใหญ่จะกระเดียดไปทาง functional เสียมากกว่า

ในอนาคต ใน python 3000 ก็ไม่ได้พูดถึงความพยายามที่จะทำให้ python เป็น OOP อย่างสมบูรณ์แบบจ๋า

python ก็เป็นอย่างที่เป็นของมัน ซึ่งคนใช้ภาษาก็รักมันอย่างที่มันเป็น

ผมเห็นด้วยกับคุณ work4best และคุณ sugree ว่า multiple inheritance จริง ๆ แล้วเป็นจุดเด่นของภาษาที่มี เช่น C++ เลยทีเดียว

การออกแบบ C++ ที่มีเงื่อนไขหลาย ๆ อย่างเช่น ต้อง compatible กับ C และ efficiency ต้องไม่ตก นั่นคือ ถ้าเขียนโปรแกรมไม่ได้ใช้ feature อะไร ความเร็วของโปรแกรมที่ได้ในภาษาควรจะต้องเท่าเทียมกับในกรณีที่ภาษาไม่มี feature นั้น ๆ ทำให้ C++ เป็นภาษาที่ต้องเรียกว่าออกแบบมาได้ดีมาก ๆ (ยกตัวอย่างเช่นการให้ผู้ใช้กำหนด virtual function เอง ไม่ใช่ว่าจะทำ dynamic look-up ตลอด)

ในกรณีของ multiple inheritance ก็เช่นกัน ถ้าไม่ใช้ โปรแกรมที่ได้ก็ทำงานได้ตามปกติ ถ้าจำเป็นต้องใช้ (เพรา่ะว่าให้ผลลัพธ์/โปรแกรม ที่ดีกว่า) ก็มีให้ใช้ อาจจะปวดหัวบ้าง แต่ก็มีให้ใช้

ภาษาที่ไม่มี multiple inheritance นั้น ผมเข้าใจว่าทำให้การ implement ตัวภาษา (เช่น คอมไพเลอร์) ง่ายขึ้น และโดยทั่วไปก็มักบอกว่า multiple inheritance นั้นไม่จำเป็น "มาก" และมีแนวทางให้พอทำได้อยู่แล้ว เช่นมี interface แยกออกมาจาก class อีกที ซึ่งจริง ๆ ผมมองว่ามันก็คือความพยายามจะทำให้ใช้ความสามารถของ multiple inheritance ได้ โดยไม่จำเป็นต้อง support ทุกอย่างนั่นเอง

แถมลิงก์ครับ
multiple and virtual inheritance จาก C++ FAQ lite
The Virtues of Multiple Inheritance

veer's picture

OOP ที่สมบูรณ์ต้องมีคุณสมบัติอะไรบ้าง?

แฟน Java กับ C++ อาจจะบอกว่า ต้องมี public, private, protected ด้วย?

แฟน smalltalk อาจจะบอกว่าทุกอย่างต้องเป็น object หมด?

ผมอาจจะบอกว่า object ต้องตอบสนอง message ได้แม้ว่า จะไม่มี method ชื่อเดียวกับ message เพราะมันเป็นคนละอย่างกัน ...

ผมคิดว่า Python กับ C++ ห่างกันกว่า Java กับ C++ ซะอีก Python ไม่ได้ใช้ vtable, python ใช้ type แบบ duck type ไม่ได้ใช้ static type แบบ C++ และ Java ฯลฯ multiple inheritance ของ Python อาจจะมาจาก CLOS? ด้วยหรือเปล่าก็ไม่รู้

คนทำ Python เลือก multiple inheritance แทนที่จะจัดระเบียบให้ใช้ได้แต่ Mixin ที่ไม่มี instance variable เหมือน Ruby หรือว่าบังว่าต้องใช้ Interface แบบ Java อาจจะเป็นเพราะแนวความคิดเรื่อง minimalism ที่ syntax ไม่ควรมีรูปแบบหลากหลาย ถ้าเลือกจะทำแบบ Java ก็ต้องมี extend กับ implement จะทำแบบ Ruby ก็ต้องมี < กับ include ถ้าใช้ multiple inheritance ก็มีแต่ inherit อย่างเดียว

cPython นี่วิธีการ lookup นี่ีค่อนข้างขัดเจนนะครับ เข้าใจว่าเพิ่งมาเปลี่ยนเอาตอน 2.5 นี่เอง ก่อนหน้านี้รู้สึกจะใช้ depth-first search ตอนหลังนี่หาข้างบนก่อนเสมอแล้ว (เหมือนจะกำหนดว่าฝั่งขวาก่อนเสมอด้วย)

ย้าย Codenone

ประกาศย้าย Codenone ไปใช้ Forum ของ Blognone แทนครับ ตามไปตั้งกระทู้ต่อได้ที่ Codenone Forum (รายละเอียดอ่านจากกระทู้ ย้าย Codenone ไปรวมกับ Blognone)

กระทู้เก่าๆ จะย้ายตามไปในภายหลัง ตอนนี้ปิดการโพสต์กระทู้ไว้ เหลือไว้เฉพาะอ้างอิงเท่านั้น