Struktury podobne do C w Pythonie

447

Czy istnieje sposób wygodnego zdefiniowania struktury podobnej do C w Pythonie? Mam dość pisania takich rzeczy jak:

class MyStruct():
    def __init__(self, field1, field2, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3
wesc
źródło
5
Powiązane ze sobą algebraiczne typy danych byłyby absolutnie cudowne, ale aby je dobrze wykorzystać, zwykle potrzebujesz dopasowania wzorca.
Edward Z. Yang,
51
Czy jest coś złego w tej metodzie niż żmudne pisanie?
levesque
2
Przydatne może być dstruct: github.com/dorkitude/dstruct
Kyle Wild
10
@levesque trudniejsze do ponownego uwzględnienia bez literówek, trudniejsze do odczytania na pierwszy rzut oka podczas przeglądania kodu, niżMyStruct = namedtuple("MyStruct", "field1 field2 field3")
sam boosalis
1
pandas.Series(a=42).apowinien to zrobić, jeśli jesteś naukowcem danych ...
Mark Horvath

Odpowiedzi:

341

Użyj nazwanej krotki , która została dodana do modułu kolekcji w standardowej bibliotece w Pythonie 2.6. Możliwe jest również użycie przepisanego krotka Raymonda Hettingera, jeśli chcesz obsługiwać Python 2.4.

Jest to miłe z punktu widzenia twojego podstawowego przykładu, ale obejmuje również kilka przypadków krawędzi, na które możesz natknąć się później. Twój fragment powyżej zostanie zapisany jako:

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

Nowo utworzonego typu można użyć w następujący sposób:

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

Możesz także użyć nazwanych argumentów:

m = MyStruct(field1="foo", field2="bar", field3="baz")
gz.
źródło
164
... ale namedtuple jest niezmienny. Przykład w OP jest zmienny.
mhowison
28
@mhowison - W moim przypadku to tylko plus.
ArtOfWarfare
3
Niezłe rozwiązanie. Jak przeszedłbyś przez szereg tych krotek? Zakładam, że pola 1-3 musiałyby mieć takie same nazwy w krotce.
Michael Smith
2
namedtuple może mieć cztery argumenty, abyśmy mogli zmapować strukturę z większą liczbą elementów danych z odpowiadającym im nazwami
Kapil
3
@Kapil - Drugim argumentem namedtuple powinna być lista nazwisk członków. Ta lista może mieć dowolną długość.
ArtOfWarfare
226

Aktualizacja : Klasy danych

Wraz z wprowadzeniem klas danych w Pythonie 3.7 jesteśmy bardzo blisko.

Poniższy przykład jest podobny do poniższego przykładu NamedTuple , ale wynikowy obiekt można modyfikować i dopuszcza wartości domyślne.

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)

To ładnie gra z nowym modułem pisania na wypadek, gdybyś chciał użyć bardziej szczegółowych adnotacji typu.

Desperacko na to czekałem! Jeśli mnie zapytasz, klasy danych i nowa deklaracja NamedTuple w połączeniu z pisaniem modułem są niebios!

Ulepszona deklaracja NamedTuple

Od wersji Python 3.6 stało się dość proste i piękne (IMHO), o ile można żyć z niezmiennością .

Wprowadzono nowy sposób deklarowania NamedTuples , który umożliwia także dodawanie adnotacji :

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'))
Rotareti
źródło
6
Kolego, właśnie wykonałeś mój dzień - niezmienne dyktanda - dziękuję: D
Dmitry Arkhipenko
10
dataclassModuł jest nowego w Pythonie 3.7, ale można pip install dataclasses. Jest to backport w Pythonie 3.6. pypi.org/project/dataclasses/#description
Lavande
+1 za ulepszoną deklarację NamedTuple. Stary sposób był naprawdę nieprzyjemny, jeśli miałeś kilka zmiennych ...
gebbissimo 28.01.19
@Landande Czy mogę wiedzieć, jakie przełomowe zmiany wydarzyły się między 3.6 a 3.7, że musicie backportować jedną mniejszą wersję z powrotem ...?
Purple Ice
1
@PurpleIce To było wdrożenie PEP 557, Klasy danych @dataclassSzczegóły są tutaj: pypi.org/project/dataclasses/#description
Lavande
96

Możesz użyć krotki do wielu rzeczy, w których użyjesz struktury w C (np. Współrzędne x, y lub kolory RGB).

