จะเข้าถึงชั้นนอกจากชั้นในได้อย่างไร?


105

ฉันมีสถานการณ์เช่นนั้น ...

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.Outer.some_method()    # <-- this is the line in question

ฉันจะเข้าถึงOuterเมธอดของคลาสจากInnerคลาสได้อย่างไร


ทำไมคุณทำเช่นนี้? เกิดอะไรขึ้นกับความสัมพันธ์แบบเพื่อนง่ายๆ? คุณกำลังพยายาม "ปกปิด" อะไรบางอย่างอยู่หรือเปล่า?
ล็อตต์

1
ตัวอย่างของสถานการณ์นี้อาจมีคลาสที่มีคลาสย่อยที่จำเป็นต้องเข้าถึงคลาสภายนอกตามปกติแต่จำเป็นต้องสร้างคลาสอื่น (ระดับบนสุด) ที่ได้มาจากคลาสแรก ในกรณีที่ย่อยชั้นเรียนชั้นสองจะพยายามที่จะเข้าถึงผู้ปกครองใช้self.<original_parent_name>และได้รับระดับเดิมไม่ได้เรียนใหม่ว่าพวกเขาเป็นชั้นย่อยจาก ฉันหวังว่าคนที่อ่านเรื่องนี้จะสามารถนึกภาพสถานการณ์ที่ยากลำบากนี้และมองเห็นประเด็นคำถามเช่นนี้
Edward

1
ทำซ้ำกับstackoverflow.com/questions/2278426และมีคำตอบที่ดี
xmedeko

คำตอบ:


72

เมธอดของคลาสที่ซ้อนกันไม่สามารถเข้าถึงแอ็ตทริบิวต์อินสแตนซ์ของคลาสภายนอกได้โดยตรง

โปรดทราบว่าไม่จำเป็นต้องเป็นกรณีที่อินสแตนซ์ของคลาสภายนอกมีอยู่แม้ว่าคุณจะสร้างอินสแตนซ์ของคลาสภายในแล้วก็ตาม

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


1
อืม Python นั้นน่ากลัวกว่า Java / C ++ ... ดูคำตอบของฉันด้านล่าง ถ้าเราแยกเส้นขนซึ่งโดยปกติเราจะเป็นฉันไม่สามารถบอกคุณได้ว่า "คลาสที่ซ้อนกันภายในวิธีการ" ของฉันนับเป็นคลาสชั้นในหรือไม่ ถึงตอนนี้ฉันต้องเรียกใช้เป็ดพิมพ์: ถ้ามันทำทุกอย่างที่ชั้นในทำได้ ... จากมุมมองของ Pythonic อาจถึงเวลาที่จะเบื่อกับการแยกเส้นขน
สัตว์ฟันแทะไมค์

22
หลักสูตรชั้นในหมายถึงความสัมพันธ์กับคลาสชั้นนอกโดยทั่วไปจะเกี่ยวข้องกับขอบเขตการใช้งานโดยนัยของคลาสภายในหรือมิฉะนั้นเนมสเปซขององค์กร
Acumenus

75

คุณกำลังพยายามเข้าถึงอินสแตนซ์คลาสของ Outer จากอินสแตนซ์คลาสภายใน ดังนั้นเพียงใช้วิธีการจากโรงงานเพื่อสร้างอินสแตนซ์ภายในและส่งอินสแตนซ์ภายนอกไป

class Outer(object):

    def createInner(self):
        return Outer.Inner(self)

    class Inner(object):
        def __init__(self, outer_instance):
            self.outer_instance = outer_instance
            self.outer_instance.somemethod()

        def inner_method(self):
            self.outer_instance.anothermethod()

นี่เป็นคำตอบที่ถูกต้อง - ควรเลือกเป็นคำตอบที่ถูกต้องเนื่องจากเป็นคำตอบสำหรับคำถามของ OP - ขอบคุณมาก!
Daniel

29

