ใช้ @property กับ getters และ setters


727

นี่เป็นคำถามออกแบบเฉพาะของ Python:

class MyClass(object):
    ...
    def get_my_attr(self):
        ...

    def set_my_attr(self, value):
        ...

และ

class MyClass(object):
    ...        
    @property
    def my_attr(self):
        ...

    @my_attr.setter
    def my_attr(self, value):
        ...

Python ช่วยให้เราสามารถทำอย่างใดอย่างหนึ่ง หากคุณต้องการออกแบบโปรแกรม Python คุณจะใช้แนวทางใดและทำไม

คำตอบ:


613

ต้องการคุณสมบัติ มันคือสิ่งที่พวกเขามีเพื่อ

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

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


90
ชื่อแอททริบิวที่มีเครื่องหมายขีดล่างคู่จะถูกจัดการเป็นพิเศษโดย Python มันไม่ใช่แค่การประชุมเท่านั้น ดูdocs.python.org/py3k/tutorial/classes.html#private-variables
6502

63
พวกเขาได้รับการจัดการแตกต่างกันไป แต่นั่นไม่ได้ทำให้คุณไม่สามารถเข้าถึงพวกเขาได้ PS: โฆษณา 30 C0
ทุกอย่าง

4
และเนื่องจากตัวอักษร "@" นั้นน่าเกลียดในรหัสไพ ธ อนและ dereferencing @decorators จึงให้ความรู้สึกเหมือนกับสปาเก็ตตี้โค้ด
Berry Tsakala

18
ฉันไม่เห็นด้วย โค้ดที่มีโครงสร้างเท่ากับโค้ดสปาเก็ตตี้อย่างไร Python เป็นภาษาที่สวยงาม แต่จะดียิ่งขึ้นด้วยการสนับสนุนที่ดีขึ้นสำหรับสิ่งที่ง่ายเช่นการห่อหุ้มที่เหมาะสมและคลาสที่มีโครงสร้าง

69
ในขณะที่ฉันเห็นด้วยในกรณีส่วนใหญ่โปรดระมัดระวังเกี่ยวกับการซ่อนวิธีการช้า ๆ ไว้เบื้องหลัง @property decorator ผู้ใช้ API ของคุณคาดว่าการเข้าถึงคุณสมบัติจะเหมือนกับการเข้าถึงตัวแปรและการหลงทางที่ไกลเกินไปจากความคาดหวังนั้นจะทำให้ API ของคุณไม่น่าใช้
defrex

153

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

แน่นอนว่ามีรหัสจำนวนมากพร้อมส่วนขยาย. py ที่ใช้ getters และ setters และการสืบทอดและคลาสที่ไม่มีจุดหมายในทุกที่เช่น tuple แบบง่าย ๆ จะทำ แต่เป็นรหัสจากคนที่เขียนด้วย C ++ หรือ Java โดยใช้ Python

นั่นไม่ใช่รหัส Python


46
@ 6502 เมื่อคุณพูดว่า“ […] คลาสที่ไม่มีจุดหมายทุกที่เช่นทูเปิลเรียบง่ายจะทำ”: ข้อดีของการเรียนเหนือทูเปิลคือว่าอินสแตนซ์ของคลาสให้ชื่อที่ชัดเจนในการเข้าถึงส่วนต่างๆในขณะที่ tuple จะไม่ . ชื่อดีกว่าในการอ่านและหลีกเลี่ยงข้อผิดพลาดได้ดีกว่าการห้อยแบบ tuples โดยเฉพาะเมื่อสิ่งนี้ถูกส่งผ่านนอกโมดูลปัจจุบัน
Hibou57

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

39
@ Hibou57 สำหรับนั้นคุณยังสามารถใช้ namedtuple: doughellmann.com/PyMOTW/collections/namedtuple.html
hugo24

5
@JonathonReinhart: มันเป็นในห้องสมุดมาตรฐานตั้งแต่ 2.6 ... ดู docs.python.org/2/library/collections.html
6502

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

118

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


3
@ GregKrsak มันดูแปลก ๆ เพราะมันเป็น "สิ่งที่ผู้ใหญ่ยินยอม" เป็น python meme จากก่อนเพิ่มคุณสมบัติ มันเป็นการตอบสนองต่อสต็อกสินค้าให้กับผู้ที่ต้องการบ่นเกี่ยวกับการขาดตัวดัดแปลงการเข้าถึง เมื่อมีการเพิ่มคุณสมบัติก็จำเป็นต้องมีการห่อหุ้มทันที สิ่งเดียวกันนี้เกิดขึ้นกับคลาสฐานที่เป็นนามธรรม "งูใหญ่มักจะทำสงครามกับการทำลายจาก encapsulation เสรีภาพเป็นทาสแลมบ์ดาควรอยู่ในบรรทัดเดียวเท่านั้น"
johncip

