ทำไม __init __ () ถูกเรียกหลังจาก __new __ () เสมอ?


568

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

แต่ฉันบิตสับสนว่าทำไมถึงอยู่เสมอเรียกว่าหลังจาก__init__ __new__ฉันไม่ได้คาดหวังสิ่งนี้ ทุกคนสามารถบอกฉันได้ว่าทำไมสิ่งนี้ถึงเกิดขึ้นและฉันจะใช้ฟังก์ชันนี้ได้อย่างไร? (นอกเหนือจากการนำการติดตั้งไปใช้__new__ซึ่งรู้สึกแฮ็คค่อนข้างมาก)

นี่คือตัวอย่าง:

class A(object):
    _dict = dict()

    def __new__(cls):
        if 'key' in A._dict:
            print "EXISTS"
            return A._dict['key']
        else:
            print "NEW"
            return super(A, cls).__new__(cls)

    def __init__(self):
        print "INIT"
        A._dict['key'] = self
        print ""

a1 = A()
a2 = A()
a3 = A()

ขาออก:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

ทำไม?


พยายามที่จะเข้าใจรูปแบบการออกแบบเช่นกันและครั้งแรกที่ได้ยิน: รูปแบบการออกแบบฟลายเวท .. และลิงค์ที่ดีมากที่มีตัวอย่างในภาษาที่เป็นที่นิยมเกือบทั้งหมด
EmmaYang

คำตอบ:


598

ใช้__new__เมื่อคุณต้องการควบคุมการสร้างอินสแตนซ์ใหม่

ใช้ __init__เมื่อคุณต้องการควบคุมการเริ่มต้นของอินสแตนซ์ใหม่

__new__เป็นขั้นตอนแรกของการสร้างอินสแตนซ์ มันถูกเรียกมาก่อนและรับผิดชอบในการคืนอินสแตนซ์ใหม่ของชั้นเรียนของคุณ

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

โดยทั่วไปคุณไม่จำเป็นต้องแทนที่__new__เว้นแต่คุณจะทำการ subclassing ประเภทที่ไม่เปลี่ยนรูปเช่น str, int, unicode หรือ tuple

ตั้งแต่เดือนเมษายน 2008 โพสต์: เมื่อใดควรใช้__new__กับ__init__? บน mail.python.org

คุณควรพิจารณาว่าสิ่งที่คุณพยายามทำมักจะทำกับโรงงานและนั่นคือวิธีที่ดีที่สุดที่จะทำ การใช้__new__ไม่ใช่โซลูชันที่สะอาดดีดังนั้นโปรดพิจารณาการใช้งานของโรงงาน ที่นี่คุณมีตัวอย่างโรงงานที่ดี


1
ลงเอยด้วยการใช้__new__ในคลาส Factory ซึ่งค่อนข้างสะอาดดังนั้นขอขอบคุณสำหรับข้อมูลของคุณ
Dan

11
ขออภัยฉันไม่เห็นด้วยที่การใช้__new__ควร จำกัด อย่างเคร่งครัดตามกรณีที่ระบุไว้ ฉันพบว่ามันมีประโยชน์มากสำหรับการใช้งานคลาสโรงงานที่ขยายได้ทั่วไป - ดูคำตอบของคำถามที่ใช้ใน__new__การสร้างคลาสใน Python อย่างไม่เหมาะสม? สำหรับตัวอย่างของการทำเช่นนั้น
martineau

170

__new__เป็นวิธีการเรียนคงที่ในขณะที่__init__เป็นวิธีการอินสแตนซ์ __new__ต้องสร้างอินสแตนซ์ก่อนดังนั้นจึง__init__สามารถเริ่มต้นได้ โปรดทราบว่า__init__ใช้selfเป็นพารามิเตอร์ selfจนกว่าคุณจะสร้างตัวอย่างไม่มี

ตอนนี้ฉันรวบรวมว่าคุณกำลังพยายามใช้รูปแบบซิงเกิลตันใน Python มีสองสามวิธีที่จะทำเช่นนั้น

นอกจากนี้ใน Python 2.6 คุณสามารถใช้เครื่องมือตกแต่งชั้นเรียนได้

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

8
@ ไทเลอร์ลองฉันไม่เข้าใจว่า "@singleton" ทำงานอย่างไร เนื่องจากมัณฑนากรส่งคืนฟังก์ชัน แต่ MyClass เป็นคลาส
Alcott

