Почему пустые классы и функции Python работают как произвольные контейнеры данных, но не другие объекты?

Я видел два разных объекта Python, используемых для группировки произвольных данных: пустые классы и функции.

def struct():
    pass

record = struct
record.number = 3
record.name = "Zoe"


class Struct:
    pass

record = Struct()
record.number = 3
record.name = "Zoe"

Даже если класс не пуст, он работает до тех пор, пока он определен во время выполнения.

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

record = set()
record.number = 3
AttributeError: 'set' object has no attribute 'number'

record = pow
pow.number = 3
AttributeError: 'builtin_function_or_method' object has no attribute 'number'

Существует ли принципиальное различие между встроенными и "настраиваемыми" классами и функциями, которые учитывают это поведение?

Ответы

Ответ 1

Отличие состоит в том, что оба объекта функции и ваш объект Struct имеют атрибут __dict__, но экземпляры set и встроенные функции не выполняются:

>>> def struct():
...     pass
...
>>> record = struct
>>> record.number = 2
>>> struct.__dict__
{'number': 2}
>>> class Struct:
...     pass
...
>>> record = Struct()
>>> record.number = 3
>>> record.__dict__
{'number': 3}
>>> record=set()
>>> record.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'set' object has no attribute '__dict__'
>>> pow.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'builtin_function_or_method' object has no attribute '__dict__'

Вы можете эмулировать поведение, используя слоты (хотя и только в классах нового стиля):

>>> class StructWithSlots(object):
...     __slots__ = []
...
>>> record = StructWithSlots()
>>> record.number = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'StructWithSlots' object has no attribute 'number'
>>> record.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'StructWithSlots' object has no attribute '__dict__'

Ответ 2

Встроенные типы записываются на C и не могут быть изменены таким образом. Но после типа/объединения классов, введенного в py2.2, теперь вы можете наследовать от встроенных типов и переопределять или добавлять свои собственные атрибуты к этому подкласс.

Вы можете использовать forbiddenfood для добавления атрибутов к встроенным типам:

Этот проект призван дать вам возможность найти небеса в тестах, но это может привести вас к ад, если вы используете его на производственном коде.

>>> from forbiddenfruit import curse 
>>> def words_of_wisdom(self):
 ...     return self * "blah "
>>> curse(int, "words_of_wisdom", words_of_wisdom)
>>> assert (2).words_of_wisdom() == "blah blah "

И, конечно, если вы достаточно дерзкие, тогда вы можете создавать свои собственные типы на C и добавлять к ним такие функции.

Ответ 3

Некоторые встроенные модули могут быть более ограничительными. Кроме того, классы, реализованные с помощью слотов, также не будут принимать произвольные атрибуты.

Ответ 4

Если вам нужна какая-то симулятивная защита в вашем собственном классе, вы можете использовать метод __setattr__().

class TestClass(object):
    # Accept the attributes in this list
    __valid_attributes = ["myattr1", "myattr2"]

    def __setattr__(self, name, value):
        if not name in TestClass.__valid_attributes:
            raise AttributeError(
                "{0} has no attribute '{1}'".format(self.__class__.__name__, name))
        self.__dict__[name] = value

Теперь вы можете сделать что-то вроде этого:

t = TestClass()
t.noattr = "test" # AttributeError: TestClass has no attribute 'noattr'

Но "действительные атрибуты" все еще могут быть установлены:

t = TestClass()
t.myattr1 = "test"
print(t.myattr1) # test