Sự khác biệt giữa các thuộc tính lớp và thể hiện là gì?


132

Có sự phân biệt có ý nghĩa giữa:

class A(object):
    foo = 5   # some default value

so với

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

Nếu bạn đang tạo nhiều trường hợp, có sự khác biệt nào về hiệu suất hoặc yêu cầu không gian cho hai kiểu không? Khi bạn đọc mã, bạn có cho rằng ý nghĩa của hai phong cách là khác nhau đáng kể?


1
Tôi mới nhận ra rằng một câu hỏi tương tự đã được hỏi và trả lời ở đây: stackoverflow.com/questions/206734/ Đổi Tôi có nên xóa câu hỏi này không?
Dan Homerick

2
Đó là câu hỏi của bạn, hãy xóa nó đi. Vì nó là của bạn, tại sao lại hỏi ý kiến ​​của ai khác?
S.Lott

Câu trả lời:


146

Ngoài những cân nhắc về hiệu suất, có một sự khác biệt đáng kể về ngữ nghĩa . Trong trường hợp thuộc tính lớp, chỉ có một đối tượng được tham chiếu. Trong trường hợp-thuộc tính-set-at-tức thời, có thể có nhiều đối tượng được tham chiếu. Ví dụ

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]

4
Chỉ các loại đột biến được chia sẻ. Thích cho intstrhọ vẫn gắn liền với từng trường hợp chứ không phải lớp.
Babu

11
@Babu: Không, intstrđược cũng chia sẻ, chính xác theo cùng một cách. Bạn có thể kiểm tra dễ dàng với ishoặc id. Hoặc chỉ cần nhìn vào từng trường hợp __dict__và lớp __dict__. Nó thường không quan trọng lắm cho dù các loại bất biến được chia sẻ hay không.
abarnert

17
Tuy nhiên, lưu ý rằng nếu bạn làm như vậy a.foo = 5, thì trong cả hai trường hợp, bạn sẽ thấy b.foolợi nhuận []. Đó là bởi vì trong trường hợp đầu tiên, bạn đang ghi đè thuộc tính lớp a.foobằng một thuộc tính thể hiện mới có cùng tên.
Konstantin Schubert

39

Sự khác biệt là thuộc tính trên lớp được chia sẻ bởi tất cả các trường hợp. Thuộc tính trên một thể hiện là duy nhất cho thể hiện đó.

Nếu đến từ C ++, các thuộc tính trên lớp giống như các biến thành viên tĩnh.


1
Không phải nó chỉ là loại đột biến được chia sẻ sao? Câu trả lời được chấp nhận hiển thị một danh sách, hoạt động, nhưng nếu đó là một int, nó dường như giống như một attr dụ: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
Rafe

6
@Rafe: Không, tất cả các loại được chia sẻ. Lý do bạn bối rối là những gì a.foo.append(5)đang làm thay đổi giá trị a.foođề cập đến, trong khi a.foo = 5đang tạo a.foothành một tên mới cho giá trị 5. Vì vậy, bạn kết thúc với một thuộc tính thể hiện ẩn thuộc tính lớp. Hãy thử tương tự a.foo = 5trong phiên bản của Alex và bạn sẽ thấy điều đó b.fookhông thay đổi.
abarnert

30

Đây là một bài viết rất tốt , và tóm tắt nó như dưới đây.

class Bar(object):
    ## No need for dot syntax
    class_var = 1

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

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

Và ở dạng trực quan

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

Phân công thuộc tính lớp

  • Nếu một thuộc tính lớp được đặt bằng cách truy cập lớp, nó sẽ ghi đè giá trị cho tất cả các trường hợp

    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
    
  • Nếu một biến lớp được đặt bằng cách truy cập một thể hiện, nó sẽ ghi đè giá trị chỉ cho thể hiện đó . Điều này về cơ bản ghi đè biến lớp và biến nó thành một biến thể hiện có sẵn, theo trực giác, chỉ dành cho thể hiện đó .

    foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1
    