11
ทำไมต้องเป็นพจนานุกรม เนื่องจาก cls จะเหมือนกันเสมอและคุณจะได้รับพจนานุกรมใหม่สำหรับแต่ละซิงเกิลคุณจึงสร้างพจนานุกรมที่มีเพียงหนึ่งรายการในนั้น
Winston Ewert

7
@Alcott: ไม่จำเป็นต้องมีความเห็น - เอกสารเห็นด้วยกับคุณ
Ethan Furman

2
@Alcott ใช่มัณฑนากรคืนค่าฟังก์ชัน แต่ทั้งคลาสและฟังก์ชั่นเป็น callable ฉันคิดว่าอินสแตนซ์ = {} ควรเป็นตัวแปรทั่วโลก
Tyler Long

4
@TylerLong Alcott มีจุดดี ซึ่งส่งผลให้ชื่อMyClassถูกผูกไว้กับฟังก์ชั่นที่ส่งกลับอินสแตนซ์ของคลาสเดิม แต่ตอนนี้ไม่มีทางที่จะอ้างถึงคลาสดั้งเดิมเลยและMyClassการแบ่งฟังก์ชันisinstance/ issubclassเช็คเข้าถึงคลาสแอ็ตทริบิวต์ / วิธีการโดยตรงเช่นMyClass.somethingการตั้งชื่อคลาสในการsuperโทร ฯลฯ ไม่ว่าจะเป็นปัญหาหรือไม่ขึ้นอยู่กับ ชั้นที่คุณใช้กับ (และส่วนที่เหลือของโปรแกรม)
Ben

148

ในภาษา OO ที่รู้จักกันดีที่สุดนิพจน์เช่นSomeClass(arg1, arg2)นั้นจะจัดสรรอินสแตนซ์ใหม่เริ่มต้นแอตทริบิวต์ของอินสแตนซ์แล้วส่งคืน

ในภาษา OO ที่รู้จักกันดีส่วน "initialise the instance" สามารถปรับแต่งได้สำหรับแต่ละคลาสโดยการกำหนดConstructorซึ่งโดยพื้นฐานแล้วจะเป็นเพียงกลุ่มของรหัสที่ทำงานกับอินสแตนซ์ใหม่ ) เพื่อตั้งค่าเงื่อนไขเริ่มต้นที่ต้องการ ในหลามสอดคล้องนี้ในชั้นเรียน__init__วิธีการ

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

แต่ก็ยังมีเพียงครึ่งหนึ่งของงานและไม่มีทางที่ระบบ Python จะรู้ว่าบางครั้งคุณต้องการที่จะทำงานอีกครึ่งหนึ่งของงาน ( __init__) หลังจากนั้นและบางครั้งคุณก็ทำไม่ได้ หากคุณต้องการพฤติกรรมดังกล่าวคุณต้องพูดอย่างชัดเจน

บ่อยครั้งที่คุณสามารถ refactor เพื่อให้คุณต้องการเท่านั้น__new__หรือดังนั้นคุณไม่ต้องการ__new__หรือเพื่อที่__init__จะทำงานแตกต่างกันในวัตถุที่เริ่มต้นแล้ว แต่ถ้าคุณอยากจะหลามจะช่วยให้คุณจริงจะกำหนด "งาน" เพื่อให้SomeClass(arg1, arg2)ไม่จำเป็นต้องโทรตามมาด้วย__new__ __init__ในการทำเช่นนี้คุณต้องสร้างเมตาคลาสและกำหนด__call__วิธีการ

metaclass เป็นเพียงคลาสของคลาส และ__call__เมธอดclass ' จะควบคุมสิ่งที่เกิดขึ้นเมื่อคุณเรียกใช้อินสแตนซ์ของคลาส ดังนั้นmetaclass ' __call__วิธีการควบคุมสิ่งที่เกิดขึ้นเมื่อคุณเรียกชั้นเรียน; คือมันช่วยให้คุณสามารถredefine กลไกเช่นการสร้างตั้งแต่ต้นจนจบ นี่คือระดับที่คุณสามารถใช้กระบวนการสร้างอินสแตนซ์ที่ไม่ได้มาตรฐานอย่างสมบูรณ์ที่สุดเช่นรูปแบบซิงเกิล ในความเป็นจริงมีน้อยกว่า 10 บรรทัดของรหัสคุณสามารถใช้Singletonmetaclass ที่ไม่ต้องการให้คุณ futz ด้วย__new__ เลยและสามารถเปลี่ยนชั้นเรียนปกติอื่นให้กลายเป็นซิงเกิลเพียงแค่เพิ่ม__metaclass__ = Singleton!

