GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-74929: Implement PEP 667 by gaogaotiantian · Pull Request #115153 · python/cpython

Conversation

@gaogaotiantian

@gaogaotiantian gaogaotiantian commented Feb 8, 2024 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

This is a prototype implementation of PEP 667.
All the examples given in PEP 667 passed.

The code only serves as a prototype, the proxy object is not even tracked by GC. However, the most essential features are implemented.

frame.f_locals is now a real-time proxy for the actual local variables in the frame (there's no mapping yet but that's an implementation detail).

locals() behaves basically like before.

All tests passed except for 3 - all expected

  • test_sys for frame size - yes it changed
  • test_frame - The pop method is not implemented yet
  • test_listcomps - the behavior of frame.f_locals changed so the corresponding test is not valid anymore
  • Issue: Local variable assignment is broken when combined with threads + tracing + closures #74929

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
gaogaotiantian added 4 commits February 6, 2024 23:15
@gaogaotiantian
Basic prototype for frame proxy
42d7186
@gaogaotiantian
Fix some lint and remove oprun check
7eeab1b
@gaogaotiantian
Not entirely work yet
60e70e7
@gaogaotiantian
Fix a bug
1454ce4
@bedevere-app bedevere-app Bot mentioned this pull request Feb 8, 2024
Local variable assignment is broken when combined with threads + tracing + closures #74929
Closed
markshannon
markshannon reviewed Feb 8, 2024
View reviewed changes

@markshannon markshannon left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for doing this.

I've a few comments, but nothing major.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Include/internal/pycore_frame.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Include/internal/pycore_frame.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Objects/frameobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Objects/frameobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Objects/frameobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@carljm

carljm commented Feb 9, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Thanks for doing this! I was hoping this would get done for 3.13 but hadn't found time to do it yet.

I don't agree that test_frame_locals in test_listcomps should be expected to fail under PEP 667. f_locals is supposed to be a real-time proxy for the live variables in the frame. Within a comprehension, comprehension-scoped variables are live and should be present in f_locals. The fact that this test is failing suggests that the PR does not yet correctly handle "fast-hidden" locals.

EDIT: on second thought, I realized the problem is likely that f_locals is now a live view instead of a snapshot, so by the time we look for the key, the comprehension is no longer running, and a is no longer a live name. So you are right that the test as written should be expected to fail; it should be updated to take a copy of f_locals within the comprehension.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented Feb 9, 2024

Copy link
Copy Markdown
Member Author

EDIT: on second thought, I realized the problem is likely that f_locals is now a live view instead of a snapshot, so by the time we look for the key, the comprehension is no longer running, and a is no longer a live name. So you are right that the test as written should be expected to fail; it should be updated to take a copy of f_locals within the comprehension.

Yes, that's why the test fails. The test is val = [sys._getframe().f_locals for a in [0]][0]["a"] so when it tries to access ["a"], it's already out of the comprehension scope. This actually represents one of the changes we made to f_locals.

carljm reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gaogaotiantian added 2 commits February 13, 2024 11:36
@gaogaotiantian
Change code style and add GC
0045274
@gaogaotiantian
Clean up code
de73bc9
@gaogaotiantian

gaogaotiantian commented Feb 13, 2024

Copy link
Copy Markdown
Member Author

I cleaned up the code a bit to address the review and added the GC part in. This is still in draft state, which only aims to demostrate the possibility of PEP 667. There's plenty of work to be done before this can be merged in. The current goal is to make it enough for the PSF to decide whether to accept PEP 667. Let me know if there's still missing pieces (like do we want to actually remove all the FAST/LOCAL conversion calls to prove the point).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon

markshannon commented Feb 20, 2024

Copy link
Copy Markdown
Member

Removing the fast locals <--> slow locals conversion is key to fixing #74929. I think that is worth doing before submitting PEP 667 to the SC.

@gaogaotiantian since you are doing most of the work on this, would you like to be added as co-author of PEP 667?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented Feb 20, 2024

Copy link
Copy Markdown
Member Author

Sure, I'm happy to be listed as the co-author. I'll work on the fast vs local thing and check if #74929 is fixed.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum

gvanrossum commented Feb 22, 2024

Copy link
Copy Markdown
Member

FWIW the discussion has been started at https://discuss.python.org/t/pep-667-consistent-views-of-namespaces/46631.

We now need at least the following updates to the PEP:

  • Add Discussion-To header pointing to that Discourse thread
  • Add Tian Gao to list of Authors
  • Link to implementation prototype PR from the Implementation section
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented Feb 22, 2024

Copy link
Copy Markdown
Member Author

I've already added myself to the list of authors with Mark's guidance. Let me know if you want me to make the PR to update the PEP.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum

gvanrossum commented Feb 22, 2024

Copy link
Copy Markdown
Member