Do wszystkiego innego możesz użyć słownika lub klasy użyteczności takiej jak ta :

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

Myślę, że „ostateczna” dyskusja jest tutaj , w opublikowanej wersji Python Cookbook.

dF.
źródło
5
Czy pusta klasa zrobiłaby to samo?
Kurt Liu,
44
Uwaga: jeśli dopiero zaczynasz korzystać z Pythona: krotki są tworzone tylko do odczytu, w przeciwieństwie do struktur C
LeBleu
2
@KurtLiu Nie, prawdopodobnie powiedziałbyTypeError: this constructor takes no arguments
Jewgienij Siergiejew
84

Być może szukasz Struktur bez konstruktorów:

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
Jose M. Balaguer
źródło
5
To, co tu robisz, działa technicznie, ale prawdopodobnie nie jest od razu oczywiste dla wielu użytkowników, dlaczego to działa. Twoje oświadczenia pod class Sample:nie robią natychmiast nic; ustawiają atrybuty klas. Można do nich zawsze uzyskać dostęp np Sample.name.
Channing Moore
22
W rzeczywistości dodajesz właściwości instancji do obiektów s1i s2w czasie wykonywania. O ile nie zabrania się tego inaczej, możesz dodawać lub modyfikować nameatrybut w dowolnej instancji dowolnej klasy w dowolnym momencie, niezależnie od tego, czy klasa ma nameatrybut. Prawdopodobnie największym problemem funkcjonalnym jest to, że różne instancje tej samej klasy będą zachowywać się inaczej w zależności od tego, czy został ustawiony name. Jeśli zaktualizujesz Sample.name, wszelkie obiekty bez jawnie ustawionej namewłaściwości zwrócą nowy name.
Channing Moore
2
Jest tak blisko, jak to możliwe, do struktury - krótkiej „klasy” bez metod, „pól” (atrybuty klas, wiem) z wartościami domyślnymi. Dopóki nie jest to zmienny typ (dyktowanie, lista), nic ci nie jest. Oczywiście możesz uderzać w PEP-8 lub „przyjazne” kontrole IDE, takie jak „klasa PyCharma nie ma metody init ”.
Tomasz Gandor
4
Eksperymentowałem z efektem ubocznym opisanym przez Channinga Moore'a. Nie warto oszczędzać kilku selfsłów kluczowych i linii konstruktora, jeśli mnie o to pytasz. Byłbym wdzięczny, gdyby Jose mógł edytować swoją odpowiedź, aby dodać ostrzeżenie o ryzyku przypadkowego dzielenia się wartościami między instancjami.
Stéphane C.
@ChanningMoore: Próbowałem odtworzyć opisany problem, ale nie udało się. Czy możesz przedstawić minimalny działający przykład, w którym pojawia się problem?
gebbissimo
67

Co powiesz na słownik?

Coś takiego:

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

Następnie możesz użyć tego do manipulowania wartościami:

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

A wartości nie muszą być łańcuchami. Mogą być praktycznie dowolnym innym przedmiotem.

Mark Biek
źródło
34
Takie też jest moje podejście, ale wydaje mi się, że jest to niebezpieczne właśnie dlatego, że słownik może zaakceptować wszystko dla klucza. Nie będzie błędu, jeśli ustawię myStruct [„pole”], gdy chciałem ustawić myStruct [„pole”]. Problem może (lub nie musi) stać się oczywisty, gdy później używam lub ponownie używam myStruct [„field”]. Lubię podejście PabloG.
mobabo
Ten sam problem występuje z PabloG. Spróbuj dodać następujący kod do jego: pt3.w = 1 print pt3.w W języku ze słownikami lepiej jest ich używać, szczególnie w przypadku serializowanych obiektów, ponieważ możesz automatycznie użyć importu json, aby zapisać je i inne biblioteki serializacji, o ile nie masz dziwnych rzeczy w twoim dyktandzie. Dicts to rozwiązanie oddzielające dane i logikę i są lepsze niż struktury dla osób, które nie chcą pisać niestandardowych funkcji serializacji i odserializować i nie chcą używać nieprzenośnych serializatorów takich jak marynata.
Poikilos,
27

dF: to całkiem fajne ... Nie wiedziałem, że mogę uzyskać dostęp do pól w klasie za pomocą dict.

Mark: sytuacje, które chciałbym mieć, są właśnie wtedy, gdy chcę krotkę, ale nic tak „ciężkiego” jak słownik.

