× К оглавлению На главную Об авторе

Дата и время публикации:    

Назначение и реализация

1. Назначение

Прокси (proxy) является объект, ссылающийся на разделяемый объект, существующий (предположительно) в разных процессах. Такой разделяемый объект считается ccылкой на прокси или доверенный объект (the referent of the proxy). При этом множество прокси-объектов могут иметь одну и туже ссылку.

Прокси-объект имеет методы, которые обращается на соответствующие методы по своей ссылке на прокси-объект. При этом не каждый метод может быть доступен по ней[3.1], что наглядно продемонстрирую ниже, во время создания прокси-объекта итегрируемых объектов последовательности данных (list) и ассоциативного массива с выборкой по ключу (dict), а также совместное использование с их встроенными аналогами в CPython

2. Решение

2.1 Создание прокси-объектов

Как показано на рисунке 2.1, функция multiprocessing.Manager() возвращает запущенный объект класса SyncManger(), который может использоваться для организации общего доступа между процессами.

Рисунок 2.1

Как наглядно показано на рисунке 2.2, регистрация базовых классов BaseListProxy и DictProxy, выступающих в роли менеджеров прокси-объектов и реализуемых своими собственными менеджарами multiprocessing.Manager().list() и multiprocessing.Manager().dict(), производится методом SyncManager.register() [3.7].

В базовых классах BaseListProxy и DictProxy определяются два набора методов, назначаемых отдельно прокси-объектам для манипуляции с интегрируемыми данными (контейнерами) в виде целочисленной индексируемой последовательности объектов (sequences,[3.7]) и ассоциативными массивами объектов с поиском/выборкой по ключу (mappings,[3.7]).

При этом оба набора формируются на основе методов класса Object, который является базовым и содержит, в большинстве своем, переопределяемые методы всем порождаемым от него классам в CPython . Поэтому следует их рассматривать отдельно в контексте применения и по типу интегрируемых данных (контейнеров) — целочисленной итеррируемой последовательности и ассоциативного массива.

2.1 Методы целочисленной итеррируемой последовательности объектов (list)

Обязательными методами являются append(), count(), index(), extend(), insert(), pop(), remove(), reverse() и sort(), которые разберем ниже. Для операций добавления (конкатенации) и тиражирования (или размножения) в базовом классе BaseListProxy определены __add__()__radd__()__iadd__()__mul__()__rmul__() и __imul__(). Для эффективного использования оператора in в классе BaseListProxy определен метод __contains__() с передачей значений поиска, во время которого используется метод __iter__().

Метод object().extend(sub)

Позволяет определить сколько совпадений встречается в подпоследовательности sub, простой случай использования которой показан в листинге 2.2.1

Листинг 2.2.1

...
   s=str("This a test string to test count method")
   L= Manager().list(s)
   print('L:count-', L.count("o"))
   print('s:count-', s.count("o"))
...

Выполнение которого покажет равное количество обнаружений подпоследовательности при вызове метода count() непосредственно из контейнера s и/или прокси объекта list, как показано в дампе 2.2.2

Дамп 2.2.2

...
L:count- 3
s:count- 3
...

При этом, нужно помнить, что count() по разному обнаруживает совпадения. Так в контейнере последовательности текстовой строки s этот метод осуществляет сопоставление путем наложения всей подпоследовательности sub, в то время как для прокси-объекта list уже выполняет посимвольное сопоставлением каждого отдельного символа.

Поэтому без изменений метод count() можно использовать со следующими встроенных типами контейнеров объектов в СPython для получения совпадений (или попаданий):

А также метод collections.deque([iterable[, maxlen]]).count(x) при определении уникальных попаданий в очереди двухстороннего доступа (double-ended queue) альтернативных контейнерных типов, предоставляемых модулем collections в СPython

Метод object().append(obj)

Позволяет добавлять объекты данных к уже имеющимся атрибутам, как показано в листинге 2.2.3

Листинг 2.2.1

import sys
import platform

class HelloBanner:

        def __init__(self):
            self.intro = []
            self.tech_info='OS'+platform.system()+', Python-'+platform.python_version()
             
        def prn(self):
               string=''.join(str(s) for s in self.intro)
               if string.__len__() > 0 :   
                  print( string+' '+self.tech_info)
               else :
                  print( self.tech_info)

        def add_intro(self,intro):
            self.intro.append(intro)