71

คำตอบสั้น ๆ คือคุณสมบัติชนะมือลง เสมอ.

บางครั้งมีความต้องการผู้ได้รับและผู้ตั้ง แต่ฉันก็จะ "ซ่อน" พวกเขาไปยังโลกภายนอก มีมากมายหลายวิธีที่จะทำเช่นนี้ในหลาม (มีgetattr, setattr, __getattribute__ฯลฯ ... แต่อย่างรัดกุมและทำความสะอาดอย่างใดอย่างหนึ่งคือ

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

ต่อไปนี้เป็นบทความสั้น ๆที่แนะนำหัวข้อของ getters และ setters ใน Python


1
@ BasicWolf - ฉันคิดว่ามันชัดแจ้งโดยปริยายว่าฉันอยู่ฝั่งทรัพย์สินของรั้ว! :) แต่ฉันเพิ่มพาราคำตอบของฉันเพื่อชี้แจงว่า
แม็ค

9
คำแนะนำ: คำว่า "เสมอ" เป็นคำใบ้ที่ผู้เขียนพยายามโน้มน้าวคุณด้วยการยืนยันไม่ใช่การโต้แย้ง ดังนั้นการปรากฏตัวอักษรหนา (ฉันหมายถึงถ้าคุณเห็น CAPS แทนแล้ว - โห - มันต้องถูกต้อง) ดูสิคุณสมบัติ "คุณสมบัติ" นั้นแตกต่างจาก Java (Python de facto nemesis ด้วยเหตุผลบางอย่าง) และด้วยเหตุนี้กลุ่มชุมชน Python จึงคิดว่า ประกาศว่าดีกว่า ในความเป็นจริงคุณสมบัติละเมิดกฎ "ชัดเจนดีกว่าโดยปริยาย" แต่ไม่มีใครอยากยอมรับ มันทำให้มันเป็นภาษาดังนั้นตอนนี้ก็มีการประกาศ "Pythonic" ผ่านการโต้เถียงอย่างรุนแรง
Stuart Berg

3
ไม่รู้สึกเจ็บ :-P ฉันแค่พยายามที่จะชี้ให้เห็นว่า "Pythonic" การประชุมจะไม่สอดคล้องกันในกรณีนี้ "ชัดเจนดีกว่านัย" propertyอยู่ในความขัดแย้งโดยตรงกับการใช้ ( ดูเหมือนว่าการมอบหมายงานง่าย ๆ แต่มันเรียกฟังก์ชั่น) ดังนั้น "Pythonic" จึงเป็นคำที่ไม่มีความหมายยกเว้นโดยนิยามที่ซับซ้อน: "อนุสัญญา Pythonic เป็นสิ่งที่เราได้กำหนดให้เป็น Pythonic"
Stuart Berg

1
ตอนนี้ความคิดของการมีชุดของการประชุมที่เป็นไปตามรูปแบบที่เป็นที่ยอดเยี่ยม หากชุดของการประชุมนั้นมีอยู่จริงคุณสามารถใช้เป็นชุดสัจพจน์เพื่อนำทางความคิดของคุณไม่ใช่รายการตรวจสอบที่ใช้เวลานานในการท่องจำซึ่งมีประโยชน์น้อยกว่าอย่างมาก สัจพจน์สามารถใช้สำหรับการอนุมานและช่วยคุณแก้ไขปัญหาที่ยังไม่มีใครเห็น มันเป็นความอัปยศที่propertyคุณลักษณะนี้ขู่ว่าจะทำให้แนวคิดของ Pythonic axioms ไร้ค่าเกือบ ดังนั้นสิ่งที่เราเหลือคือรายการตรวจสอบ
Stuart Berg

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

65

[ TL; DR? คุณสามารถข้ามไปยังจุดสิ้นสุดได้เพื่อดูตัวอย่างโค้ด ]

จริง ๆ แล้วฉันชอบใช้สำนวนที่แตกต่างกันซึ่งมีส่วนเกี่ยวข้องกับการใช้เพียงเล็กน้อย แต่ดีมากถ้าคุณมีกรณีการใช้ที่ซับซ้อนกว่า