Możesz uzyskać dostęp do pól klasy za pomocą słownika, ponieważ pola klasy, jej metody i wszystkie jej właściwości są przechowywane wewnętrznie za pomocą dykt (przynajmniej w CPython).

... Co prowadzi nas do twojego drugiego komentarza. Przekonanie, że dyktanda Pythona są „ciężkie”, jest pojęciem skrajnie pythonistycznym. A czytanie takich komentarzy zabija mojego Pythona Zen. To nie jest dobrze.

Widzisz, kiedy deklarujesz klasę, faktycznie tworzysz dość złożone opakowanie wokół słownika - więc, jeśli już, to dodajesz więcej narzutu niż przy użyciu prostego słownika. Koszty ogólne, które, nawiasem mówiąc, są w ogóle bez znaczenia. Jeśli pracujesz nad aplikacjami krytycznymi pod względem wydajności, użyj C lub czegoś takiego.

Vicent Marti
źródło
5
# 1, Cython! = CPython. Myślę, że mówiłeś o CPython, implementacji Pythona napisanej w C, a nie Cython, projekcie krzyżowania kompilacji kodu Pythona w kodzie C. Zredagowałem twoją odpowiedź, aby to naprawić. # 2, myślę, że kiedy powiedział, że dyktanda są ciężkie, miał na myśli składnię. self['member']jest o 3 znaki dłuższy niż self.memberi wszystkie te postacie są względnie nieprzyjazne dla nadgarstka.
ArtOfWarfare
19

Możesz podklasować strukturę C dostępną w standardowej bibliotece. Ctypes moduł dostarcza struktura klasowa . Przykład z dokumentów:

>>> 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
>>>
Ella Rose
źródło
18

Chciałbym również dodać rozwiązanie wykorzystujące automaty :

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

Zdecydowanie sprawdź w dokumentacji szczeliny, ale szybkie wyjaśnienie szczelin jest takie, że python tak mówi: „Jeśli możesz zablokować te atrybuty i tylko te atrybuty w klasie, tak że zobowiązujesz się, że nie dodasz żadnych nowych atrybutów, gdy klasa jest tworzony (tak, możesz dodać nowe atrybuty do instancji klasy, patrz przykład poniżej), a następnie zrezygnuję z dużej alokacji pamięci, która pozwala na dodawanie nowych atrybutów do instancji klasy i użyć tylko tego, czego potrzebuję dla tych szczelin atrybutów ".

Przykład dodawania atrybutów do instancji klasy (a więc nie używania gniazd):

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

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

Wyjście: 8

Przykład próby dodania atrybutów do instancji klasy, w której zastosowano sloty:

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

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

Dane wyjściowe: AttributeError: Obiekt „Point” nie ma atrybutu „z”

Może to skutecznie działać jako struktura i zużywa mniej pamięci niż klasa (tak jak struktury, choć nie badałem dokładnie ile). Zaleca się stosowanie miejsc, jeśli będziesz tworzyć dużą liczbę instancji obiektu i nie musisz dodawać atrybutów. Obiekt punktowy jest tego dobrym przykładem, ponieważ prawdopodobne jest, że można utworzyć wiele punktów w celu opisania zestawu danych.

Oamar Kanji
źródło
17

Możesz także przekazać parametry inicjujące do zmiennych instancji według pozycji

# 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
PabloG
źródło
7
Aktualizacja według pozycji ignoruje kolejność deklaracji atrybutów i zamiast tego używa ich sortowania alfabetycznego. Więc jeśli zmienisz kolejność wierszy w Point3dStructdeklaracji, Point3dStruct(5, 6)nie będzie działać zgodnie z oczekiwaniami. Dziwne, że nikt nie napisał tego przez wszystkie 6 lat.
lapis
Czy możesz dodać wersję Python 3 do swojego niesamowitego kodu? Świetna robota! Podoba mi się, że bierzesz coś abstrakcyjnego i wyraźnie wyrażasz to w drugiej klasie. To powinno być dobre do obsługi błędów / łapania. W przypadku Python 3 wystarczy zmienić print> print()i attrs[n]> next(attrs)(filtr jest teraz własnym iterowalnym obiektem i wymaga next).
Jonathan Komar
10

Ilekroć potrzebuję „natychmiastowego obiektu danych, który również zachowuje się jak słownik” ( nie myślę o strukturach C!), Myślę o tym uroczym hacku:

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