Go right ahead.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian
Disable all fast/local functions
ca92393
@gaogaotiantian

gaogaotiantian commented Mar 1, 2024

Copy link
Copy Markdown
Member Author

@markshannon okay I made all fast/local related functions no-op. It did not break anything new and #74929 (comment) is not an issue anymore. The original test case in #74929 has been accidentally fixed in 3.11, is there any repro case that I can test against?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@carljm

carljm commented Mar 1, 2024

Copy link
Copy Markdown
Member

Is there a reason you're leaving the failing tests as failing? I think it is easier for reviewers to evaluate the compatibility impact on tests by seeing the required changes to tests in the PR, rather than by having to go look at the CI test results.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented Mar 1, 2024

Copy link
Copy Markdown
Member Author

The current implementation is just a prototype and far from being done. I can make some modifications to the test so they can pass with the change if that's preferred.

carljm reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gaogaotiantian added 3 commits March 1, 2024 18:34
@gaogaotiantian
Update tests for the new f_locals
ff886ff
@gaogaotiantian
Comment out the pop for now
9690a2d
@gaogaotiantian
Convert f_locals to dict first
6e9848a
@gaogaotiantian

gaogaotiantian commented Mar 5, 2024

Copy link
Copy Markdown
Member Author

We have two more months until feature freeze. If we want to land this on 3.13, we probably should push it forward soon.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum

gvanrossum commented Mar 5, 2024

Copy link
Copy Markdown
Member

How is the Discourse discussion on this topic going? Is it substantial enough to send the PEP to the SC? Do we need to make any PEP updates first?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented Mar 5, 2024

Copy link
Copy Markdown
Member Author

I made a PR to the PEP and it's approved but not merged (I think Mark has to review and approve it?), if you meant the link changes mentioned before. I'm not sure if the discussion thread attracts enough attention. What point should we get for the Disclosure discussion?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

markshannon
markshannon reviewed Mar 5, 2024
View reviewed changes
Comment thread Python/ceval.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@markshannon

markshannon commented Mar 5, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

We need to decide which mapping methods we want FrameLocalsProxy to support.
Listing all the methods on UserDict:

Dunder methods

  • Comparison operators, __le__, etc.
  • __or__, __ior__, __ror__
  • __contains__
  • __copy__
  • __getitem__
  • __setitem__
  • __delitem__ (see comment below under normal methods)
  • __format__
  • __len__
  • __iter__
  • __getitem__
  • __reversed__
  • __sizeof__
  • __repr__, __str__
  • Pickling support, __reduce__, etc

I think we need to support all the above, except pickling, and that __copy__ should return a normal __dict__, not a shallow copy.

Normal methods

  • clear,
  • copy,
  • get,
  • items,
  • keys,
  • pop,
  • popitem,
  • setdefault,
  • update,
  • values

Methods that remove items are tricky as we cannot properly delete fast locals; we just set them to None and emit a warning.
So, I think it best not to implement clear, pop, or popitem.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon

markshannon commented Mar 5, 2024

Copy link
Copy Markdown
Member

We don't need to implement them all before submitting the PEP though, just decide on which methods to support.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented Mar 6, 2024

Copy link
Copy Markdown
Member Author

Should we follow some standard for the methods we should implement? The original PEP mentioned that we should do a MutableMapping, which probably lists all the methods that need to be implement.

For the remove related methods - should the debugger be able to decref the local variable? Just like del val. I think making that equivalent to f.locals.pop("val") is reasonable. Should we even emit a warning for that? Did we do that now? I think we emit a warning when we have to assign None to unassigned local variables.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented May 2, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

Okay I resolved most of the comments, and I believe there's one thing left here - comprehensions.

I'm just going to jump to my proposal and see if that makes sense. If not, we can discuss it more.

import sys
def f():
    x = 1
    [print(sys._getframe().f_locals) for a in [0]]
    print([sys._getframe().f_locals for a in [0]][0])
    print(sys._getframe().f_locals)
f()

will print

# Inside comprehension, show hidden variable a and locals in the outside frame
{'x': 1, 'a': 0}
# f_locals from inside but read outside, no hidden variable, only locals.
# This does not match the fiction because the variable should "exist" if it's a frame, but in reality it's gone
{'x': 1}
# f_locals outside, just local variables
{'x': 1}

Notice all the values above are proxies.

import sys
class C:
    x = 1
    [print(sys._getframe().f_locals) for a in [0]]
    print([sys._getframe().f_locals for a in [0]][0])
    print(sys._getframe().f_locals)

will print (ignore __module__ and __qualname__)

# This is a proxy without class-level variable
{'a': 0}
# Empty proxy
{}
# A dict with class level variable
{'x': 1}

An alternative would be:

# A dict, but you can't write to variable a to change the local value
{'a': 0, 'x': 1}
# A dict
{'x': 1}
# Still a dict
{'x': 1}