พื้นหลังเล็กน้อยก่อน

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

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

    def getX(self):
        return self.x or self.defaultX()

    def getY(self):
        return self.y or self.defaultY()

    def setX(self, x):
        self.x = x

    def setY(self, y):
        self.y = y

    def defaultX(self):
        return someDefaultComputationForX()

    def defaultY(self):
        return someDefaultComputationForY()

คุณอาจสงสัยว่าทำไมฉันไม่โทรdefaultXและdefaultYใน__init__วิธีการของวัตถุ เหตุผลก็คือสำหรับกรณีของเราฉันต้องการที่จะคิดว่าsomeDefaultComputationวิธีการส่งกลับค่าที่แตกต่างกันไปในช่วงเวลาพูดเวลาและเมื่อใดก็ตามที่x(หรือy) ไม่ได้ตั้งค่า (ที่สำหรับวัตถุประสงค์ของตัวอย่างนี้ "ไม่ได้ตั้ง" หมายถึง "ชุด เป็นไม่มี ") ฉันต้องการค่าการคำนวณเริ่มต้นxของ (หรือy)

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

class Example(object):
    def __init__(self, x=None, y=None):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self.x or self.defaultX()

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

    @property
    def y(self):
        return self.y or self.defaultY()

    @y.setter
    def y(self, value):
        self._y = value

    # default{XY} as before.

เราได้รับอะไรบ้าง เราได้รับความสามารถในการอ้างถึงคุณลักษณะเหล่านี้เป็นคุณลักษณะแม้ว่าเบื้องหลังเราจะสิ้นสุดวิธีการทำงาน

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

แต่เราจะสูญเสียอะไรและเราทำอะไรไม่ได้

ความรำคาญหลักในมุมมองของฉันคือถ้าคุณกำหนดผู้ทะเยอทะยาน (ในขณะที่เราทำที่นี่) คุณต้องกำหนดผู้ตั้ง [1] นั่นคือเสียงรบกวนพิเศษที่ตัดรหัส

ความรำคาญอีกประการหนึ่งคือเรายังต้องเริ่มต้นxและyค่า__init__ใหม่ (แน่นอนว่าเราสามารถเพิ่มพวกเขาโดยใช้setattr()แต่นั่นคือรหัสพิเศษเพิ่มเติม)

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

มันคงจะดีถ้าเราทำสิ่งที่ชอบ:

e.x[a,b,c] = 10
e.x[d,e,f] = 20

ตัวอย่างเช่น. สิ่งที่ใกล้เคียงที่สุดที่เราจะได้รับคือแทนที่การกำหนดเพื่อบ่งถึงความหมายพิเศษ:

e.x = [a,b,c,10]
e.x = [d,e,f,30]

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

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

getter / setter แบบ Java ช่วยให้เราจัดการสิ่งนี้ได้ แต่เรากลับไปต้องการ getter / setters

ในใจของฉันสิ่งที่เราต้องการจริง ๆ คือสิ่งที่มีคุณสมบัติตรงตามข้อกำหนดต่อไปนี้:

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

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

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

  • เราสามารถตั้งค่าแอตทริบิวต์เป็นแอตทริบิวต์และอ้างอิงเป็นแอตทริบิวต์

  • เราสามารถทำให้เป็นพารามิเตอร์คุณลักษณะ

ในแง่ของรหัสเราต้องการวิธีในการเขียน:

def x(self, *args):
    return defaultX()

และสามารถทำได้:

print e.x     -> The default at time T0
e.x = 1
print e.x     -> 1
e.x = None
print e.x     -> The default at time T1

และอื่น ๆ

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

