φυβλαςのβλογ
บล็อกของ phyblas



ภาษา python เบื้องต้น บทที่ ๓๔: การสร้างมอดูล
เขียนเมื่อ 2016/05/01 15:04
แก้ไขล่าสุด 2024/02/22 11:09
 

ในบทที่ ๑๕ ได้พูดถึงเรื่องการเรียกใช้มอดูลไปแล้ว ได้พูดถึงไปว่ามอดูลคืออะไร ทำงานยังไง ประกอบไปด้วยอะไร

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



รู้จักกับมอดูลให้มากขึ้นอีกสักหน่อย

ดังที่ได้กล่าวไปแล้วว่ามอดูลนั้นมีทั้งมอดูลพื้นฐานที่มีติดตัวอยู่แล้วแต่แรก เช่น math, sys, os, random และอีกชนิดคือมอดูลเพิ่มเติมที่ถูกสร้างโดยคนที่ไม่ได้เกี่ยวข้องกับกลุ่ม ที่พัฒนาภาษาไพธอนโดยตรง

มอดูลเสริมเพิ่มเติมที่นิยมกันมากเช่น numpy, scipy, matplotlib เป็นต้น โดยทั่วไปแล้วมีโครงสร้างที่ซับซ้อน ถูกสร้างขึ้นโดยกลุ่มคนหรืออาจเป็นบริษัทใหญ่ซึ่งมีความเชี่ยวชาญเฉพาะทาง เป็นอย่างดี

อย่างไรก็ตาม ความจริงแล้วมอดูลเป็นสิ่งที่สร้างขึ้นได้อย่างง่ายๆ แค่คนเดียวก็สามารถสร้างขึ้นเองได้ เพราะที่จริงแค่เขียนโปรแกรมด้วยภาษาไพธอนสร้างไฟล์ .py ขึ้นมาอันหนึ่ง เพียงเท่านี้ก็สามารถกลายเป็นมอดูลได้แล้ว

เพียงแต่ว่าโดยทั่วไปเวลาที่เราเขียนโปรแกรมขึ้นมาเราจะให้มันทำงานโดยสั่งรันธรรมดา แบบนั้นจะไม่เรียกว่าเป็นมอดูล แต่เป็นโปรแกรมธรรมดา

แต่ไฟล์โปรแกรมธรรมดานี้จะกลายมาเป็นมอดูลเมื่อมันถูกเรียกให้ทำงานโดยใช้คำสั่ง import เข้ามาในโปรแกรมอื่น

เพียงแต่ว่ามอดูลมีวิธีการสร้างอยู่หลากหลาย อาจถูกเขียนขึ้นด้วยภาษาไพธอนเอง หรือใช้ภาษาอื่นเช่นภาษาซี หรือมีโปรแกรมอื่นมาประกอบด้วยก็เป็นได้

ในที่นี้จะกล่าวถึงเฉพาะการสร้างมอดูลอย่างง่าย คือการสร้างมอดูลโดยเขียนโค้ดภาษาไพธอนด้วยความรู้แค่เท่าที่ได้กล่าวไปในบทที่ผ่านๆมา

หากไปดูในโฟลเดอร์ที่เก็บไฟล์มอดูลต่างๆจะเห็นว่าประกอบไปด้วยไฟล์ต่างๆมากมาย ซึ่งส่วนใหญ่ก็เป็น .py ที่เราคุ้นเคยนั่นเอง และอาจประกอบด้วยไฟล์ชนิดอื่นๆปนอยู่บ้าง

มอดูลที่ประกอบขึ้นจากหลายๆไฟล์มารวมอยู่ในโฟลเดอร์หนึ่ง (และอาจมีโฟลเดอร์ย่อยในนั้นอีก) จะเรียกว่าเป็นแพ็กเกจ (package)

อย่างไรก็ตามมอดูลอย่างง่ายที่สุดนั้นอาจประกอบขึ้นจากไฟล์ .py เพียงไฟล์เดียวก็ได้ ในที่นี้จะขอเริ่มจากมอดูลจากไฟล์ .py ไฟล์เดียว แล้วในบทต่อมาจึงพูดถึงการนำหลายไฟล์มารวมกันเป็นแพ็กเกจ



