Làm thế nào để trang trí @property hoạt động?


980

Tôi muốn hiểu làm thế nào các chức năng tích propertyhợp hoạt động. Điều làm tôi bối rối là nó propertycũng có thể được sử dụng như một công cụ trang trí, nhưng nó chỉ lấy các đối số khi được sử dụng như một hàm tích hợp chứ không sử dụng như một công cụ trang trí.

Ví dụ này là từ tài liệu :

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

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

property's đối số getx, setx, delxvà một chuỗi doc.

Trong mã dưới đây propertyđược sử dụng như trang trí. Đối tượng của nó là xhàm, nhưng trong đoạn mã trên không có chỗ cho hàm đối tượng trong các đối số.

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

Và, làm thế nào x.setterx.deletertrang trí được tạo ra? Tôi bị bối rối.



3
propertythực sự là một lớp (không phải là hàm), mặc dù có lẽ nó không gọi __init__()phương thức khi bạn tạo một đối tượng, tất nhiên. Sử dụng help(property)từ thiết bị đầu cuối là sâu sắc. helpcũng là một lớp học vì một số lý do.
Brōtsyorfuzthrāx 8/12/2016

Tôi nghĩ rằng liên kết này cung cấp một ví dụ hay: [property] ( journaldev.com/14893/python-property-decorator )
Sheng Bi

4
@Shule 2 tuổi, nhưng vẫn: Mọi thứ đều là một lớp học. Ngay cả các lớp học.
Artemis vẫn không tin tưởng SE

2
Điều này cũng gây nhầm lẫn với tôi. Cuối cùng tôi đã tìm thấy một bài viết có thể phá vỡ nó cho tôi. Tôi mong điều này giúp được người nào khác. programiz.com/python-programming/property Tôi không liên kết với bất kỳ cách nào với trang web.
jjwdesign

Câu trả lời:


1008

Các property()chức năng trả về một đặc biệt đối tượng mô tả :

>>> property()
<property object at 0x10ff07940>

Đó là đối tượng này có các phương thức bổ sung :

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

Những người này đóng vai trò là người trang trí quá . Họ trả lại một đối tượng tài sản mới:

>>> property().getter(None)
<property object at 0x10ff079f0>

đó là một bản sao của đối tượng cũ, nhưng với một trong các chức năng được thay thế.

Hãy nhớ rằng, @decoratorcú pháp chỉ là cú pháp đường; cú pháp:

@property
def foo(self): return self._foo

thực sự có nghĩa tương tự như

def foo(self): return self._foo
foo = property(foo)

Vì vậy foo, chức năng được thay thế bởi property(foo), mà chúng ta đã thấy ở trên là một đối tượng đặc biệt. Sau đó, khi bạn sử dụng @foo.setter(), những gì bạn đang làm là gọi property().setterphương thức mà tôi đã trình bày ở trên, nó trả về một bản sao mới của thuộc tính, nhưng lần này với hàm setter được thay thế bằng phương thức trang trí.

Trình tự sau đây cũng tạo ra một thuộc tính đầy đủ, bằng cách sử dụng các phương thức trang trí đó.

Đầu tiên chúng ta tạo một số hàm và một propertyđối tượng chỉ bằng một getter:

>>> def getter(self): print('Get!')
... 
>>> def setter(self, value): print('Set to {!r}!'.format(value))
... 
>>> def deleter(self): print('Delete!')
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

Tiếp theo chúng ta sử dụng .setter()phương thức để thêm một setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Cuối cùng, chúng ta thêm một deleter với .deleter()phương thức:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Cuối cùng nhưng không kém, propertyđối tượng hoạt động như một đối tượng mô tả , vì vậy nó có .__get__(), .__set__().__delete__()phương pháp để móc vào thuộc tính dụ nhận, thiết lập và xóa:

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

Bộ mô tả Howto bao gồm triển khai mẫu Python thuần túy của property()loại:

class Property:
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

