html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Заметки об объектной системе языка Python ч.3

Третья часть заметок об объектной системе python'a (первая и вторая части). В статье рассказывается о том, почему c.__call__() не то же самое, что и c(), как реализовать singleton с помощью метаклассов, что такое name mangling и как оно работает.



c.__call__ vs c(), c.__setattr__ vs setattr


Легко убедиться, что x(arg1, arg2) не равносильно x.__call__(arg1, arg2) для новых классов, хотя для старых это справедливо.

>>> class C(object):
...     pass
... 
>>> = C() 
>>> c.__call__ = lambda42
>>> c() 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'C' object is not callable
>>> C.__call__ = lambda self42
>>> c() 
42


На самом деле правильно:

c() <=> type©.__call__(с)

Абсолютно такая же ситуация с __setattr__/setattr и многими другими магическими (и специальными) методами и соответствующими встроенными функциями, которые определены для всех объектов, в том числе и для объектов типа — классов.

Зачем это было сделано можно рассмотреть на примере setattr [1].
В начале убедимся, что setattr(a, 'x'1)  <==> type(a).__setattr__(a, 'x'1).

a.= 1 <=> setattr(a, 'x'1)

>>> class A(object): pass
... 
>>> = A() 
>>> a.= 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 self42

Вызов, которого приводит к ошибке:

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

то 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.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
>>> = C()
>>> = C()
>>> a.= 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.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 self42
... 
>>> = 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().__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.
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

1

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • wrise007
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции