• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Python Script: Dynamic Binding

Status
Not open for further replies.
Level 31
Joined
Jul 10, 2007
Messages
6,306
This is useful for binding
  • Properties
  • Values
  • Methods
  • Functions

Python:
#
#	bind(obj, name = None, prop = None)
#		-	binds a field, method, function, or property to a given class instance
#			this enables the class to use the field
#
#			Example:	bind(instance, "hello", myFunction)
#
#						instance.myFunction()
#
#		-	this keeps a history of all binding. When something is unbound, it loads
#			up the previous binding.
#
#		-	may not bind None
#
#	bindProperty(obj, name, fget = None, fset = None)
#		-	this converts the getter and setter to a property
#			and then binds it. The getter and setter may be a function
#			or a method.
#
#		-	this is essentially a wrapper for bind
#
#			Example:		def getter(self):
#								return self._some_field_in_object
#
#							def setter(self, value):
#								self._some_field_in_object = value
#
#							bind(object, "_some_field_in_object", 9)
#							bindProperty(object, "myField", getter, setter)
#
#							print object.myField	->	9
#							object.myField = 11
#							print object.myField	->	11
#
#	unbind(obj, name, prop)
#		-	this will unbind the thing that was bound and revert the binding
#			back to what it previously was if the thing that was bound is the active
#			binding
#
#		-	for fields, if the field was a scalar, then that specific value must
#			be unbound
#
#			Example:		bind(instance, "field", 7)
#
#							print str(t.field)		-> 7
#
#							unbind(instance, "field", 7)
#
#							print str(t.field)		-> previous value
#
#	unbindProperty(obj, name, fget = None, fset = None)
#

import types
import inspect

def _getCallerName():
	fback = inspect.currentframe().f_back.f_back

	locs = fback.f_locals

	if ('self' in locs):
		locs['self'].__class__.__name__

	return fback.f_globals['__file__']

def _getHidden(name, callername):
	return "_" + name + "_" + callername + "_hidden"

def _getName(name, prop):
	if (name != None):
		return name

	if (prop == None):
		return None

	return prop.__name__

def _getArgs(prop):
	if (prop == None):
		return None

	if (inspect.isfunction(prop) or inspect.ismethod(prop)):
		args = prop.func_code.co_varnames

		if (args == []):
			return None

		return args

	return None

def _toFunction(prop):
	if (prop == None):
		return None

	if (inspect.ismethod(prop)):
		return prop.im_func

	return prop
	
def _clearProperties(objectType, name):
	props = []
	objs = []
	
	objs = inspect.getmro(objectType)
	for obj in reversed(objs):
		if (hasattr(obj, name)):
			props.insert(0, getattr(obj, name))
			delattr(obj, name)
		else:
			props.insert(0, None)
			
	return (props, objs)
	
def _setProperties(props, objs, name):
	if (props == None):
		return
		
	for i in range(len(props)):
		if (props[i] != None):
			setattr(objs[i], name, props[i])

#	each object has a binding table
#
#		bindingTable.localBinding[name].local[index] = property
#		bindingTable.globalBinding[name][index] = property
#
#	the binding at the top of the table is the current binding
#	if there is no binding, the original binding is used
#
class _BindingTable:
	def __init__(self, obj, localBinding, globalBinding):
		self.obj = obj
		self.localBinding = localBinding
		self.globalBinding = globalBinding

	@staticmethod
	def get(obj, name):
		self = None

		if (hasattr(obj, "__bindingtable")):
			self = getattr(obj, "__bindingtable")
		else:
			globalBinding = None
			if (hasattr(obj.__class__, "__bindingtableGlobal")):
				globalBinding = getattr(obj.__class__, "__bindingtableGlobal").globalBinding
			else:
				globalBinding = {}
				setattr(obj.__class__, "__bindingtableGlobal", _BindingTable(obj.__class__, None, globalBinding))

			self = _BindingTable(obj, {}, globalBinding)

			setattr(obj, "__bindingtable", self)

		globalBinding = self.globalBinding.get(name)
		if (globalBinding == None):
			globalBinding = _Binding(obj.__class__, name)
			self.globalBinding[name] = globalBinding

		binding = self.localBinding.get(name)
		if (binding == None):
			binding = _Binding(obj, name, globalBinding)
			self.localBinding[name] = binding

		return binding

	@staticmethod
	def has(obj, name, prop):
		if (not hasattr(obj, "__bindingtable")):
			return False

		self = getattr(obj, "__bindingtable")

		return name in self.localBinding and prop in self.localBinding[name].binding