10
Rất tốt. Bạn có thể thêm một thực tế là sau khi Foo.prop = propbạn có thể làm Foo().prop = 5; pront Foo().prop; del Foo().propvới kết quả mong muốn.
glglgl

12
Các đối tượng phương thức được tạo khi đang bay và có thể sử dụng lại cùng một vị trí bộ nhớ nếu có.
Martijn Pieters

1
@MarkusMeskanen: Tôi thay vì sử dụng type()như truy cập các thuộc tính và phương thức dunder được sử dụng làm điểm mở rộng bởi các hàm và toán tử tiêu chuẩn.
Martijn Pieters

2
@MarkusMeskanen: bởi vì đối tượng là bất biến, và nếu bạn biến đổi nó tại chỗ, bạn không thể chuyên môn hóa nó trong một lớp con.
Martijn Pieters

5
@MarkusMeskanen: xem Python ghi đè getter mà không cần setter ; nếu @human.name.getterthay đổi propertyđối tượng tại chỗ thay vì trả về một đối tượng mới, human.namethuộc tính sẽ bị thay đổi, thay đổi hành vi của siêu lớp đó.
Martijn Pieters

201

Tài liệu nói rằng nó chỉ là một phím tắt để tạo các thuộc tính chỉ đọc. Vì thế

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

tương đương với

def getx(self):
    return self._x
x = property(getx)

19
Toàn bộ bối cảnh (câu trả lời được đánh giá cao nhất) là tốt, nhưng câu trả lời này thực tế hữu ích để tìm ra lý do tại sao một người khác đã sử dụng @propertynhư một người trang trí trong lớp học của họ.
ijoseph

1
@property cũng có thể được sử dụng khi bạn muốn thêm một thuộc tính vào một lớp và cần duy trì khả năng tương thích với các đối tượng được tạo trước đó của lớp đó (ví dụ: có thể được lưu trong tệp dưa chua).
AndyP

111

Dưới đây là một ví dụ tối thiểu về cách @propertythực hiện:

class Thing:
    def __init__(self, my_word):
        self._word = my_word 
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

Mặt khác wordvẫn là một phương pháp thay vì một tài sản.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'

1
Ví dụ này sẽ trông như thế nào nếu hàm / thuộc tính word () cần được định nghĩa trong init ?
JJ

5
Ai đó có thể vui lòng giải thích lý do tại sao tôi sẽ tạo một nhà trang trí bất động sản ở đây, thay vì chỉ có self.word = my_word- mà sau đó sẽ hoạt động theo cách tương tựprint( Thing('ok').word ) = 'ok'
SilverSlash

1
@SilverSlash Đây chỉ là một ví dụ đơn giản, trường hợp sử dụng thực tế sẽ liên quan đến một phương pháp phức tạp hơn
AlexG

bạn có thể vui lòng giải thích cho tôi, làm thế nào in ấn Thing('ok').wordgọi chức năng nội bộ trong thời gian chạy?
Vicrobot

83

Phần đầu tiên rất đơn giản:

@property
def x(self): ...

giống như

def x(self): ...
x = property(x)
  • đến lượt nó, là cú pháp đơn giản hóa để tạo một propertychỉ với một getter.

Bước tiếp theo sẽ là mở rộng thuộc tính này với một setter và deleter. Và điều này xảy ra với các phương pháp thích hợp:

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

trả về một thuộc tính mới kế thừa mọi thứ từ bộ xcộng cũ đã đặt.

x.deleter hoạt động theo cùng một cách.


49

Điều này như sau:

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

Giống như:

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

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

Giống như:

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

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

Giống như:

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

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

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

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

Điều này giống như:

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

4
Các ví dụ mã đầu tiên và cuối cùng là giống nhau (nguyên văn).
Adomas Baliuka

47

Dưới đây là một ví dụ khác về cách @propertycó thể giúp đỡ khi người ta phải cấu trúc lại mã được lấy từ đây (tôi chỉ tóm tắt nó bên dưới):

