Structures de type C en Python

447

Existe-t-il un moyen de définir facilement une structure de type C en Python? J'en ai assez d'écrire des trucs comme:

class MyStruct():
    def __init__(self, field1, field2, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3
wesc
la source
5
De manière semi-apparentée, les types de données algébriques seraient absolument merveilleux, mais pour bien les utiliser, vous avez généralement besoin d'une correspondance de modèle.
Edward Z. Yang
51
Y a-t-il quelque chose qui cloche dans cette méthode, à part qu'il est fastidieux d'écrire?
levesque
2
Vous pouvez trouver dstruct utile: github.com/dorkitude/dstruct
Kyle Wild
10
@levesque plus difficile à re-factoriser sans fautes de frappe, plus difficile à lire en un coup d'œil en écrémant le code, queMyStruct = namedtuple("MyStruct", "field1 field2 field3")
sam boosalis
1
pandas.Series(a=42).adevrait le faire si vous êtes un scientifique des données ...
Mark Horvath

Réponses:

341

Utilisez un tuple nommé , qui a été ajouté au module collections dans la bibliothèque standard de Python 2.6. Il est également possible d'utiliser la recette de tuple nommée de Raymond Hettinger si vous devez prendre en charge Python 2.4.

C'est bien pour votre exemple de base, mais couvre également un tas de cas de bord que vous pourriez également rencontrer plus tard. Votre fragment ci-dessus serait écrit comme suit:

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

Le type nouvellement créé peut être utilisé comme ceci:

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

Vous pouvez également utiliser des arguments nommés:

m = MyStruct(field1="foo", field2="bar", field3="baz")
gz.
la source
164
... mais namedtuple est immuable. L'exemple de l'OP est modifiable.
mhowison
28
@mhowison - Dans mon cas, c'est juste un plus.
ArtOfWarfare
3
Belle solution. Comment feriez-vous pour parcourir un tableau de ces tuples? Je suppose que les champs 1-3 devraient avoir les mêmes noms sur les objets tuple.
Michael Smith
2
namedtuple peut avoir au moins quatre arguments, alors comment pouvons-nous mapper la structure avec plus de membres de données avec un namedtuple correspondant
Kapil
3
@Kapil - Le deuxième argument de namedtuple devrait être une liste des noms des membres. Cette liste peut être de n'importe quelle longueur.
ArtOfWarfare
228

Mise à jour : classes de données

Avec l'introduction des classes de données dans Python 3.7, nous sommes très proches.

L'exemple suivant est similaire à l' exemple NamedTuple ci-dessous, mais l'objet résultant est modifiable et il autorise les valeurs par défaut.

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)

Cela fonctionne bien avec le nouveau module de saisie au cas où vous voudriez utiliser des annotations de type plus spécifiques.

J'attendais désespérément ça! Si vous me demandez, les classes de données et la nouvelle déclaration NamedTuple , combinées avec le module de frappe sont une aubaine!

Déclaration NamedTuple améliorée

Depuis Python 3.6, il est devenu assez simple et beau (à mon humble avis), tant que vous pouvez vivre avec l' immuabilité .

Une nouvelle façon de déclarer NamedTuples a été introduite, qui permet également les annotations de type :

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
la source
6
Mate, tu viens de faire ma journée - dict immuables - merci: D
Dmitry Arkhipenko
10
Le dataclassmodule est nouveau dans Python 3.7 mais vous le pouvez pip install dataclasses. C'est le backport sur Python 3.6. pypi.org/project/dataclasses/#description
Lavande
+1 pour une déclaration NamedTuple améliorée. L'ancienne méthode était vraiment désagréable à lire si vous aviez plusieurs variables ...
gebbissimo
@Lavande Puis-je savoir quels changements de rupture se sont produits entre 3.6 et 3.7 que vous devez rétroporter une version mineure en arrière ...?
Purple Ice
1
@PurpleIce C'était une implémentation de PEP 557, Classes de données @dataclassLes détails sont ici: pypi.org/project/dataclasses/#description
Lavande
96

Vous pouvez utiliser un tuple pour beaucoup de choses où vous utiliseriez une structure en C (quelque chose comme les coordonnées x, y ou les couleurs RVB par exemple).