class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

อย่างไรก็ตามนี่อาจเป็นเวทย์มนตร์ที่ลึกกว่าที่รับประกันจริงสำหรับสถานการณ์นี้!


25

ในการอ้างถึงเอกสาร :

การใช้งานทั่วไปสร้างอินสแตนซ์ใหม่ของคลาสโดยเรียกใช้เมธอด __new __ () ของซูเปอร์คลาสโดยใช้ "super (currentclass, cls) .__ ใหม่ __ (cls [, ... ])" ด้วยอาร์กิวเมนต์ที่เหมาะสมจากนั้นปรับอินสแตนซ์ที่สร้างขึ้นใหม่ตามความจำเป็น ก่อนส่งคืน

...

หาก __new __ () ไม่ส่งคืนอินสแตนซ์ของ cls ดังนั้นเมธอด __init __ () ของอินสแตนซ์ใหม่จะไม่ถูกเรียกใช้

__new __ () มีวัตถุประสงค์หลักเพื่อให้คลาสย่อยของประเภทไม่เปลี่ยนรูป (เช่น int, str หรือ tuple) เพื่อปรับแต่งการสร้างอินสแตนซ์


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.นั่นเป็นจุดสำคัญ หากคุณส่งคืนอินสแตนซ์อื่น__init__จะไม่เรียกใช้ต้นฉบับ
Jeffrey Jose

@tgray ฉันรู้ว่าคำตอบของคุณมาจากเอกสาร แต่ฉันอยากรู้ว่าถ้าคุณรู้กรณีการใช้งานที่ไม่ได้ส่งคืนตัวอย่างของ cls ดูเหมือนว่าเมื่อ__new__มีการตรวจสอบวัตถุที่ส่งคืนของคลาสวิธีจะโยนข้อผิดพลาดแทนที่จะอนุญาตให้การตรวจสอบที่ล้มเหลวส่งผ่านเงียบ ๆ เพราะฉันไม่เข้าใจว่าทำไมคุณถึงต้องการคืนสิ่งใดก็ตาม แต่เป็นวัตถุของชั้นเรียน .
soporific312

@ soporific312 ฉันไม่ได้เห็นการใช้เคส คำตอบนี้กล่าวถึงเหตุผลบางประการสำหรับการออกแบบแม้ว่าพวกเขาจะยังไม่เห็นรหัสใด ๆ ที่ใช้ประโยชน์จากคุณลักษณะ "นั้น"
tgray

12

ฉันรู้ว่าคำถามนี้ค่อนข้างเก่า แต่มีปัญหาคล้ายกัน ต่อไปนี้เป็นสิ่งที่ฉันต้องการ:

class Agent(object):
    _agents = dict()

    def __new__(cls, *p):
        number = p[0]
        if not number in cls._agents:
            cls._agents[number] = object.__new__(cls)
        return cls._agents[number]

    def __init__(self, number):
        self.number = number

    def __eq__(self, rhs):
        return self.number == rhs.number

Agent("a") is Agent("a") == True

ฉันใช้หน้านี้เป็นแหล่งข้อมูลhttp://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


7
หมายเหตุ: __new__ จะส่งคืนวัตถุที่เหมาะสมเสมอดังนั้น __init__ จะถูกเรียกเสมอ - แม้ว่าอินสแตนซ์นั้นมีอยู่แล้ว
Oddthinking

10

ฉันคิดว่าคำตอบง่ายๆสำหรับคำถามนี้คือถ้า__new__ส่งคืนค่าที่เป็นชนิดเดียวกันกับคลาส__init__ฟังก์ชันจะเรียกใช้งานมิฉะนั้นจะไม่เกิดขึ้น ในกรณีนี้รหัสของคุณจะส่งคืนA._dict('key')ซึ่งเป็นระดับเดียวกันกับclsดังนั้น__init__จะถูกดำเนินการ


10

