;ò L+(;c@sËdZdddfZd„Zd„Zd„Zd„ZeZeZd„Zd „Z eZ e Z d „Z d „Z d kZheie<eie<eie<eie<eie <eie  a way to ignore "expected" cycles. filter_func is a function of one argument, a cycle. Each time find_cycles finds a cycle, the cycle is passed to filter_func. The cycle is ignored unless filter_func returns true. Passing None for filter_func restores the default behavior (do not ignore any cycle). A cycle is a list of (object, index) pairs, where the first object in the list is the same as the last object in the list, and where the object at cycle[i][0] is the cycle[i][1]'th object obtained from object cycle[i-1][0]. cycle[0][1] should be ignored (it tells us how we got to the first item in the cycle to begin with, but that's irrelevant to the cycle). CASE STUDY Below is the driver I used to track cycles in IDLE; it's a replacement for IDLE's idle.py. At first it didn't install function or module chasers, or a cycle filter, and printed everything. This turned up a bunch of easy cases, and the show_sccs output was surprisingly revealing (all the cycles fell into a handful of SCCs, which corresponded to distinct cycle-creating IDLE subsystems). show_arcs was also very helpful in getting the big picture. show_cycles output was too voluminous to be helpful. After those cycles were broken, the job got harder. A module chaser was added, which turned up another class of cycles, and then a function chaser turned up 100s more. Most of these involved expected cycles due to Python's implementation, so a cycle filter was installed to ignore cycles that didn't contain at least one class instance. The remaining cycles were isolated special cases, and only show_cycles output was of real use. After all cycles were purged, IDLE was still leaking, so driver output was added to display the root-set objects still alive at the end. This turned up many cases where objects were living only because registration in the root set was keeping them alive. So a loop was added to the driver that repeatedly purges dead root-set objects and tries again. The __del__ methods of the purged roots caused other root objects to become trash, and after several iterations of this the output reaches a steady state. IDLE is still leaking (as of 18-Jul-1999), but ever less so, and while Cyclops is no longer finding cycles, the driver's "live at the end" output is still the best clue I've got for guessing what to do next. Interesting: At the start of this, it turned out that almost all cycles were reachable from places outside themselves. That is, they would not have been considered trash even if Python used a mark-&-sweep form of garbage collection. IDLE's problems, in large part inherited from Tkinter, are simply that "everything points to everything else". The good news is that Guido was able to clean most of this up just by adding reference- purging code to widgets' explicitly-called destroy() methods. #! /usr/bin/env python import PyShell import Cyclops import types def mod_refs(x): return x.__dict__.values() def mod_tag(x, i): return "." + x.__dict__.keys()[i] def func_refs(x): return x.func_globals, x.func_defaults def func_tag(x, i): return (".func_globals", ".func_defaults")[i] def instance_filter(cycle): for obj, index in cycle: if type(obj) is types.InstanceType: return 1 return 0 # Note: PyShell binds ModifiedInterpreter.locals to __main__.__dict__, # and __main__ is *us*. So if we don't keep the CycleFinder instance # out of the global namespace here, z starts chewing over its own # instance attributes. Nothing breaks, but the output is at best # surprising. def hidez(): z = Cyclops.CycleFinder() z.chase_type(types.ModuleType, mod_refs, mod_tag) z.chase_type(types.FunctionType, func_refs, func_tag) z.install_cycle_filter(instance_filter) z.run(PyShell.main) z.find_cycles() z.show_stats() z.show_cycles() # z.show_cycleobjs() # z.show_sccs() z.show_arcs() while 1: print "*" * 70 print "non-cyclic root set objects:" sawitalready = {} numsurvivors = numdead = 0 for rc, cyclic, x in z.get_rootset(): if not sawitalready.has_key(id(x)): sawitalready[id(x)] = 1 if rc == 0: print "DEAD", numdead = numdead + 1 z.show_obj(x) elif not cyclic: numsurvivors = numsurvivors + 1 z.show_obj(x) x = None print numdead, "dead;", numsurvivors, "non-cycle & alive" if numdead == 0: break print "releasing dead root-set objects and trying again" z.find_cycles(1) z.show_stats() hidez() ii icCs|iƒ|iƒSdS(N(sxskeyssvalues(sx((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys _dict_refsZscCsEt|ƒ}||jo d|Sndt|iƒ||ƒSdS(Ns .keys()[%d]s[%s](slensxsnsis _quickreprskeys(sxsisn((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys _dict_tag\s   cCs|SdS(N(sx(sx((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys _list_refscscCs d|SdS(Ns[%d](si(sxsi((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys _list_tagescCs|iiƒSdS(N(sxs__dict__svalues(sx((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys_instance_refskscCsd|iiƒ|SdS(Ns.(sxs__dict__skeyssi(sxsi((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys _instance_tagnscCs|i|ifSdS(N(sxsim_selfsim_class(sx((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys_instance_method_refstscCsddf|SdS(Ns.im_selfs .im_class(si(sxsi((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys_instance_method_tagvsNs _CyclopsReprcBs2tZd„Zd„Zd„Zd„ZeZRS(NcCs$tii|ƒd|_|_dS(Ni((s_reprsReprs__init__sselfs maxstringsmaxother(sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys__init__¢scCsät|ƒ}|djodSn|djodSnd}xw|iƒt||iƒ D]Y\}}|o|d}n||i ||dƒ}|d|i ||dƒ}qYW||ijo|d}nd |d SdS( Nis{}s{...}ss, is: s, ...s{s}( slensxsnslevelsssitemssminsselfsmaxdictsksvsrepr1(sselfsxslevelsnsssvsk((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysrepr_dictionary®s    "cCsAy | SWn1d|iidtt|ƒƒddSnXdS(Ns(sxs __class__s__name__shexsid(sselfsxslevel((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys repr_instanceÀs cCs | SdS(N(sx(sselfsxslevel((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys repr_classÉs(s__name__s __module__s__init__srepr_dictionarys repr_instances repr_classsrepr_instance_method(((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys _CyclopsRepr s   cCsËt|tƒo t|tƒo"|ii|iif\}}nat|tƒo t|tƒo|i|if\}}n%t |ƒit |ƒif\}}t |t |ƒf|t |ƒfƒSdS(N( s isinstancesxs _InstanceTypesys __class__s__name__sxnamesynames _ClassTypestypescmpsid(sxsysynamesxname((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pystypename_address_cmpÓs  " $s CycleFindercBsûtZdZd„Zd„Zd„Zfhd„Zdd„Zed„Z d„Z d „Z e d „Z e d „Zed „Zd „Zd„Zd„Zd„Zd„Zd„Zeeed„Zd„Zd„Zd„Zd„Zd„ZRS(sgClass for finding cycles in Python data structures. See Cyclops module docstring for details. cCs5|iƒtiƒ|_tiƒ|_t|_dS(s*Create a cycle finder with empty root set.N( sselfsclears_default_refs_dispatcherscopysrefs_dispatchers_default_tag_dispatcherstag_dispatchersNones cycle_filter(sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys__init__âs  cCsg|_|iƒdS(sÑRemove all internal references to external objects. Empties the root set. Does not change the set of types this CycleFinder chases. Does not change the cycle filter in effect. N(sselfsrootss_CycleFinder__reset(sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysclearês cCs|ii|ƒdS(s&obj -> add object obj to the root set.N(sselfsrootssappendsobj(sselfsobj((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysregisterõscCsBdk}|i|iƒzt|||ƒSWd|itƒXdS(sWfunc, args=(), kwargs={} -> add objects to root set by magic. Function func is invoked with arguments args and keyword arguments kwargs. For the duration of the call, each class instance initialized by an __init__ call is automatically added to the root set. The result of invoking func is returned. N( ssyss setprofilesselfs_CycleFinder__init_tracersapplysfuncsargsskwargssNone(sselfsfuncsargsskwargsssys((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysrunús ic Cs |o|ioi|ii} g}x>|iD]3} | t| ƒtƒdjo|i | ƒq-q-W||_~ ~~ n|i ƒ|i |idƒ|i t|iƒ=|i} dk l}x7|iiƒD]&} t| ƒ}|| ƒd| | look for cycles, return true if found. Identify all cycles among objects reachable from the root set. Return true iff at least one cycle is found. This should be called before any of the show_XXX methods. Note that it's OK to add more objects to the root set and call it again, or to change the set of chased types, etc. find_cycles starts over from scratch each time it's called. If optional arg purge_dead_roots is true (default false), before searching for cycles the root set is purged of all objects that the preceding run of find_cycles determined had a true refcount of 0 (that is, the root set objects that are still alive only because they appear in the root set). Purging these allows their finalizers to get invoked, which may allow a cascade of other objects (including cycles) to go away too. See also method install_cycle_filter. i(s getrefcountiiiN(spurge_dead_rootssselfsrootssid2rcsgets survivorssxsidsNonesappends_CycleFinder__resets_CycleFinder__find_cyclessseenidsssyss getrefcounts cycleobjssvaluessxids seenpairsshas_keysisknownscyclesscyclespairspairidsAssertionErrorslen( sselfspurge_dead_rootss getrefcountsxids seenpairss survivorssisknownspairidspairsxsid2rcscycle((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys find_cycles sN              cCs ||_dS(süfilter_func=None -> a way to ignore "expected" cycles. See module docstring for details. This is a callback function invoked whenever find_cycles() finds a cycle; the cycle is ignored unless the callback returns true. N(s filter_funcsselfs cycle_filter(sselfs filter_func((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysinstall_cycle_filter\scCs€|iƒdGt|iƒGHdGt|iƒGHdGt|iƒGHdGt|iƒGHdG|iGHdGt|iƒGHdG|i GHdS( s1Print statistics for the last run of find_cycles.s# objects in root set:s(# distinct structured objects reachable:s(# distinct structured objects in cycles:s# cycles found:s# cycles filtered out:s # strongly-connected components:s# arcs examined:N( sselfs_print_separatorslensrootssseenidss cycleobjsscyclessncyclesignoreds sccno2objssnarcs(sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys show_statsfs  cCsk|iƒdGHt|iƒ}xFt|ƒD]8}|i|i|ƒ||djo ddGHq+q+WdS(sPrint all cycles to stdout.s # all cycles:is-iN(sselfs_print_separatorslenscyclessnsxrangesis _print_cycle(sselfsisn((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys show_cyclesrs  cCsM|iƒdGH|iiƒ}|i|ƒx|D]}|i|ƒq2WdS(s&compare=typename_address_cmp -> print all objects in cycles. Prints to stdout. Each distinct object find_cycles found in a cycle is displayed. The set of objects found in cycles is first sorted by the optional "compare" function. By default, objects are sorted using their type name as the primary key and their storage address (id) as the secondary key; among objects of instance type, sorts by the instances' class names; among objects of class type, sorts by the classes' names. s# objects involved in cycles:N( sselfs_print_separators cycleobjssvaluessobjsssortscomparesobjsshow_obj(sselfscomparesobjsobjs((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysshow_cycleobjs}s   cCs|iƒdGH|iiƒ}t|ƒ}x^t|ƒD]P}dG|dGdG|GH||}|i |ƒx|D]}|i |ƒqpWq7WdS(sscompare=typename_address_cmp -> print SCCs. Prints to stdout. Shows the objects in cycles partitioned into strongly connected components (that is, the largest groupings possible such that each object in an SCC is reachable from every other object in that SCC). Within each SCC, objects are sorted as for show_cycleobjs. s.# cycle objects partitioned into maximal SCCs:s--- SCCisofN(sselfs_print_separators sccno2objssvaluesssccsslensnsxrangesisobjsssortscomparesobjsshow_obj(sselfscomparesobjsisobjssnssccs((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys show_sccss     cCsk|iƒdGH|iiƒ}|o|i|ƒn |iƒx$|D]\}}d|f|GHqGWdS(scompare=None -> print unique arc types in cycles. See module docstring for details. Briefly, each arc in a cycle is categorized by the type of the source node, the kind of arc (how we got from the source to the destination), and the type of the destination node. Each line of output consists of those three pieces of info preceded by the count of arcs of that kind. By default, the rows are sorted first by column 2 (source node type), then by columns 3 and 4. s# arc types involved in cycles:s%6d %-20s %-20s -> %-20sN(sselfs_print_separatorsarctypessitemsscomparessortstriplescount(sselfscomparescountsitemsstriple((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys show_arcs¥s    cCskg}|ii}|ii}xB|iD]7}t |ƒ}|i ||t ƒ||ƒ|fƒq(W|SdS(s)Return the root set, as a list of (rc, cyclic?, obj) tuples. Should be called after find_cycles. For each object in the root set, returns a triple consisting of refcount number of outstanding references less those due to CycleFinder internals; see show_obj docstring for more details; this will be None if find_cycles hasn't been run, or not since the last clear() cyclic? true (1) iff obj is known to be in a cycle obj the object N(sresultsselfsid2rcsgetsgetrcs cycleobjsshas_keysincyclesrootssxsidsxidsappendsNone(sselfsincyclesxidsresultsxsgetrc((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys get_rootset»s    )cCs||i|<||i| chase type t. See module docstring for details. N(s t_refs_funcsselfsrefs_dispatchersts t_tag_funcstag_dispatcher(sselfsts t_refs_funcs t_tag_func((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys chase_typeÓs cCs2y|i|=|i|=Wntj onXdS(sdt -> remove type t from the set of chased types. See module docstring for details. N(sselfsrefs_dispatcherststag_dispatchersKeyError(sselfst((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysdont_chase_typeÜs cCs|iiƒSdS(s*Return the set of chased types, as a list.N(sselfsrefs_dispatcherskeys(sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysget_chased_typesèscCsŒyc|dj}|iidj}|o|o3|ii}|o|i |i |dƒqbnWn"| G|G|iG|iiGHnXdS(Nscalls__init__i( seventsasframesf_codesco_namesbs co_varnamesslocalssselfsregistersf_locals(sselfsframeseventsargssasbslocals((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys __init_tracerís  $cCspg|_h|_h|_g|_h|_h|_d|_h|_h|_ h|_ d|_ d|_ dS(Nii( sselfsstacks id2stackisseenidsscycless cycleobjssarctypess nextsccnosid2sccnos sccno2objssid2rcsnarcssncyclesignored(sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys__resets           cCsa|i} |i} | i}|i}|i} |i}|i} ||ƒ}d| |<|| ƒ||<| i ||fƒ|||ƒ|ƒ}|i||ƒ|_x¨t||ƒƒD]”}||} | || ƒƒoq|| ƒ}||ƒ o|i| |ƒqK| |ƒo2| ||}|i | |fƒ|i|ƒqKq·q·W| d=||=dS(Niiÿÿÿÿ(sselfsstacksseenidsshas_keys already_seens id2stackiscurrently_on_stacksrefs_dispatchersis_interesting_typesidsobjsmyidslensappendsistypesrefssnarcssxrangeschildschildids_CycleFinder__find_cyclesscycles_CycleFinder__study_cycle(sselfsobjsisidstypeslensrefs_dispatcherschildids id2stackisis_interesting_typeschildscurrently_on_stacksstacksseenidssrefssmyids already_seenscycle((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys __find_cycles5s2            cCsXt|tƒo|iid}n-t|tƒo |i}nt|ƒi}|SdS(Ns()(s isinstancesobjs _InstanceTypes __class__s__name__snames _ClassTypestype(sselfsobjsname((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys __obj2arcnameZs  cCs2t|ƒdjpt‚|itj o|i|ƒ o|id|_dSn|ii|ƒ|i i t |ddƒtƒ} | tjo*|i } |i d|_ g|i| print short description of obj to sdtout. This is of the form
rc: repr: where
hex address of obj If find_cycles() has been run and obj is in the root set or was found in a cycle, this is the number of references outstanding less the number held internally by CycleFinder. In most cases, this is what the true refcount would be had you not used CycleFinder at all. You can screw that up, e.g. by installing a cycle filter that holds on to references to one or more cycle elements. If find_cycles() has not been run, or has but obj wasn't found in a cycle and isn't in the root set, is "?". type(obj), as a string. If obj.__class__ exists, also prints the class name. repr(obj), but using a variant of the std module repr.py that limits the number of characters displayed. s?src:s __class__s repr:N(sidsobjsobjidsselfsid2rcsgetsrcshexsstrstypes__name__shasattrs __class__s _quickrepr(sselfsobjsobjidsrc((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysshow_obj”s % cCs ddGHdS(Ns*iF((sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys_print_separatorºscCs­t|ƒ}|djpt‚d|dGHxyt|ƒD]k}||d}|i|ƒ||djo9||dd}d|i t |ƒ||ƒGdGHq:q:WdS(Nis%d-element cycleiis thiss->( slensslicesnsAssertionErrorsxrangesisobjsselfsshow_objsindexstag_dispatcherstype(sselfsslicesindexsobjsisn((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys _print_cycle½s    (s__name__s __module__s__doc__s__init__sclearsregistersruns find_cyclessNonesinstall_cycle_filters show_statss show_cyclesstypename_address_cmpsshow_cycleobjss show_sccss show_arcss get_rootsets chase_typesdont_chase_typesget_chased_typess_CycleFinder__init_tracers_CycleFinder__resetsidstypeslens_CycleFinder__find_cycless_CycleFinder__obj2arcnames_CycleFinder__study_cyclesshow_objs_print_separators _print_cycle(((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys CycleFinderÜs0    P       4% 1 & c CsÉdfd„ƒY} | dƒ| dƒ| dƒ| dƒf\}}}}||_||_||_||_|_|_hddd |d f<|_| d ƒ}||_||_ |i |_ d fd „ƒY} | | _ | | _ | dƒ}tƒ} | i|ƒ| i|ƒ~~~~~~ ~ ~| iƒ| iƒ| iƒ| iƒ| iƒ| iƒdGHx;| iƒD]-\}}}|djo| i|ƒq}q}W| idƒ| iƒdS(NsXcBstZd„Zd„ZRS(NcCs ||_dS(N(snamesme(smesname((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys__init__ÌscCsd|i dSdS(NsX(s)(sselfsname(sself((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys__repr__Îs(s__name__s __module__s__init__s__repr__(((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysXËs sasbscsdsharrumphiiisesYcBstZRS(N(s__name__s __module__(((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pysYÜsslonelysdead root set objects:i(sXsasbscsdsksysesselfrefsgotoes__repr__sYsgotoysgotoxslonelys CycleFinderszsregisters find_cycless show_statss show_cyclessshow_cycleobjss show_sccss show_arcss get_rootsetsrcscyclicsxsshow_obj( sasxscsbsesdscyclicslonelysrcsYsXsz((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys_testÊs@6                      s__main__("s__doc__s __version__s _dict_refss _dict_tags _list_refss _list_tags _tuple_refss _tuple_tags_instance_refss _instance_tags _class_refss _class_tags_instance_method_refss_instance_method_tagstypessDictTypesListTypes TupleTypes InstanceTypes ClassTypes MethodTypes_default_refs_dispatchers_default_tag_dispatchers _InstanceTypes _ClassTypesreprs_reprsReprs _CyclopsReprs _quickreprstypename_address_cmps CycleFinders_tests__name__(s_instance_method_tags_tests _ClassTypes_default_refs_dispatchers _dict_tags_default_tag_dispatchers _CyclopsReprs _list_refss _quickreprs __version__s _class_refss _InstanceTypes _list_tagstypename_address_cmpsreprs CycleFinders _tuple_refss _dict_refss_instance_refsstypess _class_tags _tuple_tags _instance_tags_reprs_instance_method_refs((sY/mnt/gmirror/ports/devel/boaconstructor/work/boa-constructor-0.4.4/ExternalLib/Cyclops.pys?3s@         NN   .  ÿï )