Teraz możesz po prostu powiedzieć:

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

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

Idealnie przydatny w sytuacjach, gdy potrzebujesz „torby danych, która NIE JEST klasą”, i gdy imienniki są niezrozumiałe ...

Phlip
źródło
Używam pand.Series (a = 42) ;-)
Mark Horvath
8

Dostęp do struktury C-Style uzyskuje się w pythonie w następujący sposób.

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

jeśli chcesz tylko użyć obiektu 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)

jeśli chcesz stworzyć tablicę obiektów konstrukcyjnych

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)

Uwaga: zamiast nazwy „cstruct” użyj nazwy struktury zamiast var_i, var_f, var_str, proszę zdefiniuj zmienną członka swojej struktury.

Sujal Sheth
źródło
3
Czy to coś innego niż w stackoverflow.com/a/3761729/1877426 ?
lagweezle
8

Niektóre odpowiedzi tutaj są bardzo rozbudowane. Najprostszą opcją, którą znalazłem, jest (z: http://norvig.com/python-iaq.html ):

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

Inicjalizacja:

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

dodając więcej:

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

edytuj: Przykro mi, ale nie widziałem jeszcze tego przykładu

w_jay
źródło
5

To może być trochę za późno, ale zrobiłem rozwiązanie za pomocą Python Meta-Classes (poniżej również wersja dekoratora).

Kiedy __init__jest wywoływany w czasie wykonywania, pobiera każdy z argumentów i ich wartość i przypisuje je jako zmienne instancji do twojej klasy. W ten sposób możesz stworzyć klasę podobną do struktury bez konieczności przypisywania każdej wartości ręcznie.

W moim przykładzie nie ma sprawdzania błędów, więc łatwiej jest naśladować.

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 

Tutaj jest w akcji.

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


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

I napisali go na reddit i / u / matchu napisali wersję dekorator, który jest czystsze. Zachęcam do korzystania z niego, chyba że chcesz rozszerzyć wersję metaklasy.

>>> 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
>>> 
użytkownik124757
źródło
Cholera - spędziłem dziś dwie godziny na pisaniu własnego dekoratora, aby to zrobić, a potem to znalazłem. W każdym razie, wysyłając moje, ponieważ obsługuje wartości domyślne, podczas gdy twoje nie. stackoverflow.com/a/32448434/901641
ArtOfWarfare
+1 za wzmiankę o func_code. Zacząłem kopać w tym kierunku i znalazłem tam wiele interesujących rzeczy.
wombatonfire 28.03.16
5

Napisałem dekorator, którego można użyć w dowolnej metodzie, aby uczynić go tak, aby wszystkie przekazane argumenty lub wartości domyślne zostały przypisane do instancji.

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

Szybka demonstracja. Zauważ, że używam argumentu pozycyjnego a, używam domyślnej wartości dla bi nazwanego argumentu c. Następnie drukuję wszystkie 3 odwołania self, aby pokazać, że zostały poprawnie przypisane przed wprowadzeniem metody.

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

Pamiętaj, że mój dekorator powinien działać dowolną metodą, nie tylko __init__.

ArtOfWarfare
źródło
5

Nie widzę tutaj tej odpowiedzi, więc sądzę, że ją dodam, ponieważ teraz opieram Python i właśnie ją odkryłem. Poradnik Pythona (Python 2, w tym przypadku) daje następujące proste i skuteczne, na przykład:

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

Oznacza to, że tworzony jest pusty obiekt klasy, a następnie tworzona jest instancja, a pola są dodawane dynamicznie.

Wada tego jest naprawdę prosta. Minusem jest to, że nie jest to szczególnie samok dokumentujące się (zamierzeni członkowie nie są nigdzie wymienieni w „definicji” klasy), a nieuzbrojone pola mogą powodować problemy przy dostępie. Te dwa problemy można rozwiązać przez:

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

Teraz na pierwszy rzut oka możesz przynajmniej zobaczyć, jakich pól będzie oczekiwać program.

Oba są podatne na literówki, john.slarly = 1000odniesie sukces. Mimo to działa.

Jason C.
źródło
4

Oto rozwiązanie, które wykorzystuje klasę (nigdy bez instancji) do przechowywania danych. Podoba mi się, że ten sposób wymaga bardzo niewielkiego pisania i nie wymaga żadnych dodatkowych pakietów itp.

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

W razie potrzeby możesz dodać więcej pól:

myStruct.field3 = 3

Aby uzyskać wartości, pola są dostępne jak zwykle:

>>> myStruct.field1
'one'
jochen
źródło
2

Osobiście podoba mi się również ten wariant. Rozszerza odpowiedź @ 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__)