Hãy tưởng tượng bạn đã tạo một lớp Moneynhư thế này:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

và người dùng tạo thư viện tùy thuộc vào lớp này nơi họ sử dụng, vd

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Bây giờ, giả sử bạn quyết định thay đổi Moneylớp của mình và loại bỏ các thuộc tính dollarscentsthay vào đó quyết định chỉ theo dõi tổng số xu:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

Nếu người dùng được đề cập ở trên bây giờ cố gắng chạy thư viện của mình như trước đây

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

nó sẽ dẫn đến một lỗi

AttributionError: Đối tượng 'Tiền' không có thuộc tính 'đô la'

Điều đó có nghĩa là bây giờ mọi người dựa vào Moneylớp ban đầu của bạn sẽ phải thay đổi tất cả các dòng mã ở đâu dollarscents được sử dụng có thể rất đau đớn ... Vậy, làm thế nào để tránh điều này? Bằng cách sử dụng@property !

Đó là cách:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

bây giờ chúng tôi gọi từ thư viện của chúng tôi

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

nó sẽ hoạt động như mong đợi và chúng tôi không phải thay đổi một dòng mã nào trong thư viện của chúng tôi! Trên thực tế, chúng ta thậm chí sẽ không phải biết rằng thư viện mà chúng ta phụ thuộc vào đã thay đổi.

Ngoài ra các settercông trình tốt:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

Bạn cũng có thể sử dụng @propertytrong các lớp trừu tượng; Tôi đưa ra một ví dụ tối thiểu ở đây .


tóm tắt của bạn rất hay, ví dụ mà trang web lấy ra hơi lạ một chút .. Một người mới bắt đầu sẽ hỏi .. tại sao chúng ta không thể bám vào self.dollar = dollars? chúng tôi đã làm rất nhiều với @property, nhưng dường như không có chức năng giải nén nào được thêm vào.
Sheng Bi

1
@ShengBi: Không tập trung nhiều vào ví dụ thực tế mà tập trung nhiều hơn vào nguyên tắc cơ bản: Nếu - vì lý do gì - bạn phải cấu trúc lại mã, bạn có thể làm như vậy mà không ảnh hưởng đến mã của bất kỳ ai khác.
Khóa

21

Tôi đọc tất cả các bài viết ở đây và nhận ra rằng chúng ta có thể cần một ví dụ thực tế. Tại sao, thực sự, chúng ta có @property? Vì vậy, hãy xem xét một ứng dụng Flask nơi bạn sử dụng hệ thống xác thực. Bạn khai báo một Người dùng mô hình trong models.py:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

Trong mã này, chúng tôi đã "ẩn" thuộc tính passwordbằng cách sử dụng @propertykích hoạt AttributeErrorxác nhận khi bạn cố truy cập trực tiếp vào nó, trong khi chúng tôi sử dụng @ property.setter để đặt biến đối tượng thực tế password_hash.

Bây giờ auth/views.pychúng ta có thể khởi tạo một Người dùng bằng:

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

Thông báo thuộc tính passwordxuất phát từ một biểu mẫu đăng ký khi người dùng điền vào biểu mẫu. Xác nhận mật khẩu xảy ra ở mặt trước với EqualTo('password', message='Passwords must match')(trong trường hợp nếu bạn đang tự hỏi, nhưng đó là một hình thức Flask liên quan đến chủ đề khác).

Tôi hy vọng ví dụ này sẽ hữu ích


18

Điểm này đã bị xóa bởi nhiều người trên đó nhưng đây là một điểm trực tiếp mà tôi đang tìm kiếm. Đây là những gì tôi cảm thấy quan trọng để bắt đầu với trang trí @property. ví dụ:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

Việc gọi hàm "get_config ()" sẽ hoạt động như thế này.

util = UtilityMixin()
print(util.get_config)

Nếu bạn nhận thấy tôi chưa sử dụng dấu ngoặc "()" để gọi hàm. Đây là điều cơ bản mà tôi đang tìm kiếm cho trang trí @property. Vì vậy, bạn có thể sử dụng chức năng của bạn giống như một biến.