ทีนี้ก็ถึงจุด (ใช่! วิธีแก้ปัญหาที่ฉันใช้ในการนี้มีดังนี้

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

โทร Let 's UberPropertyมัน

class UberProperty(object):

    def __init__(self, method):
        self.method = method
        self.value = None
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def clearValue(self):
        self.value = None
        self.isSet = False

ฉันคิดว่าmethodที่นี่เป็นวิธีการเรียนvalueเป็นมูลค่าของUberPropertyและฉันได้เพิ่มisSetเพราะNoneอาจเป็นมูลค่าที่แท้จริงและสิ่งนี้ช่วยให้เราวิธีที่สะอาดที่จะประกาศว่าจริงๆคือ "ไม่มีค่า" อีกวิธีหนึ่งคือยามรักษาการณ์บางอย่าง

โดยพื้นฐานแล้วสิ่งนี้ทำให้เรามีวัตถุที่สามารถทำสิ่งที่เราต้องการ แต่เราจะใส่มันในชั้นเรียนของเราได้อย่างไร? คุณสมบัติใช้มัณฑนากร ทำไมเราถึงทำไม่ได้? เรามาดูกันว่ามันจะมีลักษณะอย่างไร (จากนี้ไปฉันจะใช้โดยการใช้คุณลักษณะ 'เดียว' x)

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

แน่นอนว่ามันยังใช้งานไม่ได้ เราต้องดำเนินการuberPropertyและตรวจสอบให้แน่ใจว่ามันจัดการทั้งการรับและการตั้งค่า

มาเริ่มกันที่การได้รับ

ความพยายามครั้งแรกของฉันคือเพียงแค่สร้างวัตถุ UberProperty ใหม่และส่งคืน:

def uberProperty(f):
    return UberProperty(f)

แน่นอนฉันค้นพบได้อย่างรวดเร็วว่าสิ่งนี้ไม่ได้ผล: Python ไม่ผูกกับ callable กับวัตถุและฉันต้องการวัตถุเพื่อเรียกใช้ฟังก์ชัน แม้การสร้างมัณฑนากรในชั้นเรียนก็ไม่ทำงานเหมือนตอนนี้เรามีชั้นเรียน แต่เราก็ยังไม่มีวัตถุที่จะทำงานด้วย

ดังนั้นเราจะต้องสามารถทำอะไรได้มากกว่านี้ที่นี่ เรารู้ว่าวิธีการนั้นต้องการเพียงแค่การแสดงเพียงครั้งเดียวดังนั้นไปข้างหน้าและรักษามัณฑนากรของเรา แต่แก้ไขUberPropertyเพื่อเก็บการmethodอ้างอิงเท่านั้น:

class UberProperty(object):

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

มันยังไม่สามารถเรียกได้ดังนั้นในขณะนี้ไม่มีอะไรทำงาน

เราจะเติมรูปภาพให้สมบูรณ์ได้อย่างไร? เมื่อเราสร้างคลาสตัวอย่างโดยใช้เครื่องมือตกแต่งใหม่ของเรา:

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

print Example.x     <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x   <__main__.UberProperty object at 0x10e1fb8d0>

ในทั้งสองกรณีเราได้รับคืนUberPropertyซึ่งแน่นอนว่าไม่ใช่ callable ดังนั้นจึงไม่มีประโยชน์อะไรมาก

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

ลองเขียนสิ่งที่เราต้องการให้ผลลัพธ์การค้นหาของเราเป็นอันดับแรก เราเชื่อมโยงUberPropertyกับอินสแตนซ์ดังนั้นสิ่งที่ชัดเจนในการส่งคืนจะเป็น BoundUberProperty นี่คือที่เราจะรักษาสถานะสำหรับxแอตทริบิวต์

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

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

ตอนนี้คุณสมบัติของ uber นั้นเจ๋งและเราอาจต้องการใช้มันมากดังนั้นจึงเหมาะสมที่จะสร้างคลาสพื้นฐานที่ใช้สำหรับคลาสย่อยทั้งหมด ฉันคิดว่าคุณรู้ว่าสิ่งที่ชั้นฐานจะถูกเรียกว่า

class UberObject(object):
    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)

เราเพิ่มสิ่งนี้เปลี่ยนตัวอย่างของเราเพื่อสืบทอดจากUberObjectและ ...

e = Example()
print e.x               -> <__main__.BoundUberProperty object at 0x104604c90>

หลังจากแก้ไขxเป็น:

@uberProperty
def x(self):
    return *datetime.datetime.now()*

เราสามารถทำการทดสอบอย่างง่าย:

print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()

และเราได้ผลลัพธ์ที่เราต้องการ:

2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310

(Gee ฉันทำงานช้า)

โปรดทราบว่าฉันได้ใช้getValue, setValueและclearValueที่นี่ นี่เป็นเพราะฉันยังไม่ได้เชื่อมโยงในวิธีที่จะให้สิ่งเหล่านี้กลับมาโดยอัตโนมัติ

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

