Tại sao chúng ta sử dụng __init__ trong các lớp Python?


124

Tôi đang gặp sự cố khi hiểu về Khởi tạo lớp.

Điểm của chúng là gì và làm thế nào để chúng ta biết được những gì cần đưa vào chúng? Việc viết trong các lớp có yêu cầu một kiểu tư duy khác so với việc tạo ra các hàm không (Tôi đã nghĩ rằng tôi có thể tạo các hàm và sau đó chỉ cần bọc chúng trong một lớp để tôi có thể sử dụng lại chúng. Điều đó có hiệu quả không?)

Đây là một ví dụ:

class crawler:
  # Initialize the crawler with the name of database
  def __init__(self,dbname):
    self.con=sqlite.connect(dbname)

  def __del__(self):
    self.con.close()

  def dbcommit(self):
    self.con.commit()

Hoặc mẫu mã khác:

class bicluster:
  def __init__(self,vec,left=None,right=None,distance=0.0,id=None):
    self.left=left
    self.right=right
    self.vec=vec
    self.id=id
    self.distance=distance

Có rất nhiều lớp mà __init__tôi bắt gặp khi cố gắng đọc mã của người khác, nhưng tôi không hiểu logic trong việc tạo ra chúng.


1
câu chuyện của init là ... blah, blah, blah .... hàm tạo-hủy nhưng không có trình hủy vì có bộ thu gom rác.
MisterGeeky

Câu trả lời:


289

Theo những gì bạn đã viết, bạn đang thiếu một phần hiểu biết quan trọng: sự khác biệt giữa một lớp và một đối tượng. __init__không khởi tạo một lớp, nó khởi tạo một thể hiện của một lớp hoặc một đối tượng. Mỗi con chó có màu sắc, nhưng những con chó như một lớp học thì không. Mỗi con chó có bốn chân trở xuống, nhưng loại chó thì không. Lớp là một khái niệm của một đối tượng. Khi bạn nhìn thấy Fido và Spot, bạn nhận ra sự giống nhau của họ, tính giáo điều của họ. Đó là lớp học.

Khi bạn nói

class Dog:
    def __init__(self, legs, colour):
        self.legs = legs
        self.colour = colour

fido = Dog(4, "brown")
spot = Dog(3, "mostly yellow")

Bạn đang nói, Fido là một con chó màu nâu với 4 chân trong khi Spot hơi què quặt và chủ yếu là màu vàng. Các __init__hàm được gọi một constructor, hoặc khởi tạo, và được tự động gọi khi bạn tạo một đối tượng mới của một lớp. Trong hàm đó, đối tượng mới tạo được gán cho tham số self. Ký hiệu self.legslà một thuộc tính được gọi legscủa đối tượng trong biến self. Các thuộc tính giống như các biến, nhưng chúng mô tả trạng thái của một đối tượng hoặc các hành động cụ thể (chức năng) có sẵn cho đối tượng.

Tuy nhiên, hãy lưu ý rằng bạn không đặt colourcho bản thân giáo điều - đó là một khái niệm trừu tượng. Có các thuộc tính có ý nghĩa trên các lớp. Ví dụ, population_sizecó phải là một như vậy - không hợp lý khi đếm Fido vì Fido luôn là một. Thật có ý nghĩa khi đếm chó. Giả sử có 200 triệu con chó trên thế giới. Đó là tài sản của lớp Dog. Fido không liên quan gì đến con số 200 triệu, Spot cũng vậy. Nó được gọi là "thuộc tính lớp", trái ngược với "thuộc tính cá thể" bằng colourhoặc legscao hơn.

Bây giờ, đến một thứ gì đó ít răng nanh hơn và liên quan đến lập trình nhiều hơn. Như tôi viết dưới đây, lớp để thêm những thứ là không hợp lý - nó là một lớp của cái gì? Các lớp trong Python tạo thành các tập hợp dữ liệu khác nhau, hoạt động tương tự. Lớp chó bao gồm Fido và Spot và 199999999998 động vật khác tương tự như chúng, tất cả chúng đều đi tiểu trên cột đèn. Lớp để thêm những thứ bao gồm những gì? Chúng khác nhau ở những dữ liệu nào vốn có? Và họ chia sẻ những hành động nào?