class _Binding:
	def _update(self):
		props = None
		objs = None
		
		if (self.globalBinding != None):
			props, objs = _clearProperties(self.obj.__class__, self.name)

		if (hasattr(self.obj, self.name)):
			if (self.binding[0] == None and (len(self.binding) > 1 or not self.has)):
				'''
				if (self.globalBinding == None):
					print "deleting global (", self.obj.__name__, "): ", self.name, " as was virtual property"
				else:
					print "deleting local (", self.obj.__class__.__name__, "): ", self.name, " as was virtual property"
				'''
				delattr(self.obj, self.name)
			elif (self.isProperty[0] and self.globalBinding != None):
				'''
				if (self.globalBinding == None):
					print "deleting global (", self.obj.__name__, "): ", self.name, " for being a local property"
				else:
					print "deleting local (", self.obj.__class__.__name__, "): ", self.name, " for being a local property"
				'''
				delattr(self.obj, self.name)
			else:
				'''
				if (self.globalBinding == None):
					print "setting global (", self.obj.__name__, "): ", self.name, " to ", self.binding[0]
				else:
					print "setting local (", self.obj.__class__.__name__, "): ", self.name, " to ", self.binding[0]
				'''
				setattr(self.obj, self.name, self.binding[0])
		elif (not self.isProperty[0] or self.globalBinding == None):
			if (self.binding[0] != None or (len(self.binding) == 1 and self.has)):
				'''
				if (self.globalBinding == None):
					print "setting global (", self.obj.__name__, "): ", self.name, " to ", self.binding[0]
				else:
					print "setting local (", self.obj.__class__.__name__, "): ", self.name, " to ", self.binding[0]
				'''
				setattr(self.obj, self.name, self.binding[0])
		
		_setProperties(props, objs, self.name)

	def add(self, prop):
		#	add property to history
		self.binding.insert(0, prop)
		self.isProperty.insert(0, isinstance(prop, property))
		
		#	if it was a property and on an instance, add it to the class
		if (self.initialized and self.globalBinding != None and self.isProperty[0]):
			self.globalBinding.add(prop)
		
		#	update object state
		self._update()
		
		'''
		if (self.globalBinding != None):
			print "binded local (", self.obj.__class__.__name__, "): ", self.binding
		else:
			print "binded global (", self.obj.__name__, "): ", self.binding
		'''
		
	def remove(self, prop):
		if (prop == None or len(self.binding) == 1):
			return

		if (not prop in self.binding):
			return

		#	remove property from history
		if (self.globalBinding != None and isinstance(prop, property)):
			self.globalBinding.remove(prop)

		i = self.binding.index(prop)
		self.binding.pop(i)
		self.isProperty.pop(i)
			
		self._update()
		
		'''
		if (self.globalBinding != None):
			print "unbinded local (", self.obj.__class__.__name__, "): ", self.binding
		else:
			print "unbinded global (", self.obj.__name__, "): ", self.binding
		'''
		
	def __init__(self, obj, name, globalBinding = None):
		self.obj = obj
		self.name = name
		self.has = False
		self.initialized = False

		if (globalBinding == None):
			self.table = getattr(obj, "__bindingtableGlobal")
		else:
			self.table = getattr(obj, "__bindingtable")
		
		self.globalBinding = globalBinding
		self.binding = []
		self.isProperty = []

		if (globalBinding != None):
			if (self.globalBinding.isProperty[0]):
				if (self.globalBinding.isProperty[-1]):
					self.add(self.globalBinding.binding[-1])
				else:
					props, objs = _clearProperties(self.obj.__class__, self.name)
					
					if (hasattr(obj, name)):
						self.add(getattr(obj, name))
					else:
						self.binding.append(None)
						self.isProperty.append(None)
					
					_setProperties(props, objs, self.name)
					
			elif (hasattr(obj, name)):
				self.has = True
				self.add(getattr(obj, name))
			else:
				self.binding.append(None)
				self.isProperty.append(None)
		elif (hasattr(obj, name)):
			self.has = True
			self.add(getattr(obj, name))
		else:
			self.binding.append(None)
			self.isProperty.append(None)
			
		self.initialized = True
		