การเตรียมไฟล์สำหรับทำเป็นมอดูล

ไฟล์ที่ต้องการจะใช้เป็นมอดูลนั้นไม่ใช่ว่าวางไว้ที่ไหนในเครื่องก็สามารถเรียกใช้ได้ทันที เพราะจำเป็นจะต้องถูกวางไว้ในโฟลเดอร์ที่อยู่ในพาธ (path) ที่ถูกกำหนดไว้

พาธที่วางมอดูลได้นั้นมีอยู่หลายแห่ง สามารถดูได้โดยจะอยู่ในตัวแปรที่ชื่อว่า path ของมอดูล sys
import sys
print(sys.path)

ผลที่ได้ก็คือลิสต์ของพาธทั้งหมดที่เก็บมอดูล

หากเรานำไฟล์โปรแกรมที่เราเขียนไว้ไปวางตามโฟลเดอร์ไหนสักแห่งที่อยู่ในลิสต์นั้นไฟล์นั้นก็จะสามารถถูกเรียกใช้ในฐานะมอดูลได้

อย่างไรก็ตามมีวิธีที่ง่ายกว่านั้น เพราะลิสต์ sys.path สามารถเพิ่มสมาชิกเข้าไปได้ หมายความว่าถ้าเราใส่พาธของโฟลเดอร์ที่ต้องการจะใช้ลงไป โฟลเดอร์นั้นก็จะกลายเป็นพาธสำหรับใส่มอดูลได้

การเพิ่มสมาชิกในลิสต์ก็เหมือนลิสต์ทั่วไป สามารถใช้เมธอด .append เช่น
sys.path.append('C:\Program Files\nana')

เท่านี้โฟลเดอร์ nana ก็จะสามารถเก็บไฟล์ที่จะใช้เป็นมอดูลได้แล้ว

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

ตัวอย่างเช่น ลองสร้างไฟล์ momo.py แล้วพิมพ์โค้ดสั้นๆตามนี้ลงไปแล้วเซฟ
print('มอดูล momo ได้ถูกเรียกใช้แล้ว')

เท่านี้ก็จะได้มอดูลที่ชื่อว่า momo ชื่อของโมดูลก็จะตรงกับชื่อของไฟล์ (โดยไม่รวม .py) ซึ่งชื่อนี้เป็นชื่อที่จะต้องใช้เวลาที่ import ในโปรแกรมอื่น

จากนั้นเขียนอีกไฟล์ชื่ออะไรก็ได้แล้วเซฟไว้ในโฟลเดอร์เดียวกัน
import momo

จากนั้นก็รันไฟล์นี้ ผลลัพธ์ที่ได้ก็คือมีข้อความขึ้นว่า
มอดูล momo ได้ถูกเรียกใช้แล้ว

จะเห็นว่าพอสั่ง import คำสั่งที่ถูกเขียนไว้ในไฟล์ที่เป็นมอดูลก็จะทำงานทันที เปรียบเสมือนว่าเราไปดึงโค้ดจากมอดูลมาทำงานในโปรแกรมโดยตรงเลยนั่นเอง

ขั้นตอนการสร้างมอดูลไปจนถึงการเรียกใช้อย่างง่ายสุดนั้นก็มีเพียงเท่านี้ อย่างไรก็ตาม ดังที่เราจะเห็นตัวอย่างได้จากมอดูลทั่วๆไปที่มีอยู่แล้วที่คุ้นเคยกันดี การใช้งานมอดูลมักจะใช้เพื่อประกาศฟังก์ชันหรือตัวแปรหรือคลาสเพื่อจะมา เรียกใช้

เช่นเวลาเราสั่ง import math จะพบว่าไม่มีอะไรเกิดขึ้น แต่หลังจากนั้นเราก็สามารถใช้ฟังก์ชันและตัวแปรต่างๆในมอดูลนี้ได้ทันที เช่นตัวแปร math.pi ฟังก์ชัน math.sin() นั่นเป็นเพราะว่าภายในมอดูลนี้ได้มีการนิยามตัวแปรและฟังก์ชันไว้นั่นเอง



