Ví dụ thế giới thực về cách sử dụng tính năng thuộc tính trong python?


142

Tôi quan tâm đến cách sử dụng @propertytrong Python. Tôi đã đọc các tài liệu về con trăn và ví dụ ở đó, theo tôi, chỉ là một mã đồ chơi:

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

Tôi không biết những lợi ích nào tôi có thể nhận được từ việc gói _xđầy đồ trang trí bất động sản. Tại sao không chỉ thực hiện như:

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

Tôi nghĩ rằng, tính năng tài sản có thể hữu ích trong một số tình huống. Nhưng khi? Ai đó có thể xin vui lòng cho tôi một số ví dụ thực tế?

Cảm ơn.


9
Đây là lời giải thích tốt nhất và rõ ràng nhất mà tôi đã tìm thấy về trang trí bất động sản [bấm vào đây ]
Sumudu

2
@Anubis trong ví dụ cuối cùng trong liên kết bạn cung cấp, đặt c = Celsius (-500) đã không ném bất kỳ ValueError nào, mà tôi nghĩ là không đạt được kết quả như mong muốn.
Sajuuk

Đồng ý với @Anubis. Nó được triển khai chính xác tại đây: python-cference.eu/python3_properIES.php
anon01 26/03/18

Câu trả lời:


91

Các ví dụ khác sẽ là xác nhận / lọc các thuộc tính đã đặt (buộc chúng phải nằm trong giới hạn hoặc có thể chấp nhận được) và đánh giá lười biếng các thuật ngữ phức tạp hoặc thay đổi nhanh chóng.

Tính toán phức tạp ẩn đằng sau một thuộc tính:

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

Thẩm định:

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
Tôi thích ví dụ PDB_Calculator - những thứ phức tạp được trừu tượng hóa, toàn bộ hoạt động và người dùng có thể tận hưởng sự đơn giản!
Adam Kurkiewicz

2
có thể, từ quan điểm chuyên nghiệp, đây là những ví dụ rất tốt. Nhưng, là một người mới, tôi thấy ví dụ này khá kém hiệu quả. xấu của tôi ... :(
kmonsoor

77

Một trường hợp sử dụng đơn giản sẽ là đặt thuộc tính cá thể chỉ đọc, vì bạn biết dẫn một tên biến bằng một dấu gạch dưới _xtrong python thường có nghĩa là riêng tư (sử dụng nội bộ) nhưng đôi khi chúng ta muốn có thể đọc thuộc tính cá thể và không viết vì vậy chúng ta có thể sử dụng propertycho việc này:

>>> 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

6
Một vẫn có thể thiết lập c._x, nếu người dùng muốn. Python thực sự không có thuộc tính riêng tư thực sự.

20

Hãy xem bài viết này để sử dụng rất thực tế. Nói tóm lại, nó giải thích làm thế nào trong Python bạn thường có thể bỏ phương thức getter / setter rõ ràng, vì nếu bạn cần chúng ở một số giai đoạn bạn có thể sử dụng propertyđể thực hiện liền mạch.


15

Một điều tôi đã sử dụng nó là lưu trữ các giá trị chậm tìm kiếm, nhưng không thay đổi, được lưu trữ trong cơ sở dữ liệu. Điều này khái quát cho mọi tình huống trong đó các thuộc tính của bạn yêu cầu tính toán hoặc một số hoạt động dài khác (ví dụ: kiểm tra cơ sở dữ liệu, giao tiếp mạng) mà bạn chỉ muốn làm theo yêu cầu.

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)

Đây là trong một ứng dụng web nơi mọi chế độ xem trang nhất định có thể chỉ cần một thuộc tính cụ thể thuộc loại này, nhưng bản thân các đối tượng cơ bản có thể có một số thuộc tính như vậy - khởi tạo tất cả chúng khi xây dựng sẽ lãng phí và các thuộc tính cho phép tôi linh hoạt trong đó thuộc tính là lười biếng và không.