Tuy nhiên, những con số ... đó là những môn học thú vị hơn. Nói, Số nguyên. Có rất nhiều trong số đó, nhiều hơn cả chó. Tôi biết rằng Python đã có các số nguyên, nhưng chúng ta hãy chơi ngu ngốc và "triển khai" chúng một lần nữa (bằng cách gian lận và sử dụng các số nguyên của Python).

Vì vậy, Số nguyên là một lớp. Chúng có một số dữ liệu (giá trị) và một số hành vi ("thêm tôi vào số khác này"). Hãy thể hiện điều này:

class MyInteger:
    def __init__(self, newvalue)
        # imagine self as an index card.
        # under the heading of "value", we will write
        # the contents of the variable newvalue.
        self.value = newvalue
    def add(self, other):
        # when an integer wants to add itself to another integer,
        # we'll take their values and add them together,
        # then make a new integer with the result value.
        return MyInteger(self.value + other.value)

three = MyInteger(3)
# three now contains an object of class MyInteger
# three.value is now 3
five = MyInteger(5)
# five now contains an object of class MyInteger
# five.value is now 5
eight = three.add(five)
# here, we invoked the three's behaviour of adding another integer
# now, eight.value is three.value + five.value = 3 + 5 = 8
print eight.value
# ==> 8

Điều này hơi mong manh (chúng tôi giả định othersẽ là một MyInteger), nhưng chúng tôi sẽ bỏ qua ngay bây giờ. Trong mã thực, chúng tôi sẽ không; chúng tôi sẽ kiểm tra nó để đảm bảo, và thậm chí có thể ép buộc nó ("bạn không phải là một số nguyên? Thật tuyệt vời, bạn có 10 nano giây để trở thành một! 9 ... 8 ....")

Chúng tôi thậm chí có thể xác định phân số. Phân số cũng biết tự cộng.

class MyFraction:
    def __init__(self, newnumerator, newdenominator)
        self.numerator = newnumerator
        self.denominator = newdenominator
        # because every fraction is described by these two things
    def add(self, other):
        newdenominator = self.denominator * other.denominator
        newnumerator = self.numerator * other.denominator + self.denominator * other.numerator
        return MyFraction(newnumerator, newdenominator)

Thậm chí có nhiều phân số hơn số nguyên (không hẳn, nhưng máy tính không biết điều đó). Hãy làm hai:

half = MyFraction(1, 2)
third = MyFraction(1, 3)
five_sixths = half.add(third)
print five_sixths.numerator
# ==> 5
print five_sixths.denominator
# ==> 6

Bạn không thực sự khai báo bất cứ điều gì ở đây. Các thuộc tính giống như một loại biến mới. Các biến bình thường chỉ có một giá trị. Hãy để chúng tôi nói rằng bạn viết colour = "grey". Bạn không thể có một biến có tên colourđó là "fuchsia"- không phải trong cùng một vị trí trong các mã.

Mảng giải quyết điều đó ở một mức độ. Nếu bạn nói colour = ["grey", "fuchsia"], bạn đã xếp chồng hai màu vào biến, nhưng bạn phân biệt chúng theo vị trí của chúng (0 hoặc 1, trong trường hợp này).

Thuộc tính là các biến được liên kết với một đối tượng. Giống như với mảng, chúng ta có thể có nhiều colourbiến, trên các con chó khác nhau . Vì vậy, fido.colourlà một biến, nhưng spot.colourlà một biến khác. Cái đầu tiên được liên kết với đối tượng trong biến fido; thứ hai spot,. Bây giờ, khi bạn gọi Dog(4, "brown"), hoặc three.add(five), sẽ luôn có một tham số ẩn, sẽ được gán cho tham số phụ nằm ở phía trước danh sách tham số. Nó được gọi theo quy ước self, và sẽ nhận giá trị của đối tượng đứng trước dấu chấm. Do đó, bên trong Dog's __init__(constructor), selfsẽ là bất cứ thứ gì mà Dog mới sẽ trở thành; trong MyInteger's add, selfsẽ được liên kết với đối tượng trong biến three. Vì vậy,three.valuesẽ là cùng một biến bên ngoài add, như self.valuetrong add.

