Tự động khởi tạo các biến phiên bản?


89

Tôi có một lớp python trông như thế này:

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

theo dõi bởi:

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

Có cách nào để tự động hóa các biến cá thể này, như danh sách khởi tạo của C ++ không? Nó sẽ tiết kiệm rất nhiều mã thừa.


1
Xem thêm thảo luận về autoassigncông thức kích hoạt và cách autoargstriển khai thay thế tại: Cách tốt nhất để gán thuộc tính tự động trong Python là gì và đó có phải là một ý tưởng hay không? - Stack Overflow
nealmcb

Câu trả lời:


104

Bạn có thể sử dụng trình trang trí:

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

Sử dụng nó để trang trí __init__phương pháp:

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

Đầu ra:

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

5
Điều này hoạt động và trả lời câu hỏi vì vậy tôi đã bỏ phiếu. Nhưng tôi giữ Ferdidand Beyer câu trả lời: "Rõ ràng là tốt hơn so với tiềm ẩn"
Lucas Gabriel Sánchez

14
+1 Để có câu trả lời tuyệt vời đã giải quyết được vấn đề của tôi. Nhưng nó không phải là một chức năng cốt lõi của ngôn ngữ? Bạn có nghĩ rằng nó đáng để viết một PEP?
Adam Matan

3
Đây là một câu trả lời thực sự tốt - điều này đã đi thẳng vào hộp công cụ của tôi.
Michael van der Westhuizen

3
@NadiaAlramli Tôi nghĩ rằng có một lỗi nhỏ trong mã. Xem tại đây gist.github.com/pmav99/137dbf4681be9a58de74 (original.py)
pmav99

2
Ví dụ hiện tại có một lỗi và sẽ không hoạt động nếu chữ ký không bao gồm các đối số mặc định. Bạn cần bao gồm một kiểm tra để đảm bảo tên và giá trị mặc định không phải là Không có. Ví dụ: nếu tên và giá trị mặc định:

36

Nếu bạn đang sử dụng Python 2.6 trở lên, bạn có thể sử dụng collection.nametuple :

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

Điều này phù hợp đặc biệt khi lớp học của bạn thực sự chỉ là một túi giá trị lớn.


2
"Điều này phù hợp đặc biệt khi lớp học của bạn thực sự chỉ là một túi giá trị lớn." Trong trường hợp như vậy, bạn cũng có thể thực hiện điều này: https://docs.python.org/3.3/tutorial/classes.html#odds-and-ends
Big Sharpie,

34

Đối với Python 3.7+, bạn có thể sử dụng Lớp dữ liệu , đây là một cách rất quan trọng và có thể bảo trì để làm những gì bạn muốn.

Nó cho phép bạn xác định các trường cho lớp của mình, đó là các biến phiên bản được khởi tạo tự động của bạn.

Nó sẽ trông giống như thế:

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

Các __init__phương pháp sẽ đã đang diễn ra lớp học của bạn.

Lưu ý rằng ở đây loại gợi ý là bắt buộc , đó là lý do tại sao tôi đã sử dụng intstrtrong ví dụ. Nếu bạn không biết loại trường của mình, bạn có thể sử dụng Bất kỳ từ typingmô-đun .

Lớp Dữ liệu có nhiều ưu điểm so với các giải pháp được đề xuất:

  • rõ ràng : tất cả các trường đều có thể nhìn thấy, điều này tôn trọng tính Zen của Python và làm cho nó dễ đọc và có thể bảo trì. So sánh nó với việc sử dụng **kwargs.
  • Nó có thể có các phương pháp . Cũng giống như bất kỳ lớp học nào khác.
  • Nó cho phép bạn vượt ra ngoài tính năng tự động __init__bằng __post_init__phương pháp này.

CHỈNH SỬA: Những lý do nên tránh sử dụng NamedTuples

Một số đề xuất việc sử dụng namedtuplecho trường hợp này, nhưng các cặp có tên có một số hành vi khác với các lớp Python, ban đầu chúng không thực sự rõ ràng và nên được nhiều người biết đến:

1. NamedTuples là bất biến

Tính bất biến có thể hữu ích, nhưng có thể nó không phải là điều bạn muốn cho các trường hợp của mình. DataClasses cũng có thể không thay đổi bằng cách nào đó nếu bạn sử dụng đối số frozen=Truetrên @dataclassdecorator.

2. NamedTuples __eq__hoạt động giống như Tuple

Trong Python, SomeNamedTuple(a=1, b=2) == AnotherNamedTuple(c=1, d=2)True! Các __eq__chức năng của NamedTuple, được sử dụng trong so sánh, chỉ xem xét các giá trị và vị trí của những giá trị về các trường hợp so sánh, không tên lớp hoặc các lĩnh vực của họ.


Điều này chỉ nên được sử dụng nếu mục đích của lớp là lưu trữ dữ liệu.
JC Rocamonde

Hoặc để phát triển xung quanh việc lưu trữ dữ liệu.
JC Rocamonde

3
Bạn có giải thích tại sao dataclass chỉ nên được sử dụng cho các lớp lưu trữ dữ liệu, chứ không phải cho các hành vi khác không? Tôi có thể tạo một bài SO mới hoàn toàn cho điều này để hiểu rõ hơn về các trường hợp sử dụng thích hợp của nó. Cảm ơn.
Vahid Pazirandeh

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

