Örnek değişkenleri otomatik olarak başlatılsın mı?


90

Şuna benzeyen bir python sınıfım var:

class Process:
    def __init__(self, PID, PPID, cmd, FDs, reachable, user):

bunu takiben:

        self.PID=PID
        self.PPID=PPID
        self.cmd=cmd
        ...

C ++ 'ın başlatma listesi gibi, bu örnek değişkenlerini otomatik olarak başlatmanın herhangi bir yolu var mı? Fazladan çok sayıda kodu saklar.


1
Ayrıca aktif durum autoassigntarifi tartışmasına ve alternatif bir autoargsuygulamaya bakın: Python'da otomatik öznitelik ataması yapmanın en iyi yolu nedir ve bu iyi bir fikir mi? - Stack Overflow
nealmcb

Yanıtlar:


104

Bir dekoratör kullanabilirsiniz:

from functools import wraps
import inspect

def initializer(func):
    """
    Automatically assigns the parameters.

    >>> class process:
    ...     @initializer
    ...     def __init__(self, cmd, reachable=False, user='root'):
    ...         pass
    >>> p = process('halt', True)
    >>> p.cmd, p.reachable, p.user
    ('halt', True, 'root')
    """
    names, varargs, keywords, defaults = inspect.getargspec(func)

    @wraps(func)
    def wrapper(self, *args, **kargs):
        for name, arg in list(zip(names[1:], args)) + list(kargs.items()):
            setattr(self, name, arg)

        for name, default in zip(reversed(names), reversed(defaults)):
            if not hasattr(self, name):
                setattr(self, name, default)

        func(self, *args, **kargs)

    return wrapper

__init__Yöntemi süslemek için kullanın :

class process:
    @initializer
    def __init__(self, PID, PPID, cmd, FDs, reachable, user):
        pass

Çıktı:

>>> c = process(1, 2, 3, 4, 5, 6)
>>> c.PID
1
>>> dir(c)
['FDs', 'PID', 'PPID', '__doc__', '__init__', '__module__', 'cmd', 'reachable', 'user'

5
Bu işe yarıyor ve soruyu cevaplıyor, ben de oy verdim. Ama Ferdidand Beyer'in cevabını tuttum: "Açık olmak örtük olmaktan iyidir"
Lucas Gabriel Sánchez

14
+1 Sorunumu çözen harika bir cevap için. Ancak dilin temel bir işlevi olması gerekmez mi? Bir PEP yazmaya değer mi sizce?
Adam Matan

3
Bu gerçekten iyi bir cevap - bu doğrudan alet çantama girdi.
Michael van der Westhuizen

3
@NadiaAlramli Kodda küçük bir hata olduğunu düşünüyorum. Buraya bakın gist.github.com/pmav99/137dbf4681be9a58de74 (original.py)
pmav99

2
Mevcut örnekte bir hata vardır ve imza varsayılan argümanlar içermiyorsa çalışmayacaktır. Adların ve varsayılanların Hiçbiri olmadığından emin olmak için bir kontrol eklemeniz gerekir. Ör: isimler ve varsayılanlar:

36

Python 2.6 veya üstünü kullanıyorsanız, şunu kullanabilirsiniz: collections.namedtuple'ı :

>>> from collections import namedtuple
>>> Process = namedtuple('Process', 'PID PPID cmd')
>>> proc = Process(1, 2, 3)
>>> proc.PID
1
>>> proc.PPID
2

Bu, özellikle sınıfınız gerçekten büyük bir değerler torbası olduğunda uygundur.


2
"Bu, özellikle sınıfınız gerçekten büyük bir değerler torbası olduğunda uygundur." Böyle bir senaryoda, şunu da yapabilirsiniz: https://docs.python.org/3.3/tutorial/classes.html#odds-and-ends
Big Sharpie

35

Python 3.7+ için bir Veri Sınıfı kullanabilirsiniz istediğinizi yapmanın oldukça pitonik ve sürdürülebilir bir yolu olan .

Tanımlamanıza izin verir AlanlarıOtomatik olarak başlatılan örnek değişkenleriniz olan sınıfınız için .

Şöyle bir şeye benzeyecekti:

@dataclass
class Process:
    PID: int
    PPID: int
    cmd: str
    ...

__init__Yöntem zaten sınıfta olacak.

Burada tip ipucu gerektiğine dikkat edin , bu yüzden kullandım intve strörnekte. Alanınızın türünü bilmiyorsanız, Herhangi birini kullanabilirsiniz .typing modülden .

Veri Sınıfı, önerilen çözümlere kıyasla birçok avantaja sahiptir:

  • Öyle açık : tüm alanların Python Zen saygı duyduğunu ve bunun okunabilir ve sürdürülebilir hale getiren görebilir. Kullanımıyla karşılaştırın **kwargs.
  • Yöntemleri olabilir . Tıpkı diğer sınıflar gibi.
  • Yöntemi __init__kullanarak otomatikin ötesine geçmenizi sağlar __post_init__.

DÜZENLEME: NamedTuples kullanmaktan kaçınmak için nedenler

Bazıları namedtuplebu durum için kullanımını önermektedir , ancak adlandırılmış çiftlerin Python sınıflarından farklı bazı davranışları vardır, bunlar ilk bakışta pek belirgin değildir ve iyi bilinmelidir:

1. NamedTuple'lar değişmezdir

Değişmezlik yararlı olabilir, ancak örnekleriniz için istediğiniz şey olmayabilir. Ayrıca frozen=True, @dataclassdekoratördeki argümanı kullanırsanız, DataClasses bir şekilde değişmez olabilir .

2. NamedTuples, Tuple'ınki __eq__gibi davranır

Python'da, SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2)öyle True! __eq__Karşılaştırmalarda kullanılan NamedTuple işlevi, sadece, kıyasla örneklerinde değil onların sınıfının veya alanları, isimleri değerleri ile bu değerler konumlarını göz önünde bulundurur.


Bu, yalnızca sınıfın amacı verileri depolamaksa kullanılmalıdır.
JC Rocamonde

Veya veri depolama etrafında gelişmek için.
JC Rocamonde

3
Veri sınıfının neden başka davranışlara sahip olmak yerine yalnızca veri depolayan sınıflar için kullanılması gerektiğini açıklar mısınız? Uygun kullanım durumlarını daha iyi anlamak için bunun için yeni bir SO yayını oluşturabilirim. Teşekkürler.
Vahid Pazirandeh

Data Classes can be thought of as "mutable namedtuples with defaults". - PEP557
aafulei

26

Python'un Zen'inden alıntı yaparak ,

Açık, örtük olmaktan daha iyidir.


10
Bir başlatma listesi beyanı yeterince açık olmaz mıydı?
Adam Matan

Sanırım. Ama dile böyle bir şey eklemek için bir neden göremiyorum. Sahne arkasındaki bir tür dekoratör büyüsü yerine açıkça birden fazla atama ifadesini tercih ediyorum.
Ferdinand Beyer

30
@Ferdinand, dilde stdlib'de mükemmel bir şekilde olabilecek bir şeye sahip olmanın aptalca olacağını kabul ediyorum, ancak stdlib'de OLMALIDIR, çünkü "güzel çirkinden iyidir" öncelik taşır ve birçok tekrarlayan görev çirkin (herhangi bir tekrar biçimi gibi).
Alex Martelli

Karşılaşmak için: DWIM> DWIS
Terrence Brannon

Dekoratörün bir görevler listesinden daha güzel olduğuna katılıyorum, ancak PyCharm bunu anlamayarak daha çirkin hale getiriyor :-(
user110954

24

Yapabileceğiniz başka bir şey:

class X(object):
    def __init__(self, a,b,c,d):
        vars = locals() # dict of local names
        self.__dict__.update(vars) # __dict__ holds and object's attributes
        del self.__dict__["self"] # don't need `self`

Ama sadece hecelemenin yanı sıra önereceğim tek çözüm "editörünüzde bir makro oluşturun" ;-p


1
'Kendini' silmenin iyi bir yolu.
michael

15

Bunu anahtar kelime argümanları ile kolaylıkla yapabilirsiniz, örneğin aşağıdaki gibi:

>>> class D:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

>>> D(test='d').test
'd'

Konumsal argümanlar için benzer uygulama şöyle olacaktır:

>> class C:
    def __init__(self, *args):
        self.t, self.d = args


>>> C('abc', 'def').t
'abc'
>>> C('abc', 'def').d
'def'

ki bana sorununuzu çözmüyor gibi görünüyor.


3
Sevdiğim başka bir varyasyon daself.__dict__.update( **kwargs )
S.Lott

Localals () kullanabilir ve normal argümanlar koyabilir.
mk12

7

Nadia'nın çözümü daha iyi ve daha güçlü, ancak bunun da ilginç olduğunu düşünüyorum:

def constructor(*arg_names):
  def __init__(self, *args):
    for name, val in zip(arg_names, args):
      self.__setattr__(name, val)
  return __init__


class MyClass(object):
  __init__ = constructor("var1", "var2", "var3")


>>> c = MyClass("fish", "cheese", "beans")
>>> c.var2
"cheese"

5

Aynı amaç için bir şeye ihtiyacım vardı, ancak mevcut cevapların hiçbiri test ettiğim tüm vakaları kapsamıyordu. Nadia'nın cevabı aradığıma en yakın olanıydı, bu yüzden onun kodunu temel alarak başladım.

Aşağıdaki dekoratör, tüm geçerli bağımsız değişken kombinasyonlarıyla çalışır:

Positional                                          __init__(self, a, b                )
Keyword                                             __init__(self, a=None, b=None      )
Positional + Keyword                                __init__(self, a, b, c=None, d=None)
Variable Positional                                 __init__(self, *a                  )
Variable Positional + Keyword                       __init__(self, *a, b=None          )
Variable Positional + Variable Keyword              __init__(self, *a, **kwargs        )
Positional + Variable Positional + Keyword          __init__(self, a, *b, c=None       )
Positional + Variable Positional + Variable Keyword __init__(self, a, *b, **kwargs     )
Keyword Only                                        __init__(self, *, a=None           )
Positional + Keyword Only                           __init__(self, a, *, b=None        )

Ayrıca , sınıf örneklerine atanmayacak -özel değişkenlere _izin vermek için standart -önek kuralını uygular __init__.


###  StdLib  ###
from functools import wraps
import inspect


###########################################################################################################################
#//////|   Decorator   |//////////////////////////////////////////////////////////////////////////////////////////////////#
###########################################################################################################################

def auto_assign_arguments(function):

  @wraps(function)
  def wrapped(self, *args, **kwargs):
    _assign_args(self, list(args), kwargs, function)
    function(self, *args, **kwargs)

  return wrapped


###########################################################################################################################
#//////|   Utils   |//////////////////////////////////////////////////////////////////////////////////////////////////////#
###########################################################################################################################

def _assign_args(instance, args, kwargs, function):

  def set_attribute(instance, parameter, default_arg):
    if not(parameter.startswith("_")):
      setattr(instance, parameter, default_arg)

  def assign_keyword_defaults(parameters, defaults):
    for parameter, default_arg in zip(reversed(parameters), reversed(defaults)):
      set_attribute(instance, parameter, default_arg)

  def assign_positional_args(parameters, args):
    for parameter, arg in zip(parameters, args.copy()):
      set_attribute(instance, parameter, arg)
      args.remove(arg)

  def assign_keyword_args(kwargs):
    for parameter, arg in kwargs.items():
      set_attribute(instance, parameter, arg)
  def assign_keyword_only_defaults(defaults):
    return assign_keyword_args(defaults)

  def assign_variable_args(parameter, args):
    set_attribute(instance, parameter, args)

  POSITIONAL_PARAMS, VARIABLE_PARAM, _, KEYWORD_DEFAULTS, _, KEYWORD_ONLY_DEFAULTS, _ = inspect.getfullargspec(function)
  POSITIONAL_PARAMS = POSITIONAL_PARAMS[1:] # remove 'self'

  if(KEYWORD_DEFAULTS     ): assign_keyword_defaults     (parameters=POSITIONAL_PARAMS,  defaults=KEYWORD_DEFAULTS)
  if(KEYWORD_ONLY_DEFAULTS): assign_keyword_only_defaults(defaults=KEYWORD_ONLY_DEFAULTS                          )
  if(args                 ): assign_positional_args      (parameters=POSITIONAL_PARAMS,  args=args                )
  if(kwargs               ): assign_keyword_args         (kwargs=kwargs                                           )
  if(VARIABLE_PARAM       ): assign_variable_args        (parameter=VARIABLE_PARAM,      args=args                )


###########################################################################################################################$#//////|   Tests   |//////////////////////////////////////////////////////////////////////////////////////////////////////#$###########################################################################################################################$$if __name__ == "__main__":$$#######|   Positional   |##################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b):$      pass$$  t = T(1, 2)$  assert (t.a == 1) and (t.b == 2)$$#######|   Keyword   |#####################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a="KW_DEFAULT_1", b="KW_DEFAULT_2"):$      pass$$  t = T(a="kw_arg_1", b="kw_arg_2")$  assert (t.a == "kw_arg_1") and (t.b == "kw_arg_2")$$#######|   Positional + Keyword   |########################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b, c="KW_DEFAULT_1", d="KW_DEFAULT_2"):$      pass$$  t = T(1, 2)$  assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "KW_DEFAULT_2")$$  t = T(1, 2, c="kw_arg_1")$  assert (t.a == 1) and (t.b == 2) and (t.c == "kw_arg_1") and (t.d == "KW_DEFAULT_2")$$  t = T(1, 2, d="kw_arg_2")$  assert (t.a == 1) and (t.b == 2) and (t.c == "KW_DEFAULT_1") and (t.d == "kw_arg_2")$$#######|   Variable Positional   |#########################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a):$      pass$$  t = T(1, 2, 3)$  assert (t.a == [1, 2, 3])$$#######|   Variable Positional + Keyword   |###############################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a, b="KW_DEFAULT_1"):$      pass$$  t = T(1, 2, 3)$  assert (t.a == [1, 2, 3]) and (t.b == "KW_DEFAULT_1")$$  t = T(1, 2, 3, b="kw_arg_1")$  assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1")$$#######|   Variable Positional + Variable Keyword   |######################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *a, **kwargs):$      pass$$  t = T(1, 2, 3, b="kw_arg_1", c="kw_arg_2")$  assert (t.a == [1, 2, 3]) and (t.b == "kw_arg_1") and (t.c == "kw_arg_2")$$#######|   Positional + Variable Positional + Keyword   |##################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *b, c="KW_DEFAULT_1"):$      pass$$  t = T(1, 2, 3, c="kw_arg_1")$  assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1")$$#######|   Positional + Variable Positional + Variable Keyword   |#########################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *b, **kwargs):$      pass$$  t = T(1, 2, 3, c="kw_arg_1", d="kw_arg_2")$  assert (t.a == 1) and (t.b == [2, 3]) and (t.c == "kw_arg_1") and (t.d == "kw_arg_2")$$#######|   Keyword Only   |################################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, *, a="KW_DEFAULT_1"):$      pass$$  t = T(a="kw_arg_1")$  assert (t.a == "kw_arg_1")$$#######|   Positional + Keyword Only   |###################################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, *, b="KW_DEFAULT_1"):$      pass$$  t = T(1)$  assert (t.a == 1) and (t.b == "KW_DEFAULT_1")$$  t = T(1, b="kw_arg_1")$  assert (t.a == 1) and (t.b == "kw_arg_1")$$#######|   Private __init__ Variables (underscored)   |####################################################################$$  class T:$    @auto_assign_arguments$    def __init__(self, a, b, _c):$      pass$$  t = T(1, 2, 3)$  assert hasattr(t, "a") and hasattr(t, "b") and not(hasattr(t, "_c"))

Not:

Testleri dahil ettim, ancak kısa olması için son satıra ( 58 ) ayırdım. Tüm olası kullanım durumlarını detaylandıran testleri, find/replacetüm $karakterleri yeni satır ile genişletebilirsiniz .


3

Locals () zaten değerleri içerdiğinden, değişkenleri başlatmaya gerek olmayabilir!

sınıf Dummy (nesne):

def __init__(self, a, b, default='Fred'):
    self.params = {k:v for k,v in locals().items() if k != 'self'}

d = Sahte (2, 3)

d.params

{'a': 2, 'b': 3, 'varsayılan': 'Fred'}

d.params ['b']

3

Tabii ki, bir sınıf içinde self.params kullanılabilir.


Bu güzel ve orijinal bir yaklaşım, ancak d['b']Python'un ortak dili ile yazılmış olsa d.params['b']da kod okuyucular için kafa karışıklığına neden olacaktır.
Adam Matan

3

Yakında Olarak getargspecPython 3.5 beri itiraz edildi, burada kullanarak çözüm inspect.signature:

from inspect import signature, Parameter
import functools


def auto_assign(func):
    # Signature:
    sig = signature(func)
    for name, param in sig.parameters.items():
        if param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
            raise RuntimeError('Unable to auto assign if *args or **kwargs in signature.')
    # Wrapper:
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        for i, (name, param) in enumerate(sig.parameters.items()):
            # Skip 'self' param:
            if i == 0: continue
            # Search value in args, kwargs or defaults:
            if i - 1 < len(args):
                val = args[i - 1]
            elif name in kwargs:
                val = kwargs[name]
            else:
                val = param.default
            setattr(self, name, val)
        func(self, *args, **kwargs)
    return wrapper

Çalışıp çalışmadığını kontrol edin:

class Foo(object):
    @auto_assign
    def __init__(self, a, b, c=None, d=None, e=3):
        pass

f = Foo(1, 2, d="a")
assert f.a == 1
assert f.b == 2
assert f.c is None
assert f.d == "a"
assert f.e == 3

print("Ok")

2

Python 3.3+ için:

from functools import wraps
from inspect import Parameter, signature


def instance_variables(f):
    sig = signature(f)
    @wraps(f)
    def wrapper(self, *args, **kwargs):
        values = sig.bind(self, *args, **kwargs)
        for k, p in sig.parameters.items():
            if k != 'self':
                if k in values.arguments:
                    val = values.arguments[k]
                    if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
                        setattr(self, k, val)
                    elif p.kind == Parameter.VAR_KEYWORD:
                        for k, v in values.arguments[k].items():
                            setattr(self, k, v) 
                else:
                    setattr(self, k, p.default) 
    return wrapper

class Point(object):
    @instance_variables 
    def __init__(self, x, y, z=1, *, m='meh', **kwargs):
        pass

Demo:

>>> p = Point('foo', 'bar', r=100, u=200)
>>> p.x, p.y, p.z, p.m, p.r, p.u
('foo', 'bar', 1, 'meh', 100, 200)

Çerçeve kullanan hem Python 2 hem de 3 için dekoratör olmayan bir yaklaşım:

import inspect


def populate_self(self):
    frame = inspect.getouterframes(inspect.currentframe())[1][0]
    for k, v in frame.f_locals.items():
        if k != 'self':
            setattr(self, k, v)


class Point(object):
    def __init__(self, x, y):
        populate_self(self)

Demo:

>>> p = Point('foo', 'bar')
>>> p.x
'foo'
>>> p.y
'bar'

1

nu11ptr , bu işlevi bir işlev dekoratörü olarak içeren küçük bir modül olan PyInstanceVars'ı yaptı . Modülün README'sinde " [...] performansının CPython altında açık başlatmadan sadece% 30-40 daha kötü olduğu belirtiliyor " belirtiliyor.

Kullanım örneği, doğrudan modülün belgelerinden kaldırılmıştır :

>>> from instancevars import *
>>> class TestMe(object):
...     @instancevars(omit=['arg2_'])
...     def __init__(self, _arg1, arg2_, arg3='test'):
...             self.arg2 = arg2_ + 1
...
>>> testme = TestMe(1, 2)
>>> testme._arg1
1
>>> testme.arg2_
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'TestMe' object has no attribute 'arg2_'
>>> testme.arg2
3
>>> testme.arg3
'test'

0

Belki bu kapalı bir sorudur, ancak onun hakkında ne düşündüğünüzü bilmek için çözümümü önermek istiyorum. I bir dekoratör uygulayan bir metaclass kullandık init yöntem

import inspect

class AutoInit(type):
    def __new__(meta, classname, supers, classdict):
        classdict['__init__'] = wrapper(classdict['__init__'])
        return type.__new__(meta, classname, supers, classdict)

def wrapper(old_init):
    def autoinit(*args):
        formals = inspect.getfullargspec(old_init).args
        for name, value in zip(formals[1:], args[1:]):
            setattr(args[0], name, value)
    return autoinit


0

init işlevinin sonunda :

for var in list(locals().keys()):
    setattr(self,var,locals()[var])

Daha fazla bilgi için setattr()lütfen buraya bakın


0

Fastcore lib https://fastcore.fast.ai/utils.html#store_attr içinde bunu yapmak için bir yardımcı işlev vardır .

from fastcore.utils import store_attr

class Process:
    def __init__(self, PID, PPID, cmd, FDs, reachable, user):
        store_attr() # this will do the same as self.PID = PID etc.
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.