ตัวอย่างโลกแห่งความจริงเกี่ยวกับวิธีการใช้คุณสมบัติคุณสมบัติในหลาม?


143

ฉันสนใจวิธีใช้งาน@propertyPython ฉันอ่าน python docs แล้วและตัวอย่างในความคิดของฉันเป็นเพียงรหัสของเล่น:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

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

    @x.deleter
    def x(self):
        del self._x

ฉันไม่รู้ว่าฉันจะได้ประโยชน์อะไรจากการห่อหุ้ม_xด้วยมัณฑนากร ทำไมไม่ใช้เพียงแค่เป็น:

class C(object):
    def __init__(self):
        self.x = None

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

ขอบคุณ


10
นี่เป็นคำอธิบายที่ดีที่สุดและสะอาดที่สุดที่ฉันเคยพบเกี่ยวกับมัณฑนากรตกแต่งบ้าน[คลิกที่นี่ ]
Sumudu

2
@Anubis ในตัวอย่างสุดท้ายในลิงก์ที่คุณให้ไว้การตั้งค่า c = Celsius (-500) ไม่ได้ทิ้ง ValueError ใด ๆ ซึ่งฉันคิดว่าไม่บรรลุผลตามที่ต้องการ
Sajuuk

เห็นด้วยกับ @Anubis มีการใช้งานอย่างถูกต้องที่นี่: python-course.eu/python3_properties.php
anon01

คำตอบ:


92

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

การคำนวณที่ซับซ้อนซ่อนอยู่หลังแอตทริบิวต์:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

การตรวจสอบ:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value

1
ฉันชอบตัวอย่าง PDB_Calculator - สิ่งที่ซับซ้อนถูกแยกออกไปสิ่งที่ใช้ได้ทั้งหมดและผู้ใช้สามารถเพลิดเพลินกับความเรียบง่าย!
Adam Kurkiewicz

2
เป็นไปได้ว่าจากมุมมองของมืออาชีพนี่เป็นตัวอย่างที่ดี แต่ในฐานะ noobie ฉันพบตัวอย่างนี้ค่อนข้างไม่มีประสิทธิภาพ ไม่ดีของฉัน ... :(
kmonsoor

78

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

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute

7
หนึ่งยังคงสามารถตั้งค่าc._xหากผู้ใช้ต้องการ Python ไม่มีคุณสมบัติส่วนตัวที่แท้จริง

21

ลองดูที่บทความนี้เพื่อการใช้งานจริง กล่าวโดยสรุปคืออธิบายว่าใน Python คุณสามารถทิ้งวิธี getter / setter อย่างชัดเจนได้อย่างไรถ้าคุณต้องการมันในบางช่วงคุณสามารถใช้propertyเพื่อการใช้งานที่ราบรื่น


15

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

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

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


1
คุณไม่สามารถใช้@cached_propertyสิ่งนี้ได้หรือ
adarsh

@adarsh ​​- ฟังดูน่าสนใจ อยู่ที่ไหน
Detly

ฉันใช้มันแล้วแต่ฉันลืมไปว่ามันไม่ใช่แบบในตัว แต่คุณสามารถใช้กับสิ่งนี้ได้pypi.python.org/pypi/cached-property/0.1.5
adarsh

2
น่าสนใจ ฉันคิดว่ามันถูกตีพิมพ์ครั้งแรกหลังจากคำตอบนี้ แต่ใครก็ตามที่อ่านข้อความนี้อาจใช้มันแทน
detly

10

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

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

การใช้งาน:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14

สำหรับฉันโดยส่วนตัวตัวอย่างที่ดีที่สุดของผู้ได้รับ / setters คือการแสดงให้ผู้คนเห็นถึงการเปลี่ยนแปลงที่คุณต้องทำในภายหลัง แต่ชัดเจนว่าต้องใช้เวลามากกว่า
dtc

7

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

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x

6

ใช่สำหรับตัวอย่างดั้งเดิมที่โพสต์คุณสมบัติจะทำงานเหมือนกับที่มีเพียงตัวแปรอินสแตนซ์ 'x'

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

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

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

รหัสนอกชั้นเรียนที่มีหลายภาษา (เช่น Java) ใช้

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

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

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


6

อีกคุณสมบัติที่ดีของคุณสมบัติมากกว่าการใช้ setters และ getters ซึ่งมันช่วยให้คุณสามารถใช้ OP = โอเปอเรเตอร์ (เช่น + =, - =, * = ฯลฯ ) บนแอตทริบิวต์ของคุณในขณะที่ยังคงรักษาความถูกต้องการควบคุมการเข้าถึงแคช ฯลฯ setters และ getters จะจัดหา

ตัวอย่างเช่นถ้าคุณเขียนคลาสPersonด้วย setter setage(newage)และ getter getage()ดังนั้นเมื่อต้องการเพิ่มอายุคุณจะต้องเขียน:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

แต่ถ้าคุณทำageทรัพย์สินคุณสามารถเขียนสะอาดกว่า:

bob.age += 1

5

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

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


4

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

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

ฉันชอบสิ่งนี้. ฉันกำลังอ่านสิ่งนี้อย่างถูกต้องในเครื่องอ่านนั้นอ่านได้อย่างเดียวหรือไม่ในขณะที่ accessor อ่าน / เขียนโดยไม่มีความสามารถในการลบ คุณจะเพิ่มการตรวจสอบความถูกต้องของข้อมูลอย่างไร ฉันค่อนข้างใหม่หลาม แต่ฉันคิดว่าอาจมีวิธีที่จะเพิ่มการเรียกกลับไปเป็นattr = reader('_attr')เส้นหรือรูปแบบของ prechecking attr = if self.__isValid(value): reader('_attr')เช่นบาง ข้อเสนอแนะ?
Gabe Spradlin

ขออภัยเพิ่งรู้ว่าฉันถูกถามเกี่ยวกับการตรวจสอบข้อมูลสำหรับตัวแปรอ่านอย่างเดียว แต่เห็นได้ชัดว่านี่จะใช้กับส่วน setter ของคลาส accessor เท่านั้น ดังนั้นการเปลี่ยนแปลงไปattr = reader('_attr') attr = accessor('_attr')ขอบคุณ
Gabe Spradlin

คุณถูกต้องว่าถ้าคุณต้องการการตรวจสอบแล้วคุณจะเพิ่มฟังก์ชั่นในการตรวจสอบและยกระดับข้อยกเว้นถ้าไม่ถูกต้อง (หรือพฤติกรรมใด ๆ ที่คุณชอบรวมถึงการไม่ทำอะไรเลย) ให้กับผู้เริ่มต้น ฉันแก้ไขข้างต้นด้วยรูปแบบที่เป็นไปได้หนึ่งแบบ เครื่องมือตรวจสอบควรส่งคืน True | False เพื่อชี้แนะว่าชุดเกิดขึ้นหรือไม่
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.