Есть ли способ удобно определить C-подобную структуру в Python? Я устал писать такие вещи, как:
class MyStruct():
def __init__(self, field1, field2, field3):
self.field1 = field1
self.field2 = field2
self.field3 = field3
Что-то не так с этим методом, кроме того, что его утомительно писать?
Вы можете найти dstruct полезным: github.com/dorkitude/dstruct
@levesque сложнее пересчитать без опечаток, сложнее прочитать сразу при просмотре кода, чем MyStruct = namedtuple("MyStruct", "field1 field2 field3")
Хорошо namedtuple генерирует класс, так в чем именно разница?
pandas.Series(a=42).a должен это сделать, если вы специалист по данным ...
Переходите к ответу 2018 года: stackoverflow.com/a/45426493/703382
Сам ваш вопрос имеет правильный ответ на «C-подобные структуры в Python» или «Как создать структуру в Python». Изучив все ответы ниже, я бы сказал, что использование класса, как вы это сделали, - лучший способ сделать это.Использование словаря - альтернатива и, возможно, второй лучший способ сделать это..
Если вас беспокоит производительность (ОЗУ / ЦП), тогда ничто не сравнится с настоящим кортежем.






Как насчет словаря?
Что-то вроде этого:
myStruct = {'field1': 'some val', 'field2': 'some val'}
Затем вы можете использовать это для управления значениями:
print myStruct['field1']
myStruct['field2'] = 'some other values'
И значения не обязательно должны быть строками. Они могут быть практически любым другим объектом.
Это был мой подход, но я считаю, что это опасно именно потому, что словарь может принимать что угодно в качестве ключа. Не будет ошибки, если я установлю myStruct ["ffield"], когда я хотел установить myStruct ["field"]. Проблема может (или не может) стать очевидной, когда я использую или повторно использую myStruct ["field"] позже. Мне нравится подход PabloG.
Та же проблема существует с PabloG's. Попробуйте добавить к нему следующий код: pt3.w = 1 print pt3.w На языке с dicts лучше использовать их, особенно для сериализуемых объектов, поскольку вы можете автоматически использовать import json для их сохранения и другие библиотеки сериализации, если у вас нет странные вещи внутри вашего dict. Dicts - это решение для разделения данных и логики, и они лучше, чем структуры, для людей, которые не хотят писать собственные функции сериализации и десериализации и не хотят использовать непереносимые сериализаторы, такие как pickle.
Вы можете использовать кортеж для многих вещей, где вы использовали бы структуру в C (например, что-то вроде координат x, y или цветов RGB).
Для всего остального вы можете использовать словарь или служебный класс, например Вот этот:
>>> class Bunch:
... def __init__(self, **kwds):
... self.__dict__.update(kwds)
...
>>> mystruct = Bunch(field1=value1, field2=value2)
Я думаю, что «окончательное» обсуждение - это здесь в опубликованной версии Python Cookbook.
Будет ли пустой класс делать то же самое?
Обратите внимание, если вы новичок в python: кортежи создаются только для чтения после создания, в отличие от структур C
@KurtLiu Нет, наверное, TypeError: this constructor takes no arguments
Здесь используется объект с внутренним dict __dict__ (ну, как и все объекты, за исключением случаев, когда вы используете __slots__). Так почему бы не использовать dict напрямую? mystruct = {'field1': value1, 'field2': value2}. TL; DR: здесь вы создаете объект просто с целью использования его внутреннего dictobject.__dict__, поэтому просто используйте диктовку с самого начала будет менее сложным.
... тем более, что вы можете просто сделать a = dict(foo=123, bar=456), чтобы сделать этот dict, если вам нравится синтаксис вызова функции с ключевыми словами лучше, чем обычный синтаксис dict, а также str() / repr() несколько более полезны, чем просто предоставление идентификатора объекта.
Используйте названный кортеж, который был добавлен в модуль коллекций в стандартной библиотеке Python 2.6. Также можно использовать рецепт названный кортеж Раймонда Хеттингера, если вам нужна поддержка Python 2.4.
Это хорошо для вашего базового примера, но также охватывает множество крайних случаев, с которыми вы можете столкнуться позже. Ваш фрагмент выше будет записан как:
from collections import namedtuple
MyStruct = namedtuple("MyStruct", "field1 field2 field3")
Вновь созданный тип можно использовать так:
m = MyStruct("foo", "bar", "baz")
Вы также можете использовать именованные аргументы:
m = MyStruct(field1 = "foo", field2 = "bar", field3 = "baz")
... но namedtuple неизменен. Пример в OP изменчив.
@mhowison - В моем случае это просто плюс.
Хорошее решение. Как бы вы перебрали массив этих кортежей? Я предполагаю, что поля 1-3 должны иметь одинаковые имена во всех объектах кортежа.
namedtuple может иметь не более четырех аргументов, поэтому как мы можем сопоставить структуру с большим количеством элементов данных с соответствующим namedtuple
@Kapil Для какой версии Python это применимо?
@ArtOfWarfare знает это.
@Kapil - вторым аргументом namedtuple должен быть список имен членов. Этот список может быть любой длины.
Существует изменяемый вариант namedtuple - recordclass (bitbucket.org/intellimath/recordclass/src/default/README.md)
dF: that's pretty cool... I didn't know that I could access the fields in a class using dict.
Mark: the situations that I wish I had this are precisely when I want a tuple but nothing as "heavy" as a dictionary.
Вы можете получить доступ к полям класса с помощью словаря, потому что поля класса, его методы и все его свойства хранятся внутри с помощью dicts (по крайней мере, в CPython).
... Это подводит нас к вашему второму комментарию. Вера в то, что диктовки Python «тяжелые» - это крайне непифонистская концепция. И чтение таких комментариев убивает мой Python Zen. Это не хорошо.
Видите ли, когда вы объявляете класс, вы фактически создаете довольно сложную оболочку вокруг словаря - так что, во всяком случае, вы добавляете больше накладных расходов, чем при использовании простого словаря. Накладные расходы, которые, кстати, в любом случае бессмысленны. Если вы работаете над приложениями, критичными к производительности, используйте C или что-то в этом роде.
# 1, Cython! = CPython. Я думаю, вы говорили о CPython, реализации Python, написанном на C, а не Cython, проекте кросс-компиляции кода Python в код C. Я отредактировал ваш ответ, чтобы исправить это. №2, я думаю, когда он сказал, что диктовки тяжелые, он имел в виду синтаксис. self['member'] на 3 символа длиннее, чем self.member, и все эти символы относительно неудобны для запястий.
Вы также можете передать параметры инициализации в переменные экземпляра по позиции
# 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
Обновление по позиции игнорирует порядок объявления атрибутов и вместо этого использует их алфавитную сортировку. Поэтому, если вы измените порядок строк в декларации Point3dStruct, Point3dStruct(5, 6) не будет работать должным образом. Странно, что этого никто не написал за все 6 лет.
Могли бы вы добавить версию Python 3 в свой потрясающий код? Отличная работа! Мне нравится, что вы берете что-то абстрактное и делаете это явным во втором конкретном классе. Это должно быть хорошо для обработки / отлова ошибок. Для Python 3 просто измените print> print() и attrs[n]> next(attrs) (теперь фильтр является отдельным итерируемым объектом и требует next).
Возможно, вы ищете Структуры без конструкторов:
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
То, что вы здесь делаете, технически работает, но для многих пользователей, вероятно, не сразу очевидно Почему, что это работает. Ваши заявления в соответствии с class Sample: ничего не делают немедленно; они устанавливают атрибуты класса. К ним всегда можно получить доступ, например, Sample.name.
То, что вы делаете фактически, - это добавление свойств экземпляра к объектам s1 и s2 во время выполнения. Если не запрещено иное, вы можете добавить или изменить атрибут name в любом экземпляре любого класса в любое время, независимо от того, имеет ли класс атрибут name. Вероятно, самая большая функциональная проблема с этим заключается в том, что разные экземпляры одного и того же класса будут вести себя по-разному в зависимости от того, установлен ли name. Если вы обновите Sample.name, любые объекты без явно заданного свойства name вернут новый name.
Это максимально близко к структуре - короткий «класс» без методов, «поля» (атрибуты класса, я знаю) со значениями по умолчанию. Пока это не изменяемый тип (dict, list), все в порядке. Конечно, вы можете столкнуться с PEP-8 или «дружественными» проверками IDE, такими как «класс PyCharm не имеет метода в этом».
Я экспериментировал с побочным эффектом, описанным Ченнингом Муром. Не стоит экономии нескольких ключевых слов self и строки конструктора, если вы спросите меня. Я был бы признателен, если бы Хосе мог отредактировать свой ответ, чтобы добавить предупреждающее сообщение о риске случайного обмена значениями между экземплярами.
@ChanningMoore: Я попытался воссоздать проблему, которую вы описывали, но не смог. Не могли бы вы представить минимальный рабочий пример, в котором возникает проблема?
Итак, вы определяете переменные класса, но затем фактически заслоняете их переменными экземпляров, верно? Потому что s1.__class__.name = '' даже после установки s1.name = "sample1". Это кажется странным. Более того, такая опечатка, как s1.nname = "test", не вызовет никаких ошибок ...
@gebissimo, вы столкнетесь с проблемами, если используете изменяемые типы, такие как list, dict, set. Это то, что скрыто комментарием Хосе «список не может быть инициализирован здесь»: вы может инициализируете список, но вы рискуете обновить его. Это тот же риск, что и использование списка в качестве значения kwarg по умолчанию в определении функции. docs.quantifiedcode.com/python-anti-patterns/correctness/…
@gebissimo, ты совершенно прав, что он следит за атрибутами класса. С тех пор, как я написал этот комментарий, я узнал о двух лучших вариантах: именованные кортежи и буферы протокола, которые больше похожи на структуры C.
Всякий раз, когда мне нужен «объект мгновенных данных, который также ведет себя как словарь» (я, не, думаю о структурах C!), Я думаю об этом симпатичном приеме:
class Map(dict):
def __init__(self, **kwargs):
super(Map, self).__init__(**kwargs)
self.__dict__ = self
Теперь вы можете просто сказать:
struct = Map(field1='foo', field2='bar', field3=42)
self.assertEquals('bar', struct.field2)
self.assertEquals(42, struct['field3'])
Идеально подходит для тех случаев, когда вам нужен «пакет данных, который НЕ является классом», и когда именованные кортежи непонятны ...
Я использую pandas.Series (a = 42) ;-)
Вы получаете доступ к структуре C-Style в Python следующим образом.
class cstruct:
var_i = 0
var_f = 0.0
var_str = ""
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)
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)
Примечание: вместо имени 'cstruct' используйте свое имя структуры вместо var_i, var_f, var_str определите переменную-член вашей структуры.
Это чем-то отличается от того, что находится в stackoverflow.com/a/3761729/1877426?
Это может быть немного поздно, но я сделал решение, используя метаклассы Python (версия декоратора также ниже).
Когда __init__ вызывается во время выполнения, он захватывает каждый из аргументов и их значение и назначает их как переменные экземпляра вашему классу. Таким образом, вы можете создать класс, подобный структуре, без необходимости присваивать каждое значение вручную.
В моем примере нет проверки ошибок, поэтому ему легче следовать.
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
Вот оно в действии.
>>> class MyClass(object):
__metaclass__ = MyStruct
def __init__(self, a, b, c):
pass
>>> my_instance = MyClass(1, 2, 3)
>>> my_instance.a
1
>>>
Я опубликовал это на Reddit и / u / matchu опубликовал более чистую версию декоратора. Я рекомендую вам использовать его, если вы не хотите расширять версию метакласса.
>>> 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
>>>
Черт побери, я потратил два часа на то, чтобы написать свой собственный декоратор, а потом нашел вот это. В любом случае, опубликовать мой, потому что он обрабатывает значения по умолчанию, а ваш - нет. stackoverflow.com/a/32448434/901641
+1 за упоминание func_code. Начал копать в том направлении и нашел там много интересного.
Вы можете создать подкласс структуры C, доступной в стандартной библиотеке. Модуль ctypes предоставляет Структурный класс. Пример из документов:
>>> 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
>>>
Я написал декоратор, который вы можете использовать в любом методе, чтобы все переданные аргументы или любые значения по умолчанию были назначены экземпляру.
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
Быстрая демонстрация. Обратите внимание, что я использую позиционный аргумент a, использую значение по умолчанию для b и именованный аргумент c. Затем я распечатываю все 3 ссылки self, чтобы показать, что они были правильно назначены до ввода метода.
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')
Обратите внимание, что мой декоратор должен работать с любым методом, а не только с __init__.
Я не вижу здесь этого ответа, поэтому полагаю, что добавлю его, так как сейчас я склоняюсь к Python и только что обнаружил его. Учебник по Python (в данном случае Python 2) дает следующий простой и эффективный пример:
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
То есть создается пустой объект класса, затем создается его экземпляр, а поля добавляются динамически.
Плюс в том, что это действительно просто. Обратной стороной является отсутствие самодокументирования (предполагаемые члены нигде не указаны в «определении» класса), а неустановленные поля могут вызвать проблемы при доступе. Эти две проблемы можно решить следующим образом:
class Employee:
def __init__ (self):
self.name = None # or whatever
self.dept = None
self.salary = None
Теперь вы можете сразу увидеть, какие поля ожидает программа.
Оба подвержены опечаткам, john.slarly = 1000 будет успешным. Тем не менее, это работает.
Я думаю, что словарь структур Python подходит для этого требования.
d = dict{}
d[field1] = field1
d[field2] = field2
d[field2] = field3
С введением Классы данных в Python 3.7 мы очень близко подошли.
Следующий пример похож на пример NamedTuple ниже, но результирующий объект - изменчивый, и он допускает значения по умолчанию.
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)
Это хорошо сочетается с новым модулем набор текста, если вы хотите использовать более конкретные аннотации типов.
Я так долго этого ждал! Если вы спросите меня, Классы данных и новое объявление NamedTuple в сочетании с модулем набор текста - это находка!
С Python 3.6 стало довольно просто и красиво (ИМХО), пока можно жить с неизменность.
Был введен новый способ объявления NamedTuples, который также позволяет использовать аннотации типов:
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'))
Приятель, ты только что сделал мне день - неизменные наречия - спасибо: D
Модуль dataclass является новым в Python 3.7, но вы можете использовать pip install dataclasses. Это бэкпорт на Python 3.6. pypi.org/project/dataclasses/#description
+1 за улучшенное объявление NamedTuple. Старый способ было действительно неприятно читать, если у вас было несколько переменных ...
@Lavande Могу ли я узнать, какие критические изменения произошли между 3.6 и 3.7, которые вам нужно перенести обратно на одну минорную версию ...?
@PurpleIce Это была реализация PEP 557, классы данных @dataclass. Подробности здесь: pypi.org/project/dataclasses/#description
это хорошо - за исключением того, что меня интересует решение требовать import классов данных вместо того, чтобы просто делать их всегда доступными ... весь импорт для вещей, которые достаточно полезны, чтобы быть частью базы, было бы неплохо, чтобы избежать шаблона.
Даже с типизированными полями вы можете присвоить любой тип любой переменной в классе. Например, это работает: my_item = MyStruct(123, 123, 123, 123), и каждое поле в my_item будет целым числом со значением 123. То же самое и для класса данных.
@foolo Сам Python не проверяет аннотации вашего типа. Но вы можете использовать для этого средства проверки статического типа, такие как mypy. Это также включает поддержку проверки типов в вашем редакторе. Например. вы можете указать VSCode использовать mypy.
Некоторые ответы здесь очень сложны. Самый простой вариант, который я нашел (от: http://norvig.com/python-iaq.html):
class Struct:
"A structure that can have any fields defined."
def __init__(self, **entries): self.__dict__.update(entries)
Инициализация:
>>> options = Struct(answer=42, linelen=80, font='courier')
>>> options.answer
42
добавление еще:
>>> options.cat = "dog"
>>> options.cat
dog
редактировать: Извините, я не видел этого примера ниже.
отсутствует метод __repr__(), который важен для imo
Согласовано! Могу добавить, да.
https://stackoverflow.com/a/32448434/159695 не работает в Python3.
https://stackoverflow.com/a/35993/159695 работает на Python3.
И я расширяю его, добавляя значения по умолчанию.
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')
Отличная работа, включая новый для меня self.__class__.__qualname__
Лично мне этот вариант тоже нравится. Он расширяет @ 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__)
Он поддерживает два режима инициализации (которые можно комбинировать):
# 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)
Кроме того, он лучше печатает:
print(mystruct2)
# Prints: {'field3': 3, 'field1': 1, 'field2': 2}
Следующее решение для структуры основано на реализации namedtuple и некоторых предыдущих ответах. Однако, в отличие от namedtuple, он является изменяемым по своим значениям, но, как и структура в стиле c, неизменяема в именах / атрибутах, чего нет в обычном классе или dict.
_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
Использование:
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'
Еще хотелось бы добавить решение, использующее слоты:
class Point:
__slots__ = ["x", "y"]
def __init__(self, x, y):
self.x = x
self.y = y
Определенно проверьте документацию на наличие слотов, но быстрое объяснение слотов заключается в том, что это способ Python сказать: «Если вы можете заблокировать эти атрибуты и только эти атрибуты в классе, так что вы фиксируете, что вы не добавите никаких новых атрибутов после того, как класс создается экземпляр (да, вы можете добавлять новые атрибуты в экземпляр класса, см. пример ниже), тогда я покончу с выделением большого объема памяти, которое позволяет добавлять новые атрибуты в экземпляр класса, и буду использовать только то, что мне нужно для этих атрибутов прорезанный ».
Пример добавления атрибутов к экземпляру класса (без использования слотов):
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(3,5)
p1.z = 8
print(p1.z)
Выход: 8
Пример попытки добавить атрибуты к экземпляру класса, в котором использовались слоты:
class Point:
__slots__ = ["x", "y"]
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(3,5)
p1.z = 8
Вывод: AttributeError: объект 'Point' не имеет атрибута 'z'
Это может эффективно работать как структура и использовать меньше памяти, чем класс (как структура, хотя я не исследовал точно, сколько). Рекомендуется использовать слоты, если вы будете создавать большое количество экземпляров объекта и вам не нужно добавлять атрибуты. Точечный объект является хорошим примером этого, поскольку вполне вероятно, что можно создать множество точек для описания набора данных.
Информация о новых для меня slots
Именно для этого есть пакет python. см. cstruct2py
cstruct2py - это чистая библиотека Python для создания классов Python из кода C и использования их для упаковки и распаковки данных. Библиотека может анализировать заголовки C (объявления структур, объединений, перечислений и массивов) и эмулировать их в Python. Сгенерированные питонические классы могут анализировать и упаковывать данные.
Например:
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"
How to use
Сначала нам нужно сгенерировать питонические структуры:
import cstruct2py
parser = cstruct2py.c2py.Parser()
parser.parse_file('examples/example.h')
Теперь мы можем импортировать все имена из кода C:
parser.update_globals(globals())
Мы также можем сделать это напрямую:
A = parser.parse_string('struct A { int x; int y;};')
Использование типов и определений из кода 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)
Результатом будет:
{'x':0x2d, 'y':0x0}
{'x':0x2d, 'y':0x0}
{'x':0x31316161, 'y':0x32323131}
A('aa111122', x=0x31316161, y=0x32323131)
Clone
Для клона cstruct2py запустите:
git clone https://github.com/st0ky/cstruct2py.git --recursive
Если у вас нет версии 3.7 для @dataclass и вам нужна изменчивость, следующий код может сработать для вас. Он довольно самодокументируется и удобен для IDE (автозаполнение), предотвращает повторную запись, легко расширяется и очень просто проверить, что все переменные экземпляра полностью инициализированы:
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)
Вот решение, которое использует класс (никогда не создаваемый) для хранения данных. Мне нравится, что этот способ требует очень небольшого набора текста и не требует дополнительных пакетов и Т. Д.
class myStruct:
field1 = "one"
field2 = "2"
При необходимости вы можете добавить дополнительные поля позже:
myStruct.field3 = 3
Для получения значений доступ к полям осуществляется как обычно:
>>> myStruct.field1
'one'
Он вроде работает, но не умеет распечатывать myStruct Out[5]: __main__.myStruct
И почему бы вам не создать экземпляр класса? Что, если кто-то хочет использовать одну и ту же структуру для разных значений? s1 = myStruct; s2 = myStruct; s1.field1 = "two" изменяет s2. Это зависит от варианта использования, но я думаю, что в целом лучше / безопаснее создать экземпляр структуры: s1 myStruct(); s2 = myStruct()
@StephenBoesch Да, это очень простой подход. Я иногда использую это для специальных сценариев, потому что его просто набирать, но для кода, который требует большей функциональности, я бы использовал более полноценное решение, такое как классы данных.
@normanius Конечно, если вам нужно скопировать свои «структуры», вам нужно быть более умным, и я согласен с тем, что создание экземпляров может создавать копии для вас, если это необходимо.
Вот быстрый и грязный трюк:
>>> ms = Warning()
>>> ms.foo = 123
>>> ms.bar = 'akafrit'
Как это работает? Он просто повторно использует встроенный класс Warning (производный от Exception) и использует его как собственный определенный класс.
Хорошие моменты заключаются в том, что вам не нужно сначала ничего импортировать или определять, что «Предупреждение» - это короткое имя, и оно также дает понять, что вы делаете что-то грязное, что не должно использоваться где-либо еще, кроме вашего небольшого скрипта.
Кстати, я пытался найти что-то еще более простое, например ms = object(), но не смог (последний пример не работает). Если он у вас есть, мне интересно.
Нет голосов? Это хорошо
Лучший способ, который я нашел для этого, - использовать собственный класс словаря, как описано в этом сообщении: https://stackoverflow.com/a/14620633/8484485
Если требуется поддержка автозаполнения iPython, просто определите функцию реж () следующим образом:
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
def __dir__(self):
return self.keys()
Затем вы определяете свою псевдоструктуру следующим образом: (эта вложенная)
my_struct=AttrDict ({
'com1':AttrDict ({
'inst':[0x05],
'numbytes':2,
'canpayload':False,
'payload':None
})
})
Затем вы можете получить доступ к значениям внутри my_struct следующим образом:
print(my_struct.com1.inst)
=> [5]
NamedTuple удобен. но там никто не делится производительностью и хранилищем.
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)
Если ваш __dict__ не используется, выберите между __slots__ (более высокая производительность и хранилище) или NamedTuple (доступно для чтения и использования)
Вы можете просмотреть эту ссылку (Использование слоты), чтобы получить дополнительную информацию о __slots__.
С другой стороны, алгебраические типы данных были бы просто замечательными, но для их правильного использования обычно требуется сопоставление с образцом.