“Self.x = x; self.y = y; self.z = z ”desen __init__?


170

Gibi desenler görüyorum

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

oldukça sık, genellikle çok daha fazla parametre ile. Bu tür sıkıcı tekrarlamalardan kaçınmanın iyi bir yolu var mı? Sınıf namedtuplebunun yerine miras almalı ?


31
Tüm alıcılık kötü değildir. Python'un sınıf modelinin örnek niteliklerinin açık tanımını içermediğini unutmayın, bu nedenle bu atamalar kendi kendini belgeleyen eşdeğerlerdir.
chepner

4
@ chepner: Açık bir tanım gerektirmez . __slots__Yine de amaç için kullanabilirsiniz ; hafif unpythonic (bellek tasarrufu elde etmek için daha ayrıntılı), ancak adı yazdığımda tamamen yeni bir özelliği otomatik olarak canlandırma riskinden kaçınmaktan büyük ölçüde hoşlanıyorum.
ShadowRanger

2
İyi bir düzenleyicinin şablonları olacaktır. Siz yazın ini <shortcut> x, y, z): <shortcut>ve işiniz bitti.
Gerenuk

3
Namedtuples, müthiş eğer bir değişmez değer nesnesi istiyorum. Eğer düzenli ve değişebilir bir sınıf istiyorsanız, onları kullanamazsınız.
RemcoGerlich

4
"Yapma" iyi bir seçenektir, mevcut herhangi bir seçenek yöntem imzasını (ve dolayısıyla potansiyel olarak tüm arabirimi) öldürür. Ayrıca, sınıflarınızda başlatılacak dayanılmaz miktarda alan varsa, bunları bölmeyi düşünebilirsiniz.
Kroltan

Yanıtlar:


87

Düzenle: piton varsa 3.7+ sadece kullanmak dataclasses

İmzayı saklayan bir dekoratör çözümü:

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
güzel cevap, ama signature
python2.7

3
@alexis "decorator.decorator" dekoratör işlevi otomatik olarak sarar
Siphor

4
Bunu sevip sevmeme ya da nefret edip etmeme konusunda oldukça sıkıldım. İmzayı korumayı takdir ediyorum.
Kyle Strand

14
“... Açık, örtük olmaktan daha iyidir. Basit, karmaşık olmaktan daha iyidir. ...” -Python'dan Zen
Jack Stout

9
-1 Açıkçası bu korkunç. Bu kodun bir bakışta ne yaptığı hakkında hiçbir fikrim yok ve tam anlamıyla on kat kod miktarı. Zeki olmak havalı ve her şeyden hoşlanır, ancak bu açık zekasının bir yanlış kullanımıdır.
Ian Newson

108

Yasal Uyarı: Birkaç kişi bu çözümü sunmaktan endişe duyuyor gibi görünüyor, bu yüzden çok açık bir sorumluluk reddi sunacağım. Bu çözümü kullanmamalısınız. Bunu sadece bilgi olarak sağlıyorum, böylece dilin bunu yapabildiğini biliyorsunuz. Cevabın geri kalanı sadece dil yeteneklerini gösteriyor, onları bu şekilde kullanmayı desteklemiyor.


Parametreleri özniteliklere açıkça kopyalamanın hiçbir yanlış tarafı yoktur. Ctor'da çok fazla parametreniz varsa, bazen bir kod kokusu olarak kabul edilir ve belki de bu parametreleri daha az nesneye gruplamanız gerekir. Diğer zamanlarda, gerekli ve yanlış bir şey yok. Her neyse, bunu açıkça yapmak bir yol.

Bununla birlikte, NASIL yapılabileceğini (ve yapılmasının gerekip gerekmediğini) sorduğunuz için, bir çözüm şudur:

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
iyi cevap +1 ... her self.__dict__.update(kwargs)ne kadar marjinal olarak daha pitonik olabilir
Joran Beasley

44
Bu yaklaşımdaki sorun, argümanların A.__init__gerçekte ne beklediğine dair bir kayıt olmaması ve yanlış yazılan argüman isimleri için hata denetimi yapılmamasıdır.
MaxB

7
@JoranBeasley Örnek sözlüğünü körü körüne güncelleyerek kwargssizi SQL enjeksiyon saldırısına eşdeğer bırakır. Nesnenizde bir yöntem varsa my_methodve yapıcıya my_method, sonra update()sözlüğe bir argüman iletirseniz, yöntemin üzerine yazmış olursunuz.
Pedro