ฉันจะทำตัวอย่างให้เสร็จในการโพสต์ครั้งต่อไปโดยพูดถึงสิ่งเหล่านี้:

  • เราต้องตรวจสอบให้แน่ใจว่า UberObject __init__ถูกเรียกโดยคลาสย่อยเสมอ

    • ดังนั้นเราจึงบังคับให้มันถูกเรียกที่ใดที่หนึ่งหรือเราป้องกันไม่ให้มันถูกนำไปใช้
    • เราจะเห็นวิธีการทำเช่นนี้กับ metaclass
  • เราต้องตรวจสอบให้แน่ใจว่าเราจัดการกับกรณีทั่วไปที่มี 'นามแฝง' คนอื่นทำหน้าที่อย่างอื่นเช่น:

      class Example(object):
          @uberProperty
          def x(self):
              ...
    
          y = x
  • เราต้องe.xกลับe.x.getValue()โดยปริยาย

    • สิ่งที่เราจะเห็นจริง ๆ คือนี่เป็นพื้นที่หนึ่งที่โมเดลล้มเหลว
    • ปรากฎว่าเราจะต้องใช้การเรียกใช้ฟังก์ชันเพื่อรับค่า
    • e.x.getValue()แต่เราสามารถทำให้มันมีลักษณะเหมือนการเรียกฟังก์ชันปกติและหลีกเลี่ยงการใช้งาน (การทำเช่นนี้ชัดเจนถ้าคุณยังไม่ได้แก้ไข)
  • เราจำเป็นต้องตั้งค่าการสนับสนุนในขณะที่e.x directly e.x = <newvalue>เราสามารถทำได้ในคลาสผู้ปกครองด้วย แต่เราจะต้องอัปเดต__init__รหัสของเราเพื่อจัดการ

  • สุดท้ายเราจะเพิ่มคุณสมบัติที่กำหนดพารามิเตอร์ มันควรจะชัดเจนว่าเราจะทำสิ่งนี้อย่างไร

นี่คือรหัสตามที่มีอยู่ในขณะนี้:

import datetime

class UberObject(object):
    def uberSetter(self, value):
        print 'setting'

    def uberGetter(self):
        return self

    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)


class UberProperty(object):
    def __init__(self, method):
        self.method = method

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

    def uberProperty(f):
        return UberProperty(f)

class Example(UberObject):

    @uberProperty
    def x(self):
        return datetime.datetime.now()

[1] ฉันอาจจะคิดว่าเรื่องนี้ยังเป็นปัญหาอยู่หรือเปล่า


53
ใช่นี่คือ 'tldr' คุณช่วยสรุปสิ่งที่คุณพยายามทำที่นี่ได้ไหม
poolie

9
@Adam return self.x or self.defaultX()นี่เป็นรหัสอันตราย เกิดอะไรขึ้นเมื่อself.x == 0?
Kelly Thomas

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

2
@KellyThomas เพียงพยายามทำให้ตัวอย่างง่าย หากต้องการทำอย่างถูกต้องคุณจะต้องสร้างและลบรายการ x dictทั้งหมดเนื่องจากแม้แต่ค่า None อาจถูกตั้งค่าเป็นพิเศษ แต่ใช่คุณพูดถูกนี่เป็นสิ่งที่คุณจะต้องพิจารณาในกรณีการใช้งานจริง
Adam Donahue

ตัวรับเหมือน Java อนุญาตให้คุณทำการคำนวณแบบเดียวกันทุกประการใช่ไหม
qed

26

ฉันคิดว่าทั้งสองมีสถานที่ของพวกเขา ปัญหาหนึ่งที่มีการใช้@propertyคือมันเป็นการยากที่จะขยายพฤติกรรมของ getters หรือ setters ในคลาสย่อยโดยใช้กลไกคลาสมาตรฐาน ปัญหาคือฟังก์ชั่น getter / setter จริงถูกซ่อนอยู่ในคุณสมบัติ

คุณสามารถใช้ฟังก์ชั่นได้เช่นกับ

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

คุณสามารถเข้าถึงฟังก์ชั่น getter และ setter เป็นC.p.fgetและC.p.fsetแต่คุณไม่สามารถใช้การสืบทอดวิธีปกติ (เช่น super) สิ่งอำนวยความสะดวกเพื่อขยายได้อย่างง่ายดาย หลังจากขุดลงไปในความซับซ้อนของ super คุณสามารถใช้ super ในวิธีนี้ได้:

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

อย่างไรก็ตามการใช้ super () ค่อนข้าง clunky เนื่องจากต้องมีการกำหนดคุณสมบัติใหม่และคุณต้องใช้กลไก super (cls, cls) ที่ใช้งานง่ายเล็กน้อยเพื่อรับสำเนาที่ไม่ได้ผูกไว้ของ p


20

การใช้คุณสมบัติคือฉันใช้งานง่ายขึ้นและเหมาะกับโค้ดส่วนใหญ่

เปรียบเทียบ

o.x = 5
ox = o.x

เมื่อเทียบกับ

o.setX(5)
ox = o.getX()

สำหรับฉันนั้นค่อนข้างชัดเจนซึ่งอ่านง่ายกว่า คุณสมบัติยังช่วยให้ตัวแปรส่วนตัวง่ายขึ้น


12