Khi nào bạn sẽ sử dụng thuộc tính lớp?

  • Lưu trữ hằng số . Vì các thuộc tính lớp có thể được truy cập như các thuộc tính của chính lớp đó, nên thường sử dụng chúng để lưu trữ các hằng số cụ thể của Lớp, toàn lớp

    class Circle(object):
         pi = 3.14159
    
         def __init__(self, radius):
              self.radius = radius   
        def area(self):
             return Circle.pi * self.radius * self.radius
    
    Circle.pi
    ## 3.14159
    c = Circle(10)
    c.pi
    ## 3.14159
    c.area()
    ## 314.159
    
  • Xác định giá trị mặc định . Như một ví dụ tầm thường, chúng tôi có thể tạo một danh sách giới hạn (nghĩa là danh sách chỉ có thể chứa một số phần tử nhất định hoặc ít hơn) và chọn để có giới hạn mặc định là 10 mục

    class MyClass(object):
        limit = 10
    
        def __init__(self):
            self.data = []
        def item(self, i):
            return self.data[i]
    
        def add(self, e):
            if len(self.data) >= self.limit:
                raise Exception("Too many elements")
            self.data.append(e)
    
     MyClass.limit
     ## 10
    

19

Vì những người trong các bình luận ở đây và trong hai câu hỏi khác được đánh dấu là các dup đều tỏ ra bối rối về điều này theo cùng một cách, tôi nghĩ rằng đáng để thêm một câu trả lời bổ sung trên đầu của Alex Coventry .

Việc Alex đang gán một giá trị của một loại có thể thay đổi, như danh sách, không liên quan gì đến việc mọi thứ có được chia sẻ hay không. Chúng ta có thể thấy điều này với idchức năng hoặc istoán tử:

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(Nếu bạn đang tự hỏi tại sao tôi lại sử dụng object()thay vì, nói, 5đó là để tránh gặp phải hai vấn đề khác mà tôi không muốn giải quyết ở đây; vì hai lý do khác nhau, những thứ hoàn toàn riêng biệt 5có thể trở thành cùng một thể hiện của số 5. Nhưng s hoàn toàn được tạo riêng biệt object()không thể.)


Vậy, tại sao a.foo.append(5)trong ví dụ của Alex lại ảnh hưởng b.foo, nhưng a.foo = 5trong ví dụ của tôi thì không? Vâng, hãy thử a.foo = 5trong ví dụ của Alex, và thông báo rằng nó không ảnh hưởng b.foomột trong hai .

a.foo = 5chỉ là làm cho a.foomột tên cho 5. Điều đó không ảnh hưởng b.foohoặc bất kỳ tên nào khác cho giá trị cũ a.foođược sử dụng để đề cập đến. * Có một chút khó khăn khi chúng ta tạo một thuộc tính cá thể ẩn thuộc tính lớp, ** nhưng khi bạn nhận được điều đó, không có gì phức tạp xảy ra ở đây


Hy vọng rằng bây giờ rõ ràng lý do tại sao Alex sử dụng một danh sách: thực tế là bạn có thể thay đổi danh sách có nghĩa là dễ dàng hơn để hiển thị rằng hai biến có cùng danh sách và cũng có nghĩa là nó quan trọng hơn trong mã thực tế để biết bạn có hai danh sách hay không hai tên cho cùng một danh sách.


* Sự nhầm lẫn cho những người đến từ một ngôn ngữ như C ++ là trong Python, các giá trị không được lưu trữ trong các biến. Các giá trị sống trong đất giá trị, theo cách riêng của chúng, các biến chỉ là tên của các giá trị và việc gán chỉ tạo một tên mới cho một giá trị. Nếu nó có ích, hãy nghĩ về mỗi biến Python là một shared_ptr<T>thay vì a T.

** Một số người tận dụng lợi thế này bằng cách sử dụng thuộc tính lớp làm "giá trị mặc định" cho thuộc tính thể hiện mà các thể hiện có thể hoặc không thể đặt. Điều này có thể hữu ích trong một số trường hợp, nhưng nó cũng có thể gây nhầm lẫn, vì vậy hãy cẩn thận với nó.


0

Có một tình huống nữa.

Thuộc tính lớp và thể hiện là Descriptor .

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

Trên đây sẽ xuất:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

Cùng một kiểu truy cập thông qua lớp hoặc thể hiện trả về kết quả khác nhau!

Và tôi đã tìm thấy trong định nghĩa c.PyObject_GenericGetAttr và một bài viết tuyệt vời .

Giải thích

Nếu thuộc tính được tìm thấy trong từ điển của các lớp tạo nên. các đối tượng MRO, sau đó kiểm tra xem liệu thuộc tính đang được tra cứu có trỏ đến Bộ mô tả dữ liệu hay không (không có gì khác khi một lớp thực hiện cả phương thức __get____set__phương thức). Nếu có, giải quyết tra cứu thuộc tính bằng cách gọi __get__phương thức của Bộ mô tả dữ liệu (dòng 28 Ném33).

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.