ทำไมคุณต้องมีอาร์กิวเมนต์“ ตัวตน” อย่างชัดเจนในเมธอด Python?


197

เมื่อกำหนดวิธีการในชั้นเรียนใน Python มันมีลักษณะดังนี้:

class MyClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

แต่ในบางภาษาอื่น ๆ เช่น C # คุณมีการอ้างอิงถึงวัตถุที่วิธีการที่ถูกผูกไว้กับคำหลัก "นี้" โดยไม่ต้องประกาศว่ามันเป็นข้อโต้แย้งในต้นแบบวิธีการ

นี่เป็นการตัดสินใจออกแบบภาษาโดยเจตนาใน Python หรือมีรายละเอียดการนำไปปฏิบัติบางอย่างที่ต้องใช้การผ่าน "ตนเอง" เป็นอาร์กิวเมนต์หรือไม่?


15
ฉันพนันว่าคุณสนใจที่จะรู้ว่าทำไมคุณต้องเขียนselfถึงสมาชิกอย่างชัดเจน- stackoverflow.com/questions/910020/ …
Piotr Dobrogost

1
แต่มันก็ดูครับจานหม้อไอน้ำแม้ว่า
Raghuveer

สับสนเล็กน้อย แต่คุ้มค่ากับการทำความเข้าใจstackoverflow.com/a/31367197/1815624
CrandellWS

คำตอบ:


91

ฉันชอบเสนอราคา Peters 'Zen of Python "ชัดเจนดีกว่าโดยปริยาย"

ใน Java และ C ++, ' this.' สามารถอนุมานได้ยกเว้นเมื่อคุณมีชื่อตัวแปรที่ทำให้ไม่สามารถอนุมานได้ ดังนั้นบางครั้งคุณต้องการมันและบางครั้งก็ทำไม่ได้

Python เลือกที่จะทำสิ่งนี้อย่างชัดเจนมากกว่ายึดตามกฎ

นอกจากนี้เนื่องจากไม่มีสิ่งใดที่บอกเป็นนัยหรือถือว่าเป็นส่วนหนึ่งของการนำไปปฏิบัติ self.__class__, self.__dict__และโครงสร้างอื่น ๆ "ภายใน" ที่มีอยู่ในทางที่เห็นได้ชัด


53
แม้ว่ามันจะเป็นการดีหากมีข้อความแสดงข้อผิดพลาดที่เป็นความลับน้อยลงเมื่อคุณลืม
Martin Beckett

9
อย่างไรก็ตามเมื่อคุณเรียกใช้เมธอดที่คุณไม่จำเป็นต้องผ่านตัวแปรออบเจคต์มันจะไม่ทำลายกฎของการอธิบายอย่างชัดเจนใช่ไหม หากต้องการเก็บเซนนี้ไว้จะต้องมีลักษณะดังนี้: object.method (object, param1, param2) ดูไม่ลงรอยกันอย่างใด ...
Vedmant

10
"ชัดเจนดีกว่าโดยนัย" - "สไตล์" ของ Python ไม่ได้ถูกสร้างขึ้นด้วยสิ่งต่าง ๆ โดยปริยายใช่หรือไม่? เช่นประเภทข้อมูลโดยนัยขอบเขตฟังก์ชันโดยนัย (ไม่ใช่ {}) ขอบเขตตัวแปรโดยนัย ... หากตัวแปรส่วนกลางในโมดูลมีอยู่ในฟังก์ชัน ... ทำไมจึงไม่ควรใช้ตรรกะ / เหตุผลเดียวกันกับคลาส กฎแบบง่าย ๆ จะไม่ใช่แค่ "สิ่งที่ประกาศในระดับที่สูงกว่านั้นมีให้ในระดับที่ต่ำกว่า" ตามที่กำหนดโดยการเยื้องหรือไม่
Simon

13
"ชัดเจนดีกว่าโดยปริยาย" ตรวจพบเรื่องไร้สาระ
Vahid Amiri

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

63

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

เช่น

>>> class C(object):
...     def foo(self):
...         print "Hi!"
...
>>>
>>> def bar(self):
...     print "Bork bork bork!"
...
>>>
>>> c = C()
>>> C.bar = bar
>>> c.bar()
Bork bork bork!
>>> c.foo()
Hi!
>>>