เมื่อ__new__คืนค่าอินสแตนซ์ของคลาสเดียวกันให้__init__รันหลังจากนั้นบนอ็อบเจ็กต์ที่ส่งคืน คือคุณไม่สามารถใช้__new__เพื่อป้องกันไม่ให้__init__ถูกเรียกใช้ แม้ว่าคุณจะส่งคืนวัตถุที่สร้างขึ้นก่อนหน้านี้__new__แต่ก็จะเป็นสองเท่า (สามเท่าและอื่น ๆ ... ) ที่เริ่มต้นได้__init__ครั้งแล้วครั้งเล่า

นี่คือวิธีการทั่วไปในรูปแบบซิงเกิลซึ่งขยายคำตอบของ vartec ด้านบนและแก้ไข:

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None

        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance

        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True  # Its crucial to set this variable on the class!
    return Single

เรื่องเต็มเป็นที่นี่

วิธีการอื่นซึ่งอันที่จริงเกี่ยวข้องกับ__new__การใช้ classmethods:

class Singleton(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls


class MyClass(Singleton):
    @classmethod
    def __init__(cls, x, y):
        print "init is here"

    @classmethod
    def do(cls):
        print "doing stuff"

โปรดให้ความสนใจว่าด้วยวิธีนี้คุณจะต้องตกแต่งทุกวิธีการของคุณด้วยเพราะคุณจะไม่ใช้อินสแตนซ์ของจริงใด@classmethodMyClass


6

อ้างถึงเอกสารนี้ :

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

ใหม่วิธีการที่เรียกว่ามีระดับเป็นอาร์กิวเมนต์แรกของตน ความรับผิดชอบของมันคือการส่งคืนอินสแตนซ์ใหม่ของคลาสนั้น

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

มีสถานการณ์ที่สร้างอินสแตนซ์ใหม่โดยไม่ต้องเรียกinit (ตัวอย่างเช่นเมื่อโหลดอินสแตนซ์จากผักดอง) ไม่มีวิธีในการสร้างอินสแตนซ์ใหม่โดยไม่ต้องโทรใหม่ (แม้ว่าในบางกรณีคุณสามารถหลีกเลี่ยงการเรียกคลาสใหม่ได้ )

เกี่ยวกับสิ่งที่คุณต้องการบรรลุนอกจากนี้ยังมีข้อมูลเอกสารเดียวกันเกี่ยวกับรูปแบบซิงเกิล

class Singleton(object):
        def __new__(cls, *args, **kwds):
            it = cls.__dict__.get("__it__")
            if it is not None:
                return it
            cls.__it__ = it = object.__new__(cls)
            it.init(*args, **kwds)
            return it
        def init(self, *args, **kwds):
            pass

คุณสามารถใช้การดำเนินการนี้จาก PEP 318 โดยใช้มัณฑนากร

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
...

1
โทรรูปแบบของ bastardized initจาก__new__เสียง hacky จริงๆ นี่คือสิ่งที่ metaclasses มีไว้สำหรับ
นักฟิสิกส์บ้า

6
class M(type):
    _dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print 'EXISTS'
            return cls._dict[key]
        else:
            print 'NEW'
            instance = super(M, cls).__call__(key)
            cls._dict[key] = instance
            return instance

class A(object):
    __metaclass__ = M

    def __init__(self, key):
        print 'INIT'
        self.key = key
        print

a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')

เอาท์พุท:

NEW
INIT

NEW
INIT

EXISTS

NB ในฐานะที่เป็นผลข้างเคียงของM._dictสถานที่ให้บริการโดยอัตโนมัติกลายเป็นสามารถเข้าถึงได้จากAเป็นA._dictเพื่อดูแลไม่ให้เขียนทับมันบังเอิญ


คุณกำลังขาดหายไปวิธีการที่ชุด__init__ cls._dict = {}คุณอาจไม่ต้องการให้พจนานุกรมแชร์โดยคลาสทั้งหมดของรูปแบบนี้ (แต่ +1 สำหรับแนวคิด)
นักฟิสิกส์บ้า

5

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

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


5

การอัปเดตคำตอบ @AntonyHatchkins คุณอาจต้องการพจนานุกรมแยกอินสแตนซ์สำหรับแต่ละคลาสของ metatype ซึ่งหมายความว่าคุณควรมี__init__เมธอดใน metaclass เพื่อเริ่มต้นออบเจ็กต์คลาสของคุณด้วยพจนานุกรมนั้นแทนที่จะทำให้เป็นโกลบอลในทุกคลาส

class MetaQuasiSingleton(type):
    def __init__(cls, name, bases, attibutes):
        cls._dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print('EXISTS')
            instance = cls._dict[key]
        else:
            print('NEW')
            instance = super().__call__(key)
            cls._dict[key] = instance
        return instance

class A(metaclass=MetaQuasiSingleton):
    def __init__(self, key):
        print 'INIT'
        self.key = key
        print()

ฉันได้ไปข้างหน้าและอัปเดตรหัสต้นฉบับด้วย__init__วิธีการและเปลี่ยนไวยากรณ์เป็นสัญกรณ์ Python 3 (ไม่มีการเรียกร้องให้superและ metaclass ในอาร์กิวเมนต์คลาสแทนที่จะเป็นแอตทริบิวต์)

ไม่ว่าจะด้วยวิธีใดจุดสำคัญที่นี่คือการเริ่มต้นคลาสของคุณ ( __call__เมธอด) จะไม่ดำเนินการอย่างใดอย่างหนึ่ง__new__หรือ__init__หากพบคีย์ นี่สะอาดกว่าการใช้__new__มากซึ่งคุณต้องทำเครื่องหมายวัตถุหากคุณต้องการข้าม__init__ขั้นตอนเริ่มต้น


4

ขุดลึกลงไปสักนิด!

ชนิดของคลาสทั่วไปใน CPython คือtypeและคลาสพื้นฐานคือObject(ยกเว้นว่าคุณกำหนดคลาสพื้นฐานอื่นเช่น metaclass) ลำดับของการโทรในระดับต่ำที่สามารถพบได้ที่นี่ วิธีแรกเรียกว่าเป็นtype_callที่แล้วเรียกแล้วtp_newtp_init

ส่วนที่น่าสนใจที่นี่คือที่tp_newจะเรียกObjectวิธีการใหม่ 's (คลาสฐาน) object_newซึ่งจะtp_alloc( PyType_GenericAlloc) ซึ่งจัดสรรหน่วยความจำสำหรับวัตถุ :)

ณ จุดนั้นวัตถุถูกสร้างขึ้นในหน่วยความจำแล้ว__init__วิธีการที่ได้รับการเรียก หาก__init__ไม่ได้นำไปใช้ในชั้นเรียนของคุณแล้วผู้ที่object_initถูกเรียกและไม่ทำอะไรเลย :)

