Carl Banks wrote:
On Jul 29, 6:42 pm, Matthew Fitzgibbons <[EMAIL PROTECTED]> wrote:
Carl Banks wrote:
Much like in Steven D'Aprano's example, still the only actual code
snippet I've seen, it seems that this can easily be done with a simple
explicit test by having all no-advance filters return None and testing
with "if x is not None".  So it doesn't pass my criterion of being not
replaceable with simple explicit test.
Maybe that's not workable for some reason.  Perhaps if you'd post a
code example that shows this, rather than just talking about it, you
might be more persuasive.
Carl Banks
--
http://mail.python.org/mailman/listinfo/python-list
The no-advance filters have to return the object because I don't just
forget about it; I evaluate whether I pass it to the next filter or drop
it in a completely different queue for use in the next stage of the
operation. True means 'I'm ready to move on to the next stage,' False
means 'Do the filter thing some more.'

I think I see what you're saying, and yeah I guess that could really
take advantage of polymorphism between very different types.

Furthermore, the argument that I should just change my API to make a
'simple test' work is not very convincing.

I wasn't suggesting you change it: I was trying to ascertain whether
it would have suffered much if you had written it with explicit tests
in the first place, or if Python didn't even have magical booleans.

Yes it would have suffered. I chose the implementation I did because it made the most sense to me and was the most flexible (a key requirement). Instead of cobbling together my own way, I used the way that Python gave me.

Python doesn't have magic booleans. They are instead a very well-defined language mechanism that isn't perfect for every circumstance, but is pretty good for the most part. I wanted to do meaningful boolean tests on various objects, so I used the mechanism that my language gave me.


The natural, obvious way for
a filter to work is to pass through the data it operates on; why on
Earth would it return None? I want to DO something with the data. In
this case, make a decision about where to pass the data next.

If you don't mind me asking: what do you do actually DO with a zero or
empty list?

Depends on exactly what the next stage is. Typically, zeros and empty lists are not meaningful for the next stage, so they get dropped then if they make it through. I don't want to restrict what gets passed through, though, because I could end up with several meaningful data types, making a simple test again impossible. So I pass everything through and let the next stage decide.


In Java,
to accomplish this I would have to do lots of introspection and value
checking (adding more any time I came up with a new kind of input), or
make a new kind of interface that gives me a method so I can do a
'simple test' (including wrappers for ints and arrays and anything else
I decide to pass in down the road). But Python supports duck typing and
gives me a handy __nonzero__ method; I can rebind __nonzero__ in my
filters for my own classes, and ints and lists are handled how I want
them to be by default. So why jump through hoops instead of just using
'if x'?

Ah, so it's just happens to work.  Still, just happening to work
works. (shrug)

Nonono. The point you seem to be missing is that 'if x' is very well defined. There is nothing magic or arbitrary about what it does. It works here and elsewhere because Python (a) chooses good default behavior (its treatment of lists and ints, etc) and (b) gives you a way, __nonzero__, to change the behavior to suit your needs.


I don't have any postable code (it's in a half way state and I haven't
touched it for a while), but I'll see if I can't find the time to bang
something up to give you the gist.

I wouldn't bother at this point.  I was looking to see if someone
could come up with something to disprove my belief on the polymorphic
uselessness of "if x" relative to explicit tests, and if (as I
anticipated) they did not, I could claim that "if x" is really just a
glorified keystroke saver.  But I now realize that the failure of this
bunch doesn't prove anything.  I don't think most people even realize
why answering the question I asked would demonstrate the usefulness of
"if x".

Of course you don't _need_ to have 'if x'; after all, <sarcasm>REAL programmers code in machine language</sarcasm>. The whole point of a high-level language is to make life easier. Python is very dynamic and allows duck typing so that you can use these tools to do clever things that would otherwise be very difficult. It even gives you a handy polymorphic mechanism to do boolean tests, which my example illustrates.

You asked for a use case for a polymorphic 'if x' that can't be easily replaced by a simple test. I gave one. Then you asked for a code sample, but now have dismissed my sample as not compelling without having seen it. But here it is anyway (attached). It's pretty rough since I just threw it together. I'm sure there are mistakes. I also dropped the DAG, where all the queueing and decision making is handled in the actual program. The filters and data are arbitrary but should illustrate the setup. You care about line 66. Again, the same thing could be accomplished in various ways; but keep in mind that data could be _anything_, so you can't easily rewrite line 66.


Your example isn't exactly the smoking gun I was looking for, but I
guess we'll have to admit that at least one usage will suffer for not
having it.


Carl Banks
--
http://mail.python.org/mailman/listinfo/python-list


So you hypothesized that you can easily rewrite any 'if x' as a simple, explicit test. I produced an example that shows this cannot be done; therefore your hypothesis is not correct.

For most cases, you can come up with a simple test, even if it's not the best way to implement you problem. But other times, the poylmorphic, duck typing behavior of 'if x' allows you to make your problem much easier, in a way a simple test can't. To deny this fact is to deny to power of dynamic languages in general.

