Фабрика классов для создания простых структурных классов?

Изучая Ruby, я наткнулся на это, чтобы создать простой Struct-подобный класс:

Person = Struct.new(:forname, :surname)
person1 = Person.new('John', 'Doe')
puts person1  #<struct Person forname="John", surname="Doe">

Это вызвало у меня несколько вопросов о Python. Я написал [ОЧЕНЬ] базовый клон этого механизма на Python:

def Struct(*args):
    class NewStruct:
        def __init__(self):
            for arg in args:
                self.__dict__[arg] = None

    return NewStruct

>>> Person = Struct('forename', 'surname')
>>> person1 = Person()
>>> person2 = Person()
>>> person1.forename, person1.surname = 'John','Doe'
>>> person2.forename, person2.surname = 'Foo','Bar'
>>> person1.forename
'John'
>>> person2.forename
'Foo'
  1. Есть ли в Python аналогичный механизм для решения этой проблемы? (Я обычно просто использую словари).

  2. Как заставить функцию Struct() создавать правильные __init__() аргументы. (в этом случае я хотел бы, если возможно, выполнить person1 = Person('John', 'Doe') именованных аргументов: person1 = Person(surname='Doe', forename='John')

В качестве интереса я хотел бы получить ответ на вопрос 2, даже если для этого существует лучший механизм Python.


person kjfletch    schedule 12.08.2009    source источник
comment
В Python нет типа структуры, потому что он вам редко нужен. В большинстве случаев достаточно кортежа или словаря, а для более сложных случаев используйте реальный класс. Не пытайтесь писать код Ruby или Java на Python - используйте вместо этого идиомы Python.   -  person nikow    schedule 12.08.2009


Ответы (7)


Если вы используете Python 2.6, попробуйте класс стандартной библиотеки namedtuple.

>>> from collections import namedtuple
>>> Person = namedtuple('Person', ('forename', 'surname'))
>>> person1 = Person('John', 'Doe')
>>> person2 = Person(forename='Adam', surname='Monroe')
>>> person1.forename
'John'
>>> person2.surname
'Monroe'

Изменить: Согласно комментариям, существует резервный порт для более ранних версий Python

person Alice Purcell    schedule 12.08.2009
comment
namedtuple не является изменяемым, поэтому это не то же самое, что struct - person Conchylicultor; 11.10.2018

Если вы используете python ‹2.6 или хотите расширить свой класс, чтобы делать больше, я бы предложил использовать встроенный type(). Это имеет преимущество перед вашим решением в том, что установка __dict__ происходит при создании класса, а не при создании экземпляра. Он также не определяет __init__ метод и, следовательно, не приводит к странному поведению, если по какой-то причине класс снова вызывает __init__. Например:

def Struct(*args, **kwargs):
    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), kwargs)

Используется так:

>>> MyStruct = Struct("forename", "lastname")

Эквивалентно:

class MyStruct(object):
    forename = None
    lastname = None

Пока это:

>>> TestStruct = Struct("forename", age=18, name="TestStruct")

Эквивалентно:

class TestStruct(object):
    forename = None
    age = 18

Обновить

Кроме того, вы можете отредактировать этот код, чтобы очень легко предотвратить присвоение других переменных, кроме указанных. Просто измените фабрику Struct (), чтобы назначить __slots__.

def Struct(*args, **kwargs):
    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    kwargs['__slots__'] = kwargs.keys()
    return type(name, (object,), kwargs)
person Cide    schedule 12.08.2009
comment
+1 Это та информация, которую я искал. Независимо от того, использую ли я Python ‹2.6 или нет, эти знания могут быть применены вне проблемы OP (что более важно и было реальной целью). Очень лаконично. - person kjfletch; 12.08.2009
comment
Я ошибаюсь или это так, как вы определили Struct (), создавая классы только с членами class? Разве тогда все экземпляры нового класса не будут иметь одинаковые значения для них? Разве вам не нужно предоставить ключ и функцию __init__ в kwargs, например, для переменных ?! - person ThomasH; 12.08.2009
comment
Функция Struct () определяет значения по умолчанию. Вы можете переопределить их, установив значения вручную, как обычно, т.е. my_instance.attribute = value - person Cide; 12.08.2009
comment
Это не то, что я имел ввиду. Когда у вас есть несколько экземпляров сконструированного класса и вы измените значение атрибута одного, не изменится ли оно и для всех остальных? - person ThomasH; 13.08.2009
comment
Хотя это позволяет мне создать класс с атрибутами по умолчанию, он по-прежнему не допускает аргументов для init: person1 = Person (name = 'Ted', age = 42). - person kjfletch; 13.08.2009
comment
Это не то, что я имею в виду. Если взять TestStruct из ответа, это фактически класс, поэтому вы можете создавать экземпляры этого класса: t1 = TestStruct (); t2 = TestStruct (). Если я сейчас установлю t1.age = 24, каким будет t2.age? И почему? - person ThomasH; 13.08.2009
comment
Есть еще одна вещь: если вы используете второе определение Struct () (то, которое использует слоты), мой Python 2.6 сообщает мне, что экземпляры возвращаемых классов доступны только для чтения! MyStruct2 = Struct ('имя', 'фамилия'); m = MyStruct2 (); m.forename = Jack - ›AttributeError: атрибут объекта 'MyStruct2' 'forename' доступен только для чтения. - Это делает этот подход бесполезным, учитывая требования ОП, ИМХО. - person ThomasH; 13.08.2009
comment
Мой предыдущий комментарий не был адресован вам, ThomasH. Я также вижу проблему только для чтения. Я все еще изучаю конструкцию классов для достижения желаемых результатов. Спасибо за ваш вклад. - person kjfletch; 13.08.2009
comment
Упс, извините, теперь я понял ... Если вы посмотрите на мой ответ, это решает проблему только для чтения. И вы должны иметь возможность добавлять аргументы * iargs, ** ikwargs в функцию init () и смешивать и сопоставлять их с kwargs по умолчанию. Я думаю, тогда вы сможете параметризовать создание экземпляров. - person ThomasH; 13.08.2009

