Welcome, guest | Sign In | My Account | Store | Cart

This recipe creates a collection proxy which allows several lists to be treated as one. When the collection proxy is accessed/modified, it is as though the operation was performed one the underlying lists.

Python, 130 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from collections import namedtuple, OrderedDict
from lib import *

def virtual_container(virtual_container, objects_type):
	"""Used to create a virtual object given a the type of container and what it holds.
	The object_type needs to only have normal values."""
	for name in objects_type:
		if name.startswith("__") and name.endswith("__"):
			raise ValueError("Arguments should be not start and end with __")
	
	if issubclass(virtual_container,list):
		def handle_binding(objects_type):
			class my_virtual_container_class:
				"""This singleton class represents the container"""
				def __init__(self):
					"""The attributes for the virtual container"""
					#Define the default values
					__vals__=OrderedDict([(key,None) for key in objects_type])
					#Then function to access them. Note the default parameter for the lambda to
					#enforce early binding
					d={}
					for key in objects_type:
						def func_factory(key):
							"""Function causes early binding for key"""
							def make_attribute(self, items=None):
								"""Called on the attr_cl. This function will return the list for the attribute.
								 As a default, this is [], but a list can also be passed in"""
								if items==None:
									items=[]		
								if self.__vals__[key]==None:
									self.__vals__[key]=items
								else:
									raise RuntimeError("This attribute has been already initialised")	
								return self.__vals__[key]
							return make_attribute
						#Add the function to this class
						d[key]=func_factory(key)
					
					d["__vals__"]=__vals__
					#Construct a named tuple from this
					self.__attr__=type('attr_cl',(), d)()
					
				#Define the operators
				
				def __iter__(self):
					vals=self.__attr__.__vals__
					return zip(*vals.values())
				
				def __contains__(self, x):
					return x in self.__iter__()
				
				def __delitem__(self, i):
					#Handles items and slices
					vals=self.__attr__.__vals__
					for seq in vals.values():
						del seq[i]

				def __getitem__(self, i):
					#Handles items and slices
					vals=self.__attr__.__vals__
					#Make it a slice
					isslice=isinstance(i, slice)
					if not isslice:
						i=slice(i, i+1)
					ret=[seq[i] for seq in vals.values()]
					ret=list(zip(*ret))
					if not isslice:
						ret=ret[0]
					return ret
				
				def __setitem__(self, i, x):
					#Handles items and slices
					vals=self.__attr__.__vals__
					#Split slices
					if isinstance(i,slice):
						x=zip(*x)
					for seq, setTo in zip(vals.values(), x):
						seq[i]=setTo
				
				def __add__(self, myList):
					return list(self.__iter__())+myList
				
				def __iadd__(self, myList):
					vals=self.__attr__.__vals__
					for seq, extra in zip(vals.values(), zip(*myList)):
						seq+=extra
					return self	
				
				def __str__(self):
					s="Proxy: Names: "+" ".join(self.__attr__.__vals__.keys())
					s+=" Vals: "+" ".join(map(str,self.__iter__()))
					return s
							
			return my_virtual_container_class()
		return handle_binding(objects_type)
	

#Example code
coordinates_2d=["x","y"]
coordinates=virtual_container(list, coordinates_2d)
x=coordinates.__attr__.x([1,2,3,4])
y=coordinates.__attr__.y([2,3,4,5])

print(coordinates[3])
print(coordinates[3:4])
print(coordinates[2:4])
for m in coordinates:
	print(m, end=" ")
print()
print(coordinates+[1,2])
coordinates[1]=(0,0)
print(x,y)
coordinates+=[(9,9)]
print("##",coordinates[:])
del coordinates[1:3]
print(coordinates[:])
coordinates[0:2]=[(0,1),(3,4)]
print(coordinates)

#Testing second group of coordinates
coordinates2=virtual_container(list, coordinates_2d)
x2=coordinates2.__attr__.x([])
y2=coordinates2.__attr__.y([])

print("List is: ", end=" ")
for z in coordinates2:
	print(z,end=" ")
print()
print(bool(coordinates2))
print(coordinates[0])

Lazy operations were used when possible. While efforts were made to keep this efficient, since list access is a quick operation, the overhead as a proportion with probably be significant. I would like to consider mocking up other structures as well.

1 comment

Raymond Hettinger 11 years, 2 months ago  # | flag

This is a great idea. It is reminiscent of recipe 305268 for dictionary chains.