Pour tout le reste, vous pouvez utiliser un dictionnaire ou une classe utilitaire comme celle-ci :

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

Je pense que la discussion "définitive" est ici , dans la version publiée du livre de recettes Python.

dF.
la source
5
Une classe vide ferait-elle la même chose?
Kurt Liu
44
Remarquez si vous êtes nouveau dans python: les tuples sont en lecture seule une fois créés, contrairement aux structures C
LeBleu
2
@KurtLiu Non, cela dirait probablementTypeError: this constructor takes no arguments
Evgeni Sergeev
84

Peut-être que vous recherchez des structures sans constructeurs:

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
la source
5
Ce que vous faites ici fonctionne, techniquement, mais il n'est probablement pas immédiatement évident pour de nombreux utilisateurs pourquoi cela fonctionne. Vos déclarations sous class Sample:ne font rien immédiatement; ils définissent des attributs de classe. Ceux-ci peuvent toujours être consultés comme par exemple Sample.name.
Channing Moore
22
Qu'est - ce que vous fait faire est l' ajout de propriétés d'exemple aux objets s1et s2à l' exécution. Sauf interdiction contraire, vous pouvez nameà tout moment ajouter ou modifier l' attribut sur n'importe quelle instance d'une classe, que la classe ait ou non un nameattribut. Le plus gros problème fonctionnel de cette opération est probablement que différentes instances de la même classe se comporteront différemment selon que vous l'avez définie name. Si vous mettez à jour Sample.name, tout objet sans namepropriété explicitement définie renverra le nouveau name.
Channing Moore
2
C'est aussi proche que possible d'une struct - "classe" courte sans méthodes, "champs" (attributs de classe, je sais) avec des valeurs par défaut. Tant que ce n'est pas un type mutable (dict, list), tout va bien. Bien sûr, vous pouvez frapper contre PEP-8 ou des contrôles IDE "amicaux" comme "la classe n'a pas de méthode init " de PyCharm .
Tomasz Gandor
4
J'ai expérimenté l'effet secondaire décrit par Channing Moore. Ne vaut pas l'économie de quelques selfmots-clés et d'une ligne constructeur si vous me demandez. J'apprécierais que Jose modifie sa réponse pour ajouter un message d'avertissement sur le risque de partage accidentel de valeurs entre les instances.
Stéphane C.
@ChanningMoore: J'ai essayé de recréer le problème que vous décrivez, mais j'ai échoué. Pourriez-vous présenter un exemple de travail minimal où le problème apparaît?
gebbissimo
67

Et un dictionnaire?

Quelque chose comme ça:

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

Ensuite, vous pouvez utiliser ceci pour manipuler des valeurs:

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

Et les valeurs ne doivent pas nécessairement être des chaînes. Ils peuvent être à peu près n'importe quel autre objet.

Mark Biek
la source
34
C'est aussi mon approche, mais j'ai l'impression que c'est dangereux précisément parce qu'un dictionnaire peut accepter n'importe quoi pour une clé. Il n'y aura pas d'erreur si je définis myStruct ["ffield"] alors que je voulais définir myStruct ["field"]. Le problème peut (ou peut ne pas apparaître) apparaître lorsque j'utilise ou réutilise myStruct ["champ"] plus tard. J'aime l'approche de PabloG.
mobabo
Le même problème existe avec PabloG. Essayez d'ajouter le code suivant au sien: pt3.w = 1 print pt3.w dans un langage avec dict, il est préférable de les utiliser, en particulier pour les objets en cours de sérialisation, car vous pouvez automatiquement utiliser import json pour les enregistrer et d'autres bibliothèques de sérialisation tant que vous n'avez pas d'étranges des choses à l'intérieur de votre dict. Les dictés sont la solution pour séparer les données et la logique et sont meilleurs que les structures pour les personnes qui ne veulent pas écrire de fonctions de sérialisation et de désérialisation personnalisées et ne veulent pas utiliser des sérialiseurs non portables comme le pickle.
Poikilos
27

dF: c'est plutôt cool ... Je ne savais pas que je pouvais accéder aux champs d'une classe en utilisant dict.