HelloBanner().prn()
banner=HelloBanner()
banner.add_intro('Welcome on')
banner.prn()

Где атрибуту self.intro добавляется байтовая последовательность, которая была объявлена в качестве встроенного контейнерного типа list() путем использования квадратных скобок. В методе add_intro() добавляем вводную подпоследовательность sub к self.intro в виде приветствия 'Welcome on', которая будет непременно выведена в дампе 2.2.4

Дамп 2.2.4

OS Linux, Python-3.9.2
Welcome on OS Linux, Python-3.9.2

При этом, в первой строке выводится только присвоенные значения технической платформы – наименование ОС и версия интерпретатора CPython, что можно использовать также и для прокси-объектов, как показано в листинге 2.2.5

Листинг 2.2.5

try: 
     from multiprocessing import Manager
except:
       print('Exception: invalid module import')
       sys.exit(1)

class SysBanner:
  def __init__(self):
      self.sys_banner=Manager().list(['Welcome on'])

  def prn(self): 
#     print( ''.join(str(s) for s in self.sys_banner) )
      print( self.sys_banner )   

  def add_ostype(self):
      self.sys_banner.append([' OS '+platform.system()])

  def add_python_version(self):
      self.sys_banner.append(['Python-'+platform.python_version()])

SysBanner().prn()

banner=SysBanner()
banner.add_ostype()
banner.add_python_version()
banner.prn() 

В результате выполнения которого был получен следующий вывод, как показано в дампе 2.2.6

Дамп 2.2.6

Output for Class SysBanner:
['Welcome on']
['Welcome on', [' OS Linux'], ['Python-3.9.2']]

Где для усиления эффекта использования текствых фраз применил встроенный контейнерный тип list(). В результате чего, были выведены через запятую текстовые фразы, отдельно обрамленные квадратными скобками и добавленные в конструкторе SysBanner.__init() приглашение, в методе SysBanner.add_ostype() тип операционной системы, и в методе SysBanner.add_python_version() версия CPython

Метод object().extend(sub)

Имеет тоже самое назначение, что и для встроенного контейнерного типа list(), который расширяет содержимое искомой строки за счет добавления подстроки подобно следующему s[len(s):len(s)] = sub или s.extend(sub) or s += sub . Пример использования с прокси объектом ListProxy и встроенного интергрируемого, контейнерного типа CPython последовательности данных list() показан в листинге 2.2.7

Листинг 2.2.7

class SysBanner:
      def __init__(self):
            self.sys_banner=Manager().list(['Welcome on']) 
...
      def add_detail(self):
            self.sys_banner.extend(['OS release '+platform.release()])
            self.sys_banner+=['Compiler ' + platform.python_compiler()]
...
banner.add_detail()
banner.prn()

В результате к предыдущему выводу с системной информацией добавится третья строка, как показано в дампе 2.2.8

Дамп 2.2.8

Output for Class SysBanner:
['Welcome on']
['Welcome on', [' OS Linux'], ['Python-3.9.2']]
['Welcome on', [' OS Linux'], ['Python-3.9.2'], 'OS kernel release 5.10.0-10-amd64', 'Compiler GCC 10.2.1 20210110']

При этом, если сравнить вторую строку с третьей из дампа 2.2.8, видна разница между методами append() и extend(), которая заключается в том, что append() добавляет объект в конец итеррируемой последовательности прокси-объекта, а второй — расширяет уже имеющуюся [3.12]. О чем свидетельствует отсутствие квадратных скобок в двух последних элементах последовательности в третьей строке дампа 2.2.8 и показывающее, что они стали частью 1-го контейнера, в свою очередь добавленного в конструкторе SysBanner.__init()__ . В то время, как добавленные с помощью метода append() второй и третий элемент являются вложенными контейнерами в 1-й.

Метод object().insert(i, sub)

Обеспечивает вставку подпоследовательности sub перед позицией i последовательности прокси-объекта list, как показано в листинге 2.2.9

Листинг 2.2.9

...
class SysBanner:

  def __init__(self):
      self.sys_banner=Manager().list(['Welcome on'])
...
  ''' Add an operating system identification from os-release file at end'''
  def add_libc_version(self):
      (lib,version)=platform.libc_ver() 
      self.sys_banner.insert(len(self.sys_banner),[lib+'-'+version])
...
banner.add_libc_version()
banner.prn()
print ('Sys banner has '+str(banner.get_sys_banner_nitems())+' items')

