Python динамически устанавливает атрибуты класса неэкземпляра - PullRequest
2 голосов
/ 13 декабря 2011

Я пытаюсь добавить атрибуты класса динамически, но не на уровне экземпляра.Например, что я могу сделать вручную как:

class Foo(object):
    a = 1
    b = 2
    c = 3

Я хотел бы иметь возможность делать с:

class Foo(object):
    dct = {'a' : 1, 'b' : 2, 'c' : 3}
    for key, val in dct.items():
        <update the Foo namespace here>

Я бы хотел сделать это без звонкак классу извне класса (так что он переносимый) или без дополнительных классов / декораторов.Это возможно?

Ответы [ 3 ]

4 голосов
/ 13 декабря 2011

Судя по вашему примеру кода, вы хотите сделать это одновременно с созданием класса.В этом случае, если вы используете CPython, вы можете использовать locals().

class Foo(object):
    locals().update(a=1, b=2, c=3)

. Это работает, потому что во время определения класса locals() относится к пространству имен класса.Это поведение, зависящее от реализации, и оно может не работать в более поздних версиях Python или альтернативных реализациях.

Менее грязная версия, использующая фабрику классов, показана ниже.Основная идея заключается в том, что ваш словарь преобразуется в класс с помощью конструктора type(), и затем он используется в качестве базового класса для вашего нового класса.Для удобства определения атрибутов с минимальным синтаксисом я использовал соглашение ** для принятия атрибутов.

def dicty(*bases, **attrs):
    if not bases:
        bases = (object,)
    return type("<from dict>", bases, attrs)

class Foo(dicty(a=1, b=2, c=3)):
    pass

# if you already have the dict, use unpacking

dct = dict(a=1, b=2, c=3)

class Foo(dicty(**dct)):
    pass

Это действительно просто синтаксический сахар для вызова type() самостоятельно.Это прекрасно работает, например:

 class Foo(type("<none>", (object,), dict(a=1, b=2, c=3))):
     pass
2 голосов
/ 13 декабря 2011

Вы имеете в виду что-то вроде этого:

def update(obj, dct):
    for key, val in dct.items():
        obj.setattr(key, val)

Тогда просто идите

update(Foo, {'a': 1, 'b': 2, 'c': 3})

Это работает, потому что класс тоже просто объект;)

Если вы хотите переместить все в класс, попробуйте следующее:

class Foo(object):
    __metaclass__ = lambda t, p, a: return type(t, p, a['dct'])
    dct = {'a': 1, 'b': 2, 'c': 3}

Это создаст новый класс с членами в dct, но все остальные атрибуты не будут присутствовать - * , вы хотите изменить последний аргумент на type, чтобы включить то, что вам нужно.Я узнал, как это сделать здесь: Что такое метакласс в Python?

1 голос
/ 25 июля 2017

Принятый ответ - хороший подход.Однако есть один недостаток: в результате вы получаете дополнительный родительский объект в цепочке наследования MRO, который на самом деле не нужен и может даже сбить с толку:

>>> Foo.__mro__
(<class '__main__.Foo'>, <class '__main__.<from dict>'>, <class 'object'>)

Другой подход - использовать декоратор.Примерно так:

def dicty(**attrs):
    def decorator(cls):
        vars(cls).update(**attrs)
        return cls
    return decorator

@dicty(**some_class_attr_namespace)
class Foo():
    pass

Таким образом вы избегаете дополнительного объекта в цепочке наследования.Синтаксис @decorator - это просто хороший способ сказать:

Foo = dicty(a=1, b=2, c=3)(Foo)
...