Ảnh của Christopher Gower trên unplash Các biến về cơ bản là các ký hiệu đứng trong một giá trị mà chúng tôi 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à có sự khác 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 Python, và tiếp tục đến vớiSự khác biệt giữa các biến lớp và trường hợp được giải thích.
Khi bạn viết một khối lớp, bạn tạo các thuộc tính lớp [hoặc biến lớp]. Tất cả các tên bạn gán trong khối lớp, bao gồm các phương thức bạn xác định với def
trở thành thuộc tính lớp.
Sau khi một phiên bản lớp được tạo, bất cứ điều gì có tham chiếu đến phiên bản đều có thể tạo các thuộc tính thể hiện trên đó. Các phương thức bên trong, ví dụ "hiện tại" hầu như luôn luôn bị ràng buộc với tên self
, đó là lý do tại sao bạn nghĩ về những thứ này là "biến tự". Thông thường trong thiết kế hướng đối tượng, mã được gắn vào một lớp được cho là có quyền kiểm soát các thuộc tính của các phiên bản của lớp đó, do đó, hầu như tất cả các gán thuộc tính thể hiện được thực hiện bên trong các phương thức, sử dụng tham chiếu đến thể hiện trong tham số self
của Phương pháp.
Các thuộc tính lớp thường được so sánh với các biến tĩnh [hoặc phương thức] như được tìm thấy trong các ngôn ngữ như Java, C#hoặc C ++. Tuy nhiên, nếu bạn muốn nhắm đến sự hiểu biết sâu sắc hơn, tôi sẽ tránh nghĩ về các thuộc tính của lớp là "giống nhau" với các biến tĩnh. Mặc dù chúng thường được sử dụng cho cùng một mục đích, nhưng khái niệm cơ bản khá khác nhau. Thông tin thêm về điều này trong phần "Nâng cao" bên dưới dòng.
Một ví dụ!
class SomeClass:
def __init__[self]:
self.foo = 'I am an instance attribute called foo'
self.foo_list = []
bar = 'I am a class attribute called bar'
bar_list = []
Sau khi thực hiện khối này, có một lớp
instance = SomeClass[]
1, với 3 thuộc tính lớp: instance = SomeClass[]
2, instance = SomeClass[]
3 và instance = SomeClass[]
4.Sau đó, chúng tôi sẽ tạo một thể hiện:
instance = SomeClass[]
Khi điều này xảy ra, phương thức
instance = SomeClass[]
2 của ____ 11 được thực thi, nhận phiên bản mới trong tham số self
của nó. Phương thức này tạo ra hai thuộc tính thể hiện: instance = SomeClass[]
8 và instance = SomeClass[]
9. Sau đó, trường hợp này được gán vào biến print instance.bar
0, do đó, nó bị ràng buộc với một điều với hai thuộc tính thể hiện đó: instance = SomeClass[]
8 và instance = SomeClass[]
9.But:
print instance.bar
gives:
I am a class attribute called bar
Làm sao chuyện này lại xảy ra? Khi chúng tôi cố gắng truy xuất một thuộc tính thông qua cú pháp DOT và thuộc tính không tồn tại, Python đã trải qua một loạt các bước để thử và đáp ứng yêu cầu của bạn. Điều tiếp theo nó sẽ cố gắng là xem xét các thuộc tính lớp của lớp thể hiện của bạn. Trong trường hợp này, nó đã tìm thấy một thuộc tính
instance = SomeClass[]
3 trong instance = SomeClass[]
1, vì vậy nó đã trả lại điều đó.Đó cũng là cách mà phương thức gọi hoạt động bằng cách này. Ví dụ, khi bạn gọi
print instance.bar
5, print instance.bar
6 không có thuộc tính có tên print instance.bar
7. Nhưng lớp của print instance.bar
6 cũng vậy và nó bị ràng buộc với một đối tượng phương thức. Đối tượng phương thức đó được trả về bởi bit print instance.bar
9 và sau đó bit I am a class attribute called bar
0 gọi phương thức với đối số I am a class attribute called bar
1.Cách thức này hữu ích là tất cả các trường hợp của
instance = SomeClass[]
1 sẽ có quyền truy cập vào cùng một thuộc tính instance = SomeClass[]
3. Chúng tôi có thể tạo một triệu trường hợp, nhưng chúng tôi chỉ cần lưu trữ một chuỗi đó trong bộ nhớ, bởi vì tất cả chúng đều có thể tìm thấy nó.all instances of instance = SomeClass[]
1 will have access to the same instance = SomeClass[]
3 attribute. We could create a million instances, but we only need to store that one string in memory, because they can all find it.Nhưng bạn phải cẩn thận một chút. Hãy xem các hoạt động sau:
sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
Bạn nghĩ những gì bản in này?
[1]
[2, 20]
[10]
[2, 20]
Điều này là do mỗi trường hợp có bản sao riêng của nó là
instance = SomeClass[]
9, vì vậy chúng được thêm vào một cách riêng biệt. Nhưng tất cả các trường hợp chia sẻ quyền truy cập vào cùng một instance = SomeClass[]
4. Vì vậy, khi chúng tôi đã làm I am a class attribute called bar
6, nó đã ảnh hưởng đến I am a class attribute called bar
7, mặc dù I am a class attribute called bar
7 vẫn chưa tồn tại! Và tương tự I am a class attribute called bar
9 đã ảnh hưởng đến instance = SomeClass[]
4 được lấy qua sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
1. Đây thường không phải là những gì bạn muốn.Nghiên cứu nâng cao theo sau. :]
Để thực sự Grok Python, đến từ các ngôn ngữ oo được đánh máy truyền thống như Java và C#, bạn phải học cách suy nghĩ lại các lớp một chút.
Trong Java, một lớp học không thực sự là một thứ theo đúng nghĩa của nó. Khi bạn viết một lớp, bạn sẽ tuyên bố nhiều hơn một loạt những điều mà tất cả các trường hợp của lớp đó đều có điểm chung. Trong thời gian chạy, chỉ có các trường hợp [và các phương thức/biến tĩnh, nhưng đó thực sự là các biến và chức năng toàn cầu trong không gian tên liên quan đến một lớp, không liên quan gì đến OO thực sự]. Các lớp là cách bạn viết ra trong mã nguồn của mình, các phiên bản sẽ như thế nào trong thời gian chạy; Chúng chỉ "tồn tại" trong mã nguồn của bạn, không phải trong chương trình đang chạy.
Trong Python, một lớp học không có gì đặc biệt. Đó là một đối tượng giống như bất cứ thứ gì khác. Vì vậy, "thuộc tính lớp" trên thực tế là chính xác giống như "thuộc tính thể hiện"; Trong thực tế, chỉ có "thuộc tính". Lý do duy nhất để vẽ một sự khác biệt là chúng ta có xu hướng sử dụng các đối tượng là các lớp khác với các đối tượng không phải là lớp. Các máy móc cơ bản là tất cả giống nhau. Đây là lý do tại sao tôi nói sẽ là một sai lầm khi nghĩ về các thuộc tính lớp là các biến tĩnh từ các ngôn ngữ khác.
Nhưng điều thực sự làm cho các lớp Python khác biệt với các lớp theo phong cách Java là giống như bất kỳ đối tượng nào khác mỗi lớp là một ví dụ của một số lớp!each class is an instance of some class!
Trong Python, hầu hết các lớp là các trường hợp của một lớp được xây dựng có tên là
sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
2. Chính lớp này kiểm soát hành vi chung của các lớp và làm cho tất cả các công cụ OO theo cách của nó. Cách OO mặc định có các phiên bản của các lớp có thuộc tính riêng của chúng và có các phương thức/thuộc tính chung được xác định bởi lớp của chúng, chỉ là một giao thức trong Python. Bạn có thể thay đổi hầu hết các khía cạnh của nó nếu bạn muốn. Nếu bạn đã từng nghe nói về việc sử dụng Metaclass, tất cả những gì đang xác định một lớp là một thể hiện của một lớp khác với sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
2.Điều thực sự "đặc biệt" duy nhất về các lớp [ngoài tất cả các máy móc xây dựng để làm cho chúng hoạt động theo cách mà họ làm theo mặc định], là cú pháp khối lớp, để bạn dễ dàng tạo các phiên bản là
sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
2. Đây:class Foo[BaseFoo]:
def __init__[self, foo]:
self.foo = foo
z = 28
gần như tương đương với những điều sau:
def __init__[self, foo]:
self.foo = foo
classdict = {'__init__': __init__, 'z': 28 }
Foo = type['Foo', [BaseFoo,] classdict]
Và nó sẽ sắp xếp cho tất cả các nội dung của
sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
5 để trở thành thuộc tính của đối tượng được tạo.Vì vậy, sau đó, nó trở nên gần như tầm thường khi thấy rằng bạn có thể truy cập một thuộc tính lớp bằng
sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
6 dễ dàng như sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
7. Cả sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
8 và sc1 = SomeClass[]
sc1.foo_list.append[1]
sc1.bar_list.append[2]
sc2 = SomeClass[]
sc2.foo_list.append[10]
sc2.bar_list.append[20]
print sc1.foo_list
print sc1.bar_list
print sc2.foo_list
print sc2.bar_list
9 đều là đối tượng và các đối tượng có thuộc tính. Điều này cũng giúp bạn dễ hiểu làm thế nào bạn có thể sửa đổi một lớp sau khi nó được tạo ra; Chỉ cần gán các thuộc tính của nó giống như cách bạn làm với bất kỳ đối tượng nào khác!Trong thực tế, các trường hợp không có mối quan hệ đặc biệt đặc biệt với lớp được sử dụng để tạo ra chúng. Cách Python biết lớp nào sẽ tìm kiếm các thuộc tính không được tìm thấy trong trường hợp là thuộc tính
[1]
[2, 20]
[10]
[2, 20]
0 ẩn. Mà bạn có thể đọc để tìm hiểu lớp nào đây là một ví dụ, giống như với bất kỳ thuộc tính nào khác: [1]
[2, 20]
[10]
[2, 20]
1. Bây giờ bạn có một biến [1]
[2, 20]
[10]
[2, 20]
2 bị ràng buộc với một lớp, mặc dù nó có thể không có cùng tên với lớp. Bạn có thể sử dụng điều này để truy cập các thuộc tính lớp hoặc thậm chí gọi nó là để tạo thêm các trường hợp của nó [mặc dù bạn không biết đó là lớp nào!].Và bạn thậm chí có thể gán cho
[1]
[2, 20]
[10]
[2, 20]
3 để thay đổi lớp nào đó là một ví dụ của! Nếu bạn làm điều này, không có gì đặc biệt xảy ra ngay lập tức. Nó không tan vỡ trái đất. Tất cả những gì nó có nghĩa là khi bạn tìm kiếm các thuộc tính không tồn tại trong trường hợp, Python sẽ xem xét các nội dung mới của [1]
[2, 20]
[10]
[2, 20]
0. Vì điều đó bao gồm hầu hết các phương pháp và các phương thức thường mong đợi trường hợp họ hoạt động ở một số trạng thái nhất định, điều này thường dẫn đến lỗi nếu bạn làm điều đó một cách ngẫu nhiên và nó rất khó hiểu, nhưng nó có thể được thực hiện. Nếu bạn rất cẩn thận, điều bạn lưu trữ trong [1]
[2, 20]
[10]
[2, 20]
0 thậm chí không phải là một đối tượng lớp; Tất cả Python sẽ làm với nó là tìm kiếm các thuộc tính trong một số trường hợp nhất định, vì vậy tất cả những gì bạn cần là một đối tượng có loại thuộc tính phù hợp [một số cảnh báo sang một bên nơi Python sẽ kén chọn những thứ là lớp hoặc trường hợp của một lớp cụ thể].Điều đó có lẽ là đủ cho bây giờ. Hy vọng [nếu bạn thậm chí đã đọc đến nay] tôi đã không làm bạn bối rối quá nhiều. Python gọn gàng khi bạn học cách nó hoạt động. :]