บางทีฉันอาจจะบ้า แต่มันดูเหมือนง่ายมากจริงๆ - สิ่งนี้คือการทำให้ชั้นในของคุณอยู่ในวิธีการของชั้นนอก ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

ยิ่งไปกว่านั้น ... "ตัวตน" ถูกใช้โดยอนุสัญญาเท่านั้นดังนั้นคุณสามารถทำได้:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

อาจถูกคัดค้านว่าคุณไม่สามารถสร้างคลาสภายในนี้จากภายนอกคลาสภายนอกได้ ... แต่นี่ไม่เป็นความจริง:

class Bumblebee():

    def do_sthg( self ):
        print "sthg"

    def giveMeAnInnerClass( outerClassSelf ):

        class mooble():
            def do_sthg_different( self ):
                print "something diff\n"
                outerClassSelf.do_sthg()
        return mooble

จากนั้นห่างออกไปหลายไมล์:

blob = Bumblebee().giveMeAnInnerClass()()
blob.do_sthg_different()    

แม้กระทั่งผลักเรือออกเล็กน้อยและขยายชั้นใน (NB เพื่อให้ super () ทำงานได้คุณต้องเปลี่ยนลายเซ็นคลาสของ mooble เป็น "class mooble (object)"

class InnerBumblebeeWithAddedBounce( Bumblebee().giveMeAnInnerClass() ):
    def bounce( self ):
        print "bounce"

    def do_sthg_different( self ):
        super( InnerBumblebeeWithAddedBounce, self ).do_sthg_different()
        print "and more different"


ibwab = InnerBumblebeeWithAddedBounce()    
ibwab.bounce()
ibwab.do_sthg_different()

ในภายหลัง

mrh1997 หยิบยกประเด็นที่น่าสนใจเกี่ยวกับการสืบทอดคลาสภายในที่ไม่ธรรมดาโดยใช้เทคนิคนี้ แต่ดูเหมือนว่าวิธีแก้ปัญหาค่อนข้างตรงไปตรงมา:

class Fatty():
    def do_sthg( self ):
        pass

    class InnerFatty( object ):
        pass

    def giveMeAnInnerFattyClass(self):
        class ExtendedInnerFatty( Fatty.InnerFatty ):
            pass
        return ExtendedInnerFatty

fatty1 = Fatty()
fatty2 = Fatty()

innerFattyClass1 = fatty1.giveMeAnInnerFattyClass()
innerFattyClass2 = fatty2.giveMeAnInnerFattyClass()

print ( issubclass( innerFattyClass1, Fatty.InnerFatty ))
print ( issubclass( innerFattyClass2, Fatty.InnerFatty ))

1
สิ่งนี้ใช้ได้กับฉัน โครงสร้างนี้เรียกว่าอะไรกันแน่? ฟังก์ชั่นโรงงาน? ปิด?
nakedfanatic

ฉันไม่ทราบว่ามันเรียกว่าอะไร ... แต่ฉันขอแนะนำว่าสาเหตุที่ผู้โพสต์คนอื่นไม่เห็นสิ่งนี้เป็นเพราะบางทีมันอาจจะไม่ได้รับการชื่นชมอย่างเต็มที่ว่าสิ่งต่างๆใน Python ส่วนใหญ่ไม่ศักดิ์สิทธิ์รวมถึง "self "(ชื่อตามอำเภอใจ) และคลาส - พวกมันเป็น" วัตถุชั้นหนึ่ง "ซึ่งดูเหมือนว่าคุณสามารถจัดการพวกมันได้ในรูปแบบที่ค่อนข้างอุกอาจ
ไมค์หนู

การทำงานที่ดี. แรงบันดาลใจจากแนวคิดการแก้ปัญหานี้และความคิดเล็กน้อยฉันขยายส่วนของคำตอบนี้เพื่อสร้างคำตอบด้านล่างพร้อมคำอธิบายเพิ่มเติม
Edward

1
@mikerodent เนื่องจากความคิดเห็นของคุณ (ในคำตอบของฉัน) ตอนนี้ฉันได้แก้ไขคำตอบของฉันเพื่อลบคำอธิบาย "community wiki" เช่นเดียวกับคุณฉันไม่มีความรู้เกี่ยวกับคำตอบของ "community wiki" ดังนั้นจึงเป็นการดีที่คุณจะแก้ไขข้อผิดพลาดของคุณ
Edward

@mikerodent นอกจากนี้ไม่มีความผิด แต่ฉันไม่คิดว่าคำตอบของ "community wiki" มีแท็ก "community wiki" แต่ต้องเป็นคำตอบประเภทหนึ่ง อาจมีหน้าวิธีใช้พร้อมคำอธิบาย "community wiki" คำตอบใน Stack Overflow หากคุณสนใจ
Edward

3

คุณหมายถึงใช้การสืบทอดแทนการซ้อนคลาสเช่นนี้หรือไม่? สิ่งที่คุณทำไม่ได้ทำให้เกิดความรู้สึกใน Python

คุณสามารถเข้าถึงOutersome_method ได้โดยเพียงแค่อ้างอิงOuter.some_methodภายในเมธอดของคลาสภายใน แต่จะไม่ได้ผลอย่างที่คุณคาดหวัง ตัวอย่างเช่นหากคุณลองสิ่งนี้:

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            Outer.some_method()

... คุณจะได้รับ TypeError เมื่อเริ่มต้นInnerอ็อบเจ็กต์เนื่องจากOuter.some_methodคาดว่าจะได้รับOuterอินสแตนซ์เป็นอาร์กิวเมนต์แรก (ในตัวอย่างข้างต้นคุณกำลังพยายามเรียกsome_methodเป็น class method Outer)


1
สาเหตุที่อาจไม่สมเหตุสมผลเพราะฉันกำลังแฮ็กโดยเจตนา การเพิ่มวิธีการที่กำหนดเองลงใน QuerySet ใน Django ต้องใช้โค้ดสำเร็จรูปเล็กน้อยและฉันพยายามหาวิธีที่ชาญฉลาดในการทำโดยใช้ python ที่อนุญาตให้ฉันสร้างเทมเพลตโค้ดสำเร็จรูปและเขียนส่วนที่เกี่ยวข้องในรหัสโมเดลของฉัน
T. Stone

ขอโทษ - ฉันไม่รู้จัก Django ดังนั้นจึงไม่สามารถแนะนำวิธีสร้างเทมเพลตโค้ดสำเร็จรูปได้ แต่คุณอาจกำลังเห่าต้นไม้ที่ไม่ถูกต้องในการพยายามซ้อนชั้นเรียนของคุณ ชั้นในของคุณไม่ได้รับอะไรจากชั้นนอกของคุณ การซ้อนกันภายใน Outer จะบังคับให้คุณเข้าถึงผ่านด้านนอกด้านในแทนที่จะเป็นเพียงด้านในธรรมดา
zenbot

3

คุณสามารถเข้าถึงคลาสภายนอกได้อย่างง่ายดายโดยใช้ metaclass: หลังจากสร้างคลาสภายนอกแล้วให้ตรวจสอบแอตทริบิวต์ dict สำหรับคลาสใด ๆ (หรือใช้ตรรกะที่คุณต้องการ - ของฉันเป็นเพียงตัวอย่างเล็กน้อย) และตั้งค่าที่เกี่ยวข้อง:

import six
import inspect


# helper method from `peewee` project to add metaclass
_METACLASS_ = '_metaclass_helper_'
def with_metaclass(meta, base=object):
    return meta(_METACLASS_, (base,), {})


class OuterMeta(type):
    def __new__(mcs, name, parents, dct):
        cls = super(OuterMeta, mcs).__new__(mcs, name, parents, dct)
        for klass in dct.values():
            if inspect.isclass(klass):
                print("Setting outer of '%s' to '%s'" % (klass, cls))
                klass.outer = cls

        return cls


# @six.add_metaclass(OuterMeta) -- this is alternative to `with_metaclass`
class Outer(with_metaclass(OuterMeta)):
    def foo(self):
        return "I'm outer class!"

    class Inner(object):
        outer = None  # <-- by default it's None

        def bar(self):
            return "I'm inner class"


print(Outer.Inner.outer)
>>> <class '__main__.Outer'>
assert isinstance(Outer.Inner.outer(), Outer)

print(Outer().foo())
>>> I'm outer class!
print(Outer.Inner.outer().foo())
>>> I'm outer class!
print(Outer.Inner().outer().foo())
>>> I'm outer class!
print(Outer.Inner().bar())
>>> I'm inner class!

ด้วยวิธีนี้คุณสามารถเชื่อมโยงและอ้างอิงสองคลาสระหว่างกันได้อย่างง่ายดาย


3

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

class higher_level__unknown_irrelevant_name__class:
    def __init__(self, ...args...):
        ...other code...
        # Important lines to access sub-classes.
        subclasses = self._subclass_container()
        self.some_subclass = subclasses["some_subclass"]
        del subclasses # Free up variable for other use.

    def sub_function(self, ...args...):
        ...other code...

    def _subclass_container(self):
        _parent_class = self # Create access to parent class.
        class some_subclass:
            def __init__(self):
                self._parent_class = _parent_class # Easy access from self.
                # Optional line, clears variable space, but SHOULD NOT BE USED
                # IF THERE ARE MULTIPLE SUBCLASSES as would stop their parent access.
                #  del _parent_class
        class subclass_2:
            def __init__(self):
                self._parent_class = _parent_class
        # Return reference(s) to the subclass(es).
        return {"some_subclass": some_subclass, "subclass_2": subclass_2}

รหัสหลัก "พร้อมใช้งานจริง" (ไม่มีความคิดเห็น ฯลฯ ) อย่าลืมแทนที่ค่าทั้งหมดในวงเล็บเหลี่ยม (เช่น<x>) ด้วยค่าที่ต้องการ

class <higher_level_class>:
    def __init__(self):
        subclasses = self._subclass_container()
        self.<sub_class> = subclasses[<sub_class, type string>]
        del subclasses

    def _subclass_container(self):
        _parent_class = self
        class <sub_class>:
            def __init__(self):
                self._parent_class = _parent_class
        return {<sub_class, type string>: <sub_class>}

คำอธิบายวิธีการทำงานของวิธีนี้ (ขั้นตอนพื้นฐาน):

  1. สร้างฟังก์ชันที่ตั้งชื่อ_subclass_containerเพื่อทำหน้าที่เป็น Wrapper เพื่อเข้าถึงตัวแปรselfซึ่งเป็นการอ้างอิงไปยังคลาสระดับที่สูงกว่า (จากโค้ดที่ทำงานภายในฟังก์ชัน)

    1. สร้างตัวแปรที่ตั้งชื่อ_parent_classซึ่งเป็นการอ้างอิงถึงตัวแปรselfของฟังก์ชันนี้เพื่อให้คลาสย่อยของ_subclass_containerสามารถเข้าถึงได้ (หลีกเลี่ยงความขัดแย้งของชื่อกับselfตัวแปรอื่นในคลาสย่อย)

    2. ส่งคืนคลาสย่อย / คลาสย่อยเป็นพจนานุกรม / รายการเพื่อให้โค้ดที่เรียกใช้_subclass_containerฟังก์ชันสามารถเข้าถึงคลาสย่อยภายในได้

  2. ใน__init__ฟังก์ชั่นภายในชั้นเรียนระดับที่สูงขึ้น (หรือที่ใดก็ตามที่จำเป็นอื่น ๆ ) ได้รับกลับมาย่อยชั้นเรียนจากฟังก์ชั่นลงในตัวแปร_subclass_containersubclasses

  3. กำหนดคลาสย่อยที่จัดเก็บในsubclassesตัวแปรให้เป็นแอตทริบิวต์ของคลาสระดับสูงกว่า

เคล็ดลับในการทำให้สถานการณ์ง่ายขึ้น:

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

แทรกก่อนบรรทัดที่ 12 ในรหัสหลัก:

def _subclass_init(self):

จากนั้นใส่ในฟังก์ชันบรรทัดที่ 5-6 (ของโค้ดหลัก) และแทนที่บรรทัดที่ 4-7 ด้วยรหัสต่อไปนี้:

self._subclass_init(self)

ทำให้การกำหนดคลาสย่อยให้กับคลาสระดับที่สูงขึ้นเป็นไปได้เมื่อมีคลาสย่อยจำนวนมาก / ไม่ทราบจำนวน

แทนที่บรรทัด 6 ด้วยรหัสต่อไปนี้:

for subclass_name in list(subclasses.keys()):
    setattr(self, subclass_name, subclasses[subclass_name])

ตัวอย่างสถานการณ์ที่โซลูชันนี้จะเป็นประโยชน์และไม่ควรรับชื่อคลาสระดับสูงกว่า:

สร้างคลาสชื่อ "a" ( class a:) มีคลาสย่อยที่ต้องเข้าถึง (พาเรนต์) คลาสย่อยหนึ่งเรียกว่า "x1" ในคลาสย่อยนี้โค้ดa.run_func()จะถูกรัน

จากนั้นอีกชั้นหนึ่งชื่อ "b" จะถูกสร้างขึ้นมาจากคลาส "a" ( class b(a):) หลังจากนั้นโค้ดบางส่วนจะทำงานb.x1()(เรียกฟังก์ชันย่อย "x1" ของ b ซึ่งเป็นคลาสย่อยที่ได้รับมา) ฟังก์ชันนี้ทำงานa.run_func()โดยเรียกฟังก์ชัน "run_func" ของคลาส "a" ไม่ใช่ฟังก์ชัน "run_func" ของพาเรนต์ "b" (ตามที่ควร) เนื่องจากฟังก์ชันที่กำหนดไว้ในคลาส "a" ถูกกำหนดให้อ้างถึง ไปยังฟังก์ชันของคลาส "a" เช่นเดียวกับที่เป็นพาเรนต์

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


ขอบคุณสำหรับความคิดเห็นที่ดีของคุณเกี่ยวกับคำตอบของฉัน ฉันไม่รู้จักแท็ก "community wikis answer" และตอนนี้ได้นำสิ่งนี้ออก ขอบคุณมากที่ชี้ให้เห็น คุณอาจต้องการแก้ไขคำตอบของคุณตามนั้นเพื่อป้องกันความสับสนสำหรับผู้อ่านในอนาคต!
หนูไมค์

3

ฉันพบนี้

ปรับแต่งให้เหมาะกับคำถามของคุณ:

class Outer(object):
    def some_method(self):
        # do something

    class _Inner(object):
        def __init__(self, outer):
            outer.some_method()
    def Inner(self):
        return _Inner(self)

ฉันแน่ใจว่าคุณสามารถเขียนมัณฑนากรสำหรับสิ่งนี้หรือบางอย่างได้

ที่เกี่ยวข้อง: จุดประสงค์ของคลาสภายในของ python คืออะไร?


2

ความเป็นไปได้อื่น:

class _Outer (object):
    # Define your static methods here, e.g.
    @staticmethod
    def subclassRef ():
        return Outer

class Outer (_Outer):
    class Inner (object):
        def outer (self):
            return _Outer

        def doSomething (self):
            outer = self.outer ()
            # Call your static mehthods.
            cls = outer.subclassRef ()
            return cls ()

1

การขยายความเกี่ยวกับความคิดที่เป็นศูนย์กลางของ @tsnorri ว่าวิธีการภายนอกอาจเป็นวิธีการคงที่ :

class Outer(object):

    @staticmethod
    def some_static_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.some_static_method()    # <-- this will work later

    Inner.some_static_method = some_static_method

ตอนนี้บรรทัดที่เป็นปัญหาควรใช้งานได้ตามเวลาที่เรียกจริง

บรรทัดสุดท้ายในโค้ดด้านบนทำให้คลาส Inner เป็นวิธีการแบบคงที่ซึ่งเป็นโคลนของวิธีการแบบคงที่ภายนอก


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

โดยปกติขอบเขตโลคัลจะอ้างอิงชื่อโลคัลของฟังก์ชันปัจจุบัน (เป็นข้อความ)

... หรือคลาสปัจจุบันในกรณีของเรา ดังนั้นอ็อบเจ็กต์ "local" ในนิยามของคลาสชั้นนอก ( Innerและsome_static_method) อาจถูกอ้างถึงโดยตรงภายในนิยามนั้น


1

ไม่กี่ปีที่ไปงานปาร์ตี้ ....แต่เพื่อขยาย@mike rodentคำตอบที่ยอดเยี่ยมของฉันฉันได้ให้ตัวอย่างของตัวเองด้านล่างซึ่งแสดงให้เห็นว่าโซลูชันของเขามีความยืดหยุ่นเพียงใดและทำไมจึงควร (หรือควรได้รับ) การยอมรับ ตอบ.

Python 3.7

class Parent():

    def __init__(self, name):
        self.name = name
        self.children = []

    class Inner(object):
        pass

    def Child(self, name):
        parent = self
        class Child(Parent.Inner):
            def __init__(self, name):
                self.name = name
                self.parent = parent
                parent.children.append(self)
        return Child(name)



parent = Parent('Bar')

child1 = parent.Child('Foo')
child2 = parent.Child('World')

print(
    # Getting its first childs name
    child1.name, # From itself
    parent.children[0].name, # From its parent
    # Also works with the second child
    child2.name,
    parent.children[1].name,
    # Go nuts if you want
    child2.parent.children[0].name,
    child1.parent.children[1].name
)

print(
    # Getting the parents name
    parent.name, # From itself
    child1.parent.name, # From its children
    child2.parent.name,
    # Go nuts again if you want
    parent.children[0].parent.name,
    parent.children[1].parent.name,
    # Or insane
    child2.parent.children[0].parent.children[1].parent.name,
    child1.parent.children[1].parent.children[0].parent.name
)


# Second parent? No problem
parent2 = Parent('John')
child3 = parent2.Child('Doe')
child4 = parent2.Child('Appleseed')

print(
    child3.name, parent2.children[0].name,
    child4.name, parent2.children[1].name,
    parent2.name # ....
)

เอาท์พุต:

Foo Foo World World Foo World
Bar Bar Bar Bar Bar Bar Bar
Doe Doe Appleseed Appleseed John

อีกครั้งคำตอบที่ยอดเยี่ยมอุปกรณ์สำหรับคุณไมค์!


-2

มันง่ายเกินไป:

อินพุต:

class A:
    def __init__(self):
        pass

    def func1(self):
        print('class A func1')

    class B:
        def __init__(self):
            a1 = A()
            a1.func1()

        def func1(self):
            print('class B func1')

b = A.B()
b.func1()

เอาต์พุต

คลาส A func1

คลาส B func1


3
อืมทราบคำถามและคำว่าเข้าถึงชั้นนอก func1โดยมีความสุขค่อนข้างสนุกบังเอิญเรียนด้านนอกและภายในของคุณทั้งสองมีวิธีการ และที่น่าขบขันพอ ๆ กันคือคุณสร้างตัวสร้างAภายในBซึ่งไม่ใช่Aวัตถุชั้นนอกของสิ่งนั้นอย่างBแน่นอน
หนูไมค์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.