class _PropertyValue:
	def __init__(self):
		self.stack = []

class _Property(property):
	def __init__(self, obj, name):
		if (name == None):
			raise "_Poperty requires a name to be created"
	
		property.__init__(self)

		self.obj = obj
		self._dic = {}
		self.name = name
		self.base = None
		self.isMethod = False

		setattr(obj.__class__, "___propertytable___" + name, self)
		
		if (hasattr(obj.__class__, name)):
			prop = getattr(obj.__class__, name)
			self.base = prop
			
			self._set(obj, prop)
			setattr(obj.__class__, name, self)
			
			if (hasattr(obj, name)):
				if (inspect.ismethod(getattr(obj, name))):
					self.isMethod = True
					
		elif (hasattr(obj, name)):
			prop = getattr(obj, name)
			self.base = prop
			
			self._set(obj, prop)
			setattr(obj.__class__, name, self)
			
	def _set(self, obj, prop):
		props = self._dic.get(obj)
		if (props == None):
			props = _PropertyValue()
			self._dic[obj] = props

		props.stack.insert(0, prop)

	def _rem(self, obj, prop):
		props = self._dic.get(obj)
		if (props == None):
			return
			
		props.stack.remove(prop)
				
		if (len(props.stack) == 0):
			self._dic.pop(obj)

	def _convert(self, obj, prop):
		if (not isinstance(prop, property)):
			return prop
	
		props = self._dic.get(obj)
		if (props == None):
			return None

		for p in props.stack:
			if p.fget == prop.fget and p.fset == prop.fset:
				return p

		return None

	#	self, obj, type
	def __get__(self, instance, owner):
		name = self.name
		if (instance == None):
			return self
			
		binding = _BindingTable.get(instance, name)
		if (_callable(binding.binding[0])):
			return binding.binding[0]

		#	attempt to get the property from _Property
		props = self._dic.get(instance)
		if (props != None):
			if (len(props.stack) > 0):
				prop = props.stack[0]
				
				if (isinstance(prop, property)):
					return prop.fget(instance)
				else:
					return prop
		
		#	attempt to get the local field
		props, objs = _clearProperties(owner, name)
		
		value = None
		gotValue = False
		if (hasattr(instance, name)):
			gotValue = True
			value = getattr(instance, name)
			
		_setProperties(props, objs, name)
		
		if (gotValue):
			return value
			
		#	attempt to get the base property (one defined by class)
		if (self.base != None):
			if (isinstance(self.base, property)):
				return self.base.fget(instance)
			else:
				if (self.isMethod):
					return types.MethodType(self.base, self.obj, self.obj.__class__)
				else:
					return self.base

		#	the property does not exist
		raise AttributeError("'" + instance.__class__.__name__ + "' object has no attribute '" + name + "'")
		
	def __set__(self, instance, value):
		if (instance == None):
			return

		#	attempt to set the property from _Property
		props = self._dic.get(instance)
		if (props != None):
			if (len(props.stack) > 0):
				prop = props.stack[0]
				
				if (isinstance(prop, property)):
					prop.fset(instance, value)
					return
				else:
					props.stack[0] = value
					return
					
		#	attempt to set the local field
		props, objs = _clearProperties(self.obj.__class__, self.name)
		
		setValue = False
		if (hasattr(instance, self.name)):
			setattr(instance, self.name, value)
			setValue = True
			
		_setProperties(props, objs, self.name)
				
		if (setValue):
			return
		
		#	attempt to set the base property (one defined by class)
		if (self.base != None):
			if (isinstance(self.base, property)):
				self.base.fset(instance, value)
				return
				
		#	the field does not exist for this class, so register it
		binding = _BindingTable.get(instance, self.name)
		binding.has = not binding.globalBinding.isProperty[-1]
		binding.binding[0] = value
		
		prop = getattr(self.obj.__class__, self.name)
		delattr(self.obj.__class__, self.name)
		setattr(instance, self.name, value)
		setattr(self.obj.__class__, self.name, prop)
		
		self._set(instance, value)

