Làm cách nào để khai báo các giá trị mặc định cho các biến phiên bản trong Python?


90

Tôi có nên đặt các giá trị mặc định cho các thành viên trong lớp của mình như thế này không:

class Foo:
    num = 1

hay như thế này?

class Foo:
    def __init__(self):
        self.num = 1

Trong câu hỏi này, tôi phát hiện ra rằng trong cả hai trường hợp,

bar = Foo()
bar.num += 1

là một hoạt động được xác định rõ ràng.

Tôi hiểu rằng phương thức đầu tiên sẽ cung cấp cho tôi một biến lớp trong khi phương thức thứ hai thì không. Tuy nhiên, nếu tôi không yêu cầu biến lớp mà chỉ cần đặt giá trị mặc định cho các biến cá thể của mình, thì cả hai phương thức có tốt như nhau không? Hay một trong số chúng 'khủng' hơn cái còn lại?

Một điều tôi nhận thấy là trong hướng dẫn Django, họ sử dụng phương pháp thứ hai để khai báo Mô hình. Cá nhân tôi nghĩ rằng phương pháp thứ hai là thanh lịch hơn, nhưng tôi muốn biết cách 'tiêu chuẩn' là gì.

Câu trả lời:


132

Mở rộng câu trả lời của bp, tôi muốn cho bạn thấy ý của anh ấy về các kiểu bất biến.

Đầu tiên, điều này là ổn:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

Tuy nhiên, điều này chỉ hoạt động đối với các loại không thể thay đổi (không thể thay đổi). Nếu giá trị mặc định là có thể thay đổi (nghĩa là nó có thể được thay thế), điều này sẽ xảy ra thay thế:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Lưu ý rằng cả hai abcó một thuộc tính được chia sẻ. Điều này thường không mong muốn.

Đây là cách Pythonic xác định giá trị mặc định cho các biến cá thể, khi kiểu có thể thay đổi:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

Lý do đoạn mã đầu tiên của tôi hoạt động là vì, với các kiểu bất biến, Python tạo một phiên bản mới của nó bất cứ khi nào bạn muốn. Nếu bạn cần thêm 1 vào 1, Python sẽ tạo số 2 mới cho bạn, vì số 1 cũ không thể thay đổi được. Tôi tin rằng lý do chủ yếu là vì băm.


2
Gần đây tôi đã biết cách đơn giản hơn nhiều để triển khai các giá trị mặc định cho các thuộc tính có thể thay đổi. Chỉ cần viết self.attr = attr or []trong __init__phương thức. Nó đạt được kết quả tương tự (tôi nghĩ) và vẫn rõ ràng và dễ đọc.
Bill

4
Xin lỗi mọi người. Tôi đã thực hiện một số nghiên cứu thêm về gợi ý trong nhận xét ở trên của tôi và rõ ràng nó không hoàn toàn giống và không được khuyến khích .
Bill

Tại sao lớp TestC có dấu ngoặc trống? Đó có phải là di sản của Python 2 không?
ChrisG

55

Hai đoạn mã làm những việc khác nhau, vì vậy vấn đề không phải là sở thích mà là vấn đề về hành vi phù hợp trong ngữ cảnh của bạn. Tài liệu Python giải thích sự khác biệt, nhưng đây là một số ví dụ:

Triển lãm A

class Foo:
  def __init__(self):
    self.num = 1

Điều này liên kết numvới các cá thể Foo . Thay đổi đối với trường này không được truyền sang các trường hợp khác.

Như vậy:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Triển lãm B

class Bar:
  num = 1

Điều này liên kết numvới lớp Bar . Thay đổi được tuyên truyền!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Câu trả lời thực tế

Nếu tôi không yêu cầu biến lớp mà chỉ cần đặt giá trị mặc định cho các biến cá thể của mình, thì cả hai phương thức có tốt như nhau không? Hay một trong số chúng 'khủng' hơn cái còn lại?

Mã trong triển lãm B hoàn toàn sai cho điều này: tại sao bạn lại muốn liên kết một thuộc tính lớp (giá trị mặc định khi tạo phiên bản) với một cá thể?

