Có phải là một cách thực hành tốt để khai báo các biến thể hiện là Không có trong một lớp trong Python?


68

Hãy xem xét các lớp sau:

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

Đồng nghiệp của tôi có xu hướng định nghĩa nó như thế này:

class Person:
    name = None
    age = None

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

Lý do chính cho điều này là trình soạn thảo lựa chọn của họ hiển thị các thuộc tính để tự động hoàn thành.

Cá nhân, tôi không thích cái thứ hai, bởi vì nó không có nghĩa là một lớp có các thuộc tính đó được đặt thành None.

Cái nào sẽ được thực hành tốt hơn và vì lý do gì?


63
Không bao giờ để IDE của bạn ra lệnh bạn viết mã gì?
Martijn Pieters

14
Bằng cách: sử dụng một IDE python thích hợp (ví dụ PyCharm), thiết lập các thuộc tính trong __init__đã cung cấp autocompletion vv Ngoài ra, sử dụng Nonengăn chặn các IDE để suy ra một loại tốt hơn cho các thuộc tính, vì vậy nó là tốt hơn để sử dụng một mặc định hợp lý thay vì (khi khả thi).
Bakuriu

Nếu nó chỉ dành cho tự động hoàn thành, bạn có thể sử dụng gợi ý kiểu và các tài liệu bổ sung cũng sẽ là một điểm cộng.
bảnh bao

3
"Không bao giờ để IDE của bạn ra lệnh bạn viết mã gì" là một vấn đề gây tranh cãi. Kể từ Python 3.6, có các chú thích nội tuyến và một typingmô-đun, cho phép bạn cung cấp gợi ý cho IDE và kẻ nói dối, nếu điều đó làm bạn thích thú ...
cz

1
Các bài tập này ở cấp lớp không có tác dụng đối với phần còn lại của mã. Họ không có hiệu lực trên self. Ngay cả khi self.namehoặc self.agekhông được chỉ định trong trường hợp __init__họ sẽ không xuất hiện trong trường hợp self, họ chỉ xuất hiện trong lớp Person.
jolvi

Câu trả lời:


70

Tôi gọi thực tiễn xấu thứ hai theo quy tắc "điều này không làm những gì bạn nghĩ nó làm".

Vị trí đồng nghiệp của bạn có thể được viết lại thành: "Tôi sẽ tạo ra một loạt các biến toàn cầu tĩnh lớp không bao giờ được truy cập, nhưng nó chiếm không gian trong các bảng không gian tên của lớp khác nhau ( __dict__), chỉ để làm cho IDE của tôi làm một cái gì đó


4
Một chút giống như các tài liệu sau đó ;-) Tất nhiên, Python có một chế độ tiện dụng để loại bỏ những thứ đó -OO, cho những ai cần nó.
Steve Jessop

15
Không gian được thực hiện là lý do ít quan trọng nhất quy ước này bốc mùi. Đó là một tá byte cho mỗi tên (mỗi quá trình). Tôi cảm thấy như đã lãng phí thời gian của mình khi chỉ đọc phần câu trả lời của bạn liên quan đến nó, đó là chi phí không gian không quan trọng.

8
@delnan Tôi đồng ý về kích thước bộ nhớ của các mục là vô nghĩa, tôi đã suy nghĩ nhiều hơn về không gian logic / tinh thần, làm cho việc gỡ lỗi nội tâm và như vậy đòi hỏi phải đọc và sắp xếp nhiều hơn, tôi đoán vậy. Tôi ~ LL
StarWeaver

3
Trên thực tế, nếu bạn hoang tưởng về không gian, bạn sẽ nhận thấy rằng điều này tiết kiệm không gian và lãng phí thời gian. Các thành viên chưa được khởi tạo rơi vào sử dụng giá trị lớp và do đó, không có một loạt các bản sao của tên biến được ánh xạ thành Không có (một cho mỗi trường hợp). Vì vậy, chi phí là một vài byte tại lớp và tiết kiệm là một vài byte cho mỗi thể hiện. Nhưng mỗi lần tìm kiếm tên biến không thành công tốn một ít thời gian.
Jon Jay Obermark

2
Nếu bạn lo lắng về 8 byte, bạn sẽ không sử dụng Python.
cz

26

1. Làm cho mã của bạn dễ hiểu

Mã được đọc thường xuyên hơn nhiều so với văn bản. Làm cho công việc bảo trì mã của bạn dễ dàng hơn (cũng có thể là chính bạn vào năm tới).