3
Diğerlerinin söylediği gibi, öneri gerçekten zayıf programlama tarzı. Önemli bilgileri gizler. Gösterebilirsiniz, ancak OP'yi kullanmaktan açıkça vazgeçirmelisiniz.
Gerenuk

3
@Pedro gruzczy's ve JoranBeasley sözdizimi arasında herhangi bir anlamsal fark var mı?
gerrit

29

Diğerlerinin de belirttiği gibi, tekrarlama kötü değildir, ancak bazı durumlarda adlandırılmış bir grup bu tür bir sorun için çok uygun olabilir. Bu, genellikle kötü bir fikir olan locals () veya kwarg'ları kullanmaktan kaçınır.

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

Bunun için sınırlı kullanım buldum, ancak başka bir nesneyle olduğu gibi bir adlandırılmış dizini miras alabilirsiniz (örnek devam):

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
Bunlar şunlardır senin böylece, küpe propertiesyöntem olarak sadece yazılabilir return tuple(self)gelecekte daha fazla alanlar namedtuple tanımına eklenirse daha sürdürülebilir olan.
PaulMcG

1
Ayrıca, adlandırılmış iki bildirim dizeniz alan adları arasında virgül gerektirmez, aynı şekilde XYZ = namedtuple("XYZ", "x y z")çalışır.
PaulMcG

Teşekkürler @PaulMcGuire. Kalıtımını ve çeşitliliğini göstermek için gerçekten basit bir eklenti düşünmeye çalışıyordum. % 100 haklısınız ve diğer kalıtsal nesnelerle de harika bir steno! Alan adlarının virgül veya boşlukla ayrılmış olabileceğinden bahsediyorum - CSV'yi alışkanlıktan tercih ederim
Küçük Kabuk Betiği

1
Ben genellikle namedtuplebu işlevi tam olarak kullanıyorum , özellikle de bir fonksiyonun oldukça parametrelenebileceği ve sadece birlikte anlamlı olan bir grup katsayıya sahip olabileceği matematiksel kodda.
detly

Sorun namedtuple, salt okunur olmalarıdır. Yapamazsın abc.x += 1ya da böyle bir şey.
hamstergene

29

açık, örtük olmaktan daha iyidir ... bu yüzden daha kısa ve öz olabilirsiniz:

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

Daha iyi bir soru olmalı?

... adlandırılmış bir tuple istiyorsanız, adlandırılmış bir demet kullanmanızı tavsiye ederim (tuples'in kendilerine bağlı belirli koşullara sahip olduğunu unutmayın) ... belki de sipariş edilen bir karar veya hatta sadece bir dikte istersiniz ...


Öyleyse, nesnenin kendine has bir özelliği olduğu için döngüsel çöp toplama ihtiyacı olacaktır
John La Rooy

3
@bernie (ya da bemie?), bazen ke r ning zordur
kedi

4
Biraz daha verimli testler if k != "self":için if v is not self:, dize karşılaştırması yerine ucuz kimlik testi olarak değiştirilebilir . Teknik __init__olarak inşaattan sonra ikinci kez çağrılabilir ve selfdaha sonraki bir argüman olarak geçebilirim , ama gerçekten ne tür bir canavarın bunu yapacağını düşünmek istemiyorum. :-)
ShadowRanger

Yani dönüş değerini alan bir fonksiyon içine yapılmış olabilir locals: set_fields_from_locals(locals()). O zaman daha büyülü dekoratör tabanlı çözümlerden daha uzun değildir.
Lii

20

gruszczyCevabını genişletmek için şöyle bir desen kullandım:

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

Bu yöntemi seviyorum çünkü:

  • tekrardan kaçınır
  • bir nesne inşa ederken yazım hatalarına karşı dayanıklıdır
  • alt sınıflama ile iyi çalışır (sadece olabilir super().__init(...))
  • niteliklerin sınıf yerine (ait oldukları yerde) belgelenmesine izin verir X.__init__

Python 3.6'dan önce, bu, özelliklerin ayarlanma sırası üzerinde herhangi bir denetim sağlamaz; bu, bazı özellikler diğer özelliklere erişen ayarlayıcılara sahip özellikler ise sorun olabilir.