26

Trích dẫn Thiền của Python ,

Rõ ràng là tốt hơn ngầm.


10
Một khai báo danh sách khởi tạo có đủ rõ ràng không?
Adam Matan

Tôi đoán. Nhưng tôi không thấy lý do gì để thêm những thứ như vậy vào ngôn ngữ. Tôi rõ ràng thích nhiều câu lệnh phân công hơn là một loại ma thuật trang trí đằng sau hậu trường.
Ferdinand Beyer, 07/09/09

29
@Ferdinand, tôi đồng ý rằng sẽ thật ngớ ngẩn nếu trong ngôn ngữ có thứ gì đó hoàn toàn tốt có thể có trong stdlib, nhưng nó NÊN có trong stdlib, bởi vì "đẹp còn hơn xấu" được ưu tiên và nhiều bài tập lặp đi lặp lại là xấu (cũng như bất kỳ hình thức lặp lại nào).
Alex Martelli,

Vâng, để truy cập: DWIM> DWIS
Terrence Brannon

Tôi sẽ đồng ý trang trí đẹp hơn một danh sách các nhiệm vụ nhưng PyCharm làm cho nó xấu xí bằng cách không hiểu nó :-(
user110954

23

Một điều khác bạn có thể làm:

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`

Nhưng giải pháp duy nhất tôi muốn giới thiệu, ngoài việc chỉ viết chính tả, là "tạo macro trong trình soạn thảo của bạn" ;-p


1
Nắm bắt tốt việc xóa 'bản thân'.
michael

15

Bạn có thể làm điều đó dễ dàng với các đối số từ khóa, ví dụ như thế này:

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

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

triển khai tương tự cho các đối số vị trí sẽ là:

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


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

mà đối với tôi dường như không giải quyết được vấn đề của bạn.


3
Một biến thể khác mà tôi thích làself.__dict__.update( **kwargs )
S.Lott

Cũng có thể sử dụng local () và đặt các đối số bình thường.
mk 12

7

Giải pháp của Nadia tốt hơn và mạnh mẽ hơn, nhưng tôi nghĩ điều này cũng thú vị:

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

Tôi cần một cái gì đó cho cùng mục đích, nhưng không có câu trả lời hiện có nào bao gồm tất cả các trường hợp tôi đã thử nghiệm. Câu trả lời của Nadia là câu trả lời gần nhất với những gì tôi đang tìm kiếm, vì vậy tôi bắt đầu với mã của cô ấy làm cơ sở.

Trình trang trí bên dưới hoạt động với tất cả các kết hợp hợp lệ của các đối số:

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        )

Nó cũng thực hiện _quy ước -prefix tiêu chuẩn để cho phép __init__các biến -private sẽ không được gán cho các cá thể lớp.


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

Ghi chú:

Tôi đã bao gồm các bài kiểm tra, nhưng đã thu gọn chúng vào dòng cuối cùng ( 58 ) cho ngắn gọn. Bạn có thể mở rộng các bài kiểm tra, kiểm tra chi tiết tất cả các trường hợp sử dụng tiềm năng, bằng cách thêm find/replacetất cả các $ký tự bằng một dòng mới.


3

Có thể không cần khởi tạo biến, vì local () đã chứa các giá trị!

lớp Dummy (đối tượng):

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

d = Dummy (2, 3)

d.params

{'a': 2, 'b': 3, 'default': 'Fred'}

d.params ['b']

3

Tất nhiên, trong một lớp, người ta có thể sử dụng self.params


Đó là một cách tiếp cận hay và nguyên bản, nhưng d['b']được viết bằng ngôn ngữ của Python trong khi d.params['b']sẽ gây nhầm lẫn cho người đọc mã.
Adam Matan

3

Ngay sau khi getargspeckhông được dùng nữa kể từ Python 3.5, đây là giải pháp sử dụng 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

Kiểm tra xem có hoạt động không:

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

Đối với Python 3.3+:

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

Bản giới thiệu:

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

Phương pháp tiếp cận không trang trí cho cả Python 2 và 3 bằng cách sử dụng các khung:

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)

Bản giới thiệu:

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

1

nu11ptr đã tạo một mô-đun nhỏ, PyInstanceVars , bao gồm chức năng này như một trình trang trí chức năng. Trong README của mô-đun là trạng thái rằng " [...] hiệu suất bây giờ chỉ kém hơn 30-40% so với khởi tạo rõ ràng trong CPython ".

Ví dụ về cách sử dụng, được lấy ngay từ tài liệu của mô-đun :

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

Có thể đây là một câu hỏi đóng, nhưng tôi muốn đề xuất giải pháp của mình để biết bạn nghĩ gì về nó. Tôi đã sử dụng một siêu kính áp dụng trình trang trí cho phương thức init

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

ở cuối hàm init :

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

Để biết thêm chi tiết setattr()vui lòng tham khảo tại đây


0

Có một chức năng trợ giúp để thực hiện việc này trong fastcore lib https://fastcore.fast.ai/utils.html#store_attr .

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.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.