นอกจากนี้ (เท่าที่ฉันรู้) ทำให้การใช้งาน python runtime ง่ายขึ้น


10
+1 สำหรับมันเพื่อลดความแตกต่างระหว่างวิธีการและฟังก์ชั่น คำตอบนี้ควรได้รับการยอมรับ
ผู้ใช้

นี่เป็นต้นกำเนิดของคำอธิบายที่เชื่อมโยงกันมากของกุยโด้
Marcin

1
สิ่งนี้ยังแสดงให้เห็นว่าใน Python เมื่อคุณทำ c.bar () ก่อนอื่นมันจะตรวจสอบอินสแตนซ์ของแอททริบิวจากนั้นจะตรวจสอบแอททริบิวต์คลาส ดังนั้นคุณสามารถ 'แนบ' ข้อมูลหรือฟังก์ชั่น (วัตถุ) กับคลาสได้ทุกเวลาและคาดว่าจะเข้าถึงได้ในอินสแตนซ์ของมัน (เช่น dir (อินสแตนซ์) จะเป็นอย่างไร) ไม่ใช่แค่เมื่อคุณ "สร้าง" อินสแตนซ์ c มันมีพลังมาก
Nishant

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

2
JavaScript ช่วยให้คุณสามารถเพิ่มวิธีการไปยังวัตถุที่ใช้เวลาและมันไม่จำเป็นต้องselfในการประกาศฟังก์ชัน (ใจคุณอาจมีการขว้างปาก้อนหินออกจากบ้านแก้วตั้งแต่ JavaScript มีบางส่วนที่ยุ่งยากสวยthisความหมายผูกพัน)
โจนาธานเบนน์

55

ผมขอแนะนำว่าควรอ่านบล็อกกุยรถตู้ซัมในหัวข้อนี้ - ทำไมตัวเองอย่างชัดเจนมีการเข้าพัก

เมื่อนิยามวิธีการตกแต่งเราไม่ทราบว่าจะให้พารามิเตอร์ 'ตัวเอง' โดยอัตโนมัติหรือไม่: มัณฑนากรสามารถเปลี่ยนฟังก์ชั่นเป็นวิธีคงที่ (ซึ่งไม่มี 'ตัวเอง') หรือวิธีการเรียน (ซึ่ง มีตัวตลกที่อ้างถึงคลาสแทนอินสแตนซ์) หรืออาจทำสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง (มันเป็นเรื่องไม่สำคัญที่จะเขียนมัณฑนากรที่ใช้ '@classmethod' หรือ '@staticmethod' ใน Python บริสุทธิ์) ไม่มีวิธีใดที่จะไม่รู้ว่ามัณฑนากรทำอะไรเพื่อให้วิธีการที่ถูกกำหนดด้วยอาร์กิวเมนต์ 'ตัวตน' โดยนัยหรือไม่

ฉันปฏิเสธแฮ็กเช่นปลอกพิเศษ '@classmethod' และ '@staticmethod'


16

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


โดยการประชุมมันควรจะเป็น 'ตัวเอง' สำหรับอินสแตนซ์หรือ 'cls' ที่ประเภทมีส่วนร่วม (mmmm metaclasses)
pobk

1
มันบังคับให้ใส่ตนเองเป็นพารามิเตอร์แรกในทุกวิธีเพียงแค่ข้อความพิเศษที่ไม่สมเหตุสมผลสำหรับฉัน ภาษาอื่นใช้งานได้ดีกับสิ่งนี้
Vedmant

ฉันถูกไหม ? เสมอพารามิเตอร์แรกคือการอ้างอิงถึงวัตถุ
Mohammad Mahdi KouchakYazdi

@MMKY ไม่ตัวอย่างเช่น@staticmethodไม่ใช่
ทำเครื่องหมาย

1
"คุณต้องจำไว้ว่าอาร์กิวเมนต์แรกในคำนิยามวิธีการ ... " ฉันได้ทดลองเปลี่ยนคำว่า "ตัวเอง" เป็น "kwyjibo" และมันก็ยังใช้ได้อยู่ ตามที่อธิบายไว้บ่อยๆมันไม่ใช่คำว่า "ตัวเอง" ที่สำคัญ แต่ตำแหน่งของสิ่งใดก็ตามที่ครอบครองพื้นที่นั้น (?)
RBV

7

อนุญาตให้คุณทำสิ่งนี้ด้วย: (ในระยะสั้นการเรียกใช้Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)จะคืนค่า 12 แต่จะทำในวิธีที่บ้าที่สุด

class Outer(object):
    def __init__(self, outer_num):
        self.outer_num = outer_num

    def create_inner_class(outer_self, inner_arg):
        class Inner(object):
            inner_arg = inner_arg
            def weird_sum_with_closure_scope(inner_self, num)
                return num + outer_self.outer_num + inner_arg
        return Inner

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

ยิ่งไปกว่านั้นลองจินตนาการถึงสิ่งนี้: เราต้องการปรับแต่งพฤติกรรมของวิธีการ (สำหรับการทำโปรไฟล์หรือเวทมนตร์สีดำที่บ้าคลั่ง) สิ่งนี้สามารถทำให้เราคิดได้: ถ้าเรามีคลาสMethodที่มีพฤติกรรมที่เราสามารถแทนที่หรือควบคุมได้?

นี่มันคือ:

from functools import partial

class MagicMethod(object):
    """Does black magic when called"""
    def __get__(self, obj, obj_type):
        # This binds the <other> class instance to the <innocent_self> parameter
        # of the method MagicMethod.invoke
        return partial(self.invoke, obj)


    def invoke(magic_self, innocent_self, *args, **kwargs):
        # do black magic here
        ...
        print magic_self, innocent_self, args, kwargs

class InnocentClass(object):
    magic_method = MagicMethod()

และตอนนี้: InnocentClass().magic_method()จะทำหน้าที่อย่างที่คาดไว้ วิธีการจะถูกผูกไว้กับinnocent_selfพารามิเตอร์ถึงInnocentClassและกับmagic_selfอินสแตนซ์ถึง MagicMethod แปลกเหรอ? มันเหมือนกับมี 2 คำหลักthis1และthis2ในภาษาเช่น Java และ C # เวทย์มนตร์เช่นนี้อนุญาตให้เฟรมเวิร์กทำสิ่งต่าง ๆ ที่จะเป็น verbose มากขึ้น

อีกครั้งฉันไม่ต้องการที่จะแสดงความคิดเห็นเกี่ยวกับจริยธรรมของสิ่งนี้ ฉันแค่อยากจะแสดงสิ่งต่าง ๆ ที่ยากที่จะทำโดยไม่มีการอ้างอิงด้วยตนเองอย่างชัดเจน


4
เมื่อฉันพิจารณาตัวอย่างแรกของคุณฉันสามารถทำเช่นเดียวกันใน Java: ชั้นในจำเป็นต้องโทรOuterClass.thisเพื่อรับ 'ตัวเอง' จากชั้นนอก แต่คุณยังสามารถใช้thisเป็นการอ้างอิงกับตัวเอง คล้ายกับสิ่งที่คุณทำใน Python สำหรับฉันมันไม่ยากที่จะจินตนาการเรื่องนี้ บางทีมันอาจขึ้นอยู่กับความสามารถในการใช้ภาษาที่สงสัย
klaar

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

thisคุณมีสิทธิในชวาคุณสามารถดูที่ชั้นนอกโดยการเรียกชื่อคลาสที่ชัดเจนและการใช้งานที่คำนำหน้าของมัน การอ้างอิงโดยนัยคือ impossibru ใน Java
klaar

ฉันสงสัยว่ามันจะใช้งานได้หรือไม่: ในแต่ละขอบเขต (แต่ละวิธี) มีตัวแปรเฉพาะที่จะอ้างอิงthisผลลัพธ์ ตัวอย่างเช่นObject self1 = this;(ใช้ Object หรือบางอย่างที่มีความหมายน้อยกว่า) แล้วถ้าคุณมีการเข้าถึงตัวแปรในขอบเขตที่สูงขึ้นคุณอาจมีการเข้าถึงself1, ...self2 selfnฉันคิดว่าสิ่งเหล่านี้ควรได้รับการประกาศขั้นสุดท้ายหรือบางสิ่งบางอย่าง แต่อาจใช้ได้
vlad-ardelean

