tự bằng Python, được phân cấp

Nếu bạn đã lập trình bằng Python (lập trình hướng đối tượng) một thời gian, thì bạn chắc chắn đã gặp các phương thức có selftham số đầu tiên của chúng.

Trước tiên, chúng ta hãy thử hiểu tham số tự lặp lại này là gì.

Tự trong Python là gì?

Trong lập trình hướng đối tượng, bất cứ khi nào chúng ta định nghĩa các phương thức cho một lớp, chúng ta sử dụng selflàm tham số đầu tiên trong mỗi trường hợp. Hãy xem định nghĩa của một lớp được gọi Cat.

 class Cat: def __init__(self, name, age): self.name = name self.age = age def info(self): print(f"I am a cat. My name is (self.name). I am (self.age) years old.") def make_sound(self): print("Meow")

Trong trường hợp này, tất cả các phương thức, bao gồm __init__, có tham số đầu tiên là self.

Chúng ta biết rằng lớp là một bản thiết kế cho các đối tượng. Bản thiết kế này có thể được sử dụng để tạo ra nhiều đối tượng. Hãy tạo hai đối tượng khác nhau từ lớp trên.

 cat1 = Cat('Andy', 2) cat2 = Cat('Phoebe', 3)

Các selftừ khóa được sử dụng để đại diện cho một thể hiện (đối tượng) của nhóm nhất định. Trong trường hợp này, hai Catđối tượng cat1cat2có các thuộc tính namevà riêng của chúng age. Nếu không có tự đối số, cùng một lớp không thể giữ thông tin cho cả hai đối tượng này.

Tuy nhiên, vì lớp chỉ là một bản thiết kế, selfcho phép truy cập vào các thuộc tính và phương thức của mỗi đối tượng trong python. Điều này cho phép mỗi đối tượng có các thuộc tính và phương thức riêng. Vì vậy, thậm chí rất lâu trước khi tạo các đối tượng này, chúng tôi tham chiếu các đối tượng như selftrong khi xác định lớp.

Tại sao bản thân được định nghĩa rõ ràng mọi lúc?

Ngay cả khi chúng ta hiểu việc sử dụng self, nó vẫn có vẻ kỳ lạ, đặc biệt là đối với các lập trình viên đến từ các ngôn ngữ khác, nó selfđược truyền dưới dạng tham số một cách rõ ràng mỗi khi chúng ta định nghĩa một phương thức. Như The Zen of Python đã nói, " Rõ ràng tốt hơn là ngầm hiểu ".

Vì vậy, tại sao chúng ta cần phải làm điều này? Hãy lấy một ví dụ đơn giản để bắt đầu. Chúng ta có một Pointlớp định nghĩa một phương thức distanceđể tính toán khoảng cách từ điểm gốc.

 class Point(object): def __init__(self,x = 0,y = 0): self.x = x self.y = y def distance(self): """Find distance from origin""" return (self.x**2 + self.y**2) ** 0.5

Bây giờ chúng ta hãy khởi tạo lớp này và tìm khoảng cách.

 >>> p1 = Point(6,8) >>> p1.distance() 10.0

Trong ví dụ trên, __init__()xác định ba tham số nhưng chúng tôi chỉ truyền hai (6 và 8). Tương tự, distance()yêu cầu một nhưng không có đối số nào được chuyển. Tại sao Python không phàn nàn về số đối số không khớp này?

Điều gì xảy ra bên trong?

Point.distancep1.distancetrong ví dụ trên là khác nhau và không hoàn toàn giống nhau.

 >>> type(Point.distance) >>> type(p1.distance) 

Chúng ta có thể thấy rằng cái đầu tiên là một hàm và cái thứ hai là một phương thức. Một điều đặc biệt về các phương thức (trong Python) là bản thân đối tượng được truyền làm đối số đầu tiên cho hàm tương ứng.

Trong trường hợp của ví dụ trên, cuộc gọi phương thức p1.distance()thực sự tương đương với Point.distance(p1).

Nói chung, khi chúng ta gọi một phương thức với một số đối số, thì hàm lớp tương ứng được gọi bằng cách đặt đối tượng của phương thức trước đối số đầu tiên. Vì vậy, bất cứ điều gì giống như obj.meth(args)trở thành Class.meth(obj, args). Quá trình gọi là tự động trong khi quá trình nhận thì không (rõ ràng).

Đây là lý do tại sao tham số đầu tiên của một hàm trong lớp phải là chính đối tượng. Viết tham số selfnày chỉ là một quy ước. Nó không phải là một từ khóa và không có ý nghĩa đặc biệt trong Python. Chúng tôi có thể sử dụng các tên khác (như this) nhưng nó rất không được khuyến khích. Việc sử dụng các tên khác không phải selflà điều bị hầu hết các nhà phát triển phản đối và làm giảm khả năng đọc của mã ( Số lượng khả năng đọc ).

Bản thân có thể được tránh

