ทำไม @ foo.setter ใน Python ไม่ทำงานสำหรับฉัน


155

ดังนั้นฉันเล่นกับมัณฑนากรใน Python 2.6 และฉันมีปัญหาในการทำให้พวกเขาทำงาน นี่คือไฟล์คลาสของฉัน:

class testDec:

    @property
    def x(self): 
        print 'called getter'
        return self._x

    @x.setter
    def x(self, value): 
        print 'called setter'
        self._x = value

สิ่งที่ฉันคิดว่านี่หมายถึงการปฏิบัติต่อxทรัพย์สิน แต่เรียกใช้ฟังก์ชันเหล่านี้เพื่อรับและตั้งค่า ดังนั้นฉันเลย IDLE และตรวจสอบ:

>>> from testDec import testDec
from testDec import testDec
>>> t = testDec()
t = testDec()
>>> t.x
t.x
called getter
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "testDec.py", line 18, in x
    return self._x
AttributeError: testDec instance has no attribute '_x'
>>> t.x = 5
t.x = 5
>>> t.x
t.x
5

เห็นได้ชัดว่าการโทรครั้งแรกทำงานได้อย่างที่คาดไว้เนื่องจากฉันเรียกใช้ getter และไม่มีค่าเริ่มต้นและมันล้มเหลว ตกลงดีฉันเข้าใจ อย่างไรก็ตามการเรียกเพื่อกำหนดt.x = 5ดูเหมือนว่าจะสร้างคุณสมบัติใหม่xและตอนนี้ผู้ทะเยอทะยานไม่ทำงาน!

ฉันพลาดอะไรไป


6
โปรดตั้งชื่อคลาสด้วยตัวอักษรตัวพิมพ์ใหญ่ คุณสามารถใช้ตัวพิมพ์เล็กสำหรับตัวแปรและไฟล์โมดูล
S.Lott

คำตอบ:


306

ดูเหมือนว่าคุณจะใช้คลาสคลาสสิกแบบเก่าใน python 2 เพื่อให้คุณสมบัติทำงานได้อย่างถูกต้องคุณต้องใช้คลาสสไตล์ใหม่แทน (ในไพ ธ อน 2 คุณต้องสืบทอดจากobject ) แค่ประกาศชั้นเรียนของคุณเป็นMyClass(object):

class testDec(object):

    @property
    def x(self): 
        print 'called getter'
        return self._x

    @x.setter
    def x(self, value): 
        print 'called setter'
        self._x = value

มันได้ผล:

>>> k = testDec()
>>> k.x
called getter
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/devel/class_test.py", line 6, in x
    return self._x
AttributeError: 'testDec' object has no attribute '_x'
>>> k.x = 5
called setter
>>> k.x
called getter
5
>>> 

รายละเอียดอื่นที่อาจทำให้เกิดปัญหาคือทั้งสองวิธีต้องใช้ชื่อเดียวกันสำหรับคุณสมบัติในการทำงาน หากคุณกำหนด setter ด้วยชื่ออื่นเช่นนี้มันจะไม่ทำงาน :

@x.setter
def x_setter(self, value):
    ...

และสิ่งหนึ่งที่ไม่สมบูรณ์เรื่องง่ายที่จะจุดในตอนแรกคือการสั่งซื้อ: ผู้ทะเยอทะยานต้องกำหนดครั้งแรก หากคุณกำหนดตัวตั้งค่าก่อนคุณจะได้รับ name 'x' is not definedข้อผิดพลาด


4
ใน Python3 ไม่จำเป็นอีกต่อไป - คลาส "คลาสสิก" ใช้ได้กับ setters :-)
Eenoku

20
มันทำงานได้ในหลาม 3 เพราะทุกคลาสเป็นคลาสสไตล์ใหม่ไม่มีคลาส 'คลาสสิก' อีกต่อไป
craigds

ฉันคิดว่าควรได้รับคำเตือน / ข้อผิดพลาดหากมัณฑนากรไม่ได้รับการประมวลผลอย่างถูกต้องในกรณีนี้
เขียน

82

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

@property
def x(self): pass

@x.setter
def x_setter(self, value): pass

ให้ชื่อเดียวกันทั้งสองวิธีแทน

@property
def x(self): pass

@x.setter
def x(self, value): pass

มันเป็นสิ่งสำคัญที่จะต้องทราบว่าคำสั่งของการประกาศเรื่อง getter ต้องถูกกำหนดไว้ก่อน setter ในไฟล์มิฉะนั้นคุณจะได้รับNameError: name 'x' is not defined


นี่อาจจะเป็นคำตอบที่ดีที่สุด "ใหม่" กับผู้คนจำนวนมากบน Python 3 ...
trpt4him

5
คำตอบนี้ดีมาก ฉันคิดว่าการทำให้เสร็จสมบูรณ์จะดีมากถ้าคุณสามารถสังเกตได้ว่าคำสั่งนั้นมีความสำคัญนั่นคือผู้ทะเยอทะยานต้องอยู่หน้าผู้ตั้งรหัส มิฉะนั้นคุณจะได้รับการNameError: name 'x' is not definedยกเว้น
eguaio

ขอบคุณสำหรับสิ่งนี้. ฉันเพิ่งเสียส่วนที่ดีที่สุดของวันนี้ สำหรับฉันมันไม่เคยเกิดขึ้นเลยว่าวิธีการนั้นจะต้องถูกเรียกในสิ่งเดียวกัน น่าผิดหวังจริง ๆ สำนวนน้อย!
ajkavanagh

หากต้องการทำความเข้าใจกับ "สาเหตุ" ที่อยู่เบื้องหลังคำตอบนี้ให้อ่านเอกสารที่ชัดเจนที่นี่: docs.python.org/2/library/functions.html#propertyโดยเฉพาะอย่างยิ่งโปรดสังเกตส่วน "ที่เทียบเท่า"
Evgeni Sergeev

23

คุณต้องใช้คลาสสไตล์ใหม่ที่คุณทำได้โดยการรับคลาสจากอ็อบเจกต์:

class testDec(object):
   ....

จากนั้นมันควรจะทำงาน


ทุกอย่างข้างต้นอยู่ในสถานที่ นี่เป็นสิ่งจำเป็นสำหรับฉันใน python 2.7.x สำหรับคุณสมบัติในคลาสเพื่อเตะอย่างถูกต้อง
Drone Brain

12

ในกรณีที่มีใครมาที่นี่จาก google นอกเหนือจากคำตอบข้างต้นฉันต้องการที่จะเพิ่มความระมัดระวังในการเรียกใช้ setter จาก__init__วิธีการเรียนของคุณตามคำตอบนี้ โดยเฉพาะ:

class testDec(object):                                                                                                                                            

    def __init__(self, value):
        print 'We are in __init__'
        self.x = value # Will call the setter. Note just x here
        #self._x = value # Will not call the setter

    @property
    def x(self):
        print 'called getter'
        return self._x # Note the _x here

    @x.setter
    def x(self, value): 
        print 'called setter'
        self._x = value # Note the _x here

t = testDec(17)
print t.x 

Output:
We are in __init__
called setter
called getter
17

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