3

ฉันคิดว่าเหตุผลที่แท้จริงนอกเหนือจาก "The Zen of Python" ก็คือฟังก์ชั่นเป็นพลเมืองชั้นหนึ่งใน Python

ซึ่งทำให้พวกเขาเป็นวัตถุ ตอนนี้ปัญหาพื้นฐานคือถ้าฟังก์ชั่นของคุณเป็นวัตถุในกระบวนทัศน์เชิงวัตถุคุณจะส่งข้อความไปยังวัตถุได้อย่างไรเมื่อข้อความนั้นเป็นวัตถุ

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

นี่หมายความว่าทางออกเดียวที่เป็นไปได้คือการส่ง 'ตัวเอง' อย่างชัดเจน (บริบทของการดำเนินการ)

ดังนั้นฉันเชื่อว่านี่เป็นปัญหาในการติดตั้งที่ Zen มาในภายหลัง


สวัสดีฉันยังใหม่กับงูหลาม (จากพื้นหลังของจาวา) และฉันก็ไม่ได้ไหลอย่างที่คุณพูด "คุณจะส่งข้อความไปยังวัตถุได้อย่างไรเมื่อข้อความเป็นวัตถุ" ทำไมถึงเป็นปัญหาคุณสามารถอธิบายอย่างละเอียด?
Qiulang

1
@Qiulang Aah ในการเขียนโปรแกรมเชิงวัตถุวิธีการโทรบนวัตถุเทียบเท่ากับการส่งข้อความไปยังวัตถุที่มีหรือไม่มี payload (พารามิเตอร์ไปยังฟังก์ชั่นของคุณ) วิธีการภายในจะถูกแสดงเป็นบล็อกของรหัสที่เกี่ยวข้องกับคลาส / วัตถุและใช้สภาพแวดล้อมโดยปริยายที่มีให้มันผ่านวัตถุที่มันถูกเรียก แต่ถ้าวิธีการของคุณเป็นวัตถุพวกเขาสามารถมีอิสระจากการเชื่อมโยงกับชั้นเรียน / วัตถุที่ถามคำถามถ้าคุณเรียกใช้วิธีการนี้มันจะสภาพแวดล้อมที่จะทำงานกับ?
pankajdoharey

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

2

ฉันคิดว่ามันเกี่ยวกับ PEP 227:

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


1

ตามที่อธิบายไว้ในตนเองใน Python Demystified

ทุกอย่างเช่น obj.meth (args) จะกลายเป็น Class.meth (obj, args) กระบวนการเรียกใช้โดยอัตโนมัติในขณะที่กระบวนการรับไม่ได้ (ชัดเจน) นี่คือเหตุผลที่พารามิเตอร์แรกของฟังก์ชันในคลาสต้องเป็นวัตถุเอง

class Point(object):
    def __init__(self,x = 0,y = 0):
        self.x = x
        self.y = y

    def distance(self):
        """Find distance from origin"""
        return (self.x**2 + self.y**2) ** 0.5

สวด:

>>> p1 = Point(6,8)
>>> p1.distance()
10.0

init () กำหนดสามพารามิเตอร์ แต่เราเพิ่งผ่านสอง (6 และ 8) ระยะทางในทำนองเดียวกัน () ต้องการหนึ่งอาร์กิวเมนต์ แต่ผ่านศูนย์แล้ว

ทำไมไพ ธ อนไม่บ่นเกี่ยวกับหมายเลขอาร์กิวเมนต์นี้ไม่ตรงกัน ?

โดยทั่วไปเมื่อเราเรียกเมธอดที่มีอาร์กิวเมนต์บางตัวฟังก์ชันคลาสที่สอดคล้องกันจะถูกเรียกใช้โดยการวางวัตถุของเมธอดก่อนอาร์กิวเมนต์แรก ดังนั้นทุกอย่างเช่น obj.meth (args) จะกลายเป็น Class.meth (obj, args) กระบวนการเรียกใช้โดยอัตโนมัติในขณะที่กระบวนการรับไม่ได้ (ชัดเจน)

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

ตนเองอยู่ที่นี่อยู่

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


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