Nếu tôi nói the_mangy_one = fido, tôi sẽ bắt đầu đề cập đến đối tượng được biết đến fidovới cái tên khác. Từ bây giờ, fido.colourchính xác là biến giống như the_mangy_one.colour.

Vì vậy, những thứ bên trong __init__. Bạn có thể coi chúng như những điều ghi chú vào giấy khai sinh của Chó. colourtự nó là một biến ngẫu nhiên, có thể chứa bất kỳ thứ gì. fido.colourhoặc self.colourgiống như một trường biểu mẫu trên trang nhận dạng của Chó; và __init__là nhân viên điền lần đầu tiên.

Bất kỳ rõ ràng hơn?

CHỈNH SỬA : Mở rộng bình luận bên dưới:

Ý bạn là một danh sách các đối tượng , phải không?

Trước hết, fidothực sự không phải là một đối tượng. Nó là một biến, hiện đang chứa một đối tượng, giống như khi bạn nói x = 5, xlà một biến hiện đang chứa số năm. Nếu sau đó bạn thay đổi ý định, bạn có thể làm fido = Cat(4, "pleasing")(miễn là bạn đã tạo một lớp Cat) và fidotừ đó trở đi sẽ "chứa" một đối tượng mèo. Nếu bạn làm vậy fido = x, nó sẽ chứa số năm, và hoàn toàn không phải là một đối tượng động vật.

Bản thân một lớp không biết các cá thể của nó trừ khi bạn viết mã cụ thể để theo dõi chúng. Ví dụ:

class Cat:
    census = [] #define census array

    def __init__(self, legs, colour):
        self.colour = colour
        self.legs = legs
        Cat.census.append(self)

Đây, censuslà một thuộc tính cấp độ Catlớp của lớp.

fluffy = Cat(4, "white")
spark = Cat(4, "fiery")
Cat.census
# ==> [<__main__.Cat instance at 0x108982cb0>, <__main__.Cat instance at 0x108982e18>]
# or something like that

Lưu ý rằng bạn sẽ không nhận được [fluffy, sparky]. Đó chỉ là những tên biến. Nếu bạn muốn mèo có tên, bạn phải tạo một thuộc tính riêng cho tên, sau đó ghi đè __str__phương thức để trả về tên này. Mục đích của phương thức này (tức là hàm ràng buộc lớp, giống như addhoặc __init__) là mô tả cách chuyển đổi đối tượng thành một chuỗi, giống như khi bạn in nó ra.


7
wow cảm ơn..điều này thực sự có ý nghĩa đối với tôi vì vậy bất cứ điều gì tạo nên điều gì đó giống như nó, tôi cần phải khai báo trước trong hàm init. Trong trường hợp này, Con chó, có chân và màu sắc. Ví dụ: nếu tôi tạo một lớp có thêm hai số, tôi sẽ khai báo self.firstnumber và self.secondnumber, sau đó chỉ cần thực hiện firstnumber + secondnumber sau đó trong lớp để nhận được câu trả lời?
Lostsoul

1
Đại loại. Bạn có thể làm điều đó. Nhưng hầu như không có ý nghĩa khi tạo một lớp chỉ để thêm nhiều thứ. Các lớp thường triển khai dữ liệu với các hành vi - các hành vi thuần túy chỉ là các chức năng. Tôi sẽ mở rộng câu trả lời với một cái gì đó có liên quan; chờ một chút.
Amadan