Как говорили другие, именованные кортежи в Python 2.6 / 3.x. В более старых версиях я обычно использую класс Stuff:

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

john = Stuff(forename='John', surname='Doe')

Однако это не защищает вас от неправильного написания. Также есть рецепт для именованных кортежей в ActiveState:

http://code.activestate.com/recipes/500261/

person fraca7    schedule 12.08.2009

Это продолжение ответа Сиде (и, вероятно, интересно только тем, кто хочет копнуть глубже).

У меня возникла проблема с использованием обновленного определения Cide Struct (), использующего __slots__. Проблема в том, что экземпляры возвращаемых классов имеют атрибуты только для чтения:

>>> MS = Struct('forename','lastname')
>>> m=MS()
>>> m.forename='Jack'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyStruct' object attribute 'forename' is read-only

Кажется, что __slots__ блокирует атрибуты уровня экземпляра, когда есть атрибуты класса с одинаковыми именами. Я попытался преодолеть это, предоставив метод __init__, поэтому атрибуты экземпляра могут быть установлены во время создания объекта:

def Struct1(*args, **kwargs):
    def init(self):
        for k,v in kwargs.items():
            setattr(self, k, v)
    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

В результате сконструированный класс видит только метод __init__ и член __slots__, который работает по желанию:

>>> MS1 = Struct1('forename','lastname')
>>> m=MS1()
>>> m.forename='Jack'
>>> m.forename
'Jack'
person ThomasH    schedule 13.08.2009
comment
+1 Кажется, вы больше думали об этой [в основном] теоретической проблеме, чем я. Я попробую и поиграю с этим. Спасибо. - person kjfletch; 13.08.2009
comment
У вас отсутствует скобка: последний символ вашей функции Struct1 (). - person kjfletch; 14.08.2009

Обновление варианта ThomasH:

def Struct(*args, **kwargs):
    def init(self, *iargs, **ikwargs):
        for k,v in kwargs.items():
            setattr(self, k, v)
        for i in range(len(iargs)):
            setattr(self, args[i], iargs[i])
        for k,v in ikwargs.items():
            setattr(self, k, v)

    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

Это позволяет передавать параметры (и именованные параметры) в __init__() (без какой-либо проверки - кажется грубым):

>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>> 

Обновлять

Изучив, как namedtuple() делает это в collections.py. Класс создается и расширяется как строка и оценивается. Также есть поддержка маринования и тд и тд.

person kjfletch    schedule 13.08.2009
comment
+1 Мне особенно нравится, как вы сопоставляете позиционные параметры времени создания экземпляра с позиционными параметрами времени создания класса, используя первые как значения, а вторые как ключи (setattr (self, args [i], iargs [i ])). - person ThomasH; 14.08.2009
comment
Я полагаю, что можно сделать добавления для подсчета количества аргументов, чтобы увидеть, совпадает ли число, переданное в Struct (), с числом, переданным в __init __ (). - person kjfletch; 14.08.2009

Существует namedtuple

>>> from collections import namedtuple
>>> Person = namedtuple("Person", ("forename", "surname"))
>>> john = Person("John", "Doe")
>>> john.forename 
'John'
>>> john.surname 
'Doe'
person Otto Allmendinger    schedule 12.08.2009

В пакете Python esu есть структура, которая может обеспечивать почти такую ​​же функциональность:

from esu import Struct

Customer = Struct(
        'Customer',
        'name', 'address',
        methods={
            'greeting': lambda self: "Hello {}".format(self.__dict__['name'])
        })

dave = Customer()
dave.name = 'Dave'
dave.greeting() # => Hello Dave

из https://torokmark.github.io/post/python-struct/

person StandardNerd    schedule 09.11.2020