Tôi không biết về bất kỳ quy tắc cứng nào, nhưng tôi thích có bất kỳ trạng thái ví dụ nào trong tương lai được tuyên bố rõ ràng hoàn toàn. Đâm với một AttributeErrorlà đủ xấu. Không thấy rõ vòng đời của một thuộc tính thể hiện là tồi tệ hơn. Số lượng thể dục tinh thần cần thiết để khôi phục các chuỗi cuộc gọi có thể dẫn đến thuộc tính được gán có thể dễ dàng trở nên không tầm thường, dẫn đến lỗi.

Vì vậy, tôi thường không chỉ định nghĩa mọi thứ trong hàm tạo, mà còn cố gắng giữ số lượng thuộc tính có thể thay đổi ở mức tối thiểu.

2. Không trộn lẫn các thành viên cấp lớp và cấp thể hiện

Bất cứ điều gì bạn xác định ngay bên trong classkhai báo đều thuộc về lớp và được chia sẻ bởi tất cả các phiên bản của lớp. Ví dụ, khi bạn định nghĩa một hàm bên trong một lớp, nó sẽ trở thành một phương thức giống nhau cho tất cả các thể hiện. Áp dụng tương tự cho các thành viên dữ liệu. Điều này hoàn toàn không giống với các thuộc tính thể hiện mà bạn thường xác định __init__.

Các thành viên dữ liệu cấp lớp là hữu ích nhất như các hằng số:

class Missile(object):
  MAX_SPEED = 100  # all missiles accelerate up to this speed
  ACCELERATION = 5  # rate of acceleration per game frame

  def move(self):
    self.speed += self.ACCELERATION
    if self.speed > self.MAX_SPEED:
      self.speed = self.MAX_SPEED
    # ...

2
Vâng, nhưng pha trộn các thành viên cấp độ lớp và thể hiện là khá nhiều những gì def làm. Nó tạo ra các hàm mà chúng ta nghĩ là thuộc tính của các đối tượng, nhưng thực sự là thành viên của lớp. Điều tương tự cũng xảy ra đối với tài sản và ilk của nó. Đưa ra ảo tưởng rằng công việc thuộc về các đối tượng khi nó thực sự được trung gian bởi lớp học là một cách không được tôn trọng theo thời gian. Làm thế nào nó có thể là tất cả xấu, nếu nó là OK cho chính Python.
Jon Jay Obermark

Chà, thứ tự giải quyết phương thức trong Python (cũng có thể áp dụng cho các thành viên dữ liệu) không đơn giản lắm. Những gì không tìm thấy ở cấp độ cá thể, sẽ được tìm kiếm ở cấp độ lớp, sau đó trong số các lớp cơ sở, v.v. Bạn thực sự có thể che giấu một thành viên cấp lớp (dữ liệu hoặc phương thức) bằng cách chỉ định một thành viên cấp cá thể có cùng tên. Nhưng các phương thức mức cá thể bị ràng buộc với thể hiện selfvà không cần selfphải được thông qua, trong khi các phương thức cấp lớp không bị ràng buộc và là các hàm đơn giản như đã thấy tại defthời điểm đó và chấp nhận một thể hiện làm đối số đầu tiên. Vì vậy, đây là những điều khác nhau.
9000

Tôi nghĩ rằng đây AttributeErrorlà một tín hiệu tốt hơn là có lỗi. Nếu không, bạn sẽ nuốt Không và nhận được kết quả vô nghĩa. Đặc biệt quan trọng trong trường hợp có sẵn, trong đó các thuộc tính được xác định trong __init__, do đó, một thuộc tính bị thiếu (nhưng tồn tại ở cấp độ lớp) chỉ có thể được gây ra bởi sự kế thừa lỗi.
Davidmh

@Davidmh: Một lỗi được phát hiện luôn tốt hơn một lỗi không bị phát hiện, thực sự! Tôi muốn nói rằng nếu bạn phải tạo một thuộc tính Nonevà giá trị này không có ý nghĩa tại thời điểm xây dựng thể hiện, bạn có một vấn đề trong kiến ​​trúc của mình và phải suy nghĩ lại về vòng đời của giá trị của thuộc tính hoặc giá trị ban đầu của nó. Lưu ý rằng bằng cách xác định sớm các thuộc tính của bạn, bạn có thể phát hiện vấn đề như vậy ngay cả trước khi bạn viết phần còn lại của lớp, chứ đừng nói đến việc chạy mã.
9000

Vui vẻ! tên lửa! Dù sao, tôi khá chắc chắn rằng sẽ ổn khi tạo các vars cấp độ lớp và trộn chúng ... miễn là cấp độ lớp chứa mặc định, v.v.
Erik Aronesty

18

Cá nhân tôi xác định các thành viên trong phương thức __ init __ (). Tôi chưa bao giờ nghĩ về việc xác định chúng trong phần lớp. Nhưng điều tôi luôn làm: Tôi khởi tạo tất cả các thành viên trong phương thức __ init__, ngay cả những người không cần thiết trong phương thức __ init__.