3
Cảm ơn bạn vì câu trả lời tuyệt vời. Tôi thấy và hiểu sức mạnh của các lớp học bây giờ. Xin lỗi, nếu nó nghe có vẻ ngu ngốc. Bạn chỉ cần tôi nhận ra rằng tôi có thể sắp xếp dữ liệu và duy trì trạng thái của nhiều thứ khác nhau cùng một lúc (trong khi tôi chỉ theo dõi càng nhiều biến số mà tôi có thể tạo hoặc nhiều hơn qua các vòng lặp). Vì vậy, giả sử, tôi cần phải tìm ra số lượng chân trung bình của mỗi con chó? Có cách nào để truy xuất danh sách tất cả các đối tượng mà tôi đã tạo với một lớp để tôi có thể bắt đầu một phép tính như thế này không? hoặc tôi cũng nên duy trì danh sách các lớp tôi tạo (tức là [fido, spot])
Lostsoul

23

Để đóng góp 5 xu của tôi cho lời giải thích cặn kẽ từ Amadan .

Trong đó các lớp là một mô tả "của một kiểu" theo cách trừu tượng. Đối tượng là nhận thức của chúng: vật thở sống. Trong thế giới hướng đối tượng, có những ý tưởng chính mà bạn gần như có thể gọi là bản chất của mọi thứ. Họ đang:

  1. sự đóng gói (sẽ không nói rõ về điều này)
  2. di sản
  3. đa hình

Các đối tượng có một hoặc nhiều đặc điểm (= Thuộc tính) và hành vi (= Phương thức). Hành vi chủ yếu phụ thuộc vào các đặc điểm. Các lớp định nghĩa những gì mà hành vi sẽ thực hiện một cách tổng quát, nhưng chừng nào lớp đó không được hiện thực hóa (khởi tạo) như một đối tượng thì nó vẫn là một khái niệm trừu tượng về một khả năng. Hãy để tôi minh họa với sự trợ giúp của "kế thừa" và "đa hình".

    class Human:
        gender
        nationality
        favorite_drink
        core_characteristic
        favorite_beverage
        name
        age

        def love    
        def drink
        def laugh
        def do_your_special_thing                

    class Americans(Humans)
        def drink(beverage):
            if beverage != favorite_drink: print "You call that a drink?"
            else: print "Great!" 

    class French(Humans)
        def drink(beverage, cheese):
            if beverage == favourite_drink and cheese == None: print "No cheese?" 
            elif beverage != favourite_drink and cheese == None: print "Révolution!"

    class Brazilian(Humans)
        def do_your_special_thing
            win_every_football_world_cup()

    class Germans(Humans)
        def drink(beverage):
            if favorite_drink != beverage: print "I need more beer"
            else: print "Lecker!" 

    class HighSchoolStudent(Americans):
        def __init__(self, name, age):
             self.name = name
             self.age = age

jeff = HighSchoolStudent(name, age):
hans = Germans()
ronaldo = Brazilian()
amelie = French()

for friends in [jeff, hans, ronaldo]:
    friends.laugh()
    friends.drink("cola")
    friends.do_your_special_thing()

print amelie.love(jeff)
>>> True
print ronaldo.love(hans)
>>> False

Một số đặc điểm xác định con người. Nhưng mỗi quốc tịch có phần khác nhau. Vì vậy, "loại quốc gia" là loại Con người có tính năng bổ sung. "Người Mỹ" là một loại "Con người" và thừa hưởng một số đặc điểm và hành vi trừu tượng từ loại người (base-class): đó là sự kế thừa. Vì vậy, tất cả Nhân loại có thể cười và uống, do đó tất cả các lớp trẻ em cũng có thể! Kế thừa (2).

Nhưng vì chúng đều thuộc cùng một loại (Loại / lớp cơ sở: Con người) nên đôi khi bạn có thể trao đổi chúng: xem vòng lặp for ở cuối. Nhưng chúng sẽ bộc lộ một đặc điểm cá nhân, và đó là Tính đa hình (3).

Vì vậy, mỗi con người có một loại thức uống yêu thích, nhưng mỗi quốc gia đều có xu hướng hướng tới một loại thức uống đặc biệt. Nếu bạn phân loại một quốc tịch từ loại Người, bạn có thể ghi đè hành vi kế thừa như tôi đã trình bày ở trên với drink()Phương pháp. Nhưng đó vẫn là ở cấp độ lớp và vì điều này, nó vẫn là một sự tổng quát hóa.

