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

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

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.

8 ответов

Решение

Обновление варианта 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() делает это в collection.py. Класс создается и раскрывается в виде строки и оценивается. Также есть поддержка маринования и тд и тп.

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

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

Если вы используете 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)

Как уже говорили другие, именованные кортежи в 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/

Это продолжение ответа Cide (и, вероятно, интересного только для людей, которые хотят копать глубже).

У меня возникла проблема с использованием обновленного определения 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'

Есть именованный кортеж

>>> from collections import namedtuple
>>> Person = namedtuple("Person", ("forename", "surname"))
>>> john = Person("John", "Doe")
>>> john.forename 
'John'
>>> john.surname 
'Doe'

В пакете 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/

Начиная с Python 3.3 вы можете использовать SimpleNamespace , который был создан именно для этого варианта использования. Цитирование документации:

SimpleNamespace может быть полезен в качестве замены класса NS: pass.

      In [1]: from types import SimpleNamespace
   ...: 
   ...: foo = SimpleNamespace()
   ...: foo.a = 3
   ...: foo.b = "text"
   ...: foo
Out[1]: namespace(a=3, b='text')

In [2]: foo.__dict__
Out[2]: {'a': 3, 'b': 'text'}
Другие вопросы по тегам