Obsługuje dwa tryby inicjalizacji (które można łączyć):

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

Ponadto drukuje się ładniej:

print(mystruct2)
# Prints: {'field3': 3, 'field1': 1, 'field2': 2}
normanius
źródło
2

Poniższe rozwiązanie struktury jest zainspirowane implementacją namedtuple i niektórymi wcześniejszymi odpowiedziami. Jednak w przeciwieństwie do nazwanego, jest zmienny pod względem wartości, ale podobnie jak struktura typu c niezmienna w nazwach / atrybutach, co nie jest normalną klasą lub dyktatem.

_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

Stosowanie:

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'
PS1
źródło
2

W tym celu istnieje pakiet python. patrz cstruct2py

cstruct2pyto czysta biblioteka Pythona do generowania klas Pythona z kodu C i używania ich do pakowania i rozpakowywania danych. Biblioteka może analizować nagłówki C (struktury, związki, wyliczenia i tablice) i emulować je w pythonie. Wygenerowane klasy pythonowe mogą analizować i pakować dane.

Na przykład:

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"

Jak używać

Najpierw musimy wygenerować struktury pythonowe:

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

Teraz możemy zaimportować wszystkie nazwy z kodu C:

parser.update_globals(globals())

Możemy to również zrobić bezpośrednio:

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

Używanie typów i definicji z kodu 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)

Dane wyjściowe będą:

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

Klon

Do cstruct2pyuruchomienia klonowania :

git clone https://github.com/st0ky/cstruct2py.git --recursive
שמואל ביאליסטוקי
źródło
0

Myślę, że słownik struktury Python jest odpowiedni dla tego wymagania.

d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3
Yujun Li
źródło
0

https://stackoverflow.com/a/32448434/159695 nie działa w Python3.

https://stackoverflow.com/a/35993/159695 działa w Python3.

I rozszerzam go, aby dodać wartości domyślne.

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')
Galaktyka
źródło
0

Jeśli nie masz wersji 3.7 dla @dataclass i potrzebujesz możliwości zmiany, poniższy kod może być dla Ciebie odpowiedni. Jest dość samo dokumentujący i przyjazny dla IDE (autouzupełnianie), zapobiega dwukrotnemu pisaniu, jest łatwo rozszerzalny i bardzo łatwo jest przetestować, czy wszystkie zmienne instancji są całkowicie inicjalizowane:

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)
gebbissimo
źródło
0

Oto szybka i brudna sztuczka:

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

Jak to działa? Po prostu ponownie użyj wbudowanej klasy Warning(pochodnej Exception) i użyj jej tak, jak byłaś własną zdefiniowaną klasą.

Zaletą jest to, że nie musisz najpierw niczego importować ani definiować, że „Ostrzeżenie” to krótka nazwa i że również wyjaśnia, że ​​robisz coś brudnego, czego nie należy używać w innym miejscu niż twój mały skrypt.

Nawiasem mówiąc, próbowałem znaleźć coś jeszcze prostszego, ms = object()ale nie mogłem (ten ostatni przykład nie działa). Jeśli masz, jestem zainteresowany.

calandoa
źródło
0

Najlepszym sposobem, jaki to zrobiłem, było użycie niestandardowej klasy słownika, jak wyjaśniono w tym poście: https://stackoverflow.com/a/14620633/8484485

Jeśli potrzebna jest obsługa autouzupełniania iPython, po prostu zdefiniuj funkcję dir () w następujący sposób:

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

Następnie definiujesz swoją pseudo-strukturę tak: (ten jest zagnieżdżony)

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

Następnie możesz uzyskać dostęp do wartości wewnątrz my_struct w następujący sposób:

print(my_struct.com1.inst)

=>[5]

Tioneb
źródło
0

NamedTuple jest wygodny. ale nikt nie dzieli wydajności i pamięci.

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)

Jeśli __dict__nie używasz, wybierz pomiędzy __slots__(wyższą wydajnością i pamięcią masową) i NamedTuple((jasne do czytania i użytkowania)

Możesz przejrzeć ten link ( Wykorzystanie miejsc ), aby uzyskać więcej __slots__informacji.

Carson
źródło