Thí dụ:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._selected = None

   def setSelected(self, value):
        self._selected = value

Tôi nghĩ điều quan trọng là xác định tất cả các thành viên ở một nơi. Nó làm cho mã dễ đọc hơn. Cho dù đó là bên trong __ init __ () hay bên ngoài, điều đó không quan trọng. Nhưng điều quan trọng là một nhóm phải cam kết ít nhiều cùng một kiểu mã hóa.

Ồ, và bạn có thể nhận thấy tôi từng thêm tiền tố "_" vào các biến thành viên.


13
Bạn nên đặt tất cả các giá trị trong hàm tạo. Nếu giá trị được đặt trong chính lớp đó, nó sẽ được chia sẻ giữa các trường hợp, điều này tốt cho Không, nhưng không phải cho hầu hết các giá trị. Vì vậy, đừng thay đổi bất cứ điều gì về nó. ;)
Remco Haszing

4
Các giá trị mặc định cho các tham số và khả năng lấy các đối số từ khóa và vị trí tùy ý làm cho nó ít đau đớn hơn nhiều so với mong đợi. (Và thực tế có thể ủy thác xây dựng cho các phương thức khác hoặc thậm chí các hàm độc lập, vì vậy nếu bạn thực sự cần nhiều công văn, bạn cũng có thể làm điều đó).
Sean Vieira

6
@Benedict Python không có quyền kiểm soát truy cập. Gạch dưới hàng đầu là quy ước được chấp nhận cho các chi tiết thực hiện. Xem PEP 8 .
Doval

3
@Doval Có một lý do cực kỳ tốt để đặt tiền tố tên thuộc tính với _: Để chỉ ra rằng nó là riêng tư! (Tại sao nhiều người trong chủ đề này khó hiểu hoặc nửa bối rối với các ngôn ngữ khác?)

1
À, vậy bạn là một trong những người có tính chất hoặc chức năng cho tất cả những người tương tác tiếp xúc ... Xin lỗi vì đã hiểu lầm. Phong cách đó luôn có vẻ quá mức đối với tôi, nhưng nó có một điều sau đây.
Jon Jay Obermark

11

Đây là một thực hành xấu. Bạn không cần những giá trị đó, chúng làm lộn xộn mã và chúng có thể gây ra lỗi.

Xem xét:

>>> class WithNone:
...   x = None
...   y = None
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> class InitOnly:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'

Tôi sẽ rất khó để gọi đó là 'gây ra lỗi'. Thật mơ hồ những gì bạn muốn nói khi yêu cầu 'x' ở đây, nhưng bạn rất có thể muốn giá trị ban đầu rất có thể.
Jon Jay Obermark

Tôi nên giải thích rằng nó có thể gây ra lỗi nếu sử dụng không đúng cách.
Daenyth

Nguy cơ cao hơn vẫn đang khởi tạo cho một đối tượng. Không có gì thực sự là khá an toàn. Lỗi duy nhất mà nó sẽ gây ra là nuốt những ngoại lệ thực sự khập khiễng.
Jon Jay Obermark

1
Không gây ra lỗi là một vấn đề nếu các lỗi sẽ ngăn chặn các vấn đề nghiêm trọng hơn.
Erik Aronesty

0

Tôi sẽ đi với "một chút giống như các tài liệu, sau đó" và tuyên bố điều này vô hại, miễn là nó luôn luônNone , hoặc một phạm vi hẹp của các giá trị khác, tất cả đều không thay đổi.

Nó tràn ngập sự thờ ơ và gắn bó quá mức với các ngôn ngữ được gõ tĩnh. Và nó không tốt như mã. Nhưng nó có một mục đích nhỏ còn lại, trong tài liệu.

Nó ghi lại tên dự kiến ​​là gì, vì vậy nếu tôi kết hợp mã với ai đó và một trong số chúng tôi có 'tên người dùng' và tên người dùng khác, có một manh mối cho con người rằng chúng tôi đã chia tay và không sử dụng cùng một biến.

Buộc khởi tạo đầy đủ như một chính sách đạt được điều tương tự theo cách Pythonic hơn, nhưng nếu có mã thực tế trong __init__, điều này cung cấp một nơi rõ ràng hơn để ghi lại các biến được sử dụng.

Rõ ràng vấn đề LỚN ở đây là nó cám dỗ mọi người khởi tạo với các giá trị khác None, có thể là xấu:

class X:
    v = {}
x = X()
x.v[1] = 2

để lại dấu vết toàn cầu và không tạo một thể hiện cho x.

Nhưng đó chỉ là một sự ngớ ngẩn trong Python nói chung hơn là trong thực tế này, và chúng ta đã nên hoang tưởng về nó.

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.