В результате на выводе будет получено 5-ть строк содержащие полную идентификационную информацию о типе ОС, версии бинарного интерпретатора кода CPython , ядра ОС, компилятор и системной библиотеки glibc, как показано в дампе 2.2.10

Листинг 2.2.10

['welcome on board ']
['welcome on board ', [' OS Linux'], ['Python-3.9.2']]
['welcome on board ', [' OS Linux'], ['Python-3.9.2'], 'OS kernel release 5.10.0-10-amd64', 'Compiler GCC 10.2.1 20210110']
['welcome on board ', [' OS Linux'], ['Python-3.9.2'], 'OS kernel release 5.10.0-10-amd64', 'Compiler GCC 10.2.1 20210110', ['glibc-2.31']]
Sys banner has 6 items

Метод object().pop(sub)

Удаляет (выталкивает) первый попавшийся элемент справа (конца) последовательности, как показано в листинге 2.2.11

Листинг 2.2.11

...
class SysBanner:

  def __init__(self):
      self.sys_banner=Manager().list(['Welcome on'])
...
  def erase_sys_banner(self):
      self.sys_banner.pop()
...

В результате будет удалены сведения об версии системной библиотеки glibc, как показано в дампе 2.2.12

Дамп 2.2.12


['welcome on board ', [' OS Linux'], ['Python-3.9.2'], 'OS kernel release 5.10.0-10-amd64', 'Compiler GCC 10.2.1 20210110']
Sys banner has 5 items

Методы переопределения операторов

Кроме того, базовый класс object в CPython позволяет с помощью следующих методов __add__(), __contains__(), __delitem__(), __getitem__(), __len__(), __mul__(), __reversed__(), __rmul__(), __setitem__() использовать и с помощью их переопределять операторы, связанные с числовые операциями, а также вставкой, заменой, индексацией и подсчетом числа элементов в контейнере.

2.3 Переопределение методов класса прокси-объекта путем создания собственного менеджера на базе Manager,list()

Для того, чтобы можно было использовать методы count(), remove(), __containes__() и им подобные прокси-объектом интегрируемой последовательности данных list, созданной в SysBanner , нужно создать свой собственный производный класс менеджера на основе прокси-объекта последовательности list и произвести регистрацию нового типа прокси-объекта, как показано в листинге 2.3.1

Листинг 2.3.1

...
     from multiprocessing.managers import BaseManager
     from multiprocessing.managers import BaseListProxy ...
...
class  BannerListProxy(BaseListProxy):

    def __iadd__(self, value):
        self._callmethod('extend', (value,))
        return self
...
    ''' ovirride method counts() to return the number of occurrences for each subsequence stored in sytem banner'''
    def count(self,sub):
        ncounts=int(0)
        nitems=len(self)
        for i in range(nitems):
           if str(self.__getitem__(i).__class__).count('list') > 0 or str(self.__getitem__(i).__class__).count('str') > 0 : 
              ncounts+=str(self.__getitem__(i)).count(sub)    
        return ncounts

class BannerManager(BaseManager):
      pass

BannerManager.register('banner', list, BannerListProxy )
...
class SysBanner:

  vers= { 'ostype' : 1, 'python': 2, 'kernel': 3, 'compiler' : 4, 'glib' : 5 }

  def __init__(self):
      self.manager=BannerManager()
      self.manager.start()
      self.sys_banner=self.manager.banner(['welcome on board '])
...
banner=SysBanner()
banner.add_ostype()
banner.add_python_version()
banner.add_detail()
banner.add_libc_version()
print ('Sys banner has '+str(banner.get_sys_banner_nitems())+' items')
print ( 'Found' , 'Linux', 'Count-', banner.counts('Linux') )

Класс BannerListProxy

Устанавливает новый тип прокси последовательности объектов данных на базе существующего в бинарном интерпретаторе CPython и переопределяет некоторые методы, перечисленные ниже.

Метод BannerListProxy.__iadd__()

В прокси-объектах типа ListProxy метод __iadd__ переопределяется к методу expand, потому что по умолчанию класс object предполагает посимвольное или по байтное добавление символов через оператор "+=" , а не сразу всей последовательностью.

Метод BannerListProxy.count(sub)

Производит поиск совпадений подпоследовательности sub в последовательностей объектов данных прокси-объекта, в зависимости от интегрируемого типа данных сохраняемых объектов производит извлечение этих последовательностей и суммирует количество уникальных совпадений c подпоследовательностью sub

