เป็นการดีที่จะประกาศตัวแปรอินสแตนซ์ว่าไม่มีในคลาสใน Python หรือไม่?


68

พิจารณาคลาสต่อไปนี้:

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

เพื่อนร่วมงานของฉันมักจะนิยามเช่นนี้:

class Person:
    name = None
    age = None

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

เหตุผลหลักสำหรับสิ่งนี้คือตัวเลือกที่แก้ไขจะแสดงคุณสมบัติของการเติมข้อความอัตโนมัติ

Noneส่วนตัวผมไม่ชอบหลังหนึ่งเพราะมันทำให้รู้สึกว่าชั้นมีคุณสมบัติเหล่านั้นตั้งค่าให้ไม่มี

อันไหนจะเป็นการฝึกฝนที่ดีกว่าและด้วยเหตุผลอะไร


63
อย่าปล่อยให้ IDE ของคุณกำหนดรหัสที่คุณเขียน
Martijn Pieters

14
โดยวิธีการ: ใช้ไพ ธ อน IDE ที่เหมาะสม (เช่น PyCharm), การตั้งค่าคุณสมบัติในการ__init__ให้การเติมข้อมูลอัตโนมัติเป็นต้นนอกจากนี้การใช้Noneป้องกัน IDE จะอนุมานชนิดที่ดีกว่าสำหรับแอตทริบิวต์ดังนั้นจึงควรใช้ค่าเริ่มต้นที่สมเหตุสมผลแทน (เมื่อ เป็นไปได้)
Bakuriu

หากเป็นเพียงการเติมข้อความอัตโนมัติคุณสามารถใช้การแนะนำประเภทและเอกสารเพิ่มเติมจะเป็นประโยชน์เช่นกัน
dashesy

3
"อย่าปล่อยให้ IDE ของคุณกำหนดรหัสที่คุณเขียน" เป็นปัญหาที่ถกเถียงกัน ในฐานะของงูหลาม 3.6 มีคำอธิบายประกอบในบรรทัดและtypingโมดูลที่ช่วยให้คุณที่จะให้คำแนะนำกับ IDE และ linter ถ้าเรียงลำดับของสิ่งที่ tickles จินตนาการของคุณ ...
CZ

1
การมอบหมายเหล่านี้ในระดับชั้นเรียนไม่มีผลกับส่วนที่เหลือของรหัส selfพวกเขาไม่มีผลกระทบต่อ แม้ว่าself.nameหรือself.ageไม่ได้รับมอบหมายใน__init__พวกเขาจะไม่ปรากฏในตัวอย่างselfพวกเขาจะปรากฏในชั้นเรียนPersonเท่านั้น
jolvi

คำตอบ:


70

ฉันเรียกการปฏิบัติที่ไม่ดีหลังภายใต้กฎ "สิ่งนี้ไม่ได้ทำในสิ่งที่คุณคิดว่าเป็น"

ตำแหน่งของเพื่อนร่วมงานของคุณสามารถเขียนใหม่เป็น: "ฉันจะสร้างกลุ่มตัวแปรแบบกึ่งคงที่ระดับโลกซึ่งไม่เคยเข้าถึง แต่จะใช้พื้นที่ในตารางเนมสเปซของคลาสที่หลากหลาย ( __dict__) เพื่อให้ IDE ของฉันทำ บางสิ่งบางอย่าง."


4
บิตเช่น docstrings แล้ว ;-) แน่นอนว่า Python มีโหมดสะดวกในการดึงแถบเหล่านั้นออก-OOมาสำหรับผู้ที่ต้องการมัน
Steve Jessop

15
เหตุผลที่สำคัญน้อยที่สุดที่ทำให้การประชุมครั้งนี้เริ่มต้นขึ้น มันเป็นโหลไบต์ต่อชื่อ (ต่อกระบวนการ) ฉันรู้สึกอยากเสียเวลาไปกับการอ่านคำตอบของคุณที่เกี่ยวข้องกับเรื่องนี้นั่นเป็นค่าใช้จ่ายที่ไม่สำคัญเลย

8
@delnan ฉันเห็นด้วยกับขนาดหน่วยความจำของรายการที่ไม่มีความหมายฉันคิดเพิ่มเติมว่ามีเหตุผล / พื้นที่ทางจิตที่ถูกนำมาใช้ทำให้การแก้ไขข้อบกพร่องครุ่นคิดและจำเป็นต้องอ่านและเรียงลำดับมากขึ้นฉันจะเดา I ~ LL
StarWeaver

3
ที่จริงแล้วถ้าคุณเป็นคนหวาดระแวงเกี่ยวกับพื้นที่คุณจะสังเกตเห็นว่าสิ่งนี้ช่วยประหยัดพื้นที่และเสียเวลา สมาชิกที่ไม่ได้กำหนดค่าเริ่มต้นจะใช้ค่าของคลาสและดังนั้นจึงไม่มีสำเนาของชื่อตัวแปรที่แมปกับไม่มี (กลุ่มละหนึ่งอินสแตนซ์) ดังนั้นค่าใช้จ่ายคือไม่กี่ไบต์ในชั้นเรียนและการประหยัดคือไม่กี่ไบต์ต่ออินสแตนซ์ แต่การค้นหาชื่อตัวแปรที่ล้มเหลวแต่ละครั้งใช้เวลาเล็กน้อย
Jon Jay Obermark

2
หากคุณกังวลเกี่ยวกับ 8 ไบต์คุณจะไม่ใช้ Python
cz

26

1. ทำให้รหัสของคุณง่ายต่อการเข้าใจ

รหัสอ่านบ่อยกว่าที่เขียน ทำให้งานของผู้ดูแลโค้ดของคุณง่ายขึ้น (และอาจเป็นตัวคุณเองในปีหน้า)

ฉันไม่รู้เกี่ยวกับกฎที่ยากใด ๆ แต่ฉันชอบที่จะประกาศสถานการณ์ในอนาคตอย่างชัดเจนทันที การชนกับรถถังAttributeErrorนั้นไม่ดีพอ ไม่เห็นชัดเจนว่าวงจรชีวิตของแอตทริบิวต์อินสแตนซ์นั้นแย่ลง จำนวนของนักยิมนาสติกจิตที่จำเป็นในการคืนค่าลำดับการโทรที่เป็นไปได้ซึ่งนำไปสู่คุณลักษณะที่กำหนดไว้สามารถกลายเป็นเรื่องไม่สำคัญได้ง่ายนำไปสู่ข้อผิดพลาด

ดังนั้นฉันมักจะไม่เพียง แต่กำหนดทุกอย่างในตัวสร้าง แต่ยังพยายามรักษาจำนวนของคุณลักษณะที่ไม่แน่นอนให้น้อยที่สุด

2. อย่าผสมสมาชิกระดับชั้นเรียนและระดับอินสแตนซ์

สิ่งใดก็ตามที่คุณกำหนดไว้ภายในการclassประกาศนั้นเป็นของคลาสและถูกใช้ร่วมกันโดยอินสแตนซ์ทั้งหมดของคลาส เช่นเมื่อคุณกำหนดฟังก์ชั่นภายในคลาสมันจะกลายเป็นวิธีการที่เหมือนกันสำหรับทุกกรณี เช่นเดียวกับสมาชิกข้อมูล __init__นี้จะแตกต่างกันโดยสิ้นเชิงเช่นคุณลักษณะที่คุณมักจะกำหนดใน

สมาชิกข้อมูลระดับชั้นมีประโยชน์มากที่สุดในฐานะค่าคงที่:

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
ใช่ แต่การผสมสมาชิกระดับและระดับอินสแตนซ์นั้นเป็นสิ่งที่defทำ มันสร้างฟังก์ชั่นที่เราคิดว่าเป็นคุณสมบัติของวัตถุ แต่เป็นสมาชิกของคลาสจริงๆ สิ่งเดียวกันสำหรับทรัพย์สินและตระกูลของมัน การให้ภาพลวงตาที่ทำงานเป็นของวัตถุเมื่อมีการไกล่เกลี่ยจริง ๆ ในชั้นเรียนเป็นวิธีที่เคารพเวลาที่ไม่ไปทำถั่ว มันจะเลวร้ายขนาดนั้นได้อย่างไรถ้ามันเป็นสิ่งที่ดีสำหรับ Python
Jon Jay Obermark