Mark: les situations que j'aurais aimé avoir c'est précisément quand je veux un tuple mais rien d'aussi "lourd" qu'un dictionnaire.

Vous pouvez accéder aux champs d'une classe à l'aide d'un dictionnaire car les champs d'une classe, ses méthodes et toutes ses propriétés sont stockés en interne à l'aide de dict (au moins en CPython).

... Ce qui nous amène à votre deuxième commentaire. Croire que les textes de Python sont "lourds" est un concept extrêmement non pythoniste. Et la lecture de ces commentaires tue mon Python Zen. Ce n'est pas bon.

Vous voyez, lorsque vous déclarez une classe, vous créez en fait un wrapper assez complexe autour d'un dictionnaire - donc, si vous ajoutez quoi que ce soit, vous ajoutez plus de surcharge qu'en utilisant un simple dictionnaire. Un surcoût qui, soit dit en passant, n'a de toute façon aucun sens. Si vous travaillez sur des applications critiques pour les performances, utilisez C ou quelque chose.

Vicent Marti
la source
5
# 1, Cython! = CPython. Je pense que vous parliez de CPython, l'implémentation de Python écrit en C, pas Cython, un projet de compilation croisée de code Python en code C. J'ai modifié votre réponse pour résoudre ce problème. # 2, je pense que quand il a dit que les pronostics sont lourds, il faisait référence à la syntaxe. self['member']est plus long que 3 caractères self.member, et ces caractères sont tous relativement peu adaptés au poignet.
ArtOfWarfare
19

Vous pouvez sous-classer la structure C disponible dans la bibliothèque standard. Le module ctypes fournit une classe Structure . L'exemple de la documentation:

>>> 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
la source
18

Je voudrais également ajouter une solution qui utilise des slots :

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

Vérifiez la documentation pour les emplacements, mais une explication rapide des emplacements est que c'est la façon de python de dire: "Si vous pouvez verrouiller ces attributs et uniquement ces attributs dans la classe de telle sorte que vous vous engagez à ne pas ajouter de nouveaux attributs une fois la classe est instanciée (oui, vous pouvez ajouter de nouveaux attributs à une instance de classe, voir l'exemple ci-dessous), puis je vais supprimer la grande allocation de mémoire qui permet d'ajouter de nouveaux attributs à une instance de classe et d'utiliser exactement ce dont j'ai besoin pour ceux-ci attributs fendus ".

Exemple d'ajout d'attributs à l'instance de classe (n'utilisant donc pas d'emplacements):

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

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

Sortie: 8

Exemple d'essayer d'ajouter des attributs à l'instance de classe où les emplacements ont été utilisés:

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

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

Sortie: AttributeError: l'objet 'Point' n'a pas d'attribut 'z'

Cela peut effectivement fonctionner comme une structure et utilise moins de mémoire qu'une classe (comme le ferait une structure, même si je n'ai pas recherché exactement combien). Il est recommandé d'utiliser des emplacements si vous créez une grande quantité d'instances de l'objet et n'avez pas besoin d'ajouter d'attributs. Un objet ponctuel en est un bon exemple car il est probable que l'on puisse instancier de nombreux points pour décrire un ensemble de données.

Oamar Kanji
la source
17

Vous pouvez également passer les paramètres init aux variables d'instance par position

# 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
la source
7
La mise à jour par position ignore l'ordre de déclaration des attributs et utilise leur tri alphabétique à la place. Donc, si vous changez l'ordre des lignes dans la Point3dStructdéclaration, Point3dStruct(5, 6)cela ne fonctionnera pas comme prévu. C'est étrange que personne n'ait écrit cela au cours des 6 années.
lapis
Pourrait ajouter une version Python 3 à votre code génial? Bon travail! J'aime que vous preniez quelque chose d'abstrait et que vous le rendiez explicite avec la deuxième classe spécifique. Cela devrait être bon pour la gestion / capture des erreurs. Pour Python 3, changez simplement print> print(), et attrs[n]> next(attrs)(le filtre est maintenant son propre objet itérable et nécessite next).
Jonathan Komar
10