Метод BannerManager.register()

Производит регистрацию менеджера banner() прокси-объекта последовательности объектов данных на основе класса BannerListProxy.

Конструктор SysBanner.__init__()

В конструкторе класса SysBanner() производится запуск копии менеджера, как показано в листинге 2.3.2

Листинг 2.3.1

...
class SysBanner: 
...
 def __init__(self):
      self.manager=BannerManager()
      self.manager.start()
      self.sys_banner=self.manager.banner(['welcome on board '])
...

2.4 Методы ассоциативного массива с поиском/выборкой по ключу (dict)

В листинге 2.4.1 приводится простой пример использование встроенного контейнерный тип ассоциотивного массива с поиском/выборкой по ключу (dict)

Листинг 2.4.1

 ...
class SysBanner:
  ids={ 'ostype' : 1, 'python': 2, 'kernel': 3, 'compiler' : 4, 'glib' : 5 }
 ...
  def prn_sysinfo_by_id(self,key):
      print (key,str(self.sys_banner.__getitem__(banner.ids[key])))
 ...
banner.prn_sysinfo_by_id('ostype')
banner.prn_sysinfo_by_id('python')
banner.prn_sysinfo_by_id('kernel')
banner.prn_sysinfo_by_id('compiler')
banner.prn_sysinfo_by_id('glib')
 ...

В которым показан вывод системной информации, сохраняемой в иттерируемом прокси-объекте — последовательность объектов данных (list) , описанным выше. В результате на выводе будет получена полная идентификационная информация о типе ОС, версии бинарного интерпретатора кода CPython, ядра ОС, компилятор и системной библиотеки glibc, как показано в дампе 2.4.2

Дамп 2.4.1

ostype [' OS Linux']
python ['Python-3.9.2']
kernel OS kernel release 5.10.0-10-amd64
compiler Compiler GCC 10.2.1 20210110
glib ['glibc-2.31']

Из которого видно, что контейнерного типа ассоциотивного массива с поиском/выборкой по ключу (mappings), на основе которого создается класс прокси-объектов DictProxy, содержит обязательный метод итераирования __iter__() , вместе с ним, методы подсчета и управление данными __delitem__(), __getitem__(), __len__(), __setitem__() , а также поиска и извлечение данных по ключу keys(), pop(), popitem(), setdefault(), update(), values()

При этом, нужно помнить, чио встроенный контейнерный тип ассоциотивного массива с поиском/выборкой по ключу (mappings), описывается классом dict, который позволяет создавать еще два типа словарей кроме разделяемого запятой списка, содержащий пару ключ-значение и показанного в листинге 2.4.3. А именно, генерируемого и конструируемого словарей [3.14], тип которых задается с использванием конструктора типа (type constructor) и поэтому поддерживаемых методами прокси объекта.

Генерируемый словарь задается с использованием выражения { key:value for (key,value) in dictionary ites() } или { key:value for (key,value) in range(1,n-1)/enumerate([1,2,...,n-1]) }, наподобие того, как показано в дампе 2.4.3 с использованием enumerate()

Дамп 2.4.3

>>> print ({str('192.168.22.')+str(i[0]) : i[1] for i in enumerate([1,3,6,8],start=100)})
{'192.168.22.100': 1, '192.168.22.101': 3, '192.168.22.102': 6, '192.168.22.103': 8}

или c range(), как показано в дампе 2.4.4

Дамп 2.4.4

>>> print ({str('192.168.22.')+str(i) : i for i in range(1,4)})
{'192.168.22.1': 1, '192.168.22.2': 2, '192.168.22.3': 3}

Или на основе уже существующего списка, например, {'192.168.22.1': 1, '192.168.22.2': 2, '192.168.22.3': 3, '192.168.22.4': 4 }, сгенерировать новый словарь, как показано в дампе 2.4.5

Дамп 2.4.5

>>> hosts={str('192.168.22.')+str(i) : i for i in range(1,4)}; print(hosts)
{'192.168.22.1': 1, '192.168.22.2': 2, '192.168.22.3': 3}
>>> print( {key[:key.rfind('.')+1]+str(value+100) : value + 100 for key,value in hosts.items() } )
{'192.168.22.101': 101, '192.168.22.102': 102, '192.168.22.103': 103}

Который формировался выражением типа: { key:value for (key,value) in dictinary.items() }