ลำดับการแก้ไขวิธีการใน Python (ใช้ได้กับข้อมูลสมาชิก) นั้นไม่ง่ายนัก สิ่งที่ไม่พบในระดับอินสแตนซ์จะถูกค้นหาในระดับชั้นจากนั้นในคลาสพื้นฐาน ฯลฯ คุณสามารถทำเงาสมาชิกระดับชั้น (ข้อมูลหรือวิธีการ) โดยการกำหนดสมาชิกอินสแตนซ์ระดับชื่อเดียวกัน แต่วิธีการระดับอินสแตนซ์ถูกผูกไว้กับอินสแตนซ์selfและไม่จำเป็นต้องselfถูกส่งผ่านในขณะที่เมธอดระดับชั้นไม่ได้ผูกไว้และเป็นฟังก์ชันธรรมดาตามที่เห็นในdefเวลาและยอมรับอินสแตนซ์เป็นอาร์กิวเมนต์แรก ดังนั้นสิ่งเหล่านี้จึงแตกต่างกัน
9000

ฉันคิดว่าAttributeErrorเป็นสัญญาณที่ดีกว่ามีข้อบกพร่อง มิฉะนั้นคุณจะกลืนไม่มีและได้ผลลัพธ์ที่ไร้ความหมาย มีความสำคัญเป็นพิเศษในกรณีที่มีการกำหนดแอตทริบิวต์ใน__init__ดังนั้นแอตทริบิวต์ที่ขาดหายไป (แต่มีอยู่ในระดับชั้นเรียน) อาจเกิดจากการรับมรดกแบบบั๊กเท่านั้น
Davidmh

@Davidmh: ข้อผิดพลาดที่ตรวจพบมักจะดีกว่าข้อผิดพลาดที่ตรวจไม่พบแน่นอน! ฉันจะบอกว่าถ้าคุณต้องสร้างแอตทริบิวต์Noneและค่านี้ไม่สมเหตุสมผลในขณะที่การสร้างอินสแตนซ์คุณมีปัญหาในสถาปัตยกรรมของคุณและต้องคิดใหม่วงจรชีวิตของค่าของแอตทริบิวต์หรือค่าเริ่มต้นของมัน โปรดทราบว่าด้วยการกำหนดคุณสมบัติของคุณ แต่เนิ่นๆคุณสามารถตรวจพบปัญหาดังกล่าวได้ก่อนที่คุณจะเขียนคลาสที่เหลือให้ใช้รหัสเพียงอย่างเดียว
9000

สนุก! ขีปนาวุธ! ต่อไปฉันค่อนข้างแน่ใจว่ามันตกลงที่จะทำให้ vars ระดับชั้นเรียนและผสมพวกเขา ... ตราบใดที่ระดับชั้นมีค่าเริ่มต้น ฯลฯ
Erik Aronesty

18

ส่วนตัวฉันกำหนดสมาชิกในเมธอด __ init __ () ฉันไม่เคยคิดถึงการกำหนดไว้ในส่วนของชั้นเรียน แต่สิ่งที่ฉันทำอยู่เสมอ: ฉันเริ่มสมาชิกทั้งหมดในเมธอด __ init__ แม้จะไม่จำเป็นในเมธอด __ init__ ก็ตาม

ตัวอย่าง:

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

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

ฉันคิดว่ามันเป็นสิ่งสำคัญที่จะกำหนดสมาชิกทั้งหมดในที่เดียว ทำให้โค้ดอ่านง่ายขึ้น ไม่ว่าจะอยู่ใน __ init __ () หรือภายนอกก็ไม่สำคัญ แต่มันเป็นสิ่งสำคัญสำหรับทีมที่จะมอบสไตล์การเข้ารหัสที่เหมือนกันหรือมากกว่านั้น

โอ้และคุณอาจสังเกตเห็นว่าฉันเคยเพิ่มคำนำหน้า "_" ลงในตัวแปรสมาชิก


13
คุณควรตั้งค่าทั้งหมดในตัวสร้าง ถ้าค่าถูกตั้งค่าในคลาสเองมันจะถูกแชร์ระหว่างอินสแตนซ์ซึ่งใช้ได้สำหรับ None แต่ไม่ใช่สำหรับค่าส่วนใหญ่ ดังนั้นอย่าเปลี่ยนแปลงอะไรเกี่ยวกับมัน ;)
Remco Haszing

4
ค่าเริ่มต้นสำหรับพารามิเตอร์และความสามารถในการใช้อาร์กิวเมนต์ตำแหน่งและคำหลักโดยพลการทำให้มีความเจ็บปวดน้อยกว่าที่คาดไว้ (และมันก็เป็นจริงไปได้ที่จะมอบหมายงานก่อสร้างกับวิธีอื่น ๆ หรือแม้กระทั่งยืนอยู่คนเดียวฟังก์ชั่นดังนั้นหากคุณจริงๆต้องส่งจำนวนมากที่คุณสามารถทำเกินไป)
Sean Vieira