การสร้างตัวแปรและฟังก์ชันภายในมอดูล

เราสามารถประกาศตัวแปรขึ้นมาภายในมอดูลได้โดยใช้วิธีเหมือนการประกาศตัวแปรทั่วไป

ฟังก์ชันก็สามารถประกาศได้เช่นเดียวกับฟังก์ชันทั่วไป นั่นคือใช้ def

ตัวอย่าง ลองพิมพ์ใน momo.py ว่า
akson_thai = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ'
def phim_akson_thai(tuathi):
    print(akson_thai[tuathi-1])

จากนั้นพิมพ์ตามนี้ในอีกไฟล์ที่อยู่ในโฟลเดอร์เดียวกันแล้วรัน
import momo
print(momo.akson_thai)
momo.phim_akson_thai(7)

ก็จะได้
กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ
ง

จะเห็นว่าเวลาที่เรียกใช้ตัวแปรหรือฟังก์ชันที่มาจากมอดูลจะต้องมีชื่อมอดูลนั้นนำแล้วตามด้วยจุดแล้วค่อยต่อด้วยชื่อตัวแปรหรือฟังก์ชันนั้น ซึ่งก็เป็นในทำนองเดียวกับที่เราต้องพิมพ์ math.pi และ math.sin เพื่อเรียกใช้ pi และ sin ที่อยู่ในมอดูล math นั่นเอง

และในทำนองเดียวกันก็สามารถใช้ from เพื่อที่จะใช้ตัวแปรและฟังก์ชันจากมอดูลนั้นได้โดยไม่ต้องนำด้วยชื่อมอดูล
from momo import *
print(akson_thai)
phim_akson_thai(7)



ข้อยกเว้นในการใช้ from import *

กฎการตั้งชื่อมอดูลและฟังก์ชันในมอดูลนั้นก็คล้ายกับการตั้งชื่อตัวแปรหรือ ฟังก์ชันหรือคลาสทั่วไป คือจะใช้อักษรภาษาอะไรก็ได้แต่ห้ามขึ้นต้นด้วยตัวเลขหรือใช้สัญลักษณ์พิเศษ นอกเหนือจากขีดล่าง _

อย่างไรก็ตามการตั้งชื่อตัวแปรหรือฟังก์ชันให้ ขึ้นต้นด้วยขีดล่าง _ นั้นมีนัยสำคัญบางอย่าง ซึ่งจะเกิดขึ้นเมื่อใช้ from และดอกจัน * เพื่อเรียกใช้มอดูล

โดยทั่วไปหากใช้ from และดอกจัน *
from ชื่อมอดูล import *

แบบนี้จะเป็นการนำเข้าตัวแปรและฟังก์ชันทั้งหมดที่ประกาศในมอดูลนั้น

แต่มีข้อยกเว้นอยู่เล็กน้อย นั่นคือชื่อตัวแปรและฟังก์ชันที่ขึ้นต้นด้วย _ จะไม่ถูกอ่าน

ลองแก้ใน momo.py เป็น
_akson_thai = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ'

แล้วลองรัน
from momo import *
print(_akson_thai)

ก็จะได้ว่า
NameError: name '_akson_thai' is not defined

การตั้งชื่อให้ขึ้นต้นด้วย _ นั้นจึงมีนัยสำคัญในกรณีนี้ จะเป็นชื่อฟังก์ชันหรือชื่อคลาสก็มีผลเหมือนกัน

แต่หากเปลี่ยนเป็นพิมพ์ชื่อของตัวแปรลงไปแทนที่จะใช้ดอกจัน *
from momo import _akson_thai
print(_akson_thai)

หรือ
import momo
print(momo._akson_thai)

แบบนี้ก็จะสามารถใช้ตัวแปรนี้ได้ตามปกติ

