ฉันจะหลีกเลี่ยง“ self.x = x ได้อย่างไร self.y = y; รูปแบบ self.z = z” ใน __init__?


170

ฉันเห็นรูปแบบที่ชอบ

def __init__(self, x, y, z):
    ...
    self.x = x
    self.y = y
    self.z = z
    ...

ค่อนข้างบ่อยบ่อยครั้งที่มีพารามิเตอร์มากขึ้น มีวิธีที่ดีในการหลีกเลี่ยงการทำซ้ำที่น่าเบื่อนี้หรือไม่? คลาสควรสืบทอดจากnamedtupleแทนหรือไม่?


31
การรับไม่ได้ทั้งหมดนั้นไม่ดี โปรดทราบว่าโมเดลคลาสของ Python ไม่ได้มีคำจำกัดความที่ชัดเจนของคุณลักษณะของอินสแตนซ์ดังนั้นการมอบหมายเหล่านี้จึงเทียบเท่าเอกสารที่บันทึกด้วยตนเอง
chepner

4
@chepner: ก็ไม่จำเป็นต้องมีคำจำกัดความที่ชัดเจน คุณสามารถใช้__slots__เพื่อวัตถุประสงค์แม้ว่า ; มันไม่ได้ไพเราะเล็กน้อย (verbose มากขึ้นที่จะได้รับการประหยัดหน่วยความจำ) แต่ฉันชอบมันเป็นส่วนใหญ่เพื่อหลีกเลี่ยงความเสี่ยงของการทำให้เป็นอัตโนมัติแอตทริบิวต์ใหม่ทั้งหมดถ้าฉันพิมพ์ชื่อ
ShadowRanger

2
เครื่องมือแก้ไขที่ดีจะมีเทมเพลต คุณพิมพ์ini <shortcut> x, y, z): <shortcut>และคุณทำเสร็จแล้ว
Gerenuk

3
Namedtuples นั้นยอดเยี่ยมถ้าคุณต้องการวัตถุที่ไม่เปลี่ยนค่า หากคุณต้องการชั้นเรียนปกติที่ไม่แน่นอนคุณจะไม่สามารถใช้คลาสเหล่านั้นได้
RemcoGerlich

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

คำตอบ:


87

แก้ไข: ถ้าคุณมี python 3.7+ เพียงใช้ดาต้าคลาส

วิธีการแก้ปัญหามัณฑนากรที่เก็บลายเซ็น:

import decorator
import inspect
import sys