hans = German(favorite_drink = "Cola")

khởi tạo lớp tiếng Đức và tôi đã "thay đổi" một đặc tính mặc định lúc đầu. (Nhưng nếu bạn gọi hans.drink ('Sữa'), anh ấy vẫn sẽ in "Tôi cần thêm bia" - một lỗi rõ ràng ... hoặc có thể đó là những gì tôi sẽ gọi là một tính năng nếu tôi là Nhân viên của một Công ty lớn hơn. ;-)! )

Đặc tính của một kiểu ví dụ như tiếng Đức (hans) thường được xác định thông qua hàm tạo (trong python __init__:) tại thời điểm khởi tạo. Đây là điểm mà bạn xác định một lớp để trở thành một đối tượng. Bạn có thể nói hơi thở cuộc sống thành một khái niệm trừu tượng (lớp) bằng cách lấp đầy nó với các đặc điểm riêng lẻ và trở thành một đối tượng.

Nhưng vì mọi đối tượng là một thể hiện của một lớp nên chúng chia sẻ tất cả một số kiểu đặc trưng cơ bản và một số hành vi. Đây là một ưu điểm chính của khái niệm hướng đối tượng.

Để bảo vệ các đặc tính của từng đối tượng, bạn đóng gói chúng - có nghĩa là bạn cố gắng kết hợp hành vi và đặc tính và làm cho khó thao tác nó từ bên ngoài đối tượng. Đó là sự đóng gói (1)


5

Nó chỉ để khởi tạo các biến của cá thể.

Ví dụ: tạo một crawlercá thể với tên cơ sở dữ liệu cụ thể (từ ví dụ của bạn ở trên).


Tôi xin lỗi, tôi không thực sự hiểu điều đó có nghĩa là gì..trong ví dụ trên..có thể nhà phát triển đã thêm vào mã chính của mình 'left = foo', v.v.
Lostsoul 22/12/11

Ý bạn là các giá trị mặc định của hàm? left=Noneleft sẽ được khởi tạo Nonenếu khi tạo lefttham số không được chỉ định.
jldupont

Tôi nghĩ nó bắt đầu có ý nghĩa..có giống như cách bạn phải khai báo trước các biến của mình trong java "String left" hay gì đó không? sau đó khi nó được khởi tạo vào lớp, bạn có thể thao tác các giá trị? Nó chỉ hơi khó hiểu khi so sánh với các hàm vì tôi chỉ có thể gửi các giá trị đến các hàm và không cần khởi tạo trước bất cứ thứ gì.
Lostsoul

1
@Lostsoul: left = foosẽ hoạt động - một lần. Mục đích của các lớp học là làm điều gì đó hợp lý cho mọi người khác nhau crawler. Các lớp không phải là hàm, cũng không phải là thứ có thể so sánh với các hàm (tốt, không phải cho đến khi bạn nâng cao hơn rất nhiều và tham gia vào lập trình hàm, nhưng điều đó sẽ khiến bạn bối rối ngay bây giờ). Đọc câu trả lời của tôi để biết các lớp học thực sự là gì - bạn vẫn chưa hiểu.
Amadan

4

Có vẻ như bạn cần sử dụng __init__bằng Python nếu bạn muốn khởi tạo chính xác các thuộc tính có thể thay đổi của các phiên bản của mình.

Xem ví dụ sau:

>>> class EvilTest(object):
...     attr = []
... 
>>> evil_test1 = EvilTest()
>>> evil_test2 = EvilTest()
>>> evil_test1.attr.append('strange')
>>> 
>>> print "This is evil:", evil_test1.attr, evil_test2.attr
This is evil: ['strange'] ['strange']
>>> 
>>> 
>>> class GoodTest(object):
...     def __init__(self):
...         self.attr = []
... 
>>> good_test1 = GoodTest()
>>> good_test2 = GoodTest()
>>> good_test1.attr.append('strange')
>>> 
>>> print "This is good:", good_test1.attr, good_test2.attr
This is good: ['strange'] []

Điều này hoàn toàn khác trong Java khi mỗi thuộc tính được khởi tạo tự động với một giá trị mới:

import java.util.ArrayList;
import java.lang.String;

class SimpleTest
{
    public ArrayList<String> attr = new ArrayList<String>();
}

class Main
{
    public static void main(String [] args)
    {
        SimpleTest t1 = new SimpleTest();
        SimpleTest t2 = new SimpleTest();

        t1.attr.add("strange");

        System.out.println(t1.attr + " " + t2.attr);
    }
}

tạo ra đầu ra mà chúng ta mong đợi bằng trực giác:

[strange] []

Nhưng nếu bạn khai báo attrstatic, nó sẽ hoạt động giống như Python:

[strange] [strange]

3

Sau đây với ví dụ về ô tô của bạn : khi bạn nhận một chiếc ô tô, bạn chỉ không nhận được một chiếc xe ngẫu nhiên, ý tôi là, bạn chọn màu sắc, nhãn hiệu, số chỗ ngồi, v.v. Và một số thứ cũng được "khởi tạo" mà bạn không cần chọn cho nó, như số bánh xe hoặc số đăng ký.

class Car:
    def __init__(self, color, brand, number_of_seats):
        self.color = color
        self.brand = brand
        self.number_of_seats = number_of_seats
        self.number_of_wheels = 4
        self.registration_number = GenerateRegistrationNumber()

Vì vậy, trong __init__phương thức bạn xác định các thuộc tính của phiên bản mà bạn đang tạo. Vì vậy, nếu chúng ta muốn có một chiếc ô tô Renault màu xanh lam, dành cho 2 người, chúng ta sẽ khởi tạo hoặc ví dụ Carnhư:

my_car = Car('blue', 'Renault', 2)

Bằng cách này, chúng ta đang tạo một thể hiện của Carlớp. Cái __init__là cái đang xử lý các thuộc tính cụ thể của chúng ta (như colorhoặc brand) và nó tạo ra các thuộc tính khác, như registration_number.


3

Lớp là các đối tượng có các thuộc tính (trạng thái, đặc tính) và các phương thức (chức năng, dung lượng) dành riêng cho đối tượng đó (như màu trắng và sức mạnh của ruồi, đối với vịt).

Khi bạn tạo một thể hiện của một lớp, bạn có thể cung cấp cho nó một số tính cách ban đầu (trạng thái hoặc ký tự như tên và màu váy của trẻ sơ sinh). Bạn làm điều này với __init__.

Về cơ bản, __init__thiết lập các đặc tính cá thể tự động khi bạn gọi instance = MyClass(some_individual_traits).


2

Các __init__chức năng được thiết lập tất cả các biến thành viên trong lớp. Vì vậy, sau khi nhị phân của bạn được tạo, bạn có thể truy cập thành viên và nhận lại giá trị:

mycluster = bicluster(...actual values go here...)
mycluster.left # returns the value passed in as 'left'

Kiểm tra Tài liệu Python để biết một số thông tin. Bạn sẽ muốn chọn một cuốn sách về các khái niệm OO để tiếp tục học.


1
class Dog(object):

    # Class Object Attribute
    species = 'mammal'

    def __init__(self,breed,name):
        self.breed = breed
        self.name = name

Trong ví dụ trên, chúng tôi sử dụng các loài như một toàn cầu vì nó sẽ luôn giống nhau (Bạn có thể nói là không đổi). khi bạn gọi __init__phương thức thì tất cả các biến bên trong __init__sẽ được khởi tạo (ví dụ: giống, tên).

class Dog(object):
    a = '12'

    def __init__(self,breed,name,a):
        self.breed = breed
        self.name = name
        self.a= a

nếu bạn in ví dụ trên bằng cách gọi bên dưới như thế này

Dog.a
12

Dog('Lab','Sam','10')
Dog.a
10

Điều đó có nghĩa là nó sẽ chỉ được khởi tạo trong quá trình tạo đối tượng. vì vậy bất kỳ thứ gì bạn muốn khai báo là không đổi, hãy làm cho nó ở dạng toàn cục và bất kỳ thứ gì thay đổi sử dụng __init__

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.