อนึ่ง แม้ว่าตัวแปรที่ชึ้นต้นด้วย _ จะไม่ถูกอ่านเมื่อถูกดึงเข้ามาในมอดูล แต่ก็สามารถเรียกใช้ผ่านฟังก์ชันภายในมอดูลนั้นได้ตามปกติ

เช่นแก้ใน momo.py เป็น
_akson_thai = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ'
def phim_akson_thai(tuathi):
    print(_akson_thai[tuathi-1])

จากนั้นรัน
import momo
momo.phim_akson_thai(7)

ฟังก์ชัน phim_akson_thai ก็จะไปเรียกใช้ตัวแปร _akson_thai มาแสดงผลได้ตามปกติ เพราะมอดูลก็เหมือนเป็นโปรแกรมหนึ่ง การทำงานภายในตัวมอดูลนั้นไม่ต่างจากการทำงานโดยการรันโดยตรง ชื่อตัวแปรในนั้นก็ต้องไม่เติมชื่อมอดูลนำหน้าด้วย



การสร้างคลาสภายในมอดูล

คลาสก็เช่นเดียวกับตัวแปรและฟังก์ชัน สามารถสร้างขึ้นมาใช้ได้

ตัวอย่าง ลองสร้างไฟล์ชื่อ dudu.py ขึ้นมาพิมพ์ว่า
class Phimandao:
    dao = '*'
    def __init__(self,m,n):
        self.m = m
        self.n = n
    def phimdao(self):
        return '\n'.join([self.dao*self.n]*self.m)

จากนั้นก็สร้างไฟล์ที่อยู่ในโฟลเดอร์เดียวกัน พิมพ์ตามนี้แล้วรัน
import dudu
mudao = dudu.Phimandao(3,9)
print(mudao.phimdao())
mudao.dao = '8'
mudao.m = 2
mudao.n = 17
print(mudao.phimdao())

ผลที่ได้
*********
*********
*********
88888888888888888
88888888888888888



ไฟล์ .pyc

หลังจากที่ไฟล์ .py ถูกเรียกใช้ในฐานะมอดูลไปแล้วก็จะมีการสร้างไฟล์ .pyc ปรากฏขึ้นมาในโฟลเดอร์เดียวกันกับไฟล์ .py ที่ถูกรัน หรืออาจมีการสร้างโฟล์เดอร์ขึ้นใหม่แล้วมีไฟล์ .pyc ชื่อไฟล์จะต่างกันไปตามโปรแกรมและเวอร์ชันของโปรแกรมที่เราใช้คอมไพล์

ภาพนี้เป็นกรณีที่ใช้ไพธอน 3.11 เมื่อไฟล์ชื่อ momo.py ถูกเรียกใช้ในฐานะมอดูล จะเกิดโฟล์เดอร์ __pycache__ ขึ้นและมีไฟล์ชื่อ momo.cpython-311.pyc อยู่ในนั้น



ไฟล์ .pyc นี้เป็นไฟล์ .py ที่ผ่านการคอมไพล์แล้ว ที่มันถูกสร้างขึ้นมาก็เพื่อว่าเวลาที่มีการเรียกใช้ใหม่อีกครั้งจะไม่ต้อง มีการคอมไพล์ใหม่แต่เรียกใช้ไฟล์ .pyc นี้โดยตรงเลย ซึ่งจะทำให้เรียกใช้ได้เร็วขึ้น

ขอเน้นว่าที่ว่าเร็วขึ้นในที่นี้ไม่ได้หมายถึงทำให้คำสั่งในมอดูลทำงานเร็วขึ้น เพียงแต่จะทำให้เร็วในตอนที่โหลดมอดูลใช้เท่านั้น แต่เมื่อโหลดมาแล้วการใช้ฟังก์ชันหรือคลาสต่างๆจากมอดูลนั้นก็ไม่ได้เร็วขึ้น

ตราบใดที่ไม่มีการแก้ไขไฟล์ .py ไป เวลาที่มอดูลนั้นถูกเรียกใช้ก็จะไม่มีการอ่านไฟล์ .py ซ้ำแต่จะไปอ่าน .pyc โดยตรง แต่หากมีการแก้ไฟล์ .py เมื่อไหร่จึงจะมีการอ่านไฟล์ .py ซ้ำเพื่อคอมไพล์และสร้าง .pyc ขึ้นมาใหม่