Muhtemelen biraz daha geliştirilebilir, ancak kendi kodumun tek kullanıcısıyım, bu yüzden herhangi bir giriş sanitasyonu hakkında endişelenmiyorum. Belki AttributeErrordaha uygun olur.


10

Ayrıca şunları da yapabilirsiniz:

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

Tabii ki, inspectmodülü içe aktarmanız gerekir .


8

Bu, ek ithalatı olmayan bir çözümdür.

Yardımcı işlevi

Küçük bir yardımcı işlevi onu daha rahat ve yeniden kullanılabilir hale getirir:

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)

Uygulama

Şununla aramalısınız locals():

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

Ölçek

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

Çıktı:

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

Değişmeden locals()

Değiştirmek istemiyorsanız locals()bu sürümü kullanın:

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() değiştirilmemelidir (sizin durumunuzda yorumlayıcıyı etkileyebilir self, arama işlevinin kapsamından çıkarılması)
MaxB

Eğer alıntı Dokümanlar @MaxB: ... değişiklikler olabilir değil yorumlayıcı tarafından kullanılan yerel ve ücretsiz değişkenlerin değerlerini etkiler. selfadresinde hâlâ kullanılabilir __init__.
Mike Müller

Doğru, okuyucu lokal değişkenler etkiler bekler ama o olabilir veya olabilir değil , koşullar, bir dizi bağlı olarak değişir. Mesele şu ki UB.
MaxB

Alıntı: "Bu sözlüğün içeriği değiştirilmemelidir"
MaxB

@ MaxB Yerel ayarları değiştirmeyen bir sürüm ekledim ().
Mike Müller

7

Bunu ele alan (ve diğer birçok kazan plakasından kaçınan) ilginç bir kütüphane attrs . Örneğin, örneğiniz buna indirgenebilir (sınıfın çağrıldığını varsayın MyClass):

import attr

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

__init__Başka şeyler yapmadıkça artık bir yönteme bile ihtiyacınız yok. İşte Glyph Lefkowitz tarafından güzel bir tanıtım .


attrGereksiz hale getirme işlevselliği ne ölçüde dataclasses?
gerrit

1
@gerrit Bu, attrs paketinin belgelerinde tartışılmıştır . Tbh, farklar artık o kadar büyük görünmüyor.
Ivo Merchiers

5

Benim 0.02 $. Joran Beasley cevabına çok yakın, ancak daha zarif:

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)

Ek olarak, Mike Müller'in yanıtı (zevkime en iyi cevap) bu teknikle azaltılabilir:

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

Ve sadece auto_init(locals())senin__init__


1
docs.python.org/2/library/functions.html#locals locals() değiştirilmemelidir (tanımlanmamış davranış)
MaxB

4

Python'da bir şeyler yapmanın doğal bir yolu. Daha akıllıca bir şey icat etmeye çalışmayın, bu takımınızdaki kimsenin anlayamayacağı aşırı zekice kodlara yol açacaktır. Takım oyuncusu olmak ve daha sonra bu şekilde yazmaya devam etmek istiyorsanız.


4

Python 3.7'den itibaren

Python 3.7'de, (ab) modülde bulunan dataclassdekoratörü kullanabilirsiniz dataclasses. Belgelerden:

Bu modül gibi otomatik olarak eklenmesi özel yöntemleri için bir dekoratör ve fonksiyonları sağlar __init__()ve __repr__()kullanıcı tarafından tanımlanan sınıflara. Başlangıçta PEP 557'de tarif edilmiştir.

Bu üretilen yöntemlerde kullanılacak üye değişkenler PEP 526 tipi ek açıklamalar kullanılarak tanımlanır. Örneğin bu kod:

@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

Diğer şeylerin yanı sıra, __init__()aşağıdakine benzeyen bir ekleyecektir :

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

Bu yöntemin sınıfa otomatik olarak eklendiğini unutmayın: yukarıda gösterilen InventoryItem tanımında doğrudan belirtilmez.

Sınıf büyük ve karmaşık ise, olabilecek bir kullanımı uygun olmayabilir dataclass. Bunu Python 3.7.0 yayımlandığı gün yazıyorum, bu yüzden kullanım kalıpları henüz iyi kurulmamış.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.