Làm cách nào bạn có thể đặt thuộc tính lớp từ các đối số biến (kwargs) trong python


119

Giả sử tôi có một lớp với một phương thức khởi tạo (hoặc một hàm khác) nhận một số lượng biến đối số và sau đó đặt chúng làm thuộc tính lớp theo điều kiện.

Tôi có thể đặt chúng theo cách thủ công, nhưng có vẻ như các tham số biến đủ phổ biến trong python nên cần có một thành ngữ chung để thực hiện việc này. Nhưng tôi không chắc làm thế nào để thực hiện điều này một cách linh hoạt.

Tôi có một ví dụ sử dụng eval, nhưng điều đó hầu như không an toàn. Tôi muốn biết cách thích hợp để làm điều này - có thể với lambda?

class Foo:
    def setAllManually(self, a=None, b=None, c=None):
        if a!=None: 
            self.a = a
        if b!=None:
            self.b = b
        if c!=None:
            self.c = c
    def setAllWithEval(self, **kwargs):
        for key in **kwargs:
            if kwargs[param] != None
                eval("self." + key + "=" + kwargs[param])

Có vẻ như những câu hỏi này tương tự nhau: stackoverflow.com/questions/3884612/… stackoverflow.com/questions/356718/… stackoverflow.com/questions/1446555/… vì vậy có vẻ như những gì tôi muốn có thể là cái này-- self .__ dict__ [key] = kwargs [key]
fijiaaron

Không thực sự liên quan đến câu hỏi của bạn, nhưng bạn có thể muốn kiểm tra PEP8 để biết một vài gợi ý về kiểu Python thông thường.
Thomas Orozco

Có một thư viện tuyệt vời cho điều này được gọi là attrs. đơn giản pip install attrs, trang trí lớp học của bạn @attr.svà đặt các args là a = attr.ib(); b = attr.ib()vv Đọc thêm ở đây .
Adam Barnes

Am i thiếu cái gì ở đây? Bạn vẫn cần phải làm self.x = kwargs.get'x '] Bạn không tránh khỏi lỗi đánh máy từ người gọi Bạn phải tạo ứng dụng khách có thêm ký tự instance = Class (** {}) Nếu bạn không nhảy qua vòng với sự trần tục self.x = kwargs.get'x '], không phải sau này nó sẽ cắn bạn sao? tức là Thay vì self.x, bạn sẽ kết thúc với self .__ dict __ ['x'] xuống dòng phải không? Hoặc getattr () Nhập nhiều hơn tự.
JGFMK

Câu trả lời:


148

Bạn có thể cập nhật __dict__thuộc tính (đại diện cho các thuộc tính của lớp ở dạng từ điển) với các đối số từ khóa:

class Bar(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

sau đó bạn có thể:

>>> bar = Bar(a=1, b=2)
>>> bar.a
1

và với những thứ như:

allowed_keys = {'a', 'b', 'c'}
self.__dict__.update((k, v) for k, v in kwargs.items() if k in allowed_keys)

bạn có thể lọc các khóa trước (sử dụng iteritemsthay vì itemsnếu bạn vẫn đang sử dụng Python 2.x).


2
Thậm chí tốt hơn nếu bạn sử dụng self.__dict__.update(locals())để sao chép các đối số vị trí.
Giancarlo Sportelli

2
Tôi nghĩ rằng bạn sẽ cần điều này hiện nay .. kwargs [0] .items () thay vì kwargs.iteritems () - (Tôi đang sử dụng Python 3.6.5 tại thời điểm viết bài)
JGFMK

@JGFMK Tại sao kwargs[0]thay vì chỉ kwargs? Có thể kwargsthậm chí có một phím số nguyên? Tôi khá chắc chắn rằng chúng phải là chuỗi.
wjandrea

146

Bạn có thể sử dụng setattr()phương pháp:

class Foo:
  def setAllWithKwArgs(self, **kwargs):
    for key, value in kwargs.items():
      setattr(self, key, value)

Có một getattr()phương pháp tương tự để truy xuất các thuộc tính.


@larsks, cảm ơn nhưng có ý kiến ​​gì về cách chúng tôi có thể giải nén chỉ một khóa từ điển không? stackoverflow.com/questions/41792761/…
JinSnow

Bạn có nhu cầu sử dụng .getattr()? Hoặc bạn có thể truy cập các thuộc tính với Foo.key?
Stevoisiak

@StevenVascellaro tất nhiên bạn chỉ có thể sử dụng Foo.attrname. Tôi nghĩ rằng tôi đã chỉ ra một thực tế là getattrphương pháp này tồn tại. Nó cũng hữu ích nếu bạn muốn cung cấp giá trị mặc định khi thuộc tính được đặt tên không khả dụng.
larsks

3
Sự khác biệt với câu trả lời được chấp nhận là gì? . Ưu nhược điểm của họ là gì?
Eduardo Pignatelli

15

Hầu hết các câu trả lời ở đây không đề cập đến một cách tốt để khởi tạo tất cả các thuộc tính được phép thành chỉ một giá trị mặc định. Vì vậy, để thêm vào các câu trả lời do @fqxp@mmj đưa ra :

class Myclass:

    def __init__(self, **kwargs):
        # all those keys will be initialized as class attributes
        allowed_keys = set(['attr1','attr2','attr3'])
        # initialize all allowed keys to false
        self.__dict__.update((key, False) for key in allowed_keys)
        # and update the given keys by their given values
        self.__dict__.update((key, value) for key, value in kwargs.items() if key in allowed_keys)

Tôi nghĩ rằng đây là câu trả lời đầy đủ nhất do sự biến thành False. Điểm tốt!
Kyrol

9

Tôi đề xuất một biến thể của câu trả lời fqxp , ngoài các thuộc tính được phép , cho phép bạn đặt các giá trị mặc định cho các thuộc tính:

class Foo():
    def __init__(self, **kwargs):
        # define default attributes
        default_attr = dict(a=0, b=None, c=True)
        # define (additional) allowed attributes with no default value
        more_allowed_attr = ['d','e','f']
        allowed_attr = list(default_attr.keys()) + more_allowed_attr
        default_attr.update(kwargs)
        self.__dict__.update((k,v) for k,v in default_attr.items() if k in allowed_attr)

Đây là mã Python 3.x, đối với Python 2.x, bạn cần ít nhất một điều chỉnh, iteritems()thay cho items().


1
Đây là câu trả lời linh hoạt nhất, tóm tắt các cách tiếp cận khác trong chủ đề này. Nó đặt các thuộc tính, cho phép các giá trị mặc định và chỉ thêm các tên thuộc tính được phép. Hoạt động tốt với python 3.x như được hiển thị ở đây.
squarespiral

7

Tuy nhiên, một biến thể khác dựa trên các câu trả lời xuất sắc của mmjfqxp . Nếu chúng ta muốn thì sao

  1. Tránh mã hóa cứng danh sách các thuộc tính được phép
  2. Đặt trực tiếp và rõ ràng các giá trị mặc định cho từng thuộc tính trong hàm tạo
  3. Hạn chế kwargs đối với các thuộc tính được xác định trước bởi một trong hai
    • âm thầm từ chối các đối số không hợp lệ hoặc cách khác,
    • nêu ra một lỗi.

Bằng cách "trực tiếp", tôi có nghĩa là tránh một default_attributestừ điển không liên quan .

class Bar(object):
    def __init__(self, **kwargs):

        # Predefine attributes with default values
        self.a = 0
        self.b = 0
        self.A = True
        self.B = True

        # get a list of all predefined values directly from __dict__
        allowed_keys = list(self.__dict__.keys())

        # Update __dict__ but only for keys that have been predefined 
        # (silently ignore others)
        self.__dict__.update((key, value) for key, value in kwargs.items() 
                             if key in allowed_keys)

        # To NOT silently ignore rejected keys
        rejected_keys = set(kwargs.keys()) - set(allowed_keys)
        if rejected_keys:
            raise ValueError("Invalid arguments in constructor:{}".format(rejected_keys))

Không phải là một bước đột phá lớn, nhưng có thể hữu ích cho ai đó ...

CHỈNH SỬA: Nếu lớp của chúng ta sử dụng trình @propertytrang trí để đóng gói các thuộc tính "được bảo vệ" bằng getters và setters và nếu chúng ta muốn có thể đặt các thuộc tính này bằng hàm tạo của mình, chúng ta có thể muốn mở rộng allowed_keysdanh sách với các giá trị từ dir(self), như sau:

allowed_keys = [i for i in dir(self) if "__" not in i and any([j.endswith(i) for j in self.__dict__.keys()])]

Mã trên không bao gồm

  • bất kỳ biến ẩn nào từ dir()(loại trừ dựa trên sự hiện diện của "__") và
  • bất kỳ phương thức nào dir()mà tên của nó không được tìm thấy ở cuối tên thuộc tính (được bảo vệ hoặc cách khác) từ __dict__.keys()đó, do đó có khả năng chỉ giữ các phương thức được trang trí @property.

Chỉnh sửa này có thể chỉ hợp lệ cho Python 3 trở lên.


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

x = SymbolDict(foo=1, bar='3')
assert x.foo == 1

Tôi gọi là lớp SymbolDictvì về cơ bản nó là một từ điển hoạt động bằng cách sử dụng các ký hiệu thay vì chuỗi. Nói cách khác, bạn làm x.foothay vì x['foo']nhưng dưới vỏ bọc, điều đó thực sự diễn ra tương tự.


2

Các giải pháp sau vars(self).update(kwargs)hoặcself.__dict__.update(**kwargs) không mạnh mẽ, vì người dùng có thể nhập bất kỳ từ điển nào mà không có thông báo lỗi. Nếu tôi cần kiểm tra xem người dùng chèn chữ ký sau ('a1', 'a2', 'a3', 'a4', 'a5') thì giải pháp không hoạt động. Hơn nữa, người dùng sẽ có thể sử dụng đối tượng bằng cách truyền "tham số vị trí" hoặc "tham số cặp giá trị kay".

Vì vậy, tôi đề xuất giải pháp sau đây bằng cách sử dụng một chiếc đồng hồ đeo tay.

from inspect import Parameter, Signature

class StructMeta(type):
    def __new__(cls, name, bases, dict):
        clsobj = super().__new__(cls, name, bases, dict)
        sig = cls.make_signature(clsobj._fields)
        setattr(clsobj, '__signature__', sig)
        return clsobj

def make_signature(names):
    return Signature(
        Parameter(v, Parameter.POSITIONAL_OR_KEYWORD) for v in names
    )

class Structure(metaclass = StructMeta):
    _fields = []
    def __init__(self, *args, **kwargs):
        bond = self.__signature__.bind(*args, **kwargs)
        for name, val in bond.arguments.items():
            setattr(self, name, val)

if __name__ == 'main':

   class A(Structure):
      _fields = ['a1', 'a2']

   if __name__ == '__main__':
      a = A(a1 = 1, a2 = 2)
      print(vars(a))

      a = A(**{a1: 1, a2: 2})
      print(vars(a))

1

Họ có thể là một giải pháp tốt hơn nhưng điều tôi nghĩ đến là:

class Test:
    def __init__(self, *args, **kwargs):
        self.args=dict(**kwargs)

    def getkwargs(self):
        print(self.args)

t=Test(a=1, b=2, c="cats")
t.getkwargs()


python Test.py 
{'a': 1, 'c': 'cats', 'b': 2}

Những gì tôi đang tìm kiếm là đặt các thuộc tính có điều kiện dựa trên xác thực. Tôi nhận ra rằng vấn đề với việc sử dụng kwargs là nó không xác nhận (hoặc tài liệu) mà thuộc tính này là chấp nhận được
fijiaaron

Vâng, tôi nhận ra câu trả lời của @larsks là tốt hơn. Học điều gì đó mới hàng ngày tại SO!
Tom

1

cái này dễ nhất qua larsks

class Foo:
    def setAllWithKwArgs(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

ví dụ của tôi:

class Foo:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

door = Foo(size='180x70', color='red chestnut', material='oak')
print(door.size) #180x70

smb có thể giải thích kwargs.items () là gì không?
Oleg_Kornilov

kwargslà một từ điển của các đối số từ khóa và items()là một phương thức trả về một bản sao của danh sách các (key, value)cặp của từ điển .
harryscholes

-1

Tôi nghi ngờ rằng nó có thể tốt hơn trong hầu hết các trường hợp sử dụng args có tên (để mã tự lập tài liệu tốt hơn) vì vậy nó có thể trông giống như sau:

class Foo:
    def setAll(a=None, b=None, c=None):
        for key, value in (a, b, c):
            if (value != None):
                settattr(self, key, value)

Lặp này không làm việc:for key, value in (a, b, c)
rerx
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.