การโหลดมอดูลใหม่

หากเราเรียกใช้มอดูลผ่านเชลโต้ตอบโดยไม่ได้เรียกใช้จากไฟล์โดยตรง (คือใช้อินเทอร์พรีเตอร์ แทนที่จะใช้อีดิเตอร์เขียนแล้วสั่งรันไฟล์) กรณีแบบนี้หากมีการ import มอดูลอะไรไปครั้งหนึ่งแล้ว มอดูลนั้นจะไม่สามารถ import ซ้ำได้อีก

นั่นหมายความว่าต่อให้มีการแก้ไขไฟล์มอดูลนั้นไปแล้วกด import ซ้ำ ความเปลี่ยนแปลงนั้นก็จะไม่แสดงผลให้เห็น

หากต้องการให้มีการ reload จะต้องใช้ฟังก์ชัน reload ซึ่งอยู่ในมอดูล importlib
import importlib
importlib.reload(momo)

มอดูล importlib นั้นเป็นมอดูลที่เก็บฟังก์ชันต่างๆที่เกี่ยวกับการเรียกใช้มอดูล ยังมีฟังก์ชันอีกหลายอันที่ใช้ประโยชน์ได้

การ reload นั้นจำเป็นต้องใช้แค่ในกรณีที่ import ด้วยเชลเท่านั้น ดังนั้นการแก้ปัญหาอาจทำได้ด้วยการ import จากการรันไฟล์



การรันไฟล์โดยไม่ต้องทำเป็นมอดูล

ในส่วนท้ายนี้ขอเสริมเนื้อหาที่อาจจะไม่ใช่เรื่องของมอดูล แต่มีความเกี่ยวข้องกัน

การนำโค้ดจากไฟล์อื่นมาใช้งานนอกจากการทำให้ไฟล์นั้นเป็นมอดูลแล้วยังมีอีกวิธีหนึ่ง นั่นคือใช้ฟังก์ชัน exec อ่านไฟล์

exec เป็นฟังก์ชันที่นำสายอักขระมาอ่านประมวลผลเป็นโค้ด
exec('print(1)\nprint(2)')

จะได้
1
2

ซึ่งผลเหมือนกับการที่เรารันโค้ด
print(1)
print(2)

ดังนั้นเราสามารถใช้ open เพื่อเปิดไฟล์ แล้วก็ใช้ exec เพื่อประมวลผลข้อความในไฟล์นั้นในฐานะโค้ด
f = open('momo.py',encoding='utf-8')
exec(f.read())

เพียงเท่านี้ก็จะได้ผลเหมือนเราคัดลอกโค้ดจากไฟล์ momo.py มาแปะวางโดยที่ไม่ต้องทำให้ momo.py เป็นมอดูล

นอกจากฟังก์ชัน exec แล้วหากโค้ดมีแค่บรรทัดเดียวเราอาจใช้ฟังก์ชัน eval ได้ด้วย eval จะทำงานเหมือนกับ exec เพียงแต่ว่าจะอ่านโค้ดได้แค่บรรทัดเดียว การใช้มีข้อจำกัดกว่า exec มาก

ตัวอย่าง
eval('print(\'\'\'"\'(ö)\'"\'\'\')') # ได้ "'(ö)'" 



สรุปเนื้อหา

ในบทนี้ได้พูดถึงการใช้ไฟล์มาสร้างเป็นมอดูลแล้ว ซึ่งสามารถทำได้ไม่ยาก ตรงไปตรงมา
แต่มอดูลโดยมากมักไม่ได้สร้างจากไฟล์เดียวแต่ประกอบขึ้นจากไฟล์เป็นจำนวนมาก ซึ่งเรียกว่าการรวมเป็นแพ็กเกจ เนื้อหาตรงนี้จะพูดถึงต่อในบทหน้า



อ้างอิง




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文