Третья часть заметок об объектной системе python'a (первая и вторая части). В статье рассказывается о том, почему c.__call__() не то же самое, что и c(), как реализовать singleton с помощью метаклассов, что такое name mangling и как оно работает.
Легко убедиться, что x(arg1, arg2) не равносильно x.__call__(arg1, arg2) для новых классов, хотя для старых это справедливо.
На самом деле правильно:
c() <=> type©.__call__(с)
Абсолютно такая же ситуация с __setattr__/setattr и многими другими магическими (и специальными) методами и соответствующими встроенными функциями, которые определены для всех объектов, в том числе и для объектов типа — классов.
Зачем это было сделано можно рассмотреть на примере setattr [1].
В начале убедимся, что setattr(a, 'x', 1) <==> type(a).__setattr__(a, 'x', 1).
a.x = 1 <=> setattr(a, 'x', 1)
Устанавливаем с помощью метода __setattr__ новый атрибут, который пойдет в __dict__
вроде бы все правильно:
Однако:
Установим в a.__setattr__ заведомо неправильный метод:
Вызов, которого приводит к ошибке:
Однако, несмотря на это, setattr работает:
А вот если переопределить метод класса:
то setattr для экземпляра класса выдаст ошибку:
Зачем это было сделано?
Пусть setattr(a, 'x',1) тоже самое, что a.__setattr__('x', 1), тогда
Установим новый атрибут для a. a.x = 1 <==> a.__setattr__('x', 1)
Все нормально:
А теперь попробуем установить новый атрибут для самого класса, он же ведь тоже является объектом: A.foo = 'bar' <==> A.__setattr__('foo', 'bar')
Все логично, согласно алгоритму поиска атрибутов в классах (типах), сначала атрибут ищется в __dict__ класса (типа):
Но дело в том, что он предназначен для экземпляров класса, а не для самого класса. Поэтому вызов A.__setattr__('foo', 'bar') будет неправильным. И именно поэтому setattr() должен делать явный поиск в классе (типе) объекта. Собственно, по этой же причине это сделано и для других магических методов __add__, __len__, __getattr__ и т.д.
Класс (тип) — это вызываемый (callable) тип, и его вызов — это конструктор объекта.
Эквивалентно:
Т.к. C — обычный класс, то его метаклассом является type, поэтому будет использован вызов type(C).__call__(С) <==> type.__call__(С). Внутри type.__call__(C) уже происходит вызов C.__new__(cls, ...) и C.__init__(self, ...).
Важно то, что и __new__ и __init__ ищутся с помощью обычного алгоритма поиска атрибутов в классе. И при отсутствии их в C.__dict__, будут вызваны методы из родительского класса object: object.__new__ и object.__init__, в то время как метод __call__ — это метод класса (типа) объекта — type: type.__call__(C).
Зная это, создадим метаклассную реализацию синглтона.
Что нам нужно от синглтона? Чтобы вызов A() возвращал один и тот же объект.
A() <=> type(A).__call__(A)
Значит, нам нужно изменить поведение метода __call__, который определяется в метаклассе. Сделаем это, не забывая, что в общем случае в __call__ могут передаваться любые параметры.
Заглушка готова.
Пусть единственный объект будет храниться в классовом атрибуте instance. Для этого инициализируем в cls.instance в __init__.
Проверяем, что все работает как надо.
Метаклассом может быть не только объект типа type, но и вообще любой вызываемый (callable) тип.
Достаточно просто создать функцию, в которой создается класс с помощью метакласса type.
Конструкция (statement) определения класса — это просто конструкция. Также как и любое statement оно может появляться где угодно в коде программы.
В конструкции 'class' любые определенные «внутри» переменные, функции, классы, накапливаются в __dict__. А в определении можно использовать любые другие конструкции — циклы, if'ы:.
Поэтому можно делать так:
или так
Можно вкладывать одно определение в другое.
Или же динамически создавать методы класса:
Хотя конечно такое наверняка крайне не рекомендуется делать в обычной практике, и лучше воспользоваться более идиоматичными средствами.
И еще про определения класса. Про name mangling.
Любой атрибут внутри определения класса classname вида ".__{attr}" (attr при этом имеет не более одного _ в конце) подменяется на "_{classname}__{attr}". Таким образом, внутри классов можно иметь «скрытые» приватные атрибуты, которые не «видны» наследникам и экземплярам класса.
Увидеть переменную можно так:
Ну и храниться она в __dict__ класса:
Наследники доступа не имеют:
В принципе обеспечить доступ внешний доступ к атрибутам типа __{attr} внутри определения класса, т.е. обойти name_mangling, можно с помощью __dict__.
Однако, такие вещи крайне не рекомендуется делать из-за того, что доступ к таким атрибутам будет невозможен внутри определения любого другого класса из-за подмены ".__{attr}" на "._{classname}__{attr}" вне зависимости к какому объекту или классу они относятся, т.е.
Хотя С().__value прекрасно отработает вне определения класса. Чтобы обойти также придется использовать __dict__['__value'].
[1] В официальной документации приводится пример с __len__/len и __hash__/hash.
c.__call__ vs c(), c.__setattr__ vs setattr
Легко убедиться, что x(arg1, arg2) не равносильно x.__call__(arg1, arg2) для новых классов, хотя для старых это справедливо.
>>> class C(object):
... pass
...
>>> c = C()
>>> c.__call__ = lambda: 42
>>> c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'C' object is not callable
>>> C.__call__ = lambda self: 42
>>> c()
42
На самом деле правильно:
c() <=> type©.__call__(с)
Абсолютно такая же ситуация с __setattr__/setattr и многими другими магическими (и специальными) методами и соответствующими встроенными функциями, которые определены для всех объектов, в том числе и для объектов типа — классов.
Зачем это было сделано можно рассмотреть на примере setattr [1].
В начале убедимся, что setattr(a, 'x', 1) <==> type(a).__setattr__(a, 'x', 1).
a.x = 1 <=> setattr(a, 'x', 1)
>>> class A(object): pass
...
>>> a = A()
>>> a.x = 1
>>> a
<__main__.A object at 0x7fafa9b26f90>
>>> setattr(a, 'y', 2)
>>> a.__dict__
{'y': 2, 'x': 1}
Устанавливаем с помощью метода __setattr__ новый атрибут, который пойдет в __dict__
>>> a.__setattr__('z', 3)
вроде бы все правильно:
>>> a.__dict__
{'y': 2, 'x': 1, 'z': 3}
Однако:
Установим в a.__setattr__ заведомо неправильный метод:
>>> a.__setattr__ = lambda self: 42
Вызов, которого приводит к ошибке:
>>> a.__setattr__('z', 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 1 argument (2 given)
Однако, несмотря на это, setattr работает:
>>> setattr(a, 'foo', 'bar')
>>> a.__dict__
{'y': 2, 'x': 1, '__setattr__': <function <lambda> at 0x7fafa9b3a140>, 'z': 3, 'foo': 'bar'}
А вот если переопределить метод класса:
>>> A.__setattr__ = lambda self: 42
то setattr для экземпляра класса выдаст ошибку:
>>> setattr(a, 'baz', 'quux')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 1 argument (3 given)
Зачем это было сделано?
Пусть setattr(a, 'x',1) тоже самое, что a.__setattr__('x', 1), тогда
>>> class A(object):
... def __setattr__(self, attr, value):
... print 'for instances', attr, value
... object.__setattr__(self, attr, value)
...
>>> a = A()
Установим новый атрибут для a. a.x = 1 <==> a.__setattr__('x', 1)
Все нормально:
>>> a.__setattr__('x', 1)
for instances x 1
>>> a.__dict__
{'x': 1}
А теперь попробуем установить новый атрибут для самого класса, он же ведь тоже является объектом: A.foo = 'bar' <==> A.__setattr__('foo', 'bar')
>>> A.__setattr__('foo', 'bar')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method __setattr__() must be called with A instance as first argument (got str instance instead)
Все логично, согласно алгоритму поиска атрибутов в классах (типах), сначала атрибут ищется в __dict__ класса (типа):
>>> A.__dict__['__setattr__']
<function __setattr__ at 0x7f699d22fa28>
Но дело в том, что он предназначен для экземпляров класса, а не для самого класса. Поэтому вызов A.__setattr__('foo', 'bar') будет неправильным. И именно поэтому setattr() должен делать явный поиск в классе (типе) объекта. Собственно, по этой же причине это сделано и для других магических методов __add__, __len__, __getattr__ и т.д.
Класс, как вызываемый (callable) тип
Класс (тип) — это вызываемый (callable) тип, и его вызов — это конструктор объекта.
>>> class C(object):
... pass
...
>>> С()
<__main__.C object at 0x1121e10>
Эквивалентно:
>>> type(C).__call__(C)
<__main__.C object at 0x1121ed0>
Т.к. C — обычный класс, то его метаклассом является type, поэтому будет использован вызов type(C).__call__(С) <==> type.__call__(С). Внутри type.__call__(C) уже происходит вызов C.__new__(cls, ...) и C.__init__(self, ...).
Важно то, что и __new__ и __init__ ищутся с помощью обычного алгоритма поиска атрибутов в классе. И при отсутствии их в C.__dict__, будут вызваны методы из родительского класса object: object.__new__ и object.__init__, в то время как метод __call__ — это метод класса (типа) объекта — type: type.__call__(C).
Singleton v.2
Зная это, создадим метаклассную реализацию синглтона.
Что нам нужно от синглтона? Чтобы вызов A() возвращал один и тот же объект.
A() <=> type(A).__call__(A)
Значит, нам нужно изменить поведение метода __call__, который определяется в метаклассе. Сделаем это, не забывая, что в общем случае в __call__ могут передаваться любые параметры.
>>> class SingletonMeta(type):
... def __call__(cls, *args, **kw):
... return super(SingletonMeta, cls).__call__(*args, **kw)
...
>>>
Заглушка готова.
Пусть единственный объект будет храниться в классовом атрибуте instance. Для этого инициализируем в cls.instance в __init__.
>>> class SingletonMeta(type):
... def __init__(cls, *args, **kw):
... cls.instance = None
... def __call__(cls, *args, **kw):
... return super(SingletonMeta, cls).__call__(*args, **kw)
...
>>>
И вставим проверку в __call__:
>>> class SingletonMeta(type):
... def __init__(cls, *args, **kw):
... cls.instance = None
... def __call__(cls, *args, **kw):
... if cls.instance is None:
... cls.instance = super(SingletonMeta, cls).__call__(*args, **kw)
... return cls.instance
...
>>> class C(object):
... __metaclass__ = SingletonMeta
...
Проверяем, что все работает как надо.
>>> C() is C()
True
>>> a = C()
>>> b = C()
>>> a.x = 42
>>> b.x
42
>>>
Вызываемый (callable) тип в качестве метакласса
Метаклассом может быть не только объект типа type, но и вообще любой вызываемый (callable) тип.
Достаточно просто создать функцию, в которой создается класс с помощью метакласса type.
>>> def mymeta(name, bases, attrs):
... attrs['foo'] = 'bar'
... return type(name, bases, attrs)
...
>>> class D(object):
... __metaclass__ = mymeta
...
>>> D()
<__main__.D object at 0x7fafa9abc090>
>>> d = D()
>>> d.foo
'bar'
>>> d.__dict__
{}
>>> D.__dict__
<dictproxy object at 0x7fafa9b297f8>
>>> dict(D.__dict__)
{'__module__': '__main__', '__metaclass__': <function mymeta at 0x7fafa9b3a9b0>, '__dict__': <attribute '__dict__' of 'D' objects>, 'foo': 'bar', '__weakref__': <attribute '__weakref__' of 'D' objects>, '__doc__': None}
Определения класса
Конструкция (statement) определения класса — это просто конструкция. Также как и любое statement оно может появляться где угодно в коде программы.
>>> if True:
... class A(object):
... def foo(self):
... print 42
...
>>> A
<class '__main__.A'>
>>> A().foo()
42
>>>
В конструкции 'class' любые определенные «внутри» переменные, функции, классы, накапливаются в __dict__. А в определении можно использовать любые другие конструкции — циклы, if'ы:.
Поэтому можно делать так:
>>> class A(object):
... if 1 > 2:
... def foo(self):
... print '1>2'
... else:
... def bar(self):
... print 'else'
...
>>>
>>> A()
<__main__.A object at 0x7fafa9abc150>
>>> A().foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'foo'
>>> A().bar()
else
или так
>>> class A(object):
... if 1 > 2:
... x = 1
... def foo(self):
... print 'if'
... else:
... y = 1
... def bar(self):
... print 'else'
...
>>> A.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute 'x'
>>> A.y
1
>>> A.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute 'foo'
>>> A.bar
<unbound method A.bar>
>>> A.bar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with A instance as first argument (got nothing instead)
>>> A().bar()
else
>>>
Можно вкладывать одно определение в другое.
>>> class A(object):
... class B(object):
... pass
...
...
>>> A()
<__main__.A object at 0x7fafa9abc2d0>
>>> A.__dict__
<dictproxy object at 0x7fafa9b340f8>
>>> dict(A.__dict__)
{'__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 'B': <class '__main__.B'>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
>>> A.B()
<__main__.B object at 0x7fafa9abc310>
Или же динамически создавать методы класса:
>>> FIELDS=['a', 'b', 'c']
>>> class A(object):
... for f in FIELDS:
... locals()[f] = lambda self: 42
...
>>> a = A()
>>> a.a()
42
>>> a.b()
42
>>> a.c()
42
>>> a.d()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'd'
>>>
Хотя конечно такое наверняка крайне не рекомендуется делать в обычной практике, и лучше воспользоваться более идиоматичными средствами.
Name mangling
И еще про определения класса. Про name mangling.
Любой атрибут внутри определения класса classname вида ".__{attr}" (attr при этом имеет не более одного _ в конце) подменяется на "_{classname}__{attr}". Таким образом, внутри классов можно иметь «скрытые» приватные атрибуты, которые не «видны» наследникам и экземплярам класса.
>>> class A(object):
... __private_foo=1
...
>>> A.__private_foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute '__private_foo'
Увидеть переменную можно так:
>>> A._A__private_foo
1
Ну и храниться она в __dict__ класса:
>>> dict(A.__dict__)
{'__dict__': <attribute '__dict__' of 'A' objects>, '_A__private_foo': 1, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
>>>
Наследники доступа не имеют:
>>> class B(A):
... def foo(self):
... print self.__private_foo
...
>>> B().foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in foo
AttributeError: 'B' object has no attribute '_B__private_foo'
В принципе обеспечить доступ внешний доступ к атрибутам типа __{attr} внутри определения класса, т.е. обойти name_mangling, можно с помощью __dict__.
>>> class C(object):
... def __init__(self):
... self.__dict__['__value'] = 1
...
>>> C().__value
1
>>>
Однако, такие вещи крайне не рекомендуется делать из-за того, что доступ к таким атрибутам будет невозможен внутри определения любого другого класса из-за подмены ".__{attr}" на "._{classname}__{attr}" вне зависимости к какому объекту или классу они относятся, т.е.
>>> class D(object):
... def __init__(self):
... self.c = C().__value
...
>>> D()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
AttributeError: 'C' object has no attribute '_D__value'
>>> C().__value
1
>>>
Хотя С().__value прекрасно отработает вне определения класса. Чтобы обойти также придется использовать __dict__['__value'].
Cсылки
- Unifying types and classes in Python — главный документ, объясняющий что, как и зачем в новых классах.
- Making Types Look More Like Classes — PEP 252, описывающий отличие старых классов от новых.
- Built-in functions — детальное описание работы всех встроенных функций.
- Data model — детальное описание модели данных python'а.
- Python types and objects — объяснение объектной модели python на простых примерах с картинками.
Примечания
[1] В официальной документации приводится пример с __len__/len и __hash__/hash.