ฉันต้องการใช้ทั้งในกรณีส่วนใหญ่ ปัญหาเกี่ยวกับคุณสมบัติคือมันทำให้คลาสโปร่งใสน้อยลง โดยเฉพาะนี่เป็นปัญหาถ้าคุณต้องยกข้อยกเว้นจาก setter ตัวอย่างเช่นหากคุณมีบัญชี Account.email:

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

ดังนั้นผู้ใช้ของคลาสไม่คาดหวังว่าการกำหนดค่าให้กับคุณสมบัติอาจทำให้เกิดข้อยกเว้น:

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

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

ฉันจะหลีกเลี่ยงการใช้ getters และ setters ด้วย:

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

แทนที่จะเป็นคุณสมบัติและ getters / setters ฉันชอบทำตรรกะที่ซับซ้อนในสถานที่ที่กำหนดไว้อย่างดีเช่นในวิธีการตรวจสอบความถูกต้อง:

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

หรือวิธีการบัญชีที่คุ้นเคย

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


3
@ user2239734 ฉันคิดว่าคุณเข้าใจผิดเกี่ยวกับแนวคิดของคุณสมบัติ แม้ว่าคุณจะสามารถตรวจสอบความถูกต้องของค่าในขณะที่ตั้งค่าคุณสมบัติได้ แต่ก็ไม่จำเป็นต้องทำเช่นนั้น คุณสามารถมีทั้งคุณสมบัติและvalidate()วิธีการในชั้นเรียน คุณสมบัติถูกใช้อย่างง่ายเมื่อคุณมีตรรกะที่ซับซ้อนด้านหลังการobj.x = yมอบหมายอย่างง่ายและมันก็ขึ้นอยู่กับว่าตรรกะคืออะไร
Zaur Nasibov

12

ฉันรู้สึกว่าคุณสมบัติเกี่ยวกับการให้คุณได้รับค่าใช้จ่ายในการเขียน getters และ setters เฉพาะเมื่อคุณต้องการพวกเขา

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

ใน Python ผู้คนสนใจค่าใช้จ่ายประเภทนั้นจริง ๆ เพื่อที่คุณจะได้สามารถฝึกฝนต่อไปนี้:

  • อย่าใช้ getters และ setters ในตอนแรกหากไม่ต้องการ
  • ใช้@propertyเพื่อนำไปใช้โดยไม่ต้องเปลี่ยนไวยากรณ์ของรหัสที่เหลือของคุณ

1
"และสังเกตว่า 70% ของเวลาที่พวกเขาไม่เคยถูกแทนที่ด้วยตรรกะที่ไม่สำคัญ" - นั่นเป็นตัวเลขที่ค่อนข้างเฉพาะเจาะจงหรือว่ามาจากที่ใดที่หนึ่งหรือคุณตั้งใจที่จะเป็นมือ "ส่วนใหญ่" สิ่งที่ kinda (ฉันไม่ได้ถูกจับตามองถ้ามีการศึกษาที่จำนวนตัวเลขที่ฉันจะ สนใจที่จะอ่านมันอย่างแท้จริง)
Adam Parkin

1
โอ้ไม่ขอโทษ ดูเหมือนว่าฉันจะมีการศึกษาเพื่อสำรองข้อมูลหมายเลขนี้ แต่ฉันหมายถึงว่าเป็น "เวลาส่วนใหญ่" เท่านั้น
fulmicoton

7
ไม่ใช่ว่าคนใส่ใจเรื่องค่าใช้จ่าย แต่ใน Python คุณสามารถเปลี่ยนจากการเข้าถึงโดยตรงไปยังวิธีการเข้าถึงโดยไม่ต้องเปลี่ยนรหัสลูกค้าดังนั้นคุณไม่มีอะไรจะเสียโดยการเปิดเผยคุณสมบัติโดยตรงในตอนแรก
Neil G

10

ฉันประหลาดใจที่ไม่มีใครพูดถึงว่าคุณสมบัติเป็นวิธีการผูกมัดของคลาส descriptor, Adam DonohueและNeilenMaraisเข้าใจความคิดนี้ในบทความของพวกเขา - getters และ setters เป็นหน้าที่และสามารถใช้ในการ:

  • ตรวจสอบ
  • แก้ไขข้อมูล
  • ประเภทเป็ด (ประเภท coerce เป็นประเภทอื่น)

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