จากนั้นtype_callก็ส่งคืนวัตถุที่ผูกกับตัวแปรของคุณ


4

เราควรมอง__init__ว่าเป็นตัวสร้างอย่างง่ายในภาษา OO แบบดั้งเดิม ตัวอย่างเช่นถ้าคุณคุ้นเคยกับ Java หรือ C ++ ตัวสร้างจะถูกส่งผ่านตัวชี้ไปยังอินสแตนซ์ของมันเองโดยปริยาย ในกรณีของ Java มันเป็นthisตัวแปร หากมีการตรวจสอบรหัสไบต์ที่สร้างขึ้นสำหรับ Java หนึ่งจะสังเกตเห็นสองสาย การเรียกครั้งแรกคือวิธีการ "ใหม่" จากนั้นการโทรครั้งต่อไปคือวิธีการเริ่มต้น (ซึ่งเป็นการเรียกจริงไปยังตัวสร้างที่ผู้ใช้กำหนด) กระบวนการสองขั้นตอนนี้ช่วยให้สามารถสร้างอินสแตนซ์ที่เกิดขึ้นจริงก่อนที่จะเรียกเมธอดตัวสร้างของคลาสซึ่งเป็นอีกวิธีหนึ่งของอินสแตนซ์นั้น

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


4

แต่ฉันบิตสับสนว่าทำไมถึงอยู่เสมอเรียกว่าหลังจาก__init____new__

ฉันคิดว่าการเปรียบเทียบ C ++ จะมีประโยชน์ที่นี่:

  1. __new__เพียงจัดสรรหน่วยความจำสำหรับวัตถุ ตัวแปรอินสแตนซ์ของวัตถุต้องการหน่วยความจำเพื่อเก็บไว้และนี่คือสิ่งที่ขั้นตอน__new__จะทำ

  2. __init__ เริ่มต้นตัวแปรภายในของวัตถุให้เป็นค่าเฉพาะ (อาจเป็นค่าเริ่มต้น)


