-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[wip] Playing with generator support for Python 3 so as to give more control to the advisor #13
base: master
Are you sure you want to change the base?
Conversation
This is what I perceive to be the correct behavior for advising generators, where the advisor generator gets instantiated once per iteration on the cutpoint generator function
Oooof ... so there are some test configuration issues. But the changes seem wrong at first glance - what's the problem you are trying to fix more exactly? |
hey, awesome that you're here :). Since I started work on this, I have realized that aspeclib's Python 2 and 3 code behave the same. So it seems that this behavior is entirely by design. My code completely deviates from the original idea and that the title of this PR is now misleading, given that the original code was probably not "broken". So let's start by stating what I want to do and how I arrived at the current incarnation of this PR: What I'm trying to doIn optile/ghconf github.py I'm using aspectlib to recursively patch all objects returned by PyGithub to correctly handle, among other things, GitHub API rate limiting. There is a class from ghconf.github import gh
org = gh.get_organization('myorg')
for repo in org.get_repos():
# using my code, get_repos() will return a `advising_generator_wrapper_py3`
pass I would like to be able to add my Advice to every What this PR doesWhere I arrived with this PR is a way to steer the execution of a generator per iteration:
Also this PR would totally change the behavior of aspectlib, so it's probably a non-starter. What the original Python 3 code doesThe original code employs
So, what now... :)The original code seems to give very little control to Aspects for generators, only allowing them to go ahead or blocking them. Not controlling their contents or execution. But that's what I need to do... do you have any ideas on how to accomplish that in the original framework? |
Ooof so there's lots of things going on there:
|
What I would try is to stop weaving at run time. It means you'd have to patch all the classes in github at initialization (and consequently have a bit more coupling than now) but it would be more predictable cause you won't have to weave results during runtime. |
Another advantage of not doing runtime weaving is that you will never accidentally double weave. |
Oh man, yes, you would think so. However most properties in PyGithub are constructed like this, where a pure read on the attribute can incur a network request. And aspectlib calls all properties on an instance during weave because In [1]: class Test:
...: @property
...: def a(self):
...: print('call a')
...: return "a"
...:
In [2]: hasattr(Test(), "a")
call a
Out[2]: True
# wat?
I think this comment is based on a misunderstanding of the previous PR title or old code in ghconf... I only get the decorated generator, which I believe is the correct behavior. I have not observed different behavior.
But how can I play with the outputs? Seriously, with the original code (as I pointed out above) I can only get it to request advice exactly once, after that it uses
I really don't want to have tighter coupling, but to be honest the current approach is also quite intensive and slowing things down a lot, especially with added network requests during weaving 🤷♂️ . So perhaps I should investigate "weaving through" PyGithub at startup instead. |
Thanks to your advice I rewrote the weaving to happen at startup by walking the PyGithub module tree. It seems to work (and obviously is much faster) and additionally removes the requirement to be able to see the generator outputs, therefor is works with aspectlib 1.4.2 as is. Given my comments above I still feel that some improvements could be done in the generator handling, but that's outside of the scope of this discussion I guess :) |
Open a documentation issue then? ;) But seriously, what's wrong with the current handling? |
An |
To answer my own question from above:
From the Python docs:
Obviously 🤦♂️ |
It should be possible. Afaik I considered having support for this but I deemed it unnecessary, inconsistent with regular advicing and too complex. It should be possible but you see it's not a trivial change - need to have type checks and slow unwrapping of every generator. From my perspective it's basically adding cython speedups in aspectlib. It would take serious time.
It's because you're most likely accessing it through the instance. I doubt there's a classproperty or metaclass implementation there that would call the function wrapped by property when accessing something on a class. Just weave the classes instead of the instances. |
Yes, I agree.. giving too much control about how the generator is consumed is probably counter-productive and, as you said, inconsistent with regular aspects. A compromise might be a solution that allows the @aspectlib.Advice(bind=True)
def proposal_return_different_generator(cutpoint, *args, **kwargs):
# we don't want the caller to use the original generator so we...
# wrap it
def my_gen_wrapper(gen):
sent = None
while True:
if sent:
x = gen.send(sent)
else:
x = next(gen)
x += 1 # modify x in some way
sent = yield x
# and return our wrapper instead
yield aspectlib.Return(my_gen_wrapper(cutpoint(*args, **kwargs)))
|
aspectlib.Return can't do anything else, otherwise it'd break expectations. Generators do have a return value (in addition to items being yielded), and that is what aspectlib.Return is for. |
aspectlib could, for example, behave differently if the Currently, an |
I agree that it should allow influencing the generator, just that this feature shouldn't be met halfway. IMO the only way to support this usecase is to implement a special advice (aspectlib.Yield or similar) . |
Hmm, isn't there a consistency argument to be made here? Regular function Advice
Generator function Advice
Wouldn't the consistent behavior for generator functions look like this: Proposed Generator Function Advice
|
Ok so basically the change you're proposing is to allow |
not only. In my comment I also propose to change That's how aspectlib behaves right now. On a generator cutpoint, yielding |
I believe this is the correct fix. I'm still testing, will update as it goes. I fear that yhis project is inactive.