Chaque fois que j'ai besoin d'un "objet de données instantané qui se comporte également comme un dictionnaire" (je ne pense pas aux structures C!), Je pense à ce joli hack:

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

Maintenant, vous pouvez simplement dire:

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

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

Parfaitement pratique pour les moments où vous avez besoin d'un "sac de données qui n'est PAS une classe", et pour quand les couples nommés sont incompréhensibles ...

Phlip
la source
J'utilise pandas.Series (a = 42) ;-)
Mark Horvath
8

Vous accédez à la structure C-Style en python de la manière suivante.

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

si vous voulez juste utiliser un objet de 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)

si vous voulez créer un tableau d'objets de structure

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)

Remarque: au lieu du nom 'cstruct', veuillez utiliser votre nom de structure au lieu de var_i, var_f, var_str, veuillez définir la variable membre de votre structure.

Sujal Sheth
la source
3
Est-ce différent de ce qui se trouve dans stackoverflow.com/a/3761729/1877426 ?
lagweezle
8

Certaines réponses ici sont massivement élaborées. L'option la plus simple que j'ai trouvée est (depuis: http://norvig.com/python-iaq.html ):

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

Initialisation:

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

en ajoutant plus:

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

edit: Désolé, je n'ai pas vu cet exemple plus bas.

w_jay
la source
5

Cela pourrait être un peu tard mais j'ai fait une solution en utilisant les méta-classes Python (version décoratrice ci-dessous aussi).

Quand __init__ est appelé pendant l'exécution, il saisit chacun des arguments et leur valeur et les affecte en tant que variables d'instance à votre classe. De cette façon, vous pouvez créer une classe de type structure sans avoir à attribuer chaque valeur manuellement.

Mon exemple n'a pas de vérification d'erreur, il est donc plus facile à suivre.

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 

Le voici en action.

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


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

Je l'ai posté sur reddit et / u / matchu a publié une version décorative plus propre. Je vous encourage à l'utiliser sauf si vous souhaitez étendre la version de la métaclasse.

>>> 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
>>> 
user124757
la source
Merde - J'ai passé deux heures aujourd'hui à écrire mon propre décorateur pour faire ça et puis j'ai trouvé ça. Quoi qu'il en soit, publier le mien car il gère les valeurs par défaut alors que le vôtre ne le fait pas. stackoverflow.com/a/32448434/901641
ArtOfWarfare
+1 pour mentionner func_code. J'ai commencé à creuser dans cette direction et y ai trouvé beaucoup de choses intéressantes.
wombatonfire
5

J'ai écrit un décorateur que vous pouvez utiliser sur n'importe quelle méthode pour que tous les arguments passés, ou toutes les valeurs par défaut, soient affectés à l'instance.

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

Une démonstration rapide. Notez que j'utilise un argument positionnel a, j'utilise la valeur par défaut pour bet un argument nommé c. J'imprime ensuite les 3 références self, pour montrer qu'elles ont été correctement attribuées avant la saisie de la méthode.

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

Notez que mon décorateur devrait fonctionner avec n'importe quelle méthode, pas seulement __init__.

ArtOfWarfare
la source
5

Je ne vois pas cette réponse ici, donc je pense que je vais l'ajouter puisque je penche Python en ce moment et je viens de la découvrir. Le didacticiel Python (Python 2 dans ce cas) donne l'exemple simple et efficace suivant:

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

En d'autres termes, un objet de classe vide est créé, puis instancié et les champs sont ajoutés dynamiquement.

L'avantage est que c'est vraiment simple. L'inconvénient est qu'il n'est pas particulièrement auto-documenté (les membres prévus ne sont répertoriés nulle part dans la "définition" de la classe), et les champs non définis peuvent causer des problèmes lors de l'accès. Ces deux problèmes peuvent être résolus par:

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

Maintenant, en un coup d'œil, vous pouvez au moins voir quels domaines le programme attendra.

Les deux sont sujets aux fautes de frappe, john.slarly = 1000réussiront. Mais ça marche.

Jason C
la source
4

Voici une solution qui utilise une classe (jamais instanciée) pour conserver les données. J'aime que cette méthode implique très peu de frappe et ne nécessite pas de packages supplémentaires, etc.

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

Vous pouvez ajouter plus de champs ultérieurement, si nécessaire:

myStruct.field3 = 3

Pour obtenir les valeurs, les champs sont accessibles comme d'habitude:

>>> myStruct.field1
'one'
jochen
la source
2

Personnellement, j'aime aussi cette variante. Il étend la réponse de @ 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__)

Il prend en charge deux modes d'initialisation (qui peuvent être mélangés):

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

En outre, il imprime mieux:

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

La solution suivante à une structure est inspirée de l'implémentation namedtuple et de certaines des réponses précédentes. Cependant, à la différence du tuple nommé, il est mutable, dans ses valeurs, mais comme la structure de style c immuable dans les noms / attributs, ce qui n'est pas le cas d'une classe ou d'un dict normal.

_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

Usage:

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
la source
2

Il existe un package python exactement à cet effet. voir cstruct2py

cstruct2pyest une bibliothèque python pure pour générer des classes python à partir de code C et les utiliser pour emballer et décompresser des données. La bibliothèque peut analyser les en-têtes C (déclarations de structures, d'unions, d'énumérations et de tableaux) et les émuler en python. Les classes pythoniques générées peuvent analyser et compresser les données.

Par exemple:

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"

Comment utiliser

Nous devons d'abord générer les structures pythoniques:

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

Maintenant, nous pouvons importer tous les noms du code C:

parser.update_globals(globals())

Nous pouvons également le faire directement:

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

Utilisation de types et de définitions à partir du code 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)