1
Bạn không thể sử dụng @cached_propertycho việc này?
Adarsh

@adarsh ​​- Nghe có vẻ thú vị. Đó là đâu
gièm pha

Tôi đã sử dụng nó nhưng tôi quên rằng nó không được tích hợp sẵn, nhưng bạn có thể sử dụng nó với cái này, pypi.python.org/pypi/cached-property/0.1.5
adarsh

2
Hấp dẫn. Tôi nghĩ rằng nó đã được xuất bản lần đầu tiên sau câu trả lời này, nhưng bất cứ ai đọc nó có lẽ nên sử dụng nó để thay thế.
gièm pha

10

Đọc qua các câu trả lời và bình luận, chủ đề chính dường như là câu trả lời dường như đang thiếu một ví dụ đơn giản nhưng hữu ích. Tôi đã bao gồm một cái rất đơn giản ở đây thể hiện việc sử dụng đơn giản của @propertytrang trí. Đó là một lớp cho phép người dùng chỉ định và lấy số đo khoảng cách bằng nhiều đơn vị khác nhau, nghĩa là in_feethoặc in_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")

Sử dụng:

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

đối với cá nhân tôi, những ví dụ tốt nhất về getters / setters là cho mọi người thấy loại thay đổi bạn cần thực hiện sau này nhưng rõ ràng, điều đó cần thêm một ít thời gian.
dtc

7

Thuộc tính chỉ là một sự trừu tượng xung quanh một trường cung cấp cho bạn quyền kiểm soát nhiều hơn về các cách mà một trường cụ thể có thể được thao tác và thực hiện các tính toán phần mềm trung gian. Một vài trong số các ứng dụng xuất hiện trong tâm trí là xác nhận và hạn chế truy cập và khởi tạo trước

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

    return self._x

6

Có, đối với ví dụ ban đầu được đăng, thuộc tính sẽ hoạt động chính xác giống như chỉ cần có một biến đối tượng 'x'.

Đây là điều tốt nhất về các thuộc tính python. Từ bên ngoài, chúng hoạt động chính xác như các biến thể hiện! Cho phép bạn sử dụng các biến thể hiện từ bên ngoài lớp.

Điều này có nghĩa là ví dụ đầu tiên của bạn thực sự có thể sử dụng một biến thể hiện. Nếu mọi thứ thay đổi, và sau đó bạn quyết định thay đổi triển khai của mình và một thuộc tính là hữu ích, giao diện cho thuộc tính vẫn sẽ giống với mã bên ngoài lớp. Một thay đổi từ biến đối tượng sang thuộc tính không có tác động đến mã bên ngoài lớp.

Nhiều ngôn ngữ và khóa học lập trình khác sẽ hướng dẫn rằng một lập trình viên không bao giờ nên phơi bày các biến thể hiện, và thay vào đó sử dụng 'getters' và 'setters' cho bất kỳ giá trị nào được truy cập từ bên ngoài lớp, ngay cả trường hợp đơn giản như được trích dẫn trong câu hỏi.

Mã bên ngoài lớp có nhiều ngôn ngữ (ví dụ Java) sử dụng

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

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

Và khi triển khai lớp, có nhiều 'getters' và 'setters' hoạt động chính xác như ví dụ đầu tiên của bạn: sao chép một biến đối tượng đơn giản. Các getters và setters này là bắt buộc bởi vì nếu việc triển khai lớp thay đổi, tất cả các mã bên ngoài lớp sẽ cần phải thay đổi. Nhưng các thuộc tính python cho phép mã bên ngoài lớp giống như với các biến thể hiện. Vì vậy, mã bên ngoài lớp không cần phải thay đổi nếu bạn thêm một thuộc tính hoặc có một biến thể hiện đơn giản. Vì vậy, không giống như hầu hết các ngôn ngữ hướng đối tượng, đối với ví dụ đơn giản của bạn, bạn có thể sử dụng biến đối tượng thay vì 'getters' và 'setters' thực sự không cần thiết, đảm bảo rằng bạn sẽ thay đổi thành một thuộc tính trong tương lai, sử dụng mã trong tương lai lớp học của bạn không cần thay đổi.

