Không thể đặt thuộc tính trên phiên bản của lớp “đối tượng”


87

Vì vậy, tôi đã chơi với Python trong khi trả lời câu hỏi này và tôi phát hiện ra rằng điều này không hợp lệ:

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

do an 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ó hợp lệ:

class Sub(object):
    pass

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

In s.attrhiển thị 'xin chào' 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 chỉ định rằng bạn không thể gán thuộc tính cho các đối tượng vani?


Phỏng đoán thuần túy: objectLoại 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.
Chris Lutz

2
@ S.Lott: Hãy xem dòng đầu tiên của câu hỏi này. Hoàn toàn là sự tò mò.
Smashery

3
Tiêu đề của bạn bị sai lệch, bạn đang cố gắng đặt thuộc tính trên objectcác cá thể của lớp, không phải trên objectlớp.
dhill

Câu trả lời:


129

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

Một thể hiện của objectkhông không mang theo 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, thừa hưởng từ object;-), điều này sẽ yên mỗi đối tượng trong Python với một dict, trong đó sẽ có nghĩa là một overhead của nhiều byte cho mỗi đối tượng mà hiện không có hoặc cần một dict (về cơ bản, tất cả các đối tượng mà không có tùy tiện thuộc tính chuyển nhượng không có hoặc cần một dict).

Ví dụ, bằng cách sử dụng pymplerdự án xuất sắc (bạn có thể lấy nó qua svn từ đây ), chúng tôi có thể thực hiện 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 thứ intchiếm 144 byte thay vì chỉ 16, phải không? -)

Bây giờ, khi bạn tạo một lớp (kế thừa từ bất cứ thứ gì), mọi thứ sẽ thay đổi ...:

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

... những __dict__ hiện nay gia tăng (cộng, một chút chi phí hơn) - do đó, một dintví dụ có thể có các thuộc tính tùy ý, nhưng bạn phải trả khá một chi phí không gian cho sự linh hoạt đó.

Vậy nếu bạn muốn ints chỉ với một thuộc tính bổ sung foobar...? Đó là một nhu cầu hiếm hoi, nhưng Python cung cấp một cơ chế đặc biệt cho mục đích ...

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

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

Khi lớp có __slots__thuộc tính đặc biệt (một chuỗi các chuỗi), thì classcâu lệnh (chính xác hơn là siêu kính mặc định, type) không trang bị cho mọi cá thể của lớp đó 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 các "khe" cứng nhắc (về cơ bản là các vị trí mà mỗi vị trí 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 sự linh hoạt bị mất, bạn nhận được rất nhiều byte cho mỗi phiên bản (có thể chỉ có ý nghĩa nếu bạn có nhiều phiên bản liên quan xung quanh, nhưng, có những trường hợp sử dụng cho điều đó).


5
Điều này giải thích cơ chế được thực hiện như thế nào 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ĩ đến ít nhất hai hoặc ba cách để thực hiện thêm dict khi đang bay mà sẽ không có nhược điểm trên cao nhưng sẽ thêm một số đơn giản.
Георги Кременлиев

Lưu ý rằng không có sản phẩm nào __slots__không làm việc với các loại chiều dài thay đổi như str, tuple, và bằng Python 3 cũng int.
arekolek


Đó là một lời giải thích tuyệt vời, nhưng vẫn không trả lời được tại sao (hoặc làm thế nào) Sub__dict__thuộc tính và đối tượng thì không, là thuộc tính Subkế thừa từ đó object, làm thế nào mà thuộc tính đó (và những người khác tương tự __module__) được thêm vào kế thừa? Có thể điều này có thể là một câu hỏi mới
Rodrigo E. Principe

2
Một đối tượng __dict__chỉ được tạo trong lần đầu tiên nó cần, do đó, tình hình chi phí bộ nhớ không hoàn toàn đơn giản như asizeofđầu ra làm cho nó trông. ( asizeofkhông biết cách tránh hiện tượng __dict__hóa.) Bạn có thể thấy mệnh lệnh không được hiện thực hóa cho đến khi cần thiết trong ví dụ này và bạn có thể xem một trong các đường dẫn mã chịu trách nhiệm cụ thể __dict__hóa tại đây .
user2357112 hỗ trợ Monica

17

Như những người trả lời khác đã nói, an objectkhông có __dict__. objectlà lớp cơ sở của tất cả các kiểu, bao gồm inthoặc str. Vì vậy, bất cứ thứ gì được cung cấp objectcũng sẽ là gánh nặng cho họ. Ngay cả một cái gì đó đơn giản như một tùy chọn __dict__ sẽ cần một con trỏ phụ 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 phiên bản của lớp giả, trong Python 3.3+, bạn có thể (và nên) sử dụng types.SimpleNamespacecho việc này.


4

Nó chỉ đơn giản là do tối ưu hóa.

Các môn phái tương đối lớn.

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

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

Nếu bạn nhìn vào mã nguồn, bạn sẽ thấy rằng có nhiều kiểm tra để xem liệu đối tượng có một dict hay không.


1

Vì vậy, khi đ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 cho rằng lỗi ở phần cuối là do hàm add trả về giá trị int, vì vậy tôi phải ghi đè các hàm tương tự như __add__vậy để giữ lại các thuộc tính tùy chỉnh của mình. Nhưng tất cả điều này bây giờ có ý nghĩa đối với tôi (tôi nghĩ), khi tôi nghĩ về "đối tượng" như "int".


0

Đó là bởi vì đối tượng là một "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 được tạo sẵn và những thứ như mảng numpy) không cho phép thêm các thuộc tính tùy ý.


Nhưng object () 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 một trong số đó là một kiểu được khởi tạo và cái kia là một lớp được khởi tạo không?
Smashery

Chơi lô tô. Đó chính xác là vấn đề.
Ryan

1
Trong Python 3, sự khác biệt giữa các kiểu và lớp không thực sự tồn tại. Vì vậy, "loại" & "lớp" bây giờ khá đồng nghĩa. Nhưng bạn vẫn không thể thêm thuộc tính vào những lớp không có __dict__, vì những lý do được đưa ra bởi Alex Martelli.
PM 2Ring


-2

Đâ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. Tuy nhiên, tôi tin rằng vấn đề thực tế là do các lớp được triển khai trong C không thể được sửa đổi trong thời gian chạy ... các lớp con có thể, nhưng không phải các lớp cơ sở.

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.