def _getlen(prop):
	if (prop == None):
		return 0

	return len(prop)

def _callable(prop):
	if (prop == None):
		return False

	return callable(prop) and not isinstance(prop, property)

def _bindProperty(obj, binding, prop):
	p = None

	if (hasattr(obj.__class__, "___propertytable___" + binding.name)):
		p = getattr(obj.__class__, "___propertytable___" + binding.name)
	else:
		p = _Property(obj, binding.name)
		
	p._set(obj, prop)
	binding.add(p)
			
def _bindCode(obj, binding, prop):
	binding.add(types.MethodType(_toFunction(prop), obj, obj.__class__))

def bind(obj, name = None, prop = None):
	if (obj == None or (prop == None and name == None)):
		return

	if (name != None):
		if (not isinstance(name, str)):
			return

	if (not hasattr(obj, "__class__")):
		return

	name = _getName(name, prop)
	
	if (name == None):
		return

	binding = _BindingTable.get(obj, name)

	if (inspect.ismethod(prop) or inspect.isfunction(prop)):
		_bindCode(obj, binding, prop)
	else:
		_bindProperty(obj, binding, prop)

def bindProperty(obj, name, fget = None, fset = None):
	if (name == None):
		return

	if (not isinstance(name, str)):
		return

	bind(obj, name, property(_toFunction(fget), _toFunction(fset), None, None))

def unbind(obj, name, prop):
	if (obj == None or name == None or prop == None):
		return

	if (not isinstance(name, str)):
		return

	if (not hasattr(obj, "__class__")):
		return
		
	p = None
	if (not inspect.ismethod(prop) and not inspect.isfunction(prop)):
		if (not hasattr(obj.__class__, "___propertytable___" + name)):
			return

		p = getattr(obj.__class__, "___propertytable___" + name)
		
		if (isinstance(prop, property)):
			prop = p._convert(obj, prop)

			if (prop == None):
				return
	elif (inspect.ismethod(prop) or inspect.isfunction(prop)):
		val = prop
		prop = types.MethodType(_toFunction(val), obj, obj.__class__)

	if (inspect.ismethod(prop) or inspect.isfunction(prop)):
		if (not _BindingTable.has(obj, name, prop)):
			return
	else:
		if (not _BindingTable.has(obj, name, p)):
			return

	binding = _BindingTable.get(obj, name)
	
	if (not inspect.ismethod(prop) and not inspect.isfunction(prop)):
		p._rem(obj, prop)
		binding.remove(p)
	else:
		binding.remove(prop)

def unbindProperty(obj, name, fget = None, fset = None):
	unbind(obj, name, property(_toFunction(fget), _toFunction(fset), None, None))
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Suppose that you have an entity with N components. You would like to include the properties of these components in the entity itself. For example, if an entity had a physics component, you want the entity to have a speed property. Components may be freely attached to and detached from a given entity.

This script facilitates the above. Certainly, you can have a container storing the properties of a given component (how I would personally do it), but some people prefer to put the properties into the base entity itself.

entity.speed = 5

vs

entity.physics.speed = 5


why then is binding to this degree necessary?

Suppose that an entity has a position. This position is completely virtual without a component, like a renderer, to use it. A renderer has two options, it can either copy the position into the scene manager node, or it can overwrite the position to be a property that operates on the scene manager node. I went with the latter, which required this type of script.

entity.position gets replaced by the renderer's position property

the current script is not the latest. It does not correctly handle inherited properties.

When a class inherits a property, the superclass and the current class has it. If you remove the property from the current class, it looks at the superclass property instead. This means that, in order to determine whether or not the property was bound to the class, you have to remove the property from each class it inherits from all the way up the chain.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
updated to support properties across complex inheritance

That doesn't work with properties ^)^

if you want to set a property, you have to set the class field to the property. Good game instances.

are we back to this now?

and here is a link showing exactly how to bind a property to a *class*, not an object. You can't put it in an object.

http://stackoverflow.com/a/1355444


you added a scalar to foo. You can't add a property to foo, only to Foo, What happens when you want foo1 and foo2 to have to different versions of the same property, like you would with a method or a field? Can't do it, hence this library.

The reason the library is so complex is because properties in python are so nasty.
 
Status
Not open for further replies.
Top