6
@Benedict Python ไม่มีการควบคุมการเข้าถึง ขีดเส้นใต้ชั้นนำเป็นแบบแผนที่ยอมรับสำหรับรายละเอียดการใช้งาน ดูPEP 8
Doval

3
@Doval มีเหตุผลที่ดีเป็นพิเศษในการใส่ชื่อแอตทริบิวต์ด้วย_: เพื่อระบุว่าเป็นแบบส่วนตัว! (ทำไมคนจำนวนมากในกระทู้นี้จึงทำให้ Python สับสนหรือครึ่งภาษาสับสนกับภาษาอื่น ๆ )

1
อาดังนั้นคุณเป็นหนึ่งในคนเหล่านั้นที่มีคุณสมบัติหรือฟังก์ชั่นสำหรับทุกการสัมผัส ... ขอโทษที่เข้าใจผิด สไตล์นั้นดูเหมือนจะเกินความคาดหมายสำหรับฉันเสมอ แต่มันมีดังต่อไปนี้
Jon Jay Obermark

11

นี่คือการปฏิบัติที่ไม่ดี คุณไม่ต้องการค่าเหล่านั้นมันทำให้รหัสยุ่งเหยิงและพวกมันอาจทำให้เกิดข้อผิดพลาดได้

พิจารณา:

>>> 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'

ฉันจะกดยากที่จะเรียกว่า 'ทำให้เกิดข้อผิดพลาด' มันไม่ชัดเจนว่าคุณหมายถึงอะไรโดยการขอ 'x' ที่นี่ แต่คุณอาจต้องการค่าเริ่มต้นที่น่าจะเป็นไปได้
Jon Jay Obermark

ฉันควรทำอย่างละเอียดว่ามันอาจทำให้เกิดข้อผิดพลาดหากใช้ไม่ถูกต้อง
Daenyth

ความเสี่ยงที่สูงขึ้นยังคงเป็นการเริ่มต้นกับวัตถุ ไม่มีใครปลอดภัยจริงๆ ข้อผิดพลาดเพียงอย่างเดียวที่มันจะทำให้เกิดคือกลืนข้อยกเว้นที่ทำให้ง่อยจริงๆ
Jon Jay Obermark

1
ความล้มเหลวที่จะทำให้เกิดข้อผิดพลาดเป็นปัญหาหากข้อผิดพลาดจะช่วยป้องกันปัญหาร้ายแรงมากขึ้น
Erik Aronesty

0

ฉันจะไปกับ "นิดหน่อยเหมือน docstrings แล้ว" และประกาศสิ่งนี้ไม่เป็นอันตรายตราบใดที่มันยังอยู่เสมอNoneหรือในช่วงแคบ ๆ ของค่าอื่น ๆ ทั้งหมดไม่เปลี่ยนรูป

มัน reeks ของ atavism และสิ่งที่แนบมามากเกินไปกับภาษาที่พิมพ์แบบคงที่ และมันก็ไม่ได้ดีเท่าโค้ด แต่มันมีจุดประสงค์เล็ก ๆ น้อย ๆ ที่ยังคงอยู่ในเอกสาร

มันบันทึกชื่อที่คาดหวังไว้ดังนั้นถ้าฉันรวมรหัสกับใครบางคนและหนึ่งในนั้นมี 'ชื่อผู้ใช้' และชื่อผู้ใช้คนอื่น ๆ ก็มีร่องรอยของมนุษย์ที่เราแยกทางและไม่ได้ใช้ตัวแปรเดียวกัน

การบังคับใช้การเริ่มต้นเต็มรูปแบบเป็นนโยบายที่ประสบความสำเร็จในแบบ Pythonic แต่ถ้ามีรหัสจริงใน__init__นี้จะให้ที่ที่ชัดเจนในการจัดทำเอกสารตัวแปรที่ใช้

เห็นได้ชัดว่าปัญหาใหญ่ที่นี่คือมันล่อลวงคนที่จะเริ่มต้นด้วยค่าอื่น ๆNoneซึ่งอาจไม่ดี:

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

ออกจากการติดตามทั่วโลกและไม่สร้างอินสแตนซ์สำหรับ x

แต่นั่นเป็นสิ่งที่แปลกประหลาดใน Python โดยรวมมากกว่าในการฝึกฝนนี้และเราควรหวาดระแวงเกี่ยวกับเรื่องนี้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.