Последний тип словаря, является конструируемый словарь, который формируется с использованием конструктора типа dict(), как показано в дампе 2.4.6

Дамп 2.4.6

>>> hosts=dict(host1='192.168.22.1',host2='192.168.22.2'); print(hosts)
{'host1': '192.168.22.1', 'host2': '192.168.22.2'}
>>> hosts=dict([('192.168.22.1',1),('192.168.22.2',2),('192.168.22.3',3),('192.168.22.4',4)]); print(hosts)

Из всех типов словарей прокси-объект встроенного контейнерного типа ассоциативного массива с поиском/выборкой по ключу будет создаваться с использованием конструктора типа dict(), как показано в листинге 2.4.7

Листинг 2.4.6

...
BannerDictProxy = MakeProxyType('BannerDictProxy', (	
	'__contains__', '__delitem__', '__getitem__', '__iter__', '__len__',
	'__setitem__', 'clear', 'copy', 'get', 'items',
	'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'
	))
...
	BannerDictProxy._method_to_typeid_ = {
	'__iter__': 'Iterator',
	}
   
BannerManager.register('sysids', dict, BannerDictProxy )
...
class SysBanner:
...
#     ids={ 'ostype' : 1, 'python': 2, 'kernel': 3, 'compiler' : 4, 'glib' : 5 } 
...
      self.sys_banner=self.manager.banner(['welcome on board '])
      self.syskeys=self.manager.syskeys([('ostype',1), ('python',2), ('kernel',3), ('compiler', 4), ('glib', 5)])
...
  def prn_sysinfo_by_id(self,sysid):
      print ('System attribute '+sysid,':',str(self.sys_banner.__getitem__(self.syskeys[sysid])))
...
banner.prn_sysinfo_by_id('ostype')
banner.prn_sysinfo_by_id('python')
banner.prn_sysinfo_by_id('kernel')
banner.prn_sysinfo_by_id('compiler')
banner.prn_sysinfo_by_id('glib')
...

Класс BannerDictProxy

С помощью функции (MakeProxyType) устанавливается новый тип прокси-объектов BannerDictProxy, содержащий ссылки на контейнерного типа ассоциативного массива данных с поиском/выборкой по ключу, c перечислением методов и возвращаемого типа в BannerDictProxy._method_to_typeid_ методом __iter__() [3.17].

В принципе, если взглянуть на методы ListProxyBase [3.16] и BannerDictProxy, показанного в 2.4.7, отличие итераторов в прокси-объектах, образованных от контейнерного типа dict и list, находится в том, что индексация элементов в ассоциативном массиве производится по ключу, по которому осуществляется поиск/извлечение (отбражение, т. е. mappings) данных. В тоже время в прокси-объектах, образованные прокси-объекты от последовательности объектов данных list, для итерирования последовательности используется целочисленное значение от 0 до N, где N — максимальное число членов этой последовательности данных.

Поэтому поведение будет похожим у методов __contains__(), __delitem__(), __getitem__(), __iter__(), __len__(), __setitem__(), pop(), popitem(), setdefault() в отличии от clear(), copy(), get(), items(), keys(), update(), values(), которые рассмотрим ниже.

Класс BannerDictProxy.Clear() и BannerDictProxy.__len__()

Метод .сlear() производит удаление всех членов, а метод .__len__() возвращает число элементов в прокси-объекте dict, как показано в листинге 2.4.8

Листинг 2.4.8

...
 def syskeys_clear(self):
      self.syskeys.clear()
...
  def syskeys_nitems(self):
      return self.syskeys.__len__()     
...
print('Output BannerDictProxy :' )
banner.prn_sysinfo_by_id('ostype')
banner.prn_sysinfo_by_id('python')
banner.prn_sysinfo_by_id('kernel')
banner.prn_sysinfo_by_id('compiler')
banner.prn_sysinfo_by_id('glib')
print('run BannerDictProxy.clear()')
banner.syskeys_clear()
print('BannerDictProxy %d items ' % (banner.syskeys_nitems(),))

В результате получим вывод, показанный в дампе 2.4.9

Дамп 2.4.9

...
Output BannerDictProxy :
System attribute ostype : [' OS Linux']
System attribute python : ['Python-3.9.2']
System attribute kernel : OS kernel release 5.10.0-10-amd64
System attribute compiler : Compiler GCC 10.2.1 20210110
System attribute glib : ['glibc-2.31']
run BannerDictProxy.clear()
BannerDictProxy 0 items 
...