-Matt
"""Example where polymorphic 'if' is helpful.

Greatly simplified. A simple chain of filters. No DAG.
"""


import Queue
import random
import threading


# the data to pass to the next stage
stage_out = Queue.Queue(0)


def ordinary_nonzero(self):
	"""Ordinarily, if we have a valid instance, __nonzero__ should return True."""
	return True


def call_nonzero(self):
	"""We want __nonzero__ to call _nonzero."""
	if hasattr(self, "_nonzero"):
		# _nonzero is _not_ a method!
		return self._nonzero(self)
	return True


class Cell(object):
	"""Custom data type."""
	def __init__(self):
		self.length = 0
		self.sets = []
		self.filters = []

	def __str__(self):
		return "Cell:\n\tLength: %d\n\t%s\n\t%s" % (self.length, self.sets, self.filters)


class TearDownFilters(object):
	"""Symbol telling us to tear down the filters."""
	def __nonzero__(self):
		"""We don't want these to get passed through the chain."""
		return False


class Filter(object):
	def __init__(self, in_queue, out_queue):
		self.in_queue = in_queue
		self.out_queue = out_queue
		self.thread = threading.Thread(target=self._loop)
		self.thread.start()

	def join(self):
		self.thread.join()

	def _loop(self):
		data = self.in_queue.get()
		while not isinstance(data, TearDownFilters):
			data = self._filter(data)
			# mark the data so we know what filters the Cell hit
			if isinstance(data, Cell):
				data.filters.append(self.__class__.__name__)

			# figure out what to do with the data
			if data: # !!! the *magic* line !!!
				# if data is True, we want to continue filtering
				self.out_queue.put(data)
			else:
				# otherwise, put __nonzero__ back and stop filtering
				if isinstance(data, Cell):
					data.__class__.__nonzero__ = ordinary_nonzero
					del data._nonzero
				stage_out.put(data)
			data = self.in_queue.get()
		self.out_queue.put(TearDownFilters())

	def _filter(self, data):
		"""Template for Filter subclasses."""
		raise Exception("Not implemented")
		# if we want to do something to the object
			# process the data
			# rebind __nonzero__ if you want to
		# return something interesting to pass on


class GenerateFilter(Filter):
	"""Generates a new Cell with the given number of sets."""
	def __init__(self, in_queue, out_queue, numchoices=12, setsize=3):
		self.choices = range(numchoices)
		self.setsize = setsize
		Filter.__init__(self, in_queue, out_queue)

	def _filter(self, data):
		if isinstance(data, int) and data > 0:
			cell = Cell()
			cell.sets = []
			for i in xrange(data):
				set = []
				for j in xrange(self.setsize):
					set.append(random.choice(self.choices))
				cell.sets.append(set)
			data = cell
		return data


class SetFilter(Filter):
	"""Add a set to a Cell."""
	def __init__(self, in_queue, out_queue):
		self.start = 0
		Filter.__init__(self, in_queue, out_queue)

	def _filter(self, data):
		if isinstance(data, Cell):
			start = self.start
			data.sets.append([start + 1, start + 2, start + 3])
			self.start = (start + 1) % 10
		return data


class LengthFilter(Filter):
	"""Change the length of a Cell.

	Stop changing the Cell if it's above a certain length.
	"""
	def _filter(self, data):
		if isinstance(data, Cell):
			# change the length
			data.length = random.choice(range(10))

			# create and bind the new __nonzero__ test
			def new_test(self2):
				if len(self2.sets) > 2 and self2.length > 3:
					return False
				elif self2.length > 6:
					return False
				else:
					return True
				return val
			data._nonzero = new_test
			data.__class__.__nonzero__ = call_nonzero
		return data


class ExitFilter(Filter):
	"""More interesting stuff."""

	def _filter(self, data):
		# etc....

		# make sure all Cells get into stage_out
		if isinstance(data, Cell):
			def new_test(self2):
				return False
			data._nonzero = new_test
			data.__class__.__nonzero__ = call_nonzero
		return data


if __name__ == '__main__':
	# make the queues connecting the filters
	queues = []
	for i in xrange(5):
		queues.append(Queue.Queue(0))

	# make the filters
	sf = SetFilter(queues[0], queues[1])
	gf = GenerateFilter(queues[1], queues[2])
	lf = LengthFilter(queues[2], queues[3])
	ef = ExitFilter(queues[3], queues[4])

	# put stuff through the filters
	# there aren't currently any filters that act on a list, so it gets dropped
	stuff = [3, Cell(), Cell(), [4, Cell()]]
	for item in stuff:
		queues[0].put(item)

	# teardown the filters
	queues[0].put(TearDownFilters())
	sf.join()
	gf.join()
	lf.join()
	ef.join()

	# check the result
	print "RESULT"
	while True:
		try:
			print stage_out.get(False)
		except:
			break
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to