Jak uniknąć " jaźni.x = X; self.y = y; self.Z = Z " wzorzec w init?

Widzę wzorce jak

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

Dość często, często o znacznie większej liczbie parametrów. Czy jest dobry sposób, aby uniknąć tego typu żmudnej powtarzalności? Czy klasa powinna dziedziczyć od namedtuple?

Author: Wilfredo Sánchez Vega, 2016-02-04

11 answers

Rozwiązanie dekoratorskie, które zachowuje podpis:

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__))
 89
Author: Siphor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-12 08:01:00

EDIT

Wydaje się, że kilka osób jest zaniepokojonych przedstawieniem tego rozwiązania, więc przedstawię bardzo jasne zastrzeżenie. Nie należy stosować tego roztworu. Podaję je tylko jako informacje, więc wiesz, że język jest do tego zdolny. Reszta odpowiedzi to po prostu pokazanie możliwości językowych, a nie popieranie korzystania z nich w ten sposób.

ORYGINALNA ODPOWIEDŹ

Nie ma nic złego w jawnym kopiowaniu parametrów do atrybuty. Jeśli masz zbyt wiele parametrów w ctor, jest to czasami uważane za zapach kodu i może powinieneś pogrupować te paramy w mniej obiektów. Innym razem jest to konieczne i nie ma w tym nic złego. w każdym razie, robienie tego wyraźnie jest drogą do zrobienia.

Jednak, skoro pytasz Jak to zrobić (a nie czy powinno być zrobione), to jedno rozwiązanie jest takie:

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
 104
Author: gruszczy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-05 03:10:45

Jak już wspominali inni, powtórzenie nie jest złe, ale w niektórych przypadkach namedtuple może być świetnym rozwiązaniem dla tego typu kwestii. Pozwala to uniknąć używania locals() lub kwargs, które są zazwyczaj złym pomysłem.

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

Znalazłem dla niego ograniczone zastosowanie, ale można dziedziczyć nazwaple jak w przypadku każdego innego obiektu (przykład kontynuowany):

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()
 29
Author: A Small Shell Script,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-04 02:53:43

Explicit jest lepszy niż implicit ... więc na pewno można by to bardziej zwięzłe:

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

Lepszym pytaniem jest czy powinieneś?

... to powiedziawszy, jeśli chcesz nazwaną krotkę, polecam użycie namedtuple (pamiętaj, że krotki mają pewne warunki dołączone do nich)... być może chcesz zamówić dict, a nawet po prostu dict ...

 28
Author: Joran Beasley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-29 11:36:23

Aby rozwinąć odpowiedź gruszczy, użyłem wzorca w stylu:

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

Podoba mi się ta metoda, ponieważ:

  • unika powtarzania
  • jest odporny na literówki podczas konstruowania obiektu
  • działa dobrze z podklasą (może po prostu super().__init(...))
  • pozwala na dokumentowanie atrybutów na poziomie klasy (gdzie należą), a nie w X.__init__

Przed Pythonem 3.6, nie daje to kontroli nad kolejnością ustawiania atrybutów, co może być problemem, jeśli niektóre atrybuty są właściwościami z ustawiaczami, które mają dostęp do innych atrybutów.

Prawdopodobnie da się to nieco poprawić, ale jestem jedynym użytkownikiem własnego kodu, więc nie martwię się żadną formą wprowadzania danych. Być może AttributeError byłoby bardziej odpowiednie.

 20
Author: gerrit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-13 11:17:43

Można też zrobić:

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

Oczywiście trzeba by zaimportować moduł inspect.

 10
Author: zondo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-04 01:35:38

Jest to rozwiązanie bez dodatkowego importu.

Funkcja pomocnicza

Mała funkcja pomocnicza sprawia, że jest wygodniejsza i bardziej użyteczna:

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)

Zastosowanie

Musisz zadzwonić za pomocą locals():

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

Test

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

Wyjście:

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

Bez zmiany locals()

Jeśli nie lubisz zmieniać locals() Użyj tej wersji:

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)
 8
Author: Mike Müller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-11 03:08:53

Moje 0,02$. Jest bardzo zbliżony do Jorana Beasleya, ale bardziej elegancki:

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)

Dodatkowo, odpowiedź Mike ' a Müllera (najlepsza w moim guście) można zredukować tą techniką:

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

I po prostu zadzwoń auto_init(locals()) z twojego __init__

 5
Author: bgusach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-11 08:21:22

Ciekawą biblioteką, która zajmuje się tym (i unika wielu innych kotłów) jestattrs . Twój przykład, na przykład, może być zredukowany do tego (Załóżmy, że Klasa nazywa się MyClass):

import attr

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

Nie potrzebujesz już nawet metody __init__, chyba że robi to również inne rzeczy. Oto miłe wprowadzenie Glif Lefkowitz .

 5
Author: RafG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-06 10:04:24

To naturalny sposób robienia rzeczy w Pythonie. Nie próbuj wymyślać czegoś bardziej sprytnego, to doprowadzi do zbyt sprytnego kodu, którego nikt z twojego zespołu nie zrozumie. Jeśli chcesz być graczem zespołowym, a następnie pisać to w ten sposób.

 3
Author: Peter Krumins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-21 01:35:46

Python 3.7 dalej

W Pythonie 3.7 możesz (ab)użyć dataclass dekorator, dostępny z modułu dataclasses. Z dokumentacji:

Ten moduł zapewnia dekorator i funkcje do automatycznego dodawania wygenerowanych specjalnych metod, takich jak __init__() i __repr__() do zdefiniowanych przez użytkownika klas. Został pierwotnie opisany w PEP 557.

Zmienne członkowskie używane w tych wygenerowanych metodach są definiowane za pomocą adnotacji typu PEP 526. Na przykład ten 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

Doda między innymi __init__(), który wygląda następująco:

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

Zauważ, że ta metoda jest automatycznie dodawana do klasy: nie jest bezpośrednio określona w definicji InventoryItem pokazanej powyżej.

Jeśli twoja klasa jest duża i złożona, Może być nieodpowiednie, aby używać dataclass. Piszę to w dniu wydania Pythona 3.7.0, więc wzorce użytkowania nie są jeszcze dobrze ugruntowane.

 1
Author: gerrit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-27 14:03:24