2009-10-07 32 views
64

Vì vậy, tôi đang chơi xung quanh với Python trong khi trả lời this question, và tôi phát hiện ra rằng đây là không hợp lệ:Không thể thiết lập các thuộc tính của lớp đối tượng

o = object() 
o.attr = 'hello' 

do một AttributeError: 'object' object has no attribute 'attr'. Tuy nhiên, với bất kỳ lớp nào được kế thừa từ đối tượng, nó là hợp lệ:

class Sub(object): 
    pass 

s = Sub() 
s.attr = 'hello' 

In 'hello' như mong đợi. Tại sao điều này là trường hợp? Điều gì trong đặc tả ngôn ngữ Python xác định rằng bạn không thể gán các thuộc tính cho các đối tượng vani?

+0

phỏng đoán thuần túy: Loại 'đối tượng' là bất biến và không thể thêm thuộc tính mới? Điều này có vẻ như nó sẽ có ý nghĩa nhất. –

+2

@ S.Lott: Xem dòng đầu tiên của câu hỏi này. Hoàn toàn tò mò. – Smashery

+2

Tiêu đề của bạn là gây hiểu lầm, bạn đang cố gắng thiết lập các thuộc tính trên các cá thể lớp 'object', không phải trên lớp' object'. – dhill

Trả lời

107

Để hỗ trợ phân bổ thuộc tính tùy ý, đối tượng cần có một __dict__: một dict được liên kết với đối tượng, nơi có thể lưu trữ thuộc tính tùy ý. Nếu không, không có nơi nào để đặt thuộc tính mới.

Một thể hiện của object không không carry xung quanh một __dict__ - nếu nó đã làm, trước khi vấn đề phụ thuộc tròn khủng khiếp (vì dict, giống như hầu hết mọi thứ khác, được thừa hưởng từ object ;-), điều này sẽ yên mỗi đối tượng trong Python với dict, có nghĩa là chi phí của nhiều byte cho mỗi đối tượng hiện không có hoặc không có lệnh dict (về cơ bản, tất cả các đối tượng không có thuộc tính được gán tùy ý không có hoặc cần lệnh).

Ví dụ, bằng cách sử dụng dự án xuất sắc pympler (bạn có thể lấy nó qua svn từ here), chúng ta có thể làm một số phép đo ...:

>>> from pympler import asizeof 
>>> asizeof.asizeof({}) 
144 
>>> asizeof.asizeof(23) 
16 

Bạn sẽ không muốn mỗi int để mất 144 byte thay vì chỉ 16, phải -)

Bây giờ, khi bạn thực hiện một lớp (kế thừa từ bất cứ điều gì), mọi thứ thay đổi ...:?

>>> class dint(int): pass 
... 
>>> asizeof.asizeof(dint(23)) 
184 

... __dict__ hiện đã được thêm (cộng thêm một chút chi phí) - vì vậy một cá thể dint có thể có các thuộc tính tùy ý, nhưng bạn phải trả một khoản chi phí không gian cho tính linh hoạt đó.

Vì vậy, nếu bạn muốn int s chỉ với một thuộc tính bổ sung foobar ...? Đó là một nhu cầu rất hiếm, nhưng Python không đưa ra một cơ chế đặc biệt cho mục đích này ...

>>> class fint(int): 
... __slots__ = 'foobar', 
... def __init__(self, x): self.foobar=x+100 
... 
>>> asizeof.asizeof(fint(23)) 
80 

... không khá nhỏ như một int, tâm trí bạn! (hoặc thậm chí hai số int s, một trong số self và một số self.foobar - thứ hai có thể được gán lại), nhưng chắc chắn tốt hơn nhiều so với dint.

Khi lớp có __slots__ thuộc tính đặc biệt (một chuỗi các chuỗi), sau đó các class statement (chính xác hơn, các metaclass mặc định, type) không không trang bị cho tất cả các thể hiện của lớp đó với một __dict__ (và do đó khả năng có các thuộc tính tùy ý), chỉ là một tập hợp "khe" hữu hạn, cứng nhắc (về cơ bản những nơi có thể chứa một tham chiếu đến một số đối tượng) với các tên đã cho.