โดยทั่วไปแล้วการทำ CRUD บนวัตถุมักจะเป็นเรื่องธรรมดา แต่พิจารณาตัวอย่างของข้อมูลที่จะยังคงอยู่ในฐานข้อมูลเชิงสัมพันธ์ ORM ของสามารถซ่อนรายละเอียดการใช้งานของภาษา SQL โดยเฉพาะในวิธีการที่ถูกผูกไว้กับ fget, fset, fdel ที่กำหนดไว้ในคลาสคุณสมบัติที่จะจัดการอันยิ่งใหญ่ถ้า .. elif .. อื่น ๆ บันไดที่น่าเกลียดในรหัส OO - เปิดเผยง่ายและ ฉลาดself.variable = somethingและลบล้างรายละเอียดสำหรับนักพัฒนาโดยใช้ ORM

หากใครคิดว่าคุณสมบัติเป็นเพียงร่องรอยที่น่าเบื่อของภาษา Bondage และ Discipline (เช่น Java) พวกเขาจะหายไปจากจุดอธิบาย


6

ในโครงการที่ซับซ้อนฉันชอบใช้คุณสมบัติอ่านอย่างเดียว (หรือ getters) ด้วยฟังก์ชัน setter อย่างชัดเจน:

class MyClass(object):
...        
@property
def my_attr(self):
    ...

def set_my_attr(self, value):
    ...

ในโครงการที่มีชีวิตยาวนานการดีบักและการเปลี่ยนโครงสร้างต้องใช้เวลามากกว่าการเขียนโค้ดเอง มีข้อเสียหลายประการสำหรับการใช้งาน@property.setterที่ทำให้การดีบักยิ่งยากขึ้น:

1) หลามอนุญาตให้สร้างคุณลักษณะใหม่สำหรับวัตถุที่มีอยู่ สิ่งนี้ทำให้พิมพ์ผิดต่อไปนี้ยากมากที่จะติดตาม:

my_object.my_atttr = 4.

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

2) setter บางครั้งอาจมีการพัฒนาไปสู่วิธีที่ซับซ้อนและช้า (เช่นการกดปุ่มฐานข้อมูล) มันจะค่อนข้างยากสำหรับนักพัฒนารายอื่นที่จะเข้าใจว่าทำไมฟังก์ชั่นต่อไปนี้ช้ามาก เขาอาจใช้เวลามากกับการทำโปรไฟล์do_something()แต่my_object.my_attr = 4.จริงๆแล้วสาเหตุของการชะลอตัวคือ:

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()

5

ทั้งผู้ได้รับ@propertyและผู้ตั้งถิ่นฐานดั้งเดิมมีข้อได้เปรียบ มันขึ้นอยู่กับกรณีการใช้งานของคุณ

ข้อดีของ @property

  • คุณไม่จำเป็นต้องเปลี่ยนอินเทอร์เฟซขณะเปลี่ยนการใช้งานการเข้าถึงข้อมูล เมื่อโครงการของคุณมีขนาดเล็กคุณอาจต้องการใช้การเข้าถึงแอตทริบิวต์โดยตรงเพื่อเข้าถึงสมาชิกคลาส ตัวอย่างเช่นสมมติว่าคุณมีวัตถุfooประเภทซึ่งมีสมาชิกFoo แล้วคุณก็จะได้รับของสมาชิกนี้ด้วยnum num = foo.numเมื่อโครงการของคุณเติบโตขึ้นคุณอาจรู้สึกว่าจำเป็นต้องมีการตรวจสอบหรือแก้ไขจุดบกพร่องในการเข้าถึงแอตทริบิวต์อย่างง่าย จากนั้นคุณสามารถทำเช่นนั้นกับ@property ภายในชั้นเรียน อินเตอร์เฟสการเข้าถึงข้อมูลยังคงเหมือนเดิมดังนั้นจึงไม่จำเป็นต้องแก้ไขรหัสลูกค้า

    อ้างจากPEP-8 :

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

  • การใช้การ@propertyเข้าถึงข้อมูลใน Python ถือเป็นPythonic :

    • มันสามารถเสริมสร้างการระบุตัวตนของคุณในฐานะโปรแกรมเมอร์ Python (ไม่ใช่ Java)

    • มันสามารถช่วยให้การสัมภาษณ์งานของคุณถ้าสัมภาษณ์ของคุณคิดว่า getters Java สไตล์และ setters มีการต่อต้านรูปแบบ