Класс BannerDictProxy.update(other)

Производит обновление, если такой ключ существует, или добавление, если он отсутствует, как показано в листинге 2.4.10

Дамп 2.4.10

...
class SysBanner:
...
      self.sys_banner=self.manager.banner(['welcome on board '])
      self.syskeys=self.manager.syskeys([('ostype',1), ('python',2), ('kernel',3), ('compiler', 4), ('glib', 5)])
...
  def syskeys_update(self,key,value):
      try:
         self.syskeys.update([(key,value)])
         if self.syskeys.keys().count(key) : 
            return True
      except:
            pass
      return False
...
# restore all of removed values
key='ostype'
print('update dict ([%s]):' % key,banner.syskeys_update( key , 1 ) )
key='python'
print('update dict ([%s]):' % key,banner.syskeys_update( key , 2 ) )
key='kernel'
print('update dict ([%s]):' % key,banner.syskeys_update( key , 3 ) )
key='compiler'
print('update dict ([%s]):' % key,banner.syskeys_update( key , 4 ) )
key='glib'
print('update dict ([%s]):' % key,banner.syskeys_update( key , 5 ) )
print('BannerDictProxy %d items ' % (banner.syskeys_nitems(),))
print('BannerDictProxy %d items ' % (banner.syskeys_nitems(),))
...

В результате, ранее удаленные элементы из прокси-объекта dict будут добавлены обратно, как показано в дампе 2.4.11 . При этом, хочу обратить внимание, что метод dict().has_key() c прокси-объектами типа DictProxy не работает, потому что указанный метод в нем не определен и был удален, начиная с версии Python-3.x [3.19]

Дамп 2.4.11

update dict ([ostype]): True
update dict ([python]): True
update dict ([kernel]): True
update dict ([compiler]): True
update dict ([glib]): True
BannerDictProxy 5 items

Класс BannerDictProxy.Copy() и BannerDictProxy.__delitem__()

Производит копирование содержимого прокси-объекта во вновь созданный свой экземпляр [3.18]. Очень полезная штука, особенно перед внесением изменений в текущий прокси-объект, например, удаление одного элемента с использованием метода __delitem__(), как показано в дамп 2.4.12

Дамп 2.4.12

class SysBanner:
...
      self.sys_banner=self.manager.banner(['welcome on board '])
      self.syskeys=self.manager.syskeys([('ostype',1), ('python',2), ('kernel',3), ('compiler', 4), ('glib', 5)])
...
  def syskeys_dublicate(self):
      return self.syskeys.copy()

  def syskeys_delitem(self,key):
      try:
        self.syskeys.__delitem__(key)
        return True
      except:
            pass
      return False
...
new_proxy=banner.syskeys_dublicate()
print ( 'type:', type(new_proxy) )
banner.syskeys_delitem('compiler')
print ( 'compiler: ', new_proxy.__getitem__('compiler'))

Перед тем, как удалить один из элементов в текущем прокси-объекте, выполнено сохранение его содержимого в новом прокси-объекте, а уже затем был удален элемент с ключем 'compiler' и выведено сохраненное его значение уже из new_proxy.

3. Библиография

3.1 Python Multiprocessing - Proxy Objects

3.2 Класс BaseManager() модуля multiprocessing.managers в Python.

3.3 Python - collections.deque

3.4 Python - method object.__contains__()

3.5 Python. Glossary. Sequence

3.6 Python. Glossary. Mapping

3.7 Метод SyncManager.register()

3.8 Tech Tutorials.Python count() method - Counting Substrings

3.9 Method overriding in python

3.10 Метод array.count класса array

3.11 Методы bytes.count и bytearray.count классов bytes и bytearray

3.12 What is the difference between Python's list methods append and extend?

3.13 Python. Glossary. Managers

3.14 Python. Глосcарий. Iterators

3.15 Python Dictionary Comprehension Tutorial

3.16 Исходный код менеджера прокси объектов DictProxy и ListProxy

3.17 multiprocessing — Process-based parallelism. Managers

3.18 Python Dictionary copy()

3.19 How to copy a dictionary and only edit the copy

Сайт разработан в соответствии с рекомендациями консорциума W3C для языка разметки HTML5.

Об авторе можно прочитать здесь.

Copyright © 2015-2019 Андрей Ржавсков