Các cấu trúc giống như C trong Python


446

Có cách nào để xác định thuận tiện cấu trúc giống C trong Python không? Tôi mệt mỏi với việc viết những thứ như:

class MyStruct():
    def __init__(self, field1, field2, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3

5
Các kiểu dữ liệu đại số bán liên quan sẽ rất tuyệt vời, nhưng để sử dụng chúng tốt, bạn thường cần khớp mẫu.
Edward Z. Yang

50
Có điều gì sai với phương pháp này ngoài việc viết tẻ nhạt không?
levesque

2
Bạn có thể thấy hướng dẫn hữu ích: github.com/dorkitude/dstruct
Kyle Wild

10
@levesque khó tái lập hệ số mà không có lỗi chính tả, khó đọc trong nháy mắt trong khi đọc lướt mã, hơnMyStruct = namedtuple("MyStruct", "field1 field2 field3")
sam boosalis

1
pandas.Series(a=42).anên làm điều đó nếu nhà khoa học dữ liệu của bạn ...
Mark Horvath

Câu trả lời:


341

Sử dụng một tuple có tên , đã được thêm vào mô-đun bộ sưu tập trong thư viện chuẩn trong Python 2.6. Cũng có thể sử dụng tuple của Raymond Hettinger công thức nếu bạn cần hỗ trợ Python 2.4.

Thật tốt cho ví dụ cơ bản của bạn, nhưng cũng bao gồm một loạt các trường hợp cạnh mà bạn có thể gặp phải sau này. Đoạn của bạn ở trên sẽ được viết là:

from collections import namedtuple
MyStruct = namedtuple("MyStruct", "field1 field2 field3")

Loại mới được tạo có thể được sử dụng như thế này:

m = MyStruct("foo", "bar", "baz")

Bạn cũng có thể sử dụng các đối số được đặt tên:

m = MyStruct(field1="foo", field2="bar", field3="baz")

164
... nhưng tên là bất biến. Ví dụ trong OP là đột biến.
mhowison

28
@mhowison - Trong trường hợp của tôi, đó chỉ là một điểm cộng.
ArtOfWarfare

3
Giải pháp tốt đẹp. Làm thế nào bạn sẽ lặp qua một mảng của các bộ dữ liệu này? Tôi cho rằng các trường 1-3 sẽ phải có cùng tên trên các đối tượng tuple.
Michael Smith

2
nametuple có thể có tối đa bốn đối số, vì vậy làm thế nào chúng ta có thể ánh xạ cấu trúc với nhiều thành viên dữ liệu hơn với têntuple tương ứng
Kapil

3
@Kapil - Đối số thứ hai cho nametuple phải là danh sách tên của các thành viên. Danh sách đó có thể dài bất kỳ.
ArtOfWarfare

224

Cập nhật : Các lớp dữ liệu

Với việc giới thiệu các lớp dữ liệu trong Python 3.7, chúng ta đã tiến rất gần.

Ví dụ sau tương tự như ví dụ NamedTuple bên dưới, nhưng đối tượng kết quả là có thể thay đổi và nó cho phép các giá trị mặc định.

from dataclasses import dataclass


@dataclass
class Point:
    x: float
    y: float
    z: float = 0.0


p = Point(1.5, 2.5)

print(p)  # Point(x=1.5, y=2.5, z=0.0)

Điều này chơi độc đáo với mô-đun mới trong trường hợp bạn muốn sử dụng các chú thích loại cụ thể hơn.

Tôi đã chờ đợi điều này một cách tuyệt vọng! Nếu bạn hỏi tôi, Lớp dữ liệu và khai báo NamedTuple mới , kết hợp với việc nhập mô-đun là một ơn trời!

Cải thiện khai báo NamedTuple

Kể từ Python 3.6, nó trở nên khá đơn giản và đẹp mắt (IMHO), miễn là bạn có thể sống với sự bất biến .

Một cách khai báo NamedTuples mới đã được giới thiệu, cho phép cả chú thích kiểu :

from typing import NamedTuple


class User(NamedTuple):
    name: str


class MyStruct(NamedTuple):
    foo: str
    bar: int
    baz: list
    qux: User


my_item = MyStruct('foo', 0, ['baz'], User('peter'))

print(my_item) # MyStruct(foo='foo', bar=0, baz=['baz'], qux=User(name='peter'))

6
Bạn ơi, bạn vừa làm một ngày của tôi - những câu chuyện bất di bất dịch - cảm ơn bạn: D
Dmitry Arkhipenko

10
Các dataclassmô-đun là mới trong Python 3.7 nhưng bạn có thể pip install dataclasses. Đó là backport trên Python 3.6. pypi.org/project/datac gốm / # descrip
Lavande

+1 cho khai báo NamedTuple được cải thiện. Cách cũ rất khó đọc nếu bạn có một vài biến số ...
gebbissimo

@Lavande Tôi có thể biết những thay đổi đột phá đã xảy ra giữa 3.6 và 3.7 mà bạn phải đưa một phiên bản nhỏ trở lại ...?
Băng tím

1
@PurpleIce Đó là một triển khai của PEP 557, Các lớp dữ liệu @dataclassCác chi tiết có tại đây: pypi.org/project/datacurine/#description
Lavande

96

Bạn có thể sử dụng một tuple cho rất nhiều thứ mà bạn sẽ sử dụng một cấu trúc trong C (ví dụ như tọa độ x, y hoặc màu RGB).

Đối với mọi thứ khác, bạn có thể sử dụng từ điển hoặc một lớp tiện ích như thế này :

>>> class Bunch:
...     def __init__(self, **kwds):
...         self.__dict__.update(kwds)
...
>>> mystruct = Bunch(field1=value1, field2=value2)

Tôi nghĩ rằng cuộc thảo luận "dứt khoát" ở đây , trong phiên bản đã xuất bản của Python Cookbook.


5
Một lớp trống sẽ làm như vậy?
Kurt Liu

44
Lưu ý nếu bạn chưa quen với python: tuples chỉ đọc một lần được tạo, không giống như cấu trúc C
LeBleu

2
@KurtLiu Không, có lẽ nó sẽ nóiTypeError: this constructor takes no arguments
Evgeni Sergeev

84

Có lẽ bạn đang tìm kiếm Structs mà không có nhà xây dựng:

class Sample:
  name = ''
  average = 0.0
  values = None # list cannot be initialized here!


s1 = Sample()
s1.name = "sample 1"
s1.values = []
s1.values.append(1)
s1.values.append(2)
s1.values.append(3)

s2 = Sample()
s2.name = "sample 2"
s2.values = []
s2.values.append(4)

for v in s1.values:   # prints 1,2,3 --> OK.
  print v
print "***"
for v in s2.values:   # prints 4 --> OK.
  print v

5
Những gì bạn đang làm ở đây hoạt động, về mặt kỹ thuật, nhưng có lẽ nó không rõ ràng ngay lập tức với nhiều người dùng tại sao nó hoạt động. Tuyên bố của bạn dưới class Sample:không ngay lập tức làm bất cứ điều gì; họ đặt thuộc tính lớp. Những người luôn có thể được truy cập như ví dụ Sample.name.
Channing Moore

22
Những gì bạn đang thực sự làm là thêm các thuộc tính cá thể vào các đối tượng s1s2trong thời gian chạy. Trừ khi bị cấm, bạn có thể thêm hoặc sửa đổi namethuộc tính trên bất kỳ trường hợp nào của bất kỳ lớp nào vào bất kỳ lúc nào, cho dù lớp đó có namethuộc tính hay không . Có lẽ vấn đề chức năng lớn nhất khi thực hiện điều này là các trường hợp khác nhau của cùng một lớp sẽ hành xử khác nhau tùy thuộc vào việc bạn đã đặt name. Nếu bạn cập nhật Sample.name, bất kỳ đối tượng nào không có thuộc tính được đặt rõ ràng namesẽ trả về giá trị mới name.
Channing Moore

2
Điều này gần giống với cấu trúc - lớp 'ngắn' không có phương thức, 'trường' (thuộc tính lớp, tôi biết) với các giá trị mặc định. Miễn là nó không phải là một loại có thể thay đổi (dict, list), bạn vẫn ổn. Tất nhiên, bạn có thể đánh vào các kiểm tra IDE PEP-8 hoặc "thân thiện" như "lớp không có phương thức init " của PyCharm .
Tomasz Gandor

4
Tôi đã thử nghiệm tác dụng phụ được mô tả bởi Channing Moore. Không có giá trị kinh tế của một vài selftừ khóa và một dòng xây dựng nếu bạn hỏi tôi. Tôi đánh giá cao nếu Jose có thể chỉnh sửa câu trả lời của mình để thêm một thông điệp cảnh báo về nguy cơ vô tình chia sẻ các giá trị qua các trường hợp.
Stéphane C.

@ChanningMoore: Tôi đã cố gắng tạo lại vấn đề bạn đang mô tả, nhưng không thành công. Bạn có thể trình bày một ví dụ làm việc tối thiểu khi vấn đề bật lên?
gebbissimo

67

Làm thế nào về một từ điển?

Một cái gì đó như thế này:

myStruct = {'field1': 'some val', 'field2': 'some val'}

Sau đó, bạn có thể sử dụng điều này để thao tác các giá trị:

print myStruct['field1']
myStruct['field2'] = 'some other values'

Và các giá trị không phải là chuỗi. Họ có thể là khá nhiều đối tượng khác.


34
Đây cũng là cách tiếp cận của tôi, nhưng tôi cảm thấy chính xác là nó nguy hiểm vì từ điển có thể chấp nhận bất cứ điều gì cho một khóa. Sẽ không có lỗi nếu tôi đặt mySturation ["ffield"] khi tôi có ý định đặt mySturation ["trường"]. Vấn đề có thể (hoặc có thể không) trở nên rõ ràng khi tôi sử dụng hoặc sử dụng lại mySturation ["trường"] sau này. Tôi thích cách tiếp cận của PabloG.
mobabo

Vấn đề tương tự tồn tại với PabloG's. Hãy thử thêm đoạn mã sau vào mã của anh ấy: pt3.w = 1 print pt3.w Trong một ngôn ngữ có các ký tự, tốt hơn là sử dụng chúng, đặc biệt là đối với các đối tượng được tuần tự hóa, vì bạn có thể tự động sử dụng json nhập để lưu chúng và các thư viện tuần tự hóa khác miễn là bạn không thấy lạ những thứ bên trong dict của bạn. Dicts là giải pháp để giữ dữ liệu và logic riêng biệt và tốt hơn so với cấu trúc cho những người không muốn viết các chức năng tuần tự hóa và hủy xác định tùy chỉnh và không muốn sử dụng các trình tuần tự không di động như dưa chua.
Poikilos

27

dF: Điều đó thật tuyệt ... Tôi không biết rằng tôi có thể truy cập vào các trường trong một lớp bằng cách sử dụng dict.

Mark: những tình huống mà tôi ước mình có được điều này chính xác là khi tôi muốn một tuple nhưng không có gì "nặng nề" như một cuốn từ điển.

Bạn có thể truy cập các trường của một lớp bằng từ điển vì các trường của một lớp, các phương thức của nó và tất cả các thuộc tính của nó được lưu trữ bên trong bằng cách sử dụng các ký tự (ít nhất là trong CPython).

... Điều này dẫn chúng tôi đến bình luận thứ hai của bạn. Tin rằng các dicts Python là "nặng" là một khái niệm cực kỳ phi trăn. Và đọc những bình luận như vậy sẽ giết chết Python Zen của tôi. Điều đó không tốt.

Bạn thấy, khi bạn khai báo một lớp, bạn thực sự đang tạo một trình bao bọc khá phức tạp xung quanh một từ điển - vì vậy, nếu có bất cứ điều gì, bạn đang thêm nhiều chi phí hơn bằng cách sử dụng một từ điển đơn giản. Nhân tiện, trong đó, là vô nghĩa trong mọi trường hợp. Nếu bạn đang làm việc trên các ứng dụng quan trọng về hiệu năng, hãy sử dụng C hoặc một cái gì đó.


5
# 1, Cython! = CPython. Tôi nghĩ rằng bạn đã nói về CPython, việc triển khai Python được viết bằng C, không phải Cython, một dự án để biên dịch mã Python thành mã C. Tôi đã chỉnh sửa câu trả lời của bạn để khắc phục điều đó. # 2, tôi nghĩ khi anh ấy nói dicts nặng, anh ấy đã đề cập đến cú pháp. self['member']dài hơn 3 ký tự self.membervà những ký tự này tương đối không thân thiện với cổ tay.
ArtOfWarfare

19

Bạn có thể phân lớp cấu trúc C có sẵn trong thư viện chuẩn. Các ctypes mô-đun cung cấp một lớp kết cấu . Ví dụ từ các tài liệu:

>>> from ctypes import *
>>> class POINT(Structure):
...     _fields_ = [("x", c_int),
...                 ("y", c_int)]
...
>>> point = POINT(10, 20)
>>> print point.x, point.y
10 20
>>> point = POINT(y=5)
>>> print point.x, point.y
0 5
>>> POINT(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: too many initializers
>>>
>>> class RECT(Structure):
...     _fields_ = [("upperleft", POINT),
...                 ("lowerright", POINT)]
...
>>> rc = RECT(point)
>>> print rc.upperleft.x, rc.upperleft.y
0 5
>>> print rc.lowerright.x, rc.lowerright.y
0 0
>>>

18

Tôi cũng muốn thêm một giải pháp sử dụng vị trí :

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

Chắc chắn kiểm tra tài liệu cho các vị trí nhưng một lời giải thích nhanh về các vị trí đó là cách nói của python: "Nếu bạn có thể khóa các thuộc tính này và chỉ các thuộc tính này vào lớp để bạn cam kết rằng bạn sẽ không thêm bất kỳ thuộc tính mới nào sau khi lớp được khởi tạo (có, bạn có thể thêm các thuộc tính mới vào một thể hiện của lớp, xem ví dụ bên dưới) sau đó tôi sẽ loại bỏ phân bổ bộ nhớ lớn cho phép thêm các thuộc tính mới vào một thể hiện của lớp và chỉ sử dụng những gì tôi cần cho các thuộc tính có rãnh này ".

Ví dụ về việc thêm thuộc tính vào thể hiện của lớp (do đó không sử dụng vị trí):

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(3,5)
p1.z = 8
print(p1.z)

Đầu ra: 8

Ví dụ về việc cố gắng thêm các thuộc tính vào thể hiện của lớp nơi các vị trí được sử dụng:

class Point:
    __slots__ = ["x", "y"]
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(3,5)
p1.z = 8

Đầu ra: AttributionError: Đối tượng 'Point' không có thuộc tính 'z'

Điều này có thể hoạt động hiệu quả như một cấu trúc và sử dụng ít bộ nhớ hơn một lớp (giống như một cấu trúc, mặc dù tôi chưa nghiên cứu chính xác bao nhiêu). Bạn nên sử dụng các vị trí nếu bạn sẽ tạo một số lượng lớn các đối tượng và không cần thêm thuộc tính. Một đối tượng điểm là một ví dụ tốt về điều này vì có khả năng người ta có thể khởi tạo nhiều điểm để mô tả một tập dữ liệu.


17

Bạn cũng có thể truyền tham số init cho các biến thể hiện theo vị trí

# Abstract struct class       
class Struct:
    def __init__ (self, *argv, **argd):
        if len(argd):
            # Update by dictionary
            self.__dict__.update (argd)
        else:
            # Update by position
            attrs = filter (lambda x: x[0:2] != "__", dir(self))
            for n in range(len(argv)):
                setattr(self, attrs[n], argv[n])

# Specific class
class Point3dStruct (Struct):
    x = 0
    y = 0
    z = 0

pt1 = Point3dStruct()
pt1.x = 10

print pt1.x
print "-"*10

pt2 = Point3dStruct(5, 6)

print pt2.x, pt2.y
print "-"*10

pt3 = Point3dStruct (x=1, y=2, z=3)
print pt3.x, pt3.y, pt3.z
print "-"*10

7
Cập nhật theo vị trí bỏ qua thứ tự khai báo thuộc tính và sử dụng sắp xếp chữ cái của chúng thay vào đó. Vì vậy, nếu bạn thay đổi thứ tự dòng trong Point3dStructkhai báo, Point3dStruct(5, 6)sẽ không hoạt động như mong đợi. Thật kỳ lạ khi không ai viết điều này trong cả 6 năm.
lapis

Có thể thêm phiên bản Python 3 vào mã tuyệt vời của bạn không? Công việc tuyệt vời Tôi thích rằng bạn lấy một cái gì đó trừu tượng và làm cho nó rõ ràng với lớp cụ thể thứ hai. Điều đó sẽ tốt cho việc xử lý lỗi / bắt lỗi. Đối với Python 3, chỉ cần thay đổi print> print()attrs[n]> next(attrs)(bộ lọc bây giờ là đối tượng lặp lại của chính nó và yêu cầu next).
Jonathan Komar

10

Bất cứ khi nào tôi cần một "đối tượng dữ liệu tức thời cũng hoạt động như một cuốn từ điển" (tôi không nghĩ về các cấu trúc C!), Tôi nghĩ đến bản hack dễ thương này:

class Map(dict):
    def __init__(self, **kwargs):
        super(Map, self).__init__(**kwargs)
        self.__dict__ = self

Bây giờ bạn chỉ có thể nói:

struct = Map(field1='foo', field2='bar', field3=42)

self.assertEquals('bar', struct.field2)
self.assertEquals(42, struct['field3'])

Hoàn toàn tiện dụng cho những lúc bạn cần một "túi dữ liệu KHÔNG phải là một lớp" và khi không thể hiểu được tên là ...


Tôi sử dụng pandas.Series (a = 42) ;-)
Mark Horvath

8

Bạn truy cập C-Style struct trong python theo cách sau.

class cstruct:
    var_i = 0
    var_f = 0.0
    var_str = ""

nếu bạn chỉ muốn sử dụng đối tượng của cstruct

obj = cstruct()
obj.var_i = 50
obj.var_f = 50.00
obj.var_str = "fifty"
print "cstruct: obj i=%d f=%f s=%s" %(obj.var_i, obj.var_f, obj.var_str)

nếu bạn muốn tạo một mảng các đối tượng của cstruct

obj_array = [cstruct() for i in range(10)]
obj_array[0].var_i = 10
obj_array[0].var_f = 10.00
obj_array[0].var_str = "ten"

#go ahead and fill rest of array instaces of struct

#print all the value
for i in range(10):
    print "cstruct: obj_array i=%d f=%f s=%s" %(obj_array[i].var_i, obj_array[i].var_f, obj_array[i].var_str)

Lưu ý: thay vì tên 'cstruct', vui lòng sử dụng tên cấu trúc của bạn thay vì var_i, var_f, var_str, vui lòng xác định biến thành viên của cấu trúc.


3
Đây có khác gì so với những gì trong stackoverflow.com/a/3761729/1877426 không?
lagweezle 10/2/2015

8

Một số câu trả lời ở đây được xây dựng ồ ạt. Tùy chọn đơn giản nhất mà tôi tìm thấy là (từ: http://norvig.com/python-iaq.html ):

class Struct:
    "A structure that can have any fields defined."
    def __init__(self, **entries): self.__dict__.update(entries)

Đang khởi tạo:

>>> options = Struct(answer=42, linelen=80, font='courier')
>>> options.answer
42

thêm nữa:

>>> options.cat = "dog"
>>> options.cat
dog

chỉnh sửa: Xin lỗi đã không thấy ví dụ này đã đi xuống.


5

Điều này có thể hơi muộn nhưng tôi đã thực hiện một giải pháp bằng cách sử dụng Python Meta-Classes (phiên bản trang trí bên dưới).

Khi __init__được gọi trong thời gian chạy, nó lấy từng đối số và giá trị của chúng và gán chúng làm biến thể hiện cho lớp của bạn. Bằng cách này, bạn có thể tạo một lớp giống như cấu trúc mà không phải gán mọi giá trị theo cách thủ công.

Ví dụ của tôi không có lỗi kiểm tra nên dễ theo dõi hơn.

class MyStruct(type):
    def __call__(cls, *args, **kwargs):
        names = cls.__init__.func_code.co_varnames[1:]

        self = type.__call__(cls, *args, **kwargs)

        for name, value in zip(names, args):
            setattr(self , name, value)

        for name, value in kwargs.iteritems():
            setattr(self , name, value)
        return self 

Đây là hành động.

>>> class MyClass(object):
    __metaclass__ = MyStruct
    def __init__(self, a, b, c):
        pass


>>> my_instance = MyClass(1, 2, 3)
>>> my_instance.a
1
>>> 

Tôi đã đăng nó trên reddit/ u / matchu đã đăng một phiên bản trang trí sạch hơn. Tôi khuyến khích bạn sử dụng nó trừ khi bạn muốn mở rộng phiên bản siêu dữ liệu.

>>> def init_all_args(fn):
    @wraps(fn)
    def wrapped_init(self, *args, **kwargs):
        names = fn.func_code.co_varnames[1:]

        for name, value in zip(names, args):
            setattr(self, name, value)

        for name, value in kwargs.iteritems():
            setattr(self, name, value)

    return wrapped_init

>>> class Test(object):
    @init_all_args
    def __init__(self, a, b):
        pass


>>> a = Test(1, 2)
>>> a.a
1
>>> 

Chết tiệt - tôi đã dành hai giờ hôm nay để viết lên trang trí của riêng tôi để làm điều này và sau đó tôi tìm thấy nó. Dù sao, đăng bài của tôi vì nó xử lý các giá trị mặc định trong khi của bạn thì không. stackoverflow.com/a/32448434/901641
ArtOfWarfare

+1 để đề cập đến func_code. Bắt đầu đào theo hướng đó và tìm thấy rất nhiều thứ thú vị ở đó.
wombatonfire 28/03/2016

5

Tôi đã viết một trình trang trí mà bạn có thể sử dụng trên bất kỳ phương thức nào để làm cho nó sao cho tất cả các đối số được truyền vào hoặc bất kỳ giá trị mặc định nào được gán cho thể hiện.

def argumentsToAttributes(method):
    argumentNames = method.func_code.co_varnames[1:]

    # Generate a dictionary of default values:
    defaultsDict = {}
    defaults = method.func_defaults if method.func_defaults else ()
    for i, default in enumerate(defaults, start = len(argumentNames) - len(defaults)):
        defaultsDict[argumentNames[i]] = default

    def newMethod(self, *args, **kwargs):
        # Use the positional arguments.
        for name, value in zip(argumentNames, args):
            setattr(self, name, value)

        # Add the key word arguments. If anything is missing, use the default.
        for name in argumentNames[len(args):]:
            setattr(self, name, kwargs.get(name, defaultsDict[name]))

        # Run whatever else the method needs to do.
        method(self, *args, **kwargs)

    return newMethod

Một cuộc biểu tình nhanh chóng. Lưu ý rằng tôi sử dụng đối số vị trí a, sử dụng giá trị mặc định cho bvà đối số được đặt tên c. Sau đó tôi in cả 3 tham chiếu self, để cho thấy rằng chúng đã được gán đúng trước khi phương thức được nhập.

class A(object):
    @argumentsToAttributes
    def __init__(self, a, b = 'Invisible', c = 'Hello'):
        print(self.a)
        print(self.b)
        print(self.c)

A('Why', c = 'Nothing')

Lưu ý rằng trang trí của tôi nên làm việc với bất kỳ phương pháp, không chỉ __init__.


5

Tôi không thấy câu trả lời này ở đây, vì vậy tôi cho rằng tôi sẽ thêm nó vì tôi đang nghiêng Python ngay bây giờ và mới phát hiện ra nó. Các Python hướng dẫn (Python 2 trong trường hợp này) cho đơn giản sau và gương hiệu quả:

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

Đó là, một đối tượng lớp trống được tạo, sau đó được khởi tạo và các trường được thêm động.

Mặt trái của nó là rất đơn giản. Nhược điểm là nó không đặc biệt tự ghi lại (các thành viên dự định không được liệt kê ở bất kỳ đâu trong "định nghĩa" của lớp) và các trường không đặt có thể gây ra sự cố khi truy cập. Hai vấn đề có thể được giải quyết bằng cách:

class Employee:
    def __init__ (self):
        self.name = None # or whatever
        self.dept = None
        self.salary = None

Bây giờ trong nháy mắt bạn ít nhất có thể thấy những lĩnh vực mà chương trình sẽ mong đợi.

Cả hai đều dễ mắc lỗi chính tả, john.slarly = 1000sẽ thành công. Tuy nhiên, nó hoạt động.


4

Đây là một giải pháp sử dụng một lớp (không bao giờ được khởi tạo) để giữ dữ liệu. Tôi thích cách này liên quan đến việc gõ rất ít và không yêu cầu bất kỳ gói bổ sung nào, v.v.

class myStruct:
    field1 = "one"
    field2 = "2"

Bạn có thể thêm nhiều trường sau, nếu cần:

myStruct.field3 = 3

Để có được các giá trị, các trường được truy cập như bình thường:

>>> myStruct.field1
'one'

2

Cá nhân tôi cũng thích biến thể này. Nó mở rộng câu trả lời của @ dF .

class struct:
    def __init__(self, *sequential, **named):
        fields = dict(zip(sequential, [None]*len(sequential)), **named)
        self.__dict__.update(fields)
    def __repr__(self):
        return str(self.__dict__)

Nó hỗ trợ hai chế độ khởi tạo (có thể được trộn lẫn):

# Struct with field1, field2, field3 that are initialized to None.
mystruct1 = struct("field1", "field2", "field3") 
# Struct with field1, field2, field3 that are initialized according to arguments.
mystruct2 = struct(field1=1, field2=2, field3=3)

Ngoài ra, nó in đẹp hơn:

print(mystruct2)
# Prints: {'field3': 3, 'field1': 1, 'field2': 2}

2

Giải pháp sau đây cho một cấu trúc được lấy cảm hứng từ việc triển khai có tên và một số câu trả lời trước đó. Tuy nhiên, không giống như tên được đặt, nó có thể thay đổi, trong các giá trị của nó, nhưng giống như cấu trúc kiểu c bất biến trong các tên / thuộc tính, mà một lớp hoặc dict bình thường không có.

_class_template = """\
class {typename}:
def __init__(self, *args, **kwargs):
    fields = {field_names!r}

    for x in fields:
        setattr(self, x, None)            

    for name, value in zip(fields, args):
        setattr(self, name, value)

    for name, value in kwargs.items():
        setattr(self, name, value)            

def __repr__(self):
    return str(vars(self))

def __setattr__(self, name, value):
    if name not in {field_names!r}:
        raise KeyError("invalid name: %s" % name)
    object.__setattr__(self, name, value)            
"""

def struct(typename, field_names):

    class_definition = _class_template.format(
        typename = typename,
        field_names = field_names)

    namespace = dict(__name__='struct_%s' % typename)
    exec(class_definition, namespace)
    result = namespace[typename]
    result._source = class_definition

    return result

Sử dụng:

Person = struct('Person', ['firstname','lastname'])
generic = Person()
michael = Person('Michael')
jones = Person(lastname = 'Jones')


In [168]: michael.middlename = 'ben'
Traceback (most recent call last):

  File "<ipython-input-168-b31c393c0d67>", line 1, in <module>
michael.middlename = 'ben'

  File "<string>", line 19, in __setattr__

KeyError: 'invalid name: middlename'

2

Có một gói python chính xác cho mục đích này. xem cstruct2py

cstruct2pylà một thư viện python thuần để tạo các lớp python từ mã C và sử dụng chúng để đóng gói và giải nén dữ liệu. Thư viện có thể phân tích các tiêu đề C (structs, unions, enums và mảng khai báo) và mô phỏng chúng trong python. Các lớp pythonic được tạo ra có thể phân tích cú pháp và đóng gói dữ liệu.

Ví dụ:

typedef struct {
  int x;
  int y;
} Point;

after generating pythonic class...
p = Point(x=0x1234, y=0x5678)
p.packed == "\x34\x12\x00\x00\x78\x56\x00\x00"

Cách sử dụng

Đầu tiên chúng ta cần tạo các cấu trúc pythonic:

import cstruct2py
parser = cstruct2py.c2py.Parser()
parser.parse_file('examples/example.h')

Bây giờ chúng ta có thể nhập tất cả các tên từ mã C:

parser.update_globals(globals())

Chúng tôi cũng có thể làm điều đó trực tiếp:

A = parser.parse_string('struct A { int x; int y;};')

Sử dụng các loại và định nghĩa từ mã C

a = A()
a.x = 45
print a
buf = a.packed
b = A(buf)
print b
c = A('aaaa11112222', 2)
print c
print repr(c)

Đầu ra sẽ là:

{'x':0x2d, 'y':0x0}
{'x':0x2d, 'y':0x0}
{'x':0x31316161, 'y':0x32323131}
A('aa111122', x=0x31316161, y=0x32323131)

Bản sao

Đối với bản sao cstruct2pychạy:

git clone https://github.com/st0ky/cstruct2py.git --recursive

0

Tôi nghĩ từ điển cấu trúc Python phù hợp với yêu cầu này.

d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3

0

https://stackoverflow.com/a/32448434/159695 không hoạt động trong Python3.

https://stackoverflow.com/a/35993/159695 hoạt động trong Python3.

Và tôi mở rộng nó để thêm các giá trị mặc định.

class myStruct:
    def __init__(self, **kwds):
        self.x=0
        self.__dict__.update(kwds) # Must be last to accept assigned member variable.
    def __repr__(self):
        args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()]
        return '%s(%s)' % ( self.__class__.__qualname__, ', '.join(args) )

a=myStruct()
b=myStruct(x=3,y='test')
c=myStruct(x='str')

>>> a
myStruct(x=0)
>>> b
myStruct(x=3, y='test')
>>> c
myStruct(x='str')

0

Nếu bạn không có 3.7 cho @dataclass và cần khả năng biến đổi, đoạn mã sau có thể phù hợp với bạn. Nó khá tự viết tài liệu và thân thiện với IDE (tự động hoàn thành), ngăn việc viết hai lần, có thể dễ dàng mở rộng và rất đơn giản để kiểm tra rằng tất cả các biến thể hiện được khởi tạo hoàn toàn:

class Params():
    def __init__(self):
        self.var1 : int = None
        self.var2 : str = None

    def are_all_defined(self):
        for key, value in self.__dict__.items():
            assert (value is not None), "instance variable {} is still None".format(key)
        return True


params = Params()
params.var1 = 2
params.var2 = 'hello'
assert(params.are_all_defined)

0

Đây là một mẹo nhanh và bẩn:

>>> ms = Warning()
>>> ms.foo = 123
>>> ms.bar = 'akafrit'

Nó làm việc như thế nào? Nó chỉ sử dụng lại lớp dựng sẵn Warning(xuất phát từ Exception) và sử dụng nó như lớp bạn đã xác định.

Điểm hay là bạn không cần nhập hoặc xác định bất cứ điều gì trước tiên, rằng "Cảnh báo" là một tên ngắn và điều đó cũng cho thấy rõ bạn đang làm gì đó bẩn thỉu không nên sử dụng ở nơi nào khác ngoài một tập lệnh nhỏ của bạn.

Nhân tiện, tôi đã cố gắng tìm một thứ thậm chí đơn giản hơn ms = object()nhưng không thể (ví dụ cuối cùng này không hoạt động). Nếu bạn có một, tôi quan tâm.


0

Cách tốt nhất tôi tìm thấy để làm điều này là sử dụng một lớp từ điển tùy chỉnh như được giải thích trong bài đăng này: https://stackoverflow.com/a/14620633/8484485

Nếu cần hỗ trợ tự động hoàn thành iPython, chỉ cần định nghĩa hàm dir () như thế này:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self
    def __dir__(self):
        return self.keys()

Sau đó, bạn xác định cấu trúc giả của mình như vậy: (cái này được lồng vào nhau)

my_struct=AttrDict ({
    'com1':AttrDict ({
        'inst':[0x05],
        'numbytes':2,
        'canpayload':False,
        'payload':None
    })
})

Sau đó, bạn có thể truy cập các giá trị bên trong my_struct như thế này:

print(my_struct.com1.inst)

=>[5]


0

NamedTuple thoải mái. nhưng không có ai chia sẻ hiệu suất và lưu trữ.

from typing import NamedTuple
import guppy  # pip install guppy
import timeit


class User:
    def __init__(self, name: str, uid: int):
        self.name = name
        self.uid = uid


class UserSlot:
    __slots__ = ('name', 'uid')

    def __init__(self, name: str, uid: int):
        self.name = name
        self.uid = uid


class UserTuple(NamedTuple):
    # __slots__ = ()  # AttributeError: Cannot overwrite NamedTuple attribute __slots__
    name: str
    uid: int


def get_fn(obj, attr_name: str):
    def get():
        getattr(obj, attr_name)
    return get
if 'memory test':
    obj = [User('Carson', 1) for _ in range(1000000)]      # Cumulative: 189138883
    obj_slot = [UserSlot('Carson', 1) for _ in range(1000000)]          # 77718299  <-- winner
    obj_namedtuple = [UserTuple('Carson', 1) for _ in range(1000000)]   # 85718297
    print(guppy.hpy().heap())  # Run this function individually. 
    """
    Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1000000    24 112000000 34 112000000  34 dict of __main__.User
     1 1000000    24 64000000  19 176000000  53 __main__.UserTuple
     2 1000000    24 56000000  17 232000000  70 __main__.User
     3 1000000    24 56000000  17 288000000  87 __main__.UserSlot
     ...
    """

if 'performance test':
    obj = User('Carson', 1)
    obj_slot = UserSlot('Carson', 1)
    obj_tuple = UserTuple('Carson', 1)

    time_normal = min(timeit.repeat(get_fn(obj, 'name'), repeat=20))
    print(time_normal)  # 0.12550550000000005

    time_slot = min(timeit.repeat(get_fn(obj_slot, 'name'), repeat=20))
    print(time_slot)  # 0.1368690000000008

    time_tuple = min(timeit.repeat(get_fn(obj_tuple, 'name'), repeat=20))
    print(time_tuple)  # 0.16006120000000124

    print(time_tuple/time_slot)  # 1.1694481584580898  # The slot is almost 17% faster than NamedTuple on Windows. (Python 3.7.7)

Nếu bạn __dict__không sử dụng, vui lòng chọn giữa __slots__(hiệu suất và lưu trữ cao hơn) và NamedTuple(rõ ràng để đọc và sử dụng)

Bạn có thể xem lại liên kết này ( Cách sử dụng vị trí ) để có thêm __slots__thông tin.

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.