"""Assorted novelties. Rather than setting out to show off the novel features, this does some things which might be useful, using the novel machinery I'm suggesting for python 2. See python2.html and other novelties in check.py, bonnet.py, ... but mostly see gennie in python.py $Id: novelty.py,v 1.5 2005/02/12 17:52:13 eddy Exp $ """ import python def dictionary(*bases, **what): inst, wrap = apply(python.instance, bases, what) return wrap.__dict__, wrap dictionary ordbok: a = 7 b = 8 c = a + b ordbok # -> {'a': 7, 'b': 8, 'c': 15} def extend(*bases, builder=python.instance, **what): """Like within, but reusing a base's builder. Uses the builder of the first base that has one to build a new object based on the given bases. Optional keyword parameter builder specifies a fall-back builder to use if no base has a builder. Records selected builder instead of itself as builder of the new object (in case parser supplied self). A more sophisticated variant might try to find the most feature-rich builder to use, among those found in bases; it might then be worth setting itself as builder so that an later extend on it (by this less sophisticated one) would repeat its exploration on the new set of bases.\n""" for it in bases: try: gen = it.__builder__ except AttributeError: pass else: break else: gen = builder what['__builder__'] = gen # not extend return apply(gen, bases, what) python.instance notes: def issubclass(given, *bases): """Tells you whether given borrows (without currie()ing) from base. This is equivalent to `given is in bases or, for some b in given.__bases__, apply(issubclass, (b,) + bases) is true', but this implementation folds out the tail recursion. This now might want to be called borrowsfrom or some such. """ maybe, failed = [ given ], [] while maybe: head, tail = maybe[0], maybe[1:] if head in bases: return True failed.append(head) try: up = filter(lambda b, f=failed: b not in f, head.__bases__) except AttributeError: maybe = tail else: maybe = up + tail return False import types def isinstance(obj, *classes, **, classtypes=(types.ClassType, types.TypeType), issub=issubclass): """Tells you whether an object conforms to any of various types. Takes one required argument, the object, and arbitrarily many further arguments; if the object is an instance of some class, that class is what gets tested; otherwise, the object's type is used instead. Returns true if the tested property of the object is a subclass of any entry in the list (or, rather, if issubclass(prop, arg) is true for any further argument arg, with prop as the tested property). Entries in the list which are neither classes nor types are interpreted as boolean tests to apply to the property; if any of them returns true, so does isinstance. """ try: mine = obj.__class__ except AttributeError: mine = type(obj) for test in classes: if type(test) in classtypes: if issub(mine, test): return True elif test(mine): return True return False # Use of within to modify an existing object (the module posix, in this case ...) from python import within import posix # our example victim try: posix.umask() # Check someone hasn't done this for us: except TypeError: within temp (posix): """Modify umask. A natural use for umask is to ask what the user mask currently is, without any desire to modify it. If os.umask's argument were optional (with (non-numeric) default None), it could provide this service as well as the one which makes a change. """ def umask(**, mask=None, *, old=umask): """umask([newmask]) -> oldmask Returns the prior value of the `user-mask', optionally changing it to the value given by the one optional (positional) argument. The default for this is None, in which case the user-mask is only read. Otherwise, the argument must be an integer: the user-mask will be set to this integer (but the return is still the *old* value of the user-mask). The user-mask is read in the same way as file-protection flags (see chmod), so giving it in octal helps: it tells sub-processes creating files which protections to *not* give, allowing that the creator will have some default set of protections which is sensible (and permissive) to be pruned down in this way. For example, umask(022) tells the process to forbid write access to everyone but the file's owner; directories it creates will have rwxr-xr-x protections, ordinary files will have rw-r--r--. """ if mask is None: mask = old(07777) # That read the value - now set it back again ! old(mask) # -> 07777 else: mask = old(mask) return mask # Now let's have a go at posix.copyfile() ... # ... suppose we want it to accept an argument controlling protections of the # result, and string arguments are easier to understand than octal codes. # First build tools: python.instance _handlers: """Translators for protection-munging strings. Each takes a match-object (produced by an re.match()) and un-picks it to return a pair, (operator, integer), in which the operator is a single character drawn from '&|^=' and the integer is to be so combined with (or, in the case of '=', used in place of) some prior protections. This is private to protmunger and gets deleted once this last has been defined, transcribing the contents of this into one of its safely tunnelled tuples - in the same order as their matching regexps. """ # stubs only, for the present. def chmod(matcher): who, op, what = map(matcher.group, (1, 2, 3)) # ... return '|', 0 def ls(matcher): # note: presumes [dclbs-] for file-type has been stripped text = matcher.group(0) # could enforce with text = text[ len(text)%3 : ] result = magic = count = 0 while text: result = 8 * result head, text = text[:3], text[3:] if head[0] == 'r': result = result | 4 if head[1] == 'w': result = result | 2 if head[2] == 'x': result = result | 1 elif head[2] == "tss"[count]: magic = magic | (2**count) result = result | 1 count = 1 + count while count < 3: # pad with trailing zeros (i.e. '---') if short: # so 'rwxr-x' is short for 'rwxr-x---' result, count = 8 * result, 1 + count if magic: result = result | (8**3 * magic) return '=', result def join(matcher): op, num = matcher.group(1), string.atoi(matcher.group(2), 8) try: po = { '+': '|', '-': '^', '': '=' }[op] except KeyError: pass else: op = po return op, num import re, string def protmunger(text, base= 0777 ^ posix.umask(), *, **, # safe tunnels: patterns= (re.compile('^([ugo]*|a)([+=-])([rwxXst]+)$'), re.compile('^([r-][w-][xst-]){1,3}$'), re.compile('^([&|^+=-]?)(0[0-7]*)$')), handlers=(_handlers.chmod, _handlers.ls, _handlers.join), split=string.split): """Interprets strings as file-protection specifiers. Required argument is the string, optional second argument, base, gives the protections to be perturbed by this string. Use of 0777 ^ umask as the default for base is intended to encourage you to supply the base (and obliges you to do so if you want to set any magic bits in the 07000 positions). Recognised formats for the string: * space-separated sequence of any of the following * the things ls -l produces (these will ignore base) * a strings representing an octal number to use as protections (discarding base) or, with optional leading punctuator, to combine with existing protections using an operation indicated by the punctuator: acceptable punctuators are ^, & and |, for the corresponding bitwise `xor', `and' and `or' operators in python, with + as synonym for | and - for ^; the punctuator `=' is also allowed, meaning the same as no punctuator. Sensible ones to implement in due course: * anything (unix shell) chmod recognises """ indices = range(len(patterns)) for chunk in split(text): if not chunk: continue # skip any empty strings for i in indices: match = patterns[i].match(chunk) if match: c, p = handlers[i](match) break else: raise ValueError, ('Unrecognised file-protection munging string', chunk, text) if c == '=': base = p elif c == '&': base = p & base elif c == '|': base = p | base elif c == '^': base = p ^ base else: raise AssertionError, ('File protection munger returned bad combinator', c, chunk, text) return base del _handlers import types, stat within temp (posix, types, stat): # This'll modify posix while borrowing from all three def copyfile(source, dest, protect='', *, # insist on keyword arg for ... buffer=4096, # pick a `good' value, e.g. page size **, # tunnel stuff in: String=StringType, # from base: types protindex=ST_MODE, # from base: stat munger=protmunger, # from globals chmod=chmod): # from base: posix """copyfile(source, dest, [protect,] [buffer=int]) Copies the source, a named file, to the named destination. Arguments: source -- name of file to be copied dest -- name to which to copy it protect -- indicates protections to use for new file; default is '', meaning use those of the source; value may be an integer (typically given in octal) for use as the new file's protections or a string which is combined with the source's protections. Recognised formats for the string include ... none just now ... but may eventually include the formats read by chmod and the one written by ls -l. buffer -- (can only be given as keyword): size (in bytes) of chunks in which to copy the file. Default is 4096. """ try: ins = open(source, 'r', buffer) except IOError, what: raise IOError, (source, 'cannot open for reading', what) try: if type(protect) is String: prot = fstat(ins)[protindex] if protect: prot = munger(protect, prot) else: # protect must be an integer ... prot = protect try: out = open(dest, 'w', buffer) except IOError, what: raise IOError, (dest, 'cannot open for writing', what) try: while 1: block = ins.read(buffer) if block: out.write(block) else: break finally: out.close() chmod(dest, prot) finally: ins.close() del temp, protmunger _rcs_log = """ $Log: novelty.py,v $ Revision 1.5 2005/02/12 17:52:13 eddy Have extend set builder to the one it delegates to. Document more sophisticated idea. Revision 1.4 2005/02/12 17:26:39 eddy Added extend(). Use True and False as bools. Revision 1.3 2002/01/30 17:52:21 eddy Major revolution. Inlined post-within precursors in instance and simplified the resulting mess; added __wrap__ to carry wrapper methods from class; shunted class-specific magic (redirected attribute modification) into class, unburdening (gennie renamed as) builder. Moved all surviving precursors of instance from python.py to bonnet.py, emptying old junk from bonnet.py into novelty.py. Made the function defining a scheme receive the lookups list as third parameter; removed access to this list from instance wrappers; made wrapper's __dict__-based attribute modifier fallbacks come early, like __wrap__-derived kit, rather than late. Changed __meth__ to __method__, `generate' to `build', __schemes__ to __magic__. Re-documented plenty of stuff, revised web pages. Revision 1.2 2002/01/23 04:02:34 eddy made __dir__ attribute universal; renamed initspace to extend; cleaned up scheme inheritance Revision 1.1 2001/10/17 18:00:20 eddy added in all files previously missing from RCS Revision 1.4 2000/01/15 18:19:39 eddy dictionary simplified, gennie moved to python.py, python.fixspace went back to being python.initspace. Revision 1.3 2000/01/03 23:04:12 eddy classic emigrated to class and became gennie Revision 1.2 2000/01/02 18:25:03 eddy Crucial import ! Initial Revision 1.1 2000/01/02 18:24:29 eddy """