1
điểm rất hữu ích giúp cô đọng khái niệm trừu tượng này.
Info5ek

18

Hãy bắt đầu với trang trí Python.

Trình trang trí Python là một hàm giúp thêm một số chức năng bổ sung vào một hàm đã được xác định.

Trong Python, mọi thứ đều là một đối tượng. Các hàm trong Python là các đối tượng hạng nhất, có nghĩa là chúng có thể được tham chiếu bởi một biến, được thêm vào danh sách, được truyền dưới dạng đối số cho hàm khác, v.v.

Hãy xem xét đoạn mã sau.

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

Ở đây, chúng ta có thể nói rằng hàm trang trí đã sửa đổi hàm say_hello của chúng ta và thêm một số dòng mã bổ sung trong đó.

Cú pháp Python cho trang trí

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

Chúng ta hãy kết luận tất cả mọi thứ hơn với một kịch bản trường hợp, nhưng trước đó hãy nói về một số điều tiên quyết.

Getters và setters được sử dụng trong nhiều ngôn ngữ lập trình hướng đối tượng để đảm bảo nguyên tắc đóng gói dữ liệu (được xem như là gói dữ liệu với các phương thức hoạt động trên các dữ liệu này.)

Các phương thức này tất nhiên là getter để lấy dữ liệu và setter để thay đổi dữ liệu.

Theo nguyên tắc này, các thuộc tính của một lớp được đặt ở chế độ riêng tư để ẩn và bảo vệ chúng khỏi các mã khác.

Yup, @property về cơ bản là một cách pythonic để sử dụng getters và setters.

Python có một khái niệm tuyệt vời gọi là property làm cho cuộc sống của một lập trình viên hướng đối tượng đơn giản hơn nhiều.

Hãy để chúng tôi giả định rằng bạn quyết định tạo ra một lớp có thể lưu trữ nhiệt độ ở độ Celsius.

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

Mã tái cấu trúc, đây là cách chúng ta có thể đạt được bằng tài sản.

Trong Python, property () là một hàm dựng sẵn để tạo và trả về một đối tượng thuộc tính.

Một đối tượng thuộc tính có ba phương thức, getter (), setter () và xóa ().

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

Đây,

temperature = property(get_temperature,set_temperature)

có thể đã bị phá vỡ như,

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

Điểm cần lưu ý:

  • get_tem Nhiệt độ vẫn là một tài sản thay vì một phương thức.

Bây giờ bạn có thể truy cập giá trị của nhiệt độ bằng cách viết.

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

Chúng ta có thể tiếp tục và không xác định tên get_tem Nhiệt độset_tem Nhiệt độ vì chúng không cần thiết và gây ô nhiễm không gian tên lớp.

Cách pythonic để đối phó với vấn đề trên là sử dụng @property .

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

Điểm cần lưu ý -

  1. Một phương thức được sử dụng để nhận giá trị được trang trí bằng "@property".
  2. Phương thức có chức năng như setter được trang trí bằng "@ nhiệt độ.setter", nếu hàm được gọi là "x", chúng ta sẽ phải trang trí nó bằng "@ x.setter".
  3. Chúng tôi đã viết "hai" phương thức có cùng tên và một số tham số khác nhau "def nhiệt độ (tự)" và "nhiệt độ def (tự, x)".

Như bạn có thể thấy, mã chắc chắn là ít thanh lịch.

Bây giờ, hãy nói về một scenerio thực tế ngoài đời thực.

Giả sử bạn đã thiết kế một lớp như sau:

class OurClass:

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


y = OurClass(10)
print(y.x)

Bây giờ, hãy giả sử thêm rằng lớp của chúng tôi đã trở nên phổ biến trong số các khách hàng và họ bắt đầu sử dụng nó trong các chương trình của họ, Họ đã thực hiện tất cả các loại bài tập cho đối tượng.