Bây giờ bạn đã rõ rằng bản thân đối tượng (thể hiện) được tự động chuyển cùng như đối số đầu tiên. Hành vi ngầm định này có thể tránh được khi tạo một phương thức tĩnh . Hãy xem xét ví dụ đơn giản sau:

 class A(object): @staticmethod def stat_meth(): print("Look no self was passed")

Đây, @staticmethodlà một trình trang trí hàm tạo stat_meth()tĩnh. Hãy để chúng tôi khởi tạo lớp này và gọi phương thức.

 >>> a = A() >>> a.stat_meth() Look no self was passed

Từ ví dụ trên, chúng ta có thể thấy rằng hành vi ngầm định của việc truyền đối tượng làm đối số đầu tiên đã được tránh trong khi sử dụng một phương thức tĩnh. Nói chung, các phương thức tĩnh hoạt động giống như các hàm cũ đơn giản (Vì tất cả các đối tượng của một lớp đều dùng chung các phương thức tĩnh).

 >>> type(A.stat_meth) >>> type(a.stat_meth) 

Bản thân ở đây để ở lại

Rõ ràng selfkhông phải là duy nhất đối với Python. Ý tưởng này được vay mượn từ Modula-3 . Sau đây là một trường hợp sử dụng mà nó trở nên hữu ích.

Không có khai báo biến rõ ràng trong Python. Họ bắt đầu hành động trong nhiệm vụ đầu tiên. Việc sử dụng selfgiúp dễ dàng phân biệt giữa các thuộc tính cá thể (và phương thức) với các biến cục bộ.

Trong ví dụ đầu tiên, self.x là một thuộc tính thể hiện trong khi x là một biến cục bộ. Chúng không giống nhau và chúng nằm trong các không gian tên khác nhau.

Nhiều người đã đề xuất tạo cho mình một từ khóa trong Python, như thistrong C ++ và Java. Điều này sẽ loại bỏ việc sử dụng thừa rõ ràng selfkhỏi danh sách tham số chính thức trong các phương thức.

Mặc dù ý tưởng này có vẻ đầy hứa hẹn, nhưng nó sẽ không xảy ra. Ít nhất là không phải trong tương lai gần. Nguyên nhân chính là khả năng tương thích ngược. Đây là một blog từ chính người sáng tạo Python giải thích lý do tại sao bản thân rõ ràng phải ở lại.

__init __ () không phải là một hàm tạo

Một kết luận quan trọng có thể được rút ra từ thông tin cho đến nay là __init__()phương thức không phải là một hàm tạo. Nhiều lập trình viên Python ngây thơ sẽ nhầm lẫn với nó vì nó __init__()được gọi khi chúng ta tạo một đối tượng.

A closer inspection will reveal that the first parameter in __init__() is the object itself (object already exists). The function __init__() is called immediately after the object is created and is used to initialize it.

Technically speaking, a constructor is a method which creates the object itself. In Python, this method is __new__(). A common signature of this method is:

 __new__(cls, *args, **kwargs)

When __new__() is called, the class itself is passed as the first argument automatically(cls).

Again, like self, cls is just a naming convention. Furthermore, *args and **kwargs are used to take an arbitrary number of arguments during method calls in Python.

Some important things to remember when implementing __new__() are:

  • __new__() is always called before __init__().
  • First argument is the class itself which is passed implicitly.
  • Always return a valid object from __new__(). Not mandatory, but its main use is to create and return an object.

Let's take a look at an example:

 class Point(object): def __new__(cls,*args,**kwargs): print("From new") print(cls) print(args) print(kwargs) # create our object and return it obj = super().__new__(cls) return obj def __init__(self,x = 0,y = 0): print("From init") self.x = x self.y = y

Now, let's now instantiate it.

 >>> p2 = Point(3,4) From new (3, 4) () From init

This example illustrates that __new__() is called before __init__(). We can also see that the parameter cls in __new__() is the class itself (Point). Finally, the object is created by calling the __new__() method on object base class.

In Python, object is the base class from which all other classes are derived. In the above example, we have done this using super().

Use __new__ or __init__?

You might have seen __init__() very often but the use of __new__() is rare. This is because most of the time you don't need to override it. Generally, __init__() is used to initialize a newly created object while __new__() is used to control the way an object is created.

We can also use __new__() to initialize attributes of an object, but logically it should be inside __init__().

One practical use of __new__(), however, could be to restrict the number of objects created from a class.

Suppose we wanted a class SqPoint for creating instances to represent the four vertices of a square. We can inherit from our previous class Point (the second example in this article) and use __new__() to implement this restriction. Here is an example to restrict a class to have only four instances.

 class SqPoint(Point): MAX_Inst = 4 Inst_created = 0 def __new__(cls,*args,**kwargs): if (cls.Inst_created>= cls.MAX_Inst): raise ValueError("Cannot create more objects") cls.Inst_created += 1 return super().__new__(cls)

Một cuộc chạy mẫu:

 >>> p1 = SqPoint(0,0) >>> p2 = SqPoint(1,0) >>> p3 = SqPoint(1,1) >>> p4 = SqPoint(0,1) >>> >>> p5 = SqPoint(2,2) Traceback (most recent call last):… ValueError: Cannot create more objects

thú vị bài viết...