@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()

    behaves like

    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """

    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]

    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order

    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)

    # call the original __init__
    func(self, *args, **kws)


class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)

#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))

2
คำตอบที่ดี แต่จะไม่ทำงานกับ python2.7: ไม่signature
MaxB

3
@alex เป็นมัณฑนากร "decorator.decorator" จะปิดฟังก์ชั่นโดยอัตโนมัติ
Siphor

4
ฉันค่อนข้างจะขาดใจที่จะรักหรือเกลียดมัน ฉันขอขอบคุณที่รักษาลายเซ็น
Kyle Strand

14
"... ชัดแจ้งดีกว่าโดยนัยง่ายกว่าดีกว่าซับซ้อน ... " - Zen of Python
Jack Stout

9
-1 ค่อนข้างตรงนี้น่ากลัว ฉันไม่รู้ว่าโค้ดนี้กำลังทำอะไรและมันเป็นสิบเท่าของจำนวนโค้ด การเป็นคนฉลาดจะรู้สึกเท่ห์และทั้งหมด แต่นี่เป็นการใช้ในทางที่ผิดของคุณอย่างฉลาด
Ian Newson

108

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


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

อย่างไรก็ตามเนื่องจากคุณกำลังถามว่ามันสามารถทำได้ (และไม่ว่าควรจะทำ) ดังนั้นวิธีหนึ่งคือ:

class A:
    def __init__(self, **kwargs):
        for key in kwargs:
          setattr(self, key, kwargs[key])

a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2

16
คำตอบที่ดี +1 ... แม้ว่าself.__dict__.update(kwargs)อาจจะ pythonic เพิ่มเติมเล็กน้อย
Joran Beasley

44
ปัญหาของวิธีการนี้คือไม่มีการบันทึกว่าอาร์กิวเมนต์ใดที่A.__init__คาดว่าจะเกิดขึ้นจริงและไม่มีการตรวจสอบข้อผิดพลาดสำหรับชื่ออาร์กิวเมนต์ที่พิมพ์ผิด
MaxB

7
@JoranBeasley อัปเดตพจนานุกรมอินสแตนซ์สุ่มสี่สุ่มห้าโดยkwargsปล่อยให้คุณเปิดเท่ากับการโจมตี SQL injection หากวัตถุของคุณมีวิธีการตั้งชื่อmy_methodและคุณผ่านการโต้แย้งชื่อmy_methodที่กำหนดแล้วupdate()พจนานุกรมคุณเพียงแค่เขียนทับวิธี
เปโดร

3
ดังที่คนอื่น ๆ กล่าวว่าข้อเสนอแนะนั้นเป็นรูปแบบการเขียนโปรแกรมที่แย่มาก มันซ่อนข้อมูลที่สำคัญ คุณสามารถแสดงมันได้ แต่คุณควรกีดกัน OP อย่างชัดเจนจากการใช้มัน
Gerenuk

3
@Pedro มีความแตกต่างทางความหมายระหว่างไวยากรณ์ของ gruzczy และ JoranBeasley หรือไม่?
gerrit

29

ดังที่คนอื่น ๆ ได้กล่าวถึงการทำซ้ำไม่ได้เลวร้าย แต่ในบางกรณีการตั้งชื่อแบบชื่ออาจเหมาะกับปัญหาประเภทนี้ได้ วิธีนี้หลีกเลี่ยงการใช้คนในท้องถิ่น () หรือ kwargs ซึ่งโดยปกติจะเป็นความคิดที่ไม่ดี

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")

# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

ฉันได้พบการใช้งานที่ จำกัด สำหรับมัน แต่คุณสามารถสืบทอดชื่อวัตถุเช่นเดียวกับวัตถุอื่น ๆ (ตัวอย่างต่อ):

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z

abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()

5
สิ่งเหล่านี้เป็นสิ่งอันดับดังนั้นpropertiesวิธีการของคุณสามารถเขียนได้อย่างยุติธรรมreturn tuple(self)ซึ่งสามารถบำรุงรักษาได้มากขึ้นถ้าในอนาคตจะมีการเพิ่มเขตข้อมูลเพิ่มเติมลงในคำนิยาม namedtuple
PaulMcG

1
นอกจากนี้สตริงการประกาศ namedtuple ของคุณไม่จำเป็นต้องใช้เครื่องหมายจุลภาคระหว่างชื่อฟิลด์ใช้XYZ = namedtuple("XYZ", "x y z")งานได้เช่นกัน
PaulMcG

ขอบคุณ @PaulMcGuire ฉันพยายามนึกถึงส่วนเสริมที่เรียบง่ายจริงๆเพื่อแสดงการสืบทอดและชนิดของระยะห่างบนนั้น คุณถูก 100% และเป็นชวเลขที่ยอดเยี่ยมกับวัตถุที่สืบทอดอื่น ๆ ด้วย! ฉันพูดถึงชื่อฟิลด์สามารถคั่นด้วยเครื่องหมายจุลภาคหรือเว้นวรรค - ฉันชอบ CSV จากนิสัย
A Shell Shell Script

1
ฉันมักจะใช้namedtuples เพื่อจุดประสงค์ที่แน่นอนนี้โดยเฉพาะอย่างยิ่งในโค้ดทางคณิตศาสตร์ที่ฟังก์ชั่นอาจถูกทำให้เป็นเรื่องยากมากและมีสัมประสิทธิ์จำนวนมากที่เข้ากันได้ดี
detly

ปัญหาnamedtupleคือพวกเขาเป็นแบบอ่านอย่างเดียว คุณไม่สามารถทำabc.x += 1อะไรได้เลย
hamstergene

29

ชัดเจนดีกว่าโดยนัย ... ดังนั้นคุณต้องทำให้รัดกุมขึ้น:

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

คำถามที่ดีกว่าคือคุณ?

... ที่กล่าวว่าหากคุณต้องการ tuple ที่มีชื่อฉันอยากจะแนะนำให้ใช้ namedtuple (จำไว้ว่า tuples มีเงื่อนไขบางอย่างที่แนบมากับพวกเขา) ... บางทีคุณอาจต้องการ ordereddict หรือแม้แต่ dict ...


จากนั้นวัตถุจะต้องมีการรวบรวมขยะแบบวนเนื่องจากมันมีคุณสมบัติเป็นของตัวเอง
John La Rooy

3
@bernie (หรือว่า bemie?) บางครั้ง ke ring ยาก
แมว

4
สำหรับการทดสอบที่มีประสิทธิภาพมากขึ้นเล็กน้อยif k != "self":สามารถเปลี่ยนเป็นการif v is not self:ทดสอบตัวตนราคาถูกแทนการเปรียบเทียบสตริง ฉันคิดว่าในทางเทคนิค__init__อาจเรียกได้ว่าเป็นครั้งที่สองหลังจากการก่อสร้างและผ่านไปselfเป็นข้อโต้แย้งที่ตามมา แต่ฉันไม่อยากคิดว่าสัตว์ประหลาดประเภทไหนที่จะทำเช่นนั้น :-)
ShadowRanger

ที่อาจจะทำให้เป็นฟังก์ชั่นซึ่งจะมีค่าตอบแทนของ:locals set_fields_from_locals(locals())จากนั้นไม่นานกว่าโซลูชันจากมัณฑนากรที่มีมนต์ขลังมากขึ้น
Lii

20

เพื่อขยายgruszczyคำตอบของฉันได้ใช้รูปแบบเช่น:

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

ฉันชอบวิธีนี้เพราะ:

  • หลีกเลี่ยงการทำซ้ำ
  • ทนต่อการพิมพ์ผิดเมื่อสร้างวัตถุ
  • ทำงานได้ดีกับ subclass (สามารถทำได้super().__init(...))
  • อนุญาตให้มีการจัดทำเอกสารของคุณลักษณะในระดับชั้นเรียน (ที่พวกเขาอยู่) มากกว่าใน X.__init__

ก่อนหน้า Python 3.6 สิ่งนี้ไม่ให้การควบคุมลำดับที่แอททริบิวถูกตั้งค่าซึ่งอาจเป็นปัญหาหากแอททริบิวบางตัวเป็นคุณสมบัติที่มี setters ที่เข้าถึงแอททริบิวต์อื่น ๆ

มันอาจได้รับการปรับปรุงให้ดีขึ้นเล็กน้อย แต่ฉันเป็นผู้ใช้รหัสเดียวของฉันเองดังนั้นฉันจึงไม่กังวลเกี่ยวกับการสุขาภิบาลในรูปแบบใด ๆ บางทีอาจAttributeErrorจะเหมาะสมกว่า


10

คุณสามารถทำได้:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
    setattr(self, arg, locs[arg])

แน่นอนคุณจะต้องนำเข้าinspectโมดูล


8

นี่เป็นวิธีการแก้ปัญหาโดยไม่ต้องนำเข้าเพิ่มเติม

ฟังก์ชั่นตัวช่วย

ฟังก์ชั่นตัวช่วยขนาดเล็กทำให้สะดวกและใช้งานได้อีกครั้ง:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    self = local_name_space.pop('self')
    for name, value in local_name_space.items():
        setattr(self, name, value)

ใบสมัคร

คุณต้องโทรหาด้วยlocals():

class A:
    def __init__(self, x, y, z):
        auto_init(locals())

ทดสอบ

a = A(1, 2, 3)
print(a.__dict__)

เอาท์พุท:

{'y': 2, 'z': 3, 'x': 1}

โดยไม่ต้องเปลี่ยน locals()

หากคุณไม่ต้องการเปลี่ยนlocals()ใช้เวอร์ชันนี้:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    for name, value in local_name_space.items():
        if name != 'self': 
            setattr(local_name_space['self'], name, value)

docs.python.org/2/library/functions.html#locals locals()ไม่ควรแก้ไข (อาจส่งผลกระทบต่อล่ามในกรณีของคุณลบออกselfจากขอบเขตของฟังก์ชั่นการโทร)
MaxB

@MaxB จากเอกสารที่คุณอ้างถึง: ... การเปลี่ยนแปลงอาจไม่มีผลต่อค่าของตัวแปรท้องถิ่นและตัวแปรอิสระที่ใช้โดยล่าม ยังคงมีอยู่ในself __init__
Mike Müller

ถูกต้องผู้อ่านคาดหวังว่ามันจะส่งผลกระทบต่อตัวแปรท้องถิ่น แต่อาจหรือไม่ขึ้นอยู่กับสถานการณ์ ประเด็นก็คือว่ามันคือ UB
MaxB

อ้างอิง: "เนื้อหาของพจนานุกรมนี้ไม่ควรแก้ไข"
MaxB

@MaxB ฉันเพิ่มรุ่นที่ไม่เปลี่ยนแปลงคนในท้องถิ่น ()
Mike Müller

7

ห้องสมุดที่น่าสนใจที่จัดการ (และหลีกเลี่ยงจำนวนมากสำเร็จรูปอื่น ๆ ) เป็นattrs ตัวอย่างเช่นตัวอย่างของคุณสามารถลดลงได้ถึงสิ่งนี้ (ถือว่าคลาสเรียกว่าMyClass):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

คุณไม่จำเป็นต้องมี__init__วิธีการอีกต่อไปเว้นแต่จะทำสิ่งอื่นเช่นกัน นี่คือการแนะนำที่ดีโดย Glyph โควิส


ความสามารถในการattrทำซ้ำซ้อนในระดับdataclassesใด
gerrit

1
@gerrit นี้จะกล่าวถึงในเอกสารของแพคเกจ attrs ความแตกต่างนั้นดูไม่ใหญ่อีกต่อไปแล้ว
Ivo Merchiers

5

ของฉัน 0.02 $ มันใกล้กับคำตอบของ Joran Beasley แต่สง่างามยิ่งกว่า:

def __init__(self, a, b, c, d, e, f):
    vars(self).update((k, v) for k, v in locals().items() if v is not self)

นอกจากนี้คำตอบของ Mike Müller (คำตอบที่ดีที่สุดสำหรับฉัน) สามารถลดลงได้ด้วยเทคนิคนี้:

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)

และเพียงแค่โทรauto_init(locals())จากคุณ__init__


1
docs.python.org/2/library/functions.html#locals locals()ไม่ควรแก้ไข (พฤติกรรมที่ไม่ได้กำหนด)
MaxB

4

มันเป็นวิธีธรรมชาติในการทำสิ่งต่าง ๆ ใน Python อย่าพยายามประดิษฐ์สิ่งที่ฉลาดกว่านี้มันจะนำไปสู่รหัสที่ฉลาดเกินไปจนไม่มีใครในทีมของคุณจะเข้าใจ หากคุณต้องการที่จะเป็นผู้เล่นในทีมและจากนั้นให้เขียนมันด้วยวิธีนี้


4

Python 3.7 เป็นต้นไป

ใน Python 3.7 คุณสามารถใช้dataclassมัณฑนากรได้จากdataclassesโมดูล จากเอกสาร:

โมดูลนี้จะให้มัณฑนากรและฟังก์ชั่นสำหรับการเพิ่มวิธีพิเศษที่สร้างขึ้นโดยอัตโนมัติเช่น__init__()และ__repr__()ชั้นเรียนที่ผู้ใช้กำหนด เดิมอธิบายไว้ใน PEP 557

ตัวแปรสมาชิกที่ใช้ในวิธีการที่สร้างขึ้นเหล่านี้ถูกกำหนดโดยใช้คำอธิบายประกอบแบบ PEP 526 ตัวอย่างเช่นรหัสนี้:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

จะเพิ่มสิ่งอื่นที่มี__init__()ลักษณะเช่น:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

โปรดทราบว่าวิธีนี้จะถูกเพิ่มในชั้นเรียนโดยอัตโนมัติ: มันไม่ได้ระบุโดยตรงในคำจำกัดความ InventoryItem ที่แสดงด้านบน

ถ้าชั้นของคุณมีขนาดใหญ่และซับซ้อนก็อาจdataclassจะไม่เหมาะสมที่จะใช้ ฉันเขียนสิ่งนี้ในวันที่เปิดตัว Python 3.7.0 ดังนั้นรูปแบบการใช้งานจึงยังไม่เป็นที่ยอมรับ

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