La sortie sera:

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

Cloner

Pour l' cstruct2pyexécution de clone :

git clone https://github.com/st0ky/cstruct2py.git --recursive
שמואל ביאליסטוקי
la source
0

Je pense que le dictionnaire de structure Python convient à cette exigence.

d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3
Yujun Li
la source
0

https://stackoverflow.com/a/32448434/159695 ne fonctionne pas en Python3.

https://stackoverflow.com/a/35993/159695 fonctionne en Python3.

Et je l'étends pour ajouter des valeurs par défaut.

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')
Galaxie
la source
0

Si vous n'avez pas de 3.7 pour @dataclass et avez besoin d'une mutabilité, le code suivant peut fonctionner pour vous. Il est assez auto-documenté et convivial pour l'IDE (auto-complétion), empêche l'écriture deux fois, est facilement extensible et il est très simple de tester que toutes les variables d'instance sont complètement initialisées:

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
la source
0

Voici une astuce rapide et sale:

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

Comment ça fonctionne? Il suffit de réutiliser la classe intégrée Warning(dérivée de Exception) et de l'utiliser comme si c'était votre propre classe définie.

Les bons points sont que vous n'avez pas besoin d'importer ou de définir quoi que ce soit d'abord, que "Avertissement" est un nom court, et qu'il indique également clairement que vous faites quelque chose de sale qui ne devrait pas être utilisé ailleurs qu'un petit script de la vôtre.

Soit dit en passant, j'ai essayé de trouver quelque chose d'encore plus simple, ms = object()mais sans succès (ce dernier exemple ne fonctionne pas). Si vous en avez un, je suis intéressé.

calandoa
la source
0

La meilleure façon que j'ai trouvée pour ce faire était d'utiliser une classe de dictionnaire personnalisée comme expliqué dans ce post: https://stackoverflow.com/a/14620633/8484485

Si la prise en charge de la saisie semi-automatique iPython est nécessaire, définissez simplement la fonction dir () comme ceci:

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

Vous définissez ensuite votre pseudo struct comme ceci: (celui-ci est imbriqué)

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

Vous pouvez ensuite accéder aux valeurs dans my_struct comme ceci:

print(my_struct.com1.inst)

=>[5]

Tioneb
la source
0

NamedTuple est confortable. mais personne ne partage les performances et le stockage.

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)

Si vous __dict__n'utilisez pas, veuillez choisir entre __slots__(performances et stockage supérieurs) et NamedTuple(clair pour la lecture et l'utilisation)

Vous pouvez consulter ce lien ( Utilisation des créneaux horaires ) pour obtenir plus d' __slots__informations.

Carson
la source