Để đổi lấy tính linh hoạt bị mất, bạn nhận được nhiều byte cho mỗi trường hợp (có thể chỉ có ý nghĩa nếu bạn có hàng chục trường hợp gấp rút xung quanh, nhưng, có trường hợp sử dụng cho điều đó).

+5

Điều này giải thích cách cơ chế được triển khai nhưng không giải thích tại sao nó được thực hiện theo cách này.Tôi có thể nghĩ ít nhất hai hoặc ba cách để thực hiện thêm __dict__ trên bay mà sẽ không có nhược điểm trên đầu nhưng sẽ thêm một số sự đơn giản. –

+0

Lưu ý rằng không trống rỗng '__slots__' không hoạt động với các kiểu có độ dài thay đổi như' str', 'tuple', và trong Python 3 cũng' int'. – arekolek

+0

Rất tốt và cảm ơn! [Nếu một đối tượng không có '__dict__', lớp của nó phải có thuộc tính' __slot__'?] (Https://stackoverflow.com/q/46575174/156458) – Tim

-1

Đây là (IMO) một trong những hạn chế cơ bản với Python - bạn không thể mở lại các lớp học. Tôi tin rằng vấn đề thực tế, mặc dù, được gây ra bởi thực tế là các lớp học thực hiện trong C không thể được sửa đổi trong thời gian chạy ... lớp con có thể, nhưng không phải là các lớp cơ sở.

1

Đó là vì đối tượng là "loại", không phải là một lớp. Nói chung, tất cả các lớp được định nghĩa trong phần mở rộng C (giống như tất cả các kiểu dữ liệu dựng sẵn, và các công cụ như mảng numpy) không cho phép bổ sung các thuộc tính tùy ý.

+0

Nhưng đối tượng() là một đối tượng, giống như Sub() là một đối tượng. Sự hiểu biết của tôi là cả s và o đều là các đối tượng. Vậy sự khác biệt cơ bản giữa s và o là gì? Có phải đó là một loại instantiated và cái kia là một lớp instantiated? – Smashery

+0

Bingo. Đó chính là vấn đề. – Ryan

1

Vì vậy, điều tra câu hỏi của riêng tôi, tôi phát hiện ra điều này về ngôn ngữ Python: bạn có thể kế thừa từ những thứ như int, và bạn thấy hành vi tương tự:

>>> class MyInt(int): 
     pass 

>>> x = MyInt() 
>>> print x 
0 
>>> x.hello = 4 
>>> print x.hello 
4 
>>> x = x + 1 
>>> print x 
1 
>>> print x.hello 
Traceback (most recent call last): 
    File "<interactive input>", line 1, in <module> 
AttributeError: 'int' object has no attribute 'hello' 

Tôi giả sử các lỗi ở cuối là vì hàm add trả về một int, vì vậy tôi phải ghi đè các hàm như __add__ và như vậy để giữ lại các thuộc tính tùy chỉnh của tôi. Nhưng điều này tất cả bây giờ có ý nghĩa với tôi (tôi nghĩ), khi tôi nghĩ về "đối tượng" như "int".

5

Đơn giản là do tối ưu hóa.

Dicts tương đối lớn.

>>> import sys 
>>> sys.getsizeof((lambda:1).__dict__) 
140 

Hầu hết (có thể tất cả) các lớp được định nghĩa trong C không có chỉ số để tối ưu hóa.

Nếu bạn nhìn vào số source code, bạn sẽ thấy rằng có nhiều kiểm tra để xem đối tượng có chỉ số hay không.

16

Khi người trả lời khác đã nói, số object không có __dict__. object là lớp cơ sở của tất cả các loại, bao gồm int hoặc str. Vì vậy, bất cứ điều gì được cung cấp bởi object sẽ là một gánh nặng cho họ là tốt. Ngay cả một cái gì đó đơn giản như một tùy chọn__dict__ sẽ cần thêm một con trỏ cho mỗi giá trị; điều này sẽ lãng phí thêm 4-8 byte bộ nhớ cho mỗi đối tượng trong hệ thống, cho một tiện ích rất hạn chế.


Thay vì thực hiện một thể hiện của lớp giả, trong Python 3.3+, bạn có thể (và nên) sử dụng types.SimpleNamespace cho việc này.

Các vấn đề liên quan