Và Một ngày định mệnh, một khách hàng đáng tin cậy đã đến với chúng tôi và cho rằng "x" phải có giá trị từ 0 đến 1000, đây thực sự là một kịch bản khủng khiếp!

Do thuộc tính dễ dàng: Chúng tôi tạo một phiên bản thuộc tính của "x".

class OurClass:

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

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

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

Điều này thật tuyệt vời phải không: Bạn có thể bắt đầu với việc triển khai đơn giản nhất có thể tưởng tượng được và bạn có thể tự do di chuyển sang phiên bản thuộc tính mà không phải thay đổi giao diện! Vì vậy, các thuộc tính không chỉ là sự thay thế cho getters và setter!

Bạn có thể kiểm tra việc thực hiện này tại đây


2
Lớp Celsius của bạn sẽ lặp lại vô hạn khi cài đặt (có nghĩa là khi khởi tạo).
Ted Petrou

1
@Ted Petrou Tôi không hiểu bạn? Làm thế nào nó sẽ vô hạn tái diễn khi thiết lập?
Divyanshu Rawat

Điều này thực sự không rõ ràng ... mọi người đang hỏi tại sao, nhưng ví dụ này không thuyết phục ...
Sheng Bi

1
Nó chỉ là một bình luận, ý kiến ​​cá nhân của tôi. Câu trả lời của bạn có thể thực sự tốt. vậy hãy bỏ nó đi
Sheng Bi

1
so với các câu trả lời được bình chọn hàng đầu, câu trả lời này được thiết kế cho con người; cảm ơn.
Info5ek

6

propertylà một lớp học đằng sau @propertytrang trí.

Bạn luôn có thể kiểm tra điều này:

print(property) #<class 'property'>

Tôi viết lại ví dụ từ help(property) để chỉ ra rằng @propertycú pháp

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

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

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

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

c = C()
c.x="a"
print(c.x)

có chức năng giống hệt như property() cú pháp:

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

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

Không có sự khác biệt về cách chúng tôi sử dụng tài sản như bạn có thể thấy.

Để trả lời @propertytrang trí câu hỏi được thực hiện thông qua propertylớp.


Vì vậy, câu hỏi là để giải thích các propertylớp một chút. Đường thẳng này:

prop = property(g,s,d)

Là sự khởi đầu. Chúng ta có thể viết lại nó như thế này:

prop = property(fget=g,fset=s,fdel=d)

Các nghĩa của fget, fsetfdel:

 |    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring

Hình ảnh tiếp theo cho thấy các bộ ba chúng ta có, từ lớp property:

nhập mô tả hình ảnh ở đây

__get__, __set____delete__ở đó để được ghi đè . Đây là việc thực hiện mô hình mô tả trong Python.

Nói chung, một bộ mô tả là một thuộc tính đối tượng với hành vi ràng buộc của Viking, một thuộc tính có quyền truy cập thuộc tính đã bị ghi đè bởi các phương thức trong giao thức mô tả.

Chúng tôi cũng có thể sử dụng tài sản setter, getterdeletercác phương pháp để ràng buộc chức năng đến tài sản. Kiểm tra ví dụ tiếp theo. Phương thức s2của lớp Csẽ đặt thuộc tính nhân đôi .

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

    def g(self):
        return self._x

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

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x


    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      


c = C()
c.x="a"
print(c.x) # outputs "a"

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"

1

Một tài sản có thể được khai báo theo hai cách.

  • Tạo getter, setter cho một thuộc tính và sau đó đi qua những như là đối số để sở hữu chức năng
  • Sử dụng trang trí @property .

Bạn có thể xem một vài ví dụ tôi đã viết về các thuộc tính trong python .


bạn có thể cập nhật câu trả lời của bạn nói rằng tài sản là một lớp để tôi có thể nâng cấp.
prosti


0

Đây là một ví dụ khác:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