ข้อได้เปรียบของ getters และ setters แบบดั้งเดิม

  • ระบบเก็ตเตอร์และเซทเตอร์แบบดั้งเดิมช่วยให้สามารถเข้าถึงข้อมูลที่ซับซ้อนได้ง่ายกว่าการเข้าถึงแอ็ตทริบิวต์แบบง่าย ตัวอย่างเช่นเมื่อคุณตั้งค่าสมาชิกของคลาสบางครั้งคุณจำเป็นต้องมีการตั้งค่าสถานะเพื่อระบุว่าคุณต้องการบังคับใช้การดำเนินการนี้แม้ว่าบางสิ่งจะดูไม่สมบูรณ์แบบ แม้ว่าจะไม่ชัดเจนว่าจะเพิ่มการเข้าถึงโดยตรงของสมาชิกได้อย่างไรfoo.num = numคุณสามารถเพิ่ม setter ดั้งเดิมของคุณด้วยforceพารามิเตอร์เพิ่มเติม:

    def Foo:
        def set_num(self, num, force=False):
            ...
  • getters และ setters แบบดั้งเดิมทำให้มันชัดเจนว่าการเข้าถึงสมาชิกของคลาสนั้นผ่านวิธีการ หมายความว่า:

    • สิ่งที่คุณได้รับเนื่องจากผลลัพธ์อาจไม่เหมือนกับสิ่งที่เก็บไว้อย่างแน่นอนภายในคลาสนั้น

    • แม้ว่าการเข้าถึงจะดูเหมือนการเข้าถึงแอตทริบิวต์อย่างง่าย แต่ประสิทธิภาพอาจแตกต่างจากที่เป็นอยู่

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

  • ตามที่กล่าวถึงโดย@NeilenMaraisและในโพสต์นี้การขยายตัวรับแบบดั้งเดิมและตัวตั้งค่าในคลาสย่อยนั้นง่ายกว่าการขยายคุณสมบัติ

  • เครื่องกัดแบบดั้งเดิมและเครื่องเซ็ทเตอร์ถูกใช้กันอย่างแพร่หลายเป็นเวลานานในภาษาต่างๆ @propertyถ้าคุณมีคนที่มาจากภูมิหลังที่แตกต่างกันในทีมงานของคุณที่พวกเขามองที่คุ้นเคยมากกว่า นอกจากนี้เมื่อโครงการของคุณเติบโตขึ้นถ้าคุณอาจต้องย้ายจาก Python ไปเป็นภาษาอื่นที่ไม่มีอยู่การ@propertyใช้ getters และ setters แบบดั้งเดิมจะทำให้การโยกย้ายราบรื่นขึ้น

คำเตือน

  • ทั้ง@propertygetters และ setters แบบดั้งเดิมไม่ทำให้สมาชิกคลาสเป็นส่วนตัวแม้ว่าคุณจะใช้เครื่องหมายขีดล่างคู่หน้าชื่อ:

    class Foo:
        def __init__(self):
            self.__num = 0
    
        @property
        def num(self):
            return self.__num
    
        @num.setter
        def num(self, num):
            self.__num = num
    
        def get_num(self):
            return self.__num
    
        def set_num(self, num):
            self.__num = num
    
    foo = Foo()
    print(foo.num)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0

2

นี่คือข้อความที่ตัดตอนมาจาก"Python ที่มีประสิทธิภาพ: 90 วิธีเฉพาะในการเขียน Python ที่ดีกว่า" (หนังสือที่น่าตื่นตาตื่นใจฉันขอแนะนำให้มัน)

สิ่งที่ต้องจำ

✦กำหนดอินเทอร์เฟซคลาสใหม่โดยใช้แอตทริบิวต์สาธารณะอย่างง่ายและหลีกเลี่ยงการกำหนดเมธอด setter และ getter

✦ใช้ @property เพื่อกำหนดลักษณะพิเศษเมื่อเข้าถึงแอตทริบิวต์บนวัตถุของคุณหากจำเป็น

✦ปฏิบัติตามกฎของความประหลาดใจน้อยที่สุดและหลีกเลี่ยงผลข้างเคียงที่แปลกในวิธีการ @property ของคุณ

✦ตรวจสอบให้แน่ใจว่าวิธีการ @ property นั้นรวดเร็ว สำหรับการทำงานที่ช้าหรือซับซ้อนโดยเฉพาะที่เกี่ยวข้องกับ I / O หรือก่อให้เกิดผลข้างเคียงให้ใช้วิธีการปกติแทน

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

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

✦ใช้ @property เพื่อมอบคุณสมบัติใหม่ของอินสแตนซ์ที่มีอยู่

✦เพิ่มความคืบหน้าไปยังโมเดลข้อมูลที่ดีขึ้นโดยใช้ @property

✦พิจารณาการปรับโครงสร้างคลาสและไซต์การโทรทั้งหมดเมื่อคุณพบว่าตัวเองใช้ @property มากเกินไป

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