2.21. Phương thức và thuộc tính

Một phương thức là một hàm được định nghĩa bên trong một lớp. Tham số đầu tiên là thể hiện mà phương thức được gọi trên đó; quy ước là đặt tên nó là self.

2.21.1. Phương thức thể hiện

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def magnitude(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5

p = Point(3, 4)
print(p.magnitude())

Đầu ra:

5.0

Lời gọi p.magnitude() truyền p làm self tự động. Thân phương thức đọc và cập nhật các thuộc tính qua self theo cùng cách các hàm đọc và cập nhật tên qua tham số.

2.21.2. Thuộc tính thể hiện vs thuộc tính lớp

Các thuộc tính được gán cho self -- thường bên trong __init__ -- thuộc về thể hiện đó:

a = Point(1, 2)
b = Point(10, 20)
a.x = 99
print(a.x, b.x)           # 99 10

a.xb.x là bộ lưu trữ khác nhau; thay đổi cái này không ảnh hưởng đến cái kia.

Các tên được gán trong thân lớp, bên ngoài bất kỳ phương thức nào, được chia sẻ bởi mọi thể hiện của lớp:

class Counter:
    kind = "tally"        # class attribute -- shared

    def __init__(self):
        self.value = 0    # instance attribute -- per instance

a = Counter()
b = Counter()
print(a.kind, b.kind)     # tally tally
A class block at the top holding shared methods and the class attribute "kind"; below it, two instance boxes each holding their own "value" attribute, with arrows from each instance up to the class.

Thuộc tính lớp nằm trên lớp và được chia sẻ. Thuộc tính thể hiện nằm trên từng thể hiện.

Một thuộc tính lớp được truy cập theo cùng cách như thuộc tính thể hiện (a.kind); Python tìm trước trên thể hiện, sau đó trên lớp. Gán cho a.kind sẽ tạo ra một thuộc tính thể hiện mới che khuất thuộc tính lớp, để b.kind không bị ảnh hưởng.

Hãy dùng thuộc tính lớp cho hằng số và giá trị mặc định mà mọi thể hiện nên thấy theo cùng cách. Hãy dùng thuộc tính thể hiện cho trạng thái mà mỗi đối tượng nên có bản sao riêng.

2.21.3. __str__ và __repr__

Hai phương thức đặc biệt kiểm soát cách một thể hiện được in ra. __str__ trả về chuỗi "thân thiện" được dùng bởi print(); __repr__ trả về chuỗi "nhà phát triển" được dùng tại REPL và bên trong hiển thị container:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return "(" + str(self.x) + ", " + str(self.y) + ")"

    def __repr__(self):
        return "Point(" + str(self.x) + ", " + str(self.y) + ")"

p = Point(3, 4)
print(p)            # uses __str__
print([p, p])       # uses __repr__ inside the list

Đầu ra:

(3, 4)
[Point(3, 4), Point(3, 4)]

Nếu chỉ định nghĩa __repr__, print() sẽ dùng nó -- vì vậy một __repr__ được viết tốt thường đủ rồi. Hãy định nghĩa __str__ chỉ khi dạng thân thiện nên trông khác dạng nhà phát triển. Hướng đến một __repr__ trông giống như lời gọi sẽ tái tạo đối tượng; việc gỡ lỗi trở nên dễ dàng hơn đáng kể khi các giá trị được in ra rõ ràng không mơ hồ.