The latter seems more consistent with the function level result, and it makes locals() backwards compatible, but it creates a horrible exemption where the f_locals is not "write through".

Maybe we could stick to a simple rule here - f_locals will always return something that's write through. locals() always return dict(proxy) or the dict itself (in class/module level, outside of the comprehension).

However, this will break the backwards compatibility for locals() in comprehension in class/module level.

carljm reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@carljm

carljm commented May 2, 2024

Copy link
Copy Markdown
Member

I think that proposal looks reasonable.

I think your first proposal for the class behavior is better. Comprehensions in class scope cannot see class-level variables (as if the comprehension were still a function, since functions defined in a class scope can't see variables in the enclosing class scope either), so those class variables should not show up when f_locals is accessed inside the comprehension.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gaogaotiantian

gaogaotiantian commented May 2, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

I think your first proposal for the class behavior is better. Comprehensions in class scope cannot see class-level variables (as if the comprehension were still a function, since functions defined in a class scope can't see variables in the enclosing class scope either), so those class variables should not show up when f_locals is accessed inside the comprehension.

But we will probably treat module-level the same as class-level. Do you think it's reasonable to have a module-level comprehension to return a proxy without the global variables?

edit: I guess that makes sense as well. Even in a function in a module, f_locals should not return any global variables.

carljm and ncoghlan reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gaogaotiantian added 6 commits May 2, 2024 18:44
@gaogaotiantian
Fix the list comp
f29e6a3
@gaogaotiantian
Fix mapping check
e0ca4fe
@gaogaotiantian
Fix frame_getlocals
f78156a
@gaogaotiantian
Fix test error
4503145
@gaogaotiantian
Change the new ref for getcode
cdac22c
@gaogaotiantian
Avoid creating the frame object if possible
49287ff
@gaogaotiantian

gaogaotiantian commented May 3, 2024

Copy link
Copy Markdown
Member Author

Okay I finished the last piece and cleaned up the code a bit.

Now we have some very consistent behavior for f_locals and locals().

locals() will be either

  • dict(f_locals) for function scope frames and all comprehensions
  • or f_locals for class and module frames

f_locals will be

  • a proxy for function scope frames and all comprehensions (comprehension variables are not accessible outside of the comprehension)
  • or a dict for class and module frames

f_locals will always be write through, no exceptions.

This breaks the behavior introduced by pep 709 that [locals() for a in [0]] will include outside variables if it's under a module or class level scope, but I think this specific behavior is not desired nor intentional, it's a compromise to begin with. If we consider the comprehension as a "function", the locals() should never include the outside variables.

locals() in a function scope will include the cell variables as expected.

Let me know if there are missing pieces that I don't know of.

ncoghlan, carljm, gvanrossum, and zetaloop reacted with thumbs up emoji
All reactions
  • 👍 4 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon
markshannon self-requested a review May 3, 2024 20:21
markshannon
markshannon approved these changes May 3, 2024
View reviewed changes

@markshannon markshannon left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. Thanks for your perseverance.

last chance for comments.
I'm going to merge this in 12 hours or so, if no one objects.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gvanrossum reacted with thumbs up emoji ncoghlan and CoolCat467 reacted with hooray emoji
All reactions
  • 👍 1 reaction
  • 🎉 2 reactions
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting review labels May 3, 2024
@gaogaotiantian
Remove a single blank line
378aacf
@markshannon
markshannon merged commit b034f14 into python:main May 4, 2024
@bedevere-app bedevere-app Bot removed the awaiting merge label May 4, 2024
@sobolevn sobolevn mentioned this pull request May 5, 2024
gh-118613: Fix error handling of _PyEval_GetFrameLocals in ceval.c #118614
Merged
@ncoghlan ncoghlan mentioned this pull request May 6, 2024
Clarify the required behaviour of locals() #62160
Closed
@gaogaotiantian
gaogaotiantian deleted the pep667 branch May 6, 2024 19:42
SonicField pushed a commit to SonicField/cpython that referenced this pull request May 8, 2024
@gaogaotiantian @SonicField
pythongh-74929: Implement PEP 667 (pythonGH-115153)
48dc6ad
eli-schwartz added a commit to eli-schwartz/meson that referenced this pull request May 13, 2024
@eli-schwartz
msetup: fix regression under py3.13 causing profile.runctx to not wri…
242ace9
…te locals()

"PEP 667: Consistent views of namespaces" caused locals() to be
inconsistent between uses since it is now created afresh every time you
invoke it and writes to it are dropped. `sys._getframe().f_locals` is
equivalent but preserves writes (it doesn't create a new dict) and
unfortunately doesn't help at all as it's documented to be a private
implementation detail of CPython that "should be used for internal and
specialized purposes only".

Work around this by saving locals to a variable reference and both
passing it into runctx and reusing it in lookups of the result. This
works okay for both new and older versions of python.

Bug: python/cpython#115153
eli-schwartz added a commit to eli-schwartz/meson that referenced this pull request May 13, 2024
@eli-schwartz
msetup: fix regression under py3.13 causing profile.runctx to not wri…
0eb1276
…te locals()

"PEP 667: Consistent views of namespaces" caused locals() to be
inconsistent between uses since it is now created afresh every time you
invoke it and writes to it are dropped. `sys._getframe().f_locals` is
equivalent but preserves writes (it doesn't create a new dict) and
unfortunately doesn't help at all as it's documented to be a private
implementation detail of CPython that "should be used for internal and
specialized purposes only".

Work around this by saving locals to a variable reference and both
passing it into runctx and reusing it in lookups of the result. This
works okay for both new and older versions of python.

Per the documentation for locals():

> The contents of this dictionary should not be modified; changes may
> not affect the values of local and free variables used by the
> interpreter.

So... lesson learned? :) This was introduced in commit
c34ee37; before that, we still used
locals() but only to pass local variables *in*.

Bug: python/cpython#115153
@henryiii henryiii mentioned this pull request May 23, 2024
fix: support Python 3.13.0b1 (PEP 667 fix) pybind/pybind11#5127
Merged
@henryiii

henryiii commented May 23, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

FYI, docs are missing here: https://docs.python.org/3.13/c-api/reflection.html

The back-compat functions were causing a few destructors to not be called properly in three tests in pybind11; switching to the new functions fixed it, though. Not sure if that's expected. I believe it’s because the back compat function for eval frame locals is basically the same as the new function and returns a new reference.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@A5rocks A5rocks mentioned this pull request May 24, 2024
Remaining test issues with Python 3.13.0b1: not ki_protected, pathlib.Path.resolve siganture python-trio/trio#3004
Closed
eli-schwartz added a commit to eli-schwartz/meson that referenced this pull request Jun 23, 2024
@eli-schwartz
msetup: fix regression under py3.13 causing profile.runctx to not wri…
8835ad4
…te locals()

"PEP 667: Consistent views of namespaces" caused locals() to be
inconsistent between uses since it is now created afresh every time you
invoke it and writes to it are dropped. `sys._getframe().f_locals` is
equivalent but preserves writes (it doesn't create a new dict) and
unfortunately doesn't help at all as it's documented to be a private
implementation detail of CPython that "should be used for internal and
specialized purposes only".

Work around this by saving locals to a variable reference and both
passing it into runctx and reusing it in lookups of the result. This
works okay for both new and older versions of python.

Per the documentation for locals():

> The contents of this dictionary should not be modified; changes may
> not affect the values of local and free variables used by the
> interpreter.

So... lesson learned? :) This was introduced in commit
c34ee37; before that, we still used
locals() but only to pass local variables *in*.

Bug: python/cpython#115153
@gaogaotiantian gaogaotiantian mentioned this pull request Jul 8, 2024
PEP 667: Clarify specification ambiguities and the expected impact python/peps#3845
Merged
3 tasks done
@SigureMo SigureMo mentioned this pull request Nov 1, 2024
[SOT][3.13] eval_frame support python 3.13 PaddlePaddle/Paddle#69126
Merged
Volker-Weissmann pushed a commit to Volker-Weissmann/meson that referenced this pull request May 1, 2025
@eli-schwartz @Volker-Weissmann
msetup: fix regression under py3.13 causing profile.runctx to not wri…
0c44d00
…te locals()

"PEP 667: Consistent views of namespaces" caused locals() to be
inconsistent between uses since it is now created afresh every time you
invoke it and writes to it are dropped. `sys._getframe().f_locals` is
equivalent but preserves writes (it doesn't create a new dict) and
unfortunately doesn't help at all as it's documented to be a private
implementation detail of CPython that "should be used for internal and
specialized purposes only".

Work around this by saving locals to a variable reference and both
passing it into runctx and reusing it in lookups of the result. This
works okay for both new and older versions of python.

Per the documentation for locals():

> The contents of this dictionary should not be modified; changes may
> not affect the values of local and free variables used by the
> interpreter.

So... lesson learned? :) This was introduced in commit
c34ee37; before that, we still used
locals() but only to pass local variables *in*.

Bug: python/cpython#115153
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@carljm carljm carljm left review comments
@ncoghlan ncoghlan ncoghlan left review comments
@markshannon markshannon markshannon approved these changes
@encukou encukou Awaiting requested review from encukou encukou is a code owner
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently is a code owner
@gvanrossum gvanrossum Awaiting requested review from gvanrossum

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

6 participants

@gaogaotiantian @carljm @markshannon @gvanrossum @ncoghlan @henryiii
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.