2

__init__เรียกว่าหลังจาก__new__เพื่อที่ว่าเมื่อคุณแทนที่ในประเภทรองรหัสเพิ่มคุณจะยังคงได้รับการเรียก

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

__init__ยังคงต้องการการอนุญาตสำหรับพารามิเตอร์ใด ๆ ซับคลาส__new__จำเป็น แต่ล้มเหลวในการทำเช่นนั้นมักจะสร้างข้อผิดพลาด runtime ชัดเจน และ__new__ควรอนุญาตอย่างชัดเจนสำหรับ*argsและ '** kw' เพื่อให้ชัดเจนว่าส่วนขยายนั้นใช้ได้

โดยทั่วไปแล้วเป็นรูปแบบที่ไม่ดีที่จะมีทั้ง__new__และ__init__ในระดับเดียวกันในระดับเดียวกันของการสืบทอดเนื่องจากพฤติกรรมที่โปสเตอร์ดั้งเดิมอธิบายไว้


1

แต่ฉันบิตสับสนว่าทำไมถึงอยู่เสมอเรียกว่าหลังจาก__init____new__

ไม่มากไปกว่าเหตุผลอื่นนอกจากที่เพิ่งทำแบบนั้น __new__ไม่มีความรับผิดชอบในการเริ่มต้นชั้นเรียนวิธีอื่น ๆ ทำ ( __call__อาจ - ฉันไม่รู้แน่)

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

คุณไม่สามารถ__init__ทำอะไรได้เลยถ้ามันถูกเริ่มต้นแล้วหรือคุณสามารถเขียน metaclass ใหม่ด้วยตัวใหม่__call__ที่เรียก__init__ใช้อินสแตนซ์ใหม่__new__(...)เท่านั้น


1

เหตุผลง่าย ๆ คือมีการใช้ใหม่สำหรับการสร้างอินสแตนซ์ในขณะที่initใช้สำหรับเริ่มต้นอินสแตนซ์ ก่อนเริ่มต้นควรสร้างอินสแตนซ์ก่อน นั่นเป็นเหตุผลที่ใหม่ควรจะเรียกว่าก่อนinit


1

ตอนนี้ฉันมีปัญหาเดียวกันและด้วยเหตุผลบางอย่างฉันตัดสินใจที่จะหลีกเลี่ยงการตกแต่งโรงงานและ metaclasses ฉันทำแบบนี้:

ไฟล์หลัก

def _alt(func):
    import functools
    @functools.wraps(func)
    def init(self, *p, **k):
        if hasattr(self, "parent_initialized"):
            return
        else:
            self.parent_initialized = True
            func(self, *p, **k)

    return init


class Parent:
    # Empty dictionary, shouldn't ever be filled with anything else
    parent_cache = {}

    def __new__(cls, n, *args, **kwargs):

        # Checks if object with this ID (n) has been created
        if n in cls.parent_cache:

            # It was, return it
            return cls.parent_cache[n]

        else:

            # Check if it was modified by this function
            if not hasattr(cls, "parent_modified"):
                # Add the attribute
                cls.parent_modified = True
                cls.parent_cache = {}

                # Apply it
                cls.__init__ = _alt(cls.__init__)

            # Get the instance
            obj = super().__new__(cls)

            # Push it to cache
            cls.parent_cache[n] = obj

            # Return it
            return obj

ตัวอย่างคลาส

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)


class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

ในการใช้งาน

>>> A(1)
A.__init__ 1  # First A(1) initialized 
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1)      # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2  # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
  • คำเตือน:คุณไม่ควรเริ่มต้น Parent มันจะชนกับคลาสอื่น ๆ ยกเว้นว่าคุณได้กำหนดแคชแยกต่างหากในเด็กแต่ละคนนั่นไม่ใช่สิ่งที่เราต้องการ
  • คำเตือน:ดูเหมือนว่าคลาสกับผู้ปกครองในขณะที่ปู่ย่าตายายมีพฤติกรรมแปลก ๆ [ไม่ยืนยัน]

ลองออนไลน์!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.