Về cơ bản, giống như ví dụ C (đối tượng) ngoại trừ tôi đang sử dụng x thay vào đó ... Tôi cũng không khởi tạo trong __init - ... tốt .. tôi làm, nhưng nó có thể bị xóa vì __x được định nghĩa là một phần của lớp ....

Đầu ra là:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

và nếu tôi nhận xét self.x = 1234 trong init thì đầu ra là:

[ Test Class ] Get x = None
[ x ] None

và nếu tôi đặt _default = Không thành _default = 0 trong hàm getter (vì tất cả các getters nên có một giá trị mặc định nhưng nó không được truyền vào bởi các giá trị thuộc tính từ những gì tôi đã thấy để bạn có thể xác định nó ở đây và nó thực sự không tệ vì bạn có thể xác định mặc định một lần và sử dụng nó ở mọi nơi) tức là: def x (self, _default = 0):

[ Test Class ] Get x = 0
[ x ] 0

Lưu ý: Logic getter ở đó chỉ cần có giá trị được thao tác bởi nó để đảm bảo nó được thao tác bởi nó - tương tự cho các câu lệnh in ...

Lưu ý: Tôi đã quen với Lua và có thể tự động tạo hơn 10 trình trợ giúp khi tôi gọi một hàm duy nhất và tôi đã tạo một thứ tương tự cho Python mà không sử dụng các thuộc tính và nó hoạt động ở một mức độ, nhưng, mặc dù các hàm đã được tạo trước đó đang được sử dụng, đôi khi vẫn có vấn đề với chúng được gọi trước khi được tạo ra, điều này thật lạ vì nó không được mã hóa theo cách đó ... Tôi thích sự linh hoạt của các bảng meta Lua và thực tế tôi có thể sử dụng setters / getters thực tế thay vì chủ yếu truy cập trực tiếp vào một biến ... Tôi thực sự thích cách nhanh chóng một số thứ có thể được xây dựng bằng Python - ví dụ như các chương trình gui. mặc dù tôi không thể thiết kế một thư viện mà không có nhiều thư viện bổ sung - nếu tôi viết mã trong AutoHotkey, tôi có thể truy cập trực tiếp các cuộc gọi dll tôi cần và điều tương tự có thể được thực hiện trong Java, C #, C ++,

Lưu ý: Đầu ra mã trong diễn đàn này bị hỏng - Tôi đã phải thêm khoảng trắng vào phần đầu tiên của mã để nó hoạt động - khi sao chép / dán đảm bảo bạn chuyển đổi tất cả khoảng trắng sang tab .... Tôi sử dụng các tab cho Python vì trong một tệp có 10.000 dòng kích thước tệp có thể là 512KB đến 1MB với khoảng trắng và 100 đến 200KB với các tab tương đương với sự khác biệt lớn về kích thước tệp và giảm thời gian xử lý ...

Các tab cũng có thể được điều chỉnh cho mỗi người dùng - vì vậy nếu bạn thích 2 chiều rộng không gian, 4, 8 hoặc bất cứ điều gì bạn có thể làm điều đó có nghĩa là nó rất chu đáo cho các nhà phát triển có khiếm khuyết về thị lực.

Lưu ý: Tất cả các chức năng được xác định trong lớp không được thụt lề đúng cách do lỗi trong phần mềm diễn đàn - đảm bảo bạn thụt lề nếu bạn sao chép / dán


-3

Một nhận xét: đối với tôi, đối với Python 2.x, @propertyđã không hoạt động như quảng cáo khi tôi không được thừa kế từ object:

class A():
    pass

nhưng làm việc khi:

class A(object):
    pass

cho Python 3, làm việc luôn.


5
Đó là bởi vì trong Python 2, một lớp không được kế thừa objectlà một lớp kiểu cũ và các lớp kiểu cũ không hỗ trợ giao thức mô tả (đó là thứ propertythực hiện để hoạt động theo cách của nó). Trong Python 3, các lớp kiểu cũ không còn tồn tại nữa; tất cả các lớp là những gì chúng ta gọi là các lớp kiểu mới trong Python 2.
chepner
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.