Biến lớp - Một biến được chia sẻ bởi tất cả các phiên bản của một lớp. Các biến lớp được định nghĩa trong một lớp nhưng bên ngoài bất kỳ phương thức nào của lớp. Các biến lớp không được sử dụng thường xuyên như các biến thể hiện
điều khoản giáo dân
Như chúng ta đều biết các biến thể hiện là các biến duy nhất cho từng thể hiện đang được tạo cho lớp cụ thể đó. Trong khi đó, các biến lớp có thể được coi là một biến thường giữ nguyên đối với bất kỳ trường hợp nào được tạo cho lớp đó
Đủ lý thuyết rồi. Bây giờ chúng ta hãy đi sâu vào một số ví dụ mã hóa để hiểu rõ hơn
Trong hình trên, bạn có thể thấy rằng các biến đang được khai báo bằng từ khóa self là các biến thể hiện và là duy nhất, thường thay đổi đối với mọi thể hiện được tạo cho lớp đó. Ở đây emp_1 là thể hiện được tạo cho lớp Nhân viên
Như tôi đã đề cập rằng các biến lớp có nghĩa là được chia sẻ giữa tất cả các phiên bản được tạo cho lớp đó. Một ứng cử viên tuyệt vời cho hạng nhân viên sẽ là tiền thưởng cho nhân viên, thường sẽ phổ biến cho tất cả nhân viên trong một tổ chức cụ thể (về tỷ lệ phần trăm tăng lương). Bây giờ bạn có thể có một câu hỏi là tại sao phải có các biến lớp riêng biệt khi nó có thể được thực hiện bằng cách sử dụng một cái gì đó như bên dưới
Vì vậy, có một số điều cần lưu ý trước khi tiếp tục với phương pháp này. Vấn đề đầu tiên là raise_amount không được khai báo cả dưới dạng biến thể hiện và biến lớp. Và do đó, nó không thể được gọi thông qua một thuộc tính tương tự như các biến thể hiện khác như lương, tên và họ
Vấn đề thứ hai là nếu số tiền tăng (1. 04) được đề cập ở nhiều nơi, chúng tôi phải thực hiện các thay đổi ở nhiều nơi, đây là loại nội dung cập nhật thủ công. Vì vậy, để khắc phục những tình huống này, chúng tôi sử dụng các biến lớp
Biến lớp
Lỗi biến lớp
Trong hình ảnh biến lớp ở trên, bạn có thể thấy cách gọi biến lớp (raise_amount) dẫn đến lỗi trong hình ảnh tiếp theo. Không có sự nhầm lẫn nào ở đây, chúng ta phải gọi biến lớp bằng một từ khóa thể hiện (bản thân) hoặc bằng cách sử dụng chính lớp đó
Do đó, dưới đây là cách đúng để gọi một biến. Bạn có thể gọi một biến lớp tương tự như hàm apply_raise của hàm apply_rais1e
Cách đúng để gọi biến lớp
Tôi có thể hiểu sự nhầm lẫn của bạn ở giai đoạn này vì raise_amount là một biến lớp, làm cách nào tôi có thể truy cập chúng bằng cách sử dụng cá thể (từ khóa self). Hãy để chúng tôi làm cho mọi thứ rõ ràng ở đây. Xem đoạn mã dưới đây
Điểm mấu chốt trong hình trên là bất cứ khi nào tôi cố gắng truy cập một thuộc tính (tăng_số_lượng) ở đây bằng cách sử dụng các phiên bản (emp_1), trước tiên, nó sẽ kiểm tra xem phiên bản đó có thuộc tính đó hay không, nếu không, thì nó sẽ tìm kiếm thuộc tính đó . Như vậy ở đây emp_1 không có thuộc tính raise_amount mà class Employee có thuộc tính raise_amount
Và vâng, chúng ta đã đi đến phần cuối của blog này, giúp bây giờ bạn có thể hiểu rõ hơn về biến lớp và sự khác biệt giữa biến lớp và biến thể hiện
Các biến về cơ bản là các ký hiệu đại diện cho một giá trị mà chúng ta đang sử dụng trong một chương trình. Lập trình hướng đối tượng cho phép các biến được sử dụng ở cấp độ lớp hoặc cấp độ thể hiện. Mục đích của bài viết này là phân biệt rõ ràng giữa các loại biến được cung cấp bởi mô hình đối tượng của Python và tiếp tục…
Trong một màn hình điện thoại gần đây, tôi đã quyết định sử dụng một thuộc tính lớp để triển khai một API Python nhất định. Người phỏng vấn của tôi đã thách thức tôi, đặt câu hỏi liệu mã của tôi có hợp lệ về mặt cú pháp hay không, khi nó được thực thi, v.v. Trên thực tế, bản thân tôi cũng không chắc chắn về câu trả lời. Vì vậy, tôi đã làm một số đào. Thuộc tính lớp Python. khi nào (hoặc làm thế nào) sử dụng chúng. Trong hướng dẫn này, tôi sẽ hướng dẫn bạn những cạm bẫy phổ biến và kết luận bằng một danh sách các trường hợp sử dụng hợp lệ có thể giúp bạn tiết kiệm thời gian, năng lượng và các dòng mã
Qua
Charles đầm lầy
Charles đã từng là trưởng nhóm kỹ thuật tại Khan Academy, Cedar và Spring Discovery
CHIA SẺ
CHIA SẺ
Ghi chú của biên tập viên. Bài viết này đã được cập nhật vào ngày 29/11/22 bởi nhóm biên tập của chúng tôi. Nó đã được sửa đổi để bao gồm các nguồn gần đây và phù hợp với các tiêu chuẩn biên tập hiện tại của chúng tôi
Gần đây tôi đã có một màn hình điện thoại phỏng vấn lập trình, trong đó chúng tôi đã sử dụng một trình soạn thảo văn bản hợp tác và điều đó khiến tôi suy nghĩ về các thuộc tính của lớp Python
Tôi được yêu cầu triển khai một API nhất định và đã chọn thực hiện bằng Python. Trừu tượng hóa câu lệnh vấn đề, giả sử tôi cần một lớp có các thể hiện được lưu trữ một số {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 3 và một số {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 4
Tôi hít một hơi thật sâu và bắt đầu gõ. Sau một vài dòng, tôi đã có một cái gì đó như thế này
class Service(object): data = [] def __init__(self, other_data): self.other_data = other_data ...Người phỏng vấn của tôi đã ngăn tôi lại
người phỏng vấn. “Dòng đó {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 5. Tôi không nghĩ đó là Python hợp lệ. ”
Tôi. “Tôi khá chắc là. Nó chỉ đặt giá trị mặc định cho thuộc tính thể hiện. ”
người phỏng vấn. “Khi nào đoạn mã đó được thực thi?”
Tôi. “Tôi không thực sự chắc chắn. Tôi sẽ chỉ sửa nó để tránh nhầm lẫn. ”
Để tham khảo và cung cấp cho bạn ý tưởng về mục đích của tôi, đây là cách tôi sửa đổi mã
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ...Hóa ra, cả hai chúng ta đều sai. Câu trả lời thực sự nằm ở việc hiểu được sự khác biệt giữa các thuộc tính lớp Python và các thuộc tính thể hiện của Python
Ghi chú. Nếu bạn là một chuyên gia xử lý các thuộc tính lớp Python, bạn có thể bỏ qua để chuyển sang các trường hợp sử dụng
Thuộc tính lớp Python
Người phỏng vấn của tôi đã sai ở chỗ đoạn mã trên hợp lệ về mặt cú pháp
Tôi đã sai ở chỗ mã không đặt "giá trị mặc định" cho thuộc tính thể hiện. Thay vào đó, nó định nghĩa {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 3 như một thuộc tính lớp có giá trị {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 7
Theo kinh nghiệm của tôi, các thuộc tính lớp Python là một chủ đề mà nhiều người biết điều gì đó, nhưng ít người hiểu hoàn toàn
Biến lớp Python so với. Biến thể hiện. Có gì khác biệt?
Thuộc tính lớp Python là thuộc tính của lớp (thông tư, tôi biết), chứ không phải là thuộc tính của một thể hiện của lớp
Hãy sử dụng một ví dụ lớp Python để minh họa sự khác biệt. Ở đây, {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 8 là thuộc tính lớp và {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 9 là thuộc tính thể hiện
{:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_varLưu ý rằng tất cả các phiên bản của lớp đều có quyền truy cập vào {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 8 và nó cũng có thể được truy cập như một thuộc tính của chính lớp đó
{:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1Đối với các lập trình viên Java hoặc C++, thuộc tính lớp tương tự—nhưng không giống hệt—với thành viên tĩnh. Chúng ta sẽ thấy chúng khác nhau như thế nào sau
Thuộc tính lớp Python so với. Không gian tên đối tượng
Để hiểu những gì đang xảy ra ở đây, hãy nói ngắn gọn về không gian tên Python
Không gian tên là ánh xạ từ tên đến đối tượng, với thuộc tính không có mối quan hệ nào giữa các tên trong các không gian tên khác nhau. Chúng thường được triển khai dưới dạng từ điển Python, mặc dù điều này được trừu tượng hóa
Tùy thuộc vào ngữ cảnh, bạn có thể cần truy cập một không gian tên bằng cách sử dụng cú pháp dấu chấm (e. g. , {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 1) hoặc dưới dạng biến cục bộ (e. g. , {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 2). Như một ví dụ cụ thể
{:lang='python'} class MyClass(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 MyClass.class_var ## 1Các lớp Python và các thể hiện của các lớp đều có các không gian tên riêng biệt được đại diện bởi {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 3 và {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 4, tương ứng
Khi bạn cố gắng truy cập các thuộc tính Python từ một thể hiện của một lớp, trước tiên nó sẽ xem không gian tên thể hiện của nó. Nếu nó tìm thấy thuộc tính, nó sẽ trả về giá trị được liên kết. Nếu không, nó sẽ tìm trong không gian tên của lớp và trả về thuộc tính (nếu có, nếu không sẽ báo lỗi). Ví dụ
{:lang='python'} foo = MyClass(2) ## Finds i_var in foo’s instance namespace foo.i_var ## 2 ## Doesn’t find class_var in instance namespace… ## So looks in class namespace (MyClass.__dict__) foo.class_var ## 1Không gian tên cá thể được ưu tiên hơn không gian tên lớp. Nếu có một thuộc tính Python có cùng tên trong cả hai, không gian tên đối tượng sẽ được kiểm tra trước và giá trị của nó được trả về. Đây là một tra cứu thuộc tính for
{:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name]Và, ở dạng trực quan
Cách thuộc tính lớp Python xử lý chuyển nhượng
Với suy nghĩ này, chúng ta có thể hiểu được cách các thuộc tính của lớp xử lý việc gán
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 phiên bản. Ví dụ
{:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2
Ở cấp độ không gian tên, chúng tôi đang đặt {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 5. (Ghi chú. Đây không phải là mã chính xác mà sẽ là {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 6 vì {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 7 trả về một , một trình bao bọc bất biến để ngăn việc gán trực tiếp, nhưng nó giúp ích cho mục đích trình diễn). Sau đó, khi chúng tôi truy cập {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 8, {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 8 có một giá trị mới trong không gian tên lớp và do đó 2 được trả về
Nếu một biến lớp Python được đặt bằng cách truy cập một thể hiện, thì nó sẽ chỉ ghi đè giá trị cho thể hiện đó. Về cơ bản, điều này sẽ ghi đè biến lớp và biến nó thành một biến thể hiện chỉ có sẵn bằng trực giác cho thể hiện đó. Ví dụ
foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1
Ở cấp độ không gian tên, chúng tôi đang thêm thuộc tính {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 8 vào {:lang='python'} class MyClass(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 MyClass.class_var ## 1 1, vì vậy khi chúng tôi tra cứu {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 8, chúng tôi trả về 2. Trong khi đó, các phiên bản khác của {:lang='python'} class MyClass(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 MyClass.class_var ## 1 3 sẽ không có {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 8 trong không gian tên phiên bản của chúng, vì vậy chúng tiếp tục tìm thấy {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 8 trong {:lang='python'} foo = MyClass(2) bar = MyClass(3) foo.class_var, foo.i_var ## 1, 2 bar.class_var, bar.i_var ## 1, 3 MyClass.class_var ## <— This is key ## 1 3 và do đó trả về 1
khả năng biến đổi
Nếu thuộc tính lớp của bạn có loại có thể thay đổi thì sao?
Hãy quay lại {:lang='python'} class MyClass(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 MyClass.class_var ## 1 7 mà tôi đã xác định trước đó và xem việc sử dụng biến lớp của tôi có thể dẫn đến các vấn đề sau này như thế nào
{:lang='python'} class Service(object): data = [] def __init__(self, other_data): self.other_data = other_data ...Mục tiêu của tôi là có danh sách trống (_______0_______7) làm giá trị mặc định cho {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 3 và đối với mỗi trường hợp của {:lang='python'} class MyClass(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 MyClass.class_var ## 1 7 để có dữ liệu riêng sẽ được thay đổi theo thời gian trên cơ sở từng trường hợp. Nhưng trong trường hợp này, chúng tôi có hành vi sau (lưu ý rằng {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 4 là tùy ý trong ví dụ này)
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 0Điều này là không tốt—việc thay đổi biến lớp Python của chúng ta thông qua một phiên bản sẽ thay đổi nó cho tất cả các phiên bản khác
Ở cấp độ không gian tên, tất cả các phiên bản của {:lang='python'} class MyClass(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 MyClass.class_var ## 1 7 đang truy cập và sửa đổi cùng một danh sách trong {:lang='python'} foo = MyClass(2) ## Finds i_var in foo’s instance namespace foo.i_var ## 2 ## Doesn’t find class_var in instance namespace… ## So looks in class namespace (MyClass.__dict__) foo.class_var ## 1 3 mà không tạo thuộc tính {:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 3 của riêng chúng trong không gian tên của phiên bản
Chúng ta có thể giải quyết vấn đề này bằng cách sử dụng bài tập;
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 1Trong trường hợp này, chúng tôi đang thêm {:lang='python'} foo = MyClass(2) ## Finds i_var in foo’s instance namespace foo.i_var ## 2 ## Doesn’t find class_var in instance namespace… ## So looks in class namespace (MyClass.__dict__) foo.class_var ## 1 6, vì vậy bản gốc {:lang='python'} foo = MyClass(2) ## Finds i_var in foo’s instance namespace foo.i_var ## 2 ## Doesn’t find class_var in instance namespace… ## So looks in class namespace (MyClass.__dict__) foo.class_var ## 1 7 không thay đổi
Thật không may, điều này đòi hỏi người dùng {:lang='python'} class MyClass(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 MyClass.class_var ## 1 7 phải có kiến thức sâu sắc về các biến của nó và cách giải quyết này chắc chắn dễ mắc lỗi. Theo một nghĩa nào đó, chúng tôi sẽ giải quyết các triệu chứng hơn là nguyên nhân. Tốt hơn là có một cái gì đó chính xác bằng cách xây dựng
Giải pháp cá nhân của tôi. Nếu bạn chỉ đang sử dụng một biến lớp để gán một giá trị mặc định cho một biến thể hiện của Python, thì đừng sử dụng các giá trị có thể thay đổi. Trong trường hợp này, mọi phiên bản của {:lang='python'} class MyClass(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 MyClass.class_var ## 1 7 cuối cùng sẽ ghi đè lên {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 0 bằng thuộc tính phiên bản riêng của nó, vì vậy việc sử dụng một danh sách trống làm mặc định dẫn đến một lỗi nhỏ dễ bị bỏ qua. Thay vì ở trên, chúng ta có thể có một trong hai
Bị mắc kẹt hoàn toàn với các thuộc tính thể hiện, như đã trình bày trong phần giới thiệu
Tránh sử dụng danh sách trống (một giá trị có thể thay đổi) làm “mặc định của chúng tôi. ”
Ví dụ
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 2Tất nhiên, chúng tôi phải xử lý trường hợp {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 1 một cách thích hợp, nhưng đó là một cái giá nhỏ phải trả
Khi nào bạn nên sử dụng thuộc tính lớp Python?
Các thuộc tính của lớp rất phức tạp, nhưng hãy xem xét một số trường hợp chúng sẽ hữu ích
Lưu trữ hằng số. Vì các thuộc tính của lớp có thể được truy cập dưới dạng 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ố dành riêng cho lớp, toàn lớp. Ví dụ
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 3Xác định giá trị mặc định. Như một ví dụ nhỏ, chúng ta có thể tạo một danh sách giới hạn (i. e. , một 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 giới hạn mặc định là 10 mục
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 4Sau đó, chúng tôi cũng có thể tạo các phiên bản có giới hạn cụ thể của riêng chúng, bằng cách gán chúng cho thuộc tính {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 2 của phiên bản
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 5Điều này chỉ có ý nghĩa nếu bạn muốn phiên bản điển hình của {:lang='python'} class MyClass(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 MyClass.class_var ## 1 3 chỉ chứa 10 phần tử hoặc ít hơn—nếu bạn đang đưa ra các giới hạn khác nhau cho tất cả các phiên bản của mình, thì {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 2 phải là một biến thể hiện. (Hãy nhớ cẩn thận khi sử dụng các giá trị có thể thay đổi làm giá trị mặc định của bạn. )
Theo dõi tất cả dữ liệu trên tất cả các phiên bản của một lớp nhất định. Đây là một loại cụ thể, nhưng tôi có thể thấy một tình huống trong đó bạn có thể muốn truy cập một phần dữ liệu liên quan đến mọi phiên bản hiện có của một lớp nhất định
Để làm cho kịch bản cụ thể hơn, giả sử chúng ta có một lớp {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 5 và mỗi người có một lớp {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 6. Chúng tôi muốn theo dõi tất cả các tên đã được sử dụng. Một cách tiếp cận có thể là lặp qua danh sách các đối tượng của trình thu gom rác, nhưng việc sử dụng các biến lớp sẽ đơn giản hơn
Lưu ý rằng, trong trường hợp này, {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 7 sẽ chỉ được truy cập dưới dạng biến lớp, vì vậy giá trị mặc định có thể thay đổi được chấp nhận
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 6Chúng tôi thậm chí có thể sử dụng mẫu thiết kế này để theo dõi tất cả các phiên bản hiện có của một lớp nhất định, thay vì chỉ một số dữ liệu liên quan
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 7Xem xét hiệu suất
Có liên quan. Mẹo và phương pháp hay nhất về Python của Toptal Developers
Dưới mui xe
Ghi chú. Nếu bạn lo lắng về hiệu suất ở cấp độ này, thì bạn có thể không muốn sử dụng Python ngay từ đầu, vì sự khác biệt sẽ ở mức phần mười của mili giây—nhưng vẫn rất thú vị khi tìm hiểu một chút và điều đó sẽ giúp ích cho bạn
Nhớ lại rằng không gian tên của một lớp được tạo và điền vào thời điểm định nghĩa lớp. Điều đó có nghĩa là chúng ta chỉ thực hiện một phép gán cho một biến lớp nhất định, trong khi các biến thể hiện phải được gán mỗi khi một thể hiện mới được tạo
Hãy lấy một ví dụ
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 8Chúng tôi chỉ định cho {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 8 một lần, nhưng {:lang='python'} def instlookup(inst, name): ## simplified algorithm if inst.__dict__.has_key(name): return inst.__dict__[name] else: return inst.__class__.__dict__[name] 9 trên mọi cuộc gọi tới {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 0
Để có thêm bằng chứng, hãy sử dụng trình dịch ngược Python
class Service(object): def __init__(self, other_data): self.data = [] self.other_data = other_data ... 9Khi chúng ta nhìn vào mã byte, một lần nữa rõ ràng là {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 1 phải thực hiện hai nhiệm vụ, trong khi {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 2 chỉ thực hiện một nhiệm vụ
Trong thực tế, mức tăng này thực sự trông như thế nào?
Tuy nhiên, tôi nghĩ rằng những đoạn mã nhỏ này (chạy với mô-đun timeit của Python) giúp minh họa sự khác biệt giữa các biến lớp và biến thể hiện, vì vậy tôi vẫn đưa chúng vào
Ghi chú. Tôi đang dùng MacBook Pro với OS X 10. 8. 5 và Trăn 2. 7. 2
Khởi tạo
{:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 0Việc khởi tạo của {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 3 nhanh hơn hơn một giây, vì vậy sự khác biệt ở đây dường như có ý nghĩa thống kê
Vậy tại sao lại như vậy? . Chúng tôi làm hai bài tập trong {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 1, nhưng chỉ một bài trong {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 2
Phân công
{:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 1Ghi chú. Không có cách nào để chạy lại mã thiết lập của bạn trong mỗi lần dùng thử theo thời gian, vì vậy chúng tôi phải khởi tạo lại biến của mình trong lần dùng thử. Dòng thời gian thứ hai biểu thị thời gian trên với thời gian khởi tạo được tính toán trước đó đã trừ
Từ những điều trên, có vẻ như {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 6 chỉ mất khoảng 60% miễn là {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 3 để xử lý các bài tập
Tại sao điều này là trường hợp? . Khi chúng tôi gán cho {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 8, trước tiên chúng tôi tìm trong vùng tên cá thể ( {:lang='python'} foo = MyClass(2) foo.class_var ## 1 MyClass.class_var = 2 foo.class_var ## 2 9), không tìm thấy foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1 0, sau đó tìm trong vùng tên lớp (foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1 1), sau đó thực hiện phép gán thích hợp. Khi chúng tôi gán cho foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1 2, chúng tôi thực hiện số lần tra cứu bằng một nửa so với số lần chúng tôi gán ngay cho không gian tên đối tượng (_______25_______3)
Tóm lại, mặc dù những mức tăng hiệu suất này không quan trọng trong thực tế, nhưng những thử nghiệm này rất thú vị ở cấp độ khái niệm. Nếu có bất cứ điều gì, tôi hy vọng những khác biệt này sẽ giúp minh họa sự khác biệt cơ học giữa các biến lớp và biến thể hiện
Tóm lại là
Các thuộc tính lớp dường như không được sử dụng đúng mức trong Python;
của tôi. Các biến lớp Python có vị trí của chúng trong trường mã tốt. Khi được sử dụng cẩn thận, chúng có thể đơn giản hóa mọi thứ và cải thiện khả năng đọc. Nhưng khi bất cẩn ném vào một lớp nhất định, họ chắc chắn sẽ làm bạn vấp ngã
ruột thừa. Biến thể hiện cá nhân
Một biến bổ sung cần đề cập. biến thể hiện riêng tư
Có thể nói, Python không có các biến riêng tư, nhưng một mối quan hệ thú vị khác giữa cách đặt tên lớp và cá thể đi kèm với việc xáo trộn tên
Trong hướng dẫn về phong cách Python, người ta nói rằng các biến giả riêng phải được thêm tiền tố bằng dấu gạch dưới kép. '__'. Đây không chỉ là một dấu hiệu cho những người khác biết rằng biến của bạn được xử lý riêng tư mà còn là một cách để ngăn chặn quyền truy cập vào nó. Đây là những gì tôi muốn nói
{:lang='python'} class MyClass(object): class_var = 1 def __init__(self, i_var): self.i_var = i_var 2Nhìn kìa. Thuộc tính thể hiện foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1 4 được tự động thêm tiền tố vào tên lớp để tạo ra foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1 5
Mặc dù vẫn có thể giải quyết và có thể lấy được bằng cách sử dụng foo = MyClass(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 MyClass.class_var ## 1 6, việc xáo trộn tên này là một phương tiện để tạo một biến "riêng tư" vì nó ngăn cản bạn và những người khác truy cập nó một cách tình cờ hoặc do thiếu hiểu biết
Chỉnh sửa. Như Pedro Werneck đã chỉ ra, hành vi này chủ yếu nhằm giúp phân lớp. Trong , họ thấy nó phục vụ hai mục đích. (1) ngăn các lớp con truy cập các thuộc tính nhất định và (2) ngăn xung đột không gian tên trong các lớp con này. Mặc dù hữu ích, nhưng việc xáo trộn biến không nên được coi là lời mời viết mã với sự phân biệt công khai-riêng tư giả định, chẳng hạn như có trong Java
Đọc thêm trên Blog Kỹ thuật Toptal
- Đảm bảo mã sạch. Nhìn vào Python, được tham số hóa
- Mẫu thiết kế Python. Cho Kiểu Dáng Đẹp Và Thời Trang
- 10 sai lầm hàng đầu mà các nhà phát triển Django mắc phải
- Giới thiệu về Mocking trong Python
- Sáu điều răn của mã tốt. Viết mã vượt qua thử thách của thời gian
Hiểu những điều cơ bản
Không gian tên Python là gì?
Không gian tên Python là ánh xạ từ tên đến đối tượng, với thuộc tính không có mối quan hệ nào giữa các tên trong các không gian tên khác nhau. Không gian tên thường được triển khai dưới dạng từ điển Python, mặc dù điều này được trừu tượng hóa
Phương thức lớp Python so với phương thức thể hiện. Có gì khác biệt?
Trong Python, một phương thức lớp là một phương thức được gọi với lớp làm ngữ cảnh. Đây thường được gọi là phương thức tĩnh trong các ngôn ngữ lập trình khác. Mặt khác, một phương thức thể hiện được gọi với một thể hiện là bối cảnh
Điều gì xảy ra nếu cả thuộc tính thể hiện và thuộc tính lớp được xác định?
Trong trường hợp đó, không gian tên cá thể được ưu tiên hơn không gian tên lớp. Nếu có một thuộc tính có cùng tên trong cả hai, không gian tên đối tượng sẽ được kiểm tra trước và giá trị của nó được trả về