Mã trong triển lãm A là được.

Tuy nhiên, nếu bạn muốn đặt giá trị mặc định cho các biến cá thể trong hàm tạo của mình, tôi sẽ thực hiện điều này:

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

... hoặc thậm chí:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

... hoặc thậm chí: (ưu tiên, nhưng nếu và chỉ khi bạn đang xử lý các loại không thay đổi!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

Bằng cách này bạn có thể làm:

foo1 = Foo(4)
foo2 = Foo() #use default

1
là phương pháp init sử dụng trong ví dụ khác nhau này từ underscore___init__underscore để khởi @badp
Eliethesaiyan

Ví dụ đầu tiên không nên làdef __init__(self, num = None)
PyWalker2797

6

Việc sử dụng các thành viên lớp để cung cấp các giá trị mặc định hoạt động rất tốt miễn là bạn chỉ cẩn thận làm điều đó với các giá trị không thay đổi. Nếu bạn cố gắng làm điều đó với một danh sách hoặc một mệnh lệnh sẽ khá nguy hiểm. Nó cũng hoạt động khi thuộc tính instance là một tham chiếu đến một lớp miễn là giá trị mặc định là Không.

Tôi đã thấy kỹ thuật này được sử dụng rất thành công trong repoze, một khuôn khổ chạy trên Zope. Ưu điểm ở đây không chỉ là khi lớp của bạn được duy trì trong cơ sở dữ liệu, chỉ các thuộc tính không mặc định cần được lưu, mà còn khi bạn cần thêm một trường mới vào lược đồ, tất cả các đối tượng hiện có sẽ thấy trường mới với mặc định của nó giá trị mà không cần thực sự thay đổi dữ liệu được lưu trữ.

Tôi thấy nó cũng hoạt động tốt trong mã hóa tổng quát hơn, nhưng đó là một thứ phong cách. Sử dụng bất cứ thứ gì bạn thấy vui nhất.


3

Sử dụng các thành viên lớp cho các giá trị mặc định của các biến cá thể không phải là một ý tưởng hay và đây là lần đầu tiên tôi thấy ý tưởng này được đề cập đến. Nó hoạt động trong ví dụ của bạn, nhưng nó có thể bị lỗi trong nhiều trường hợp. Ví dụ: nếu giá trị có thể thay đổi, việc thay đổi nó trên một phiên bản chưa được sửa đổi sẽ thay đổi giá trị mặc định:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]

ngoại trừ chỉ có người đầu tiên []được nhân bản xung quanh đó; x.ly.lđều là tên ác và các giá trị tức thời khác nhau liên kết đến cùng một tham chiếu. (Điều khoản luật sư ngôn ngữ được tạo thành)
Phlip

1

Bạn cũng có thể khai báo các biến lớp là Không có gì sẽ ngăn cản sự lan truyền. Điều này rất hữu ích khi bạn cần một lớp được xác định rõ ràng và muốn ngăn AttributeErrors. Ví dụ:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Ngoài ra nếu bạn cần mặc định:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Tất nhiên vẫn làm theo thông tin trong các câu trả lời khác về việc sử dụng các loại có thể thay đổi và bất biến làm mặc định trong __init__.


0

Với dataclasses , một tính năng được thêm vào trong Python 3.7, hiện có một cách khác (khá tiện lợi) để đạt được thiết lập giá trị mặc định trên các cá thể lớp. Trình trang trí dataclasssẽ tự động tạo một vài phương thức trên lớp của bạn, chẳng hạn như hàm tạo. Như tài liệu được liên kết ở trên ghi chú, "[t] anh ấy biến thành viên để sử dụng trong các phương pháp được tạo này được xác định bằng cách sử dụng chú thích loại PEP 526 ".

Xem xét ví dụ của OP, chúng ta có thể triển khai nó như sau:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Khi xây dựng một đối tượng thuộc kiểu của lớp này, chúng ta có thể tùy ý ghi đè giá trị.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
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.