Điều này có nghĩa là bạn chỉ cần tạo thuộc tính nếu có hành vi phức tạp và trong trường hợp đơn giản rất phổ biến trong đó, như được mô tả trong câu hỏi, một biến đối tượng đơn giản là tất cả những gì cần thiết, bạn chỉ có thể sử dụng biến thể hiện.


6

một tính năng hay khác của các thuộc tính khi sử dụng setters và getters mà chúng cho phép bạn tiếp tục sử dụng toán tử OP = (ví dụ + =, - =, * = etc) trên các thuộc tính của bạn trong khi vẫn giữ bất kỳ xác thực, kiểm soát truy cập, bộ đệm, v.v. các setters và getters sẽ cung cấp.

ví dụ: nếu bạn đã viết lớp Personvới setter setage(newage)và getter getage(), thì để tăng tuổi bạn sẽ phải viết:

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

nhưng nếu bạn tạo agemột tài sản, bạn có thể viết sạch hơn nhiều:

bob.age += 1

5

Câu trả lời ngắn cho câu hỏi của bạn, là trong ví dụ của bạn, không có lợi ích. Bạn có thể nên sử dụng các hình thức không liên quan đến tài sản.

Các thuộc tính lý do tồn tại là nếu mã của bạn thay đổi trong tương lai và bạn đột nhiên cần phải làm nhiều hơn với dữ liệu của mình: giá trị bộ đệm, bảo vệ quyền truy cập, truy vấn một số tài nguyên bên ngoài ... bất cứ điều gì, bạn có thể dễ dàng sửa đổi lớp của mình để thêm getters và setters cho dữ liệu mà không thay đổi giao diện, vì vậy bạn không phải tìm ở mọi nơi trong mã của mình nơi dữ liệu đó được truy cập và cũng thay đổi điều đó.


4

Điều mà nhiều người không chú ý lúc đầu là bạn có thể tạo các lớp tài sản của riêng mình. Điều này tôi đã thấy rất hữu ích cho việc hiển thị các thuộc tính hoặc thuộc tính chỉ đọc mà bạn có thể đọc và viết nhưng không xóa. Nó cũng là một cách tuyệt vời để bọc chức năng như theo dõi sửa đổi đối với các trường đối tượng.

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')

Tôi thích điều này. Tôi có đọc chính xác điều này trong đó người đọc chỉ đọc trong khi người truy cập đọc / ghi mà không có khả năng xóa không? Làm thế nào bạn sẽ thêm xác nhận dữ liệu mặc dù? Tôi khá mới đối với Python nhưng tôi nghĩ có lẽ có một cách để thêm một cuộc gọi lại vào attr = reader('_attr')đường dây hoặc một số hình thức prechecking như thế nào attr = if self.__isValid(value): reader('_attr'). Gợi ý?
Gabe Spradlin

Xin lỗi chỉ nhận ra tôi đã hỏi về xác thực dữ liệu cho một biến chỉ đọc. Nhưng rõ ràng điều này sẽ chỉ áp dụng cho phần setter của lớp accessor. Vì vậy, thay đổi attr = reader('_attr')để attr = accessor('_attr'). Cảm ơn
Gabe Spradlin

Bạn đúng rằng nếu bạn muốn xác thực thì bạn sẽ thêm một hàm để xác thực và nâng cao Ngoại lệ nếu không hợp lệ (hoặc bất kỳ hành vi nào bạn thích bao gồm không làm gì) cho init . Tôi đã sửa đổi ở trên với một mô hình có thể. Trình xác nhận sẽ trả về True | false để hướng dẫn xem tập hợp có xảy ra hay không.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.