[Python-ideas] pickle.reduce and deconstruct

2020-01-23 Thread Andrew Barnert via Python-ideas
Pickling uses an extensible protocol that lets any class determine how its 
instances can be deconstructed and reconstructed. Both `pickle` and `copy` use 
this protocol, but it could be useful more generally. Unfortunately, to use it 
more generally requires relying on undocumented details. I think we should 
expose a couple of helpers to fix that:
    # Return the same (shallow) reduction tuple that pickle.py, copy.py, and 
_pickle.c would use    pickle.reduce(obj) -> (callable, args[, state[, litems[, 
ditem[, statefunc)
    # Return a callable and arguments to construct a (shallow) equivalent 
object    # Raise a TypeError when that isn't possible    
pickle.deconstruct(obj) -> callable, args, kw

So, why do you want these?

There are many cases where you want to "deconstruct" an object if possible. 
Pattern matching depends on being able to deconstruct objects like this. 
Auto-generating a `__repr__` as suggested in Chris's thread. Quick REPL 
stuff, and deeper reflection stuff using `inspect.Signature` and friends.
Of course not every type tells `pickle` what to do in an appropriate way that 
we can use, but a pretty broad range of types do, including (I think; I haven't 
double-checked all of them) `@dataclass`, `namedtuple`, `@attr.s`, many builtin 
and extension types, almost all reasonable types that use `copyreg`, and any 
class that pickles via the simplest customization hook `__getnewargs[_ex]__`. 
That's more than enough to be useful. And, just as important, it won't (except 
in intentionally pathological cases) give us a false positive, where a type is 
correctly pickleable and we think we can deconstruct it but the deconstruction 
is wrong. (For some uses, you are going to want to fall back to heuristics that 
are often right but sometimes misleadingly wrong, but I don't think the 
`pickle` module should offer anything like that. Maybe `inspect` should.)
The way to get the necessary information isn't fully documented, and neither is 
the way to interpret it. And I don't think it _should_ be documented, because 
it changes every so often, and for good reasons; we don't want anyone writing 
third-party code that relies on those details. Plus, a different Python 
implementation might conceivably do it differently. Public helpers exposed from 
`pickle` itself won't have those problems.
Here's a first take at the code.
    def reduce(obj, proto=pickle.DEFAULT_PROTOCOL):        """reduce(obj) -> 
(callable, args[, state[, litems[, ditem[, statefunc)        Return the 
same reduction tuple that the pickle and copy modules use        """        cls 
= type(obj)        if reductor := copyreg.dispatch_table.get(cls):     return 
reductor(obj) # Note that this is not a special method call (not looked up on 
the type) if reductor := getattr(obj, "__reduce_ex__"):     return 
reductor(proto) if reductor := getattr(obj, "__reduce__"):     return 
reductor()        raise TypeError(f"{cls.__name__} objects are not reducible")
    def deconstruct(obj):        """deconstruct(obj) -> callable, args, kw      
  callable(*args, **kw) will construct an equivalent object        """        
reduction = reduce(obj)        # If any of the optional members are included, 
pickle/copy has to # modify the object after construction, so there is no 
useful single # call we can deconstruct to.        if any(reduction[2:]):       
     raise TypeError(f"{type(obj).__name__} objects are not deconstrutable")    
    func, args, *_ = reduction        # Most types (including @dataclass, 
namedtuple, and many builtins) # use copyreg.__newobj__ as the constructor 
func. The args tuple is # the type (or, when appropriate, some other registered 
 # constructor) followed by the actual args. However, any function # with the 
same name will be treated the same way (because under the # covers, this is 
optimized to a special opcode).        if func.__name__ == "__newobj__":        
    return args[0], args[1:], {} # Mainly only used by types that implement 
__getnewargs_ex__ use # copyreg.__newobj_ex__ as the constructor func. The args 
tuple # holds the type, *args tuple, and **kwargs dict. Again, this is # 
special-cased by name. if func.__name__ == "__newobj_ex__":            return 
args # If any other special copyreg functions are added in the future, # this 
code won't know how to handle them, so bail.        if func.__module__ == 
'copyreg':            raise TypeError(f"{type(obj).__name__} objects are not 
deconstrutable") # Otherwise, the type implements a custom __reduce__ or 
__reduce_ex__, # and whatever it specifies as the constructor is the real 
constructor.        return func, args, {}
Actually looking at that code, I think it makes a better argument for why we 
don't want to make all the internal details public. :)
Here are some quick (completely untested) examples of other things we could 
build on it.
    # in inspect    def deconstruct(obj):        """deconstruct(obj) -> 
callable, bound_args Calling the 

[Python-ideas] Re: addition of "nameof" operator

2020-01-23 Thread Johan Vergeer
Thanks everyone for your responses. 

I would like to take all the information you gave me and do some research on 
it. I'll try to get it done this weekend or beginning of next week.
___
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/BGYKWTLGPQR34DTOVF52HPF5NIZ5OCQN/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Suggestion for language addition

2020-01-23 Thread Neil Girdhar


On Tuesday, December 3, 2019 at 7:54:03 PM UTC-5, Jan Bakuwel wrote:
>
> Hi Guido, 
>
> On 4/12/19 1:06 pm, Guido van Rossum wrote: 
> > I was playing the devil's advocate in jest. There is no way this will 
> > be added to Python, for a large variety of sociological and software 
> > engineering reasons. While some of the responses may to you seem like 
> > they come from inexperienced people who have not carefully read your 
> > argument, consider that to us, your proposal appears to come from yet 
> > another person who is relatively new to Python asking for their 
> > favorite feature from language X without understanding how that 
> > feature would interact with the rest of Python. 
>
> I was not implying at all that any response was from inexperienced 
> people. However no-one, at least not in a way clear to me, responded to 
> the value of the suggestion to get early warnings (ie. at parse time) 
> when an "end while" is written where an "end if" should have been written. 
>

I can see how you would find it useful, and I can also see why you're 
getting a lot of opposition.

One compromise suggestion is to take Andrew's advice and add your own  # 
end for's.  Then take Serhiy's advice and add the check to a linter.  After 
you've been coding like this for a while, start your movement to popularize 
"# end for" :)
 

>
> While I like Python a lot, I do miss the support of advanced compilers 
> that tell me at compile time where I made a typo or logic error so I 
> find myself spending relatively more time debugging at runtime. Not a 
> worry as Python easily makes up for the lost time for 1000+ reasons. I'm 
> just thinking aloud how it could, in my eyes, be even better while also 
> considering the sociological and software engineering impact on the 
> Python community to the best of my abilities. 
>
> I appreciate that I have much less experience with Python than many of 
> the folks on the list here. So please help me understand how the 
> suggested feature would, in any way, unfavorably interact with the rest 
> of Python as you say. 
>
> cheers, 
>
> Jan 
>
> ___ 
> Python-ideas mailing list -- python...@python.org  
> To unsubscribe send an email to python-id...@python.org  
> https://mail.python.org/mailman3/lists/python-ideas.python.org/ 
> Message archived at 
> https://mail.python.org/archives/list/python-ideas@python.org/message/WH4TOP57AZSMRQG26YRWHDMDBEXV5WQV/
>  
> Code of Conduct: http://python.org/psf/codeofconduct/ 
>
___
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/4LMK2RQMFIG6FJPWNWZPW3KM53CC5RWV/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: __repr__ helper(s) in reprlib

2020-01-23 Thread Neil Girdhar
I really like this feature suggestion and I would definitely use it if it 
were available.

It might be easiest to have the repr helper do as much as it can, but give 
the user the flexibility to override things with varying levels of 
precision.  For example:

def kwargs(obj,
   keys: Optional[Collection[str]] = None,
   keys_values: Optional[Mapping[str, Any]] = None,
   *,
   qualified=True):
if keys is None and keys_values is None:
pass  # Fallback?
keys_strings = (
[]
if keys is None
else [(k, repr(getattr(obj, k))) for k in keys])
keys_strings += (
[]
if keys_values is None
else [(k, repr(v)) for k, v in keys_values.items()])
class_name = (obj.__class__.__qualname__
  if qualified
  else obj.__class__.__name__)
parameters = ", ".join(
f"{k}={v}" for k, v in keys_strings)
return f"{class_name}({parameters})"




On Tuesday, January 21, 2020 at 10:33:43 PM UTC-5, Andrew Barnert via 
Python-ideas wrote:
>
> On Jan 21, 2020, at 14:30, Chris Angelico > 
> wrote: 
> > 
> > On Wed, Jan 22, 2020 at 9:17 AM Andrew Barnert  > wrote: 
> >> 
> >>> On Jan 21, 2020, at 12:29, Chris Angelico  > wrote: 
> >>> 
> >>> For non-dataclass classes, it would be extremely helpful to have an 
> >>> easy helper function available: 
> >>> 
> >>> class Spam: 
> >>>  def __repr__(self): 
> >>>  return reprlib.kwargs(self, ["quality", "recipe", "ham"]) 
> >>> 
> >>> The implementation for this function would be very similar to what 
> >>> dataclasses already do: 
> >>> 
> >>> # in reprlib.py 
> >>> def kwargs(obj, attrs): 
> >>>  attrs = [f"{a}={getattr(obj, a)!r}" for a in attrs] 
> >>>  return f"{obj.__class__.__qualname__}({", ".join(attrs)})" 
> >>> 
> >>> A similar function for positional args would be equally easy. 
> >> 
> >> I like this, but I think it’s still more complex than it needs to be 
> for 80% of the cases (see below) 
> > 
> > IMO that's not a problem. The implementation of reprlib.kwargs() is 
> > allowed to be complex, since it's buried away as, well, implementation 
> > details. As long as it's easy to call, that's all that matters. 
>
> Sorry, I didn’t mean the implementation, I meant the interface. You 
> shouldn’t have to specify the names in simple cases, and there are no 
> correct names to specify in more complicated cases, and the range in which 
> names are useful but also necessary seems pretty narrow. 
>
> Even this example can’t work with your kwargs function: 
>
> class Spam 
> def __init__(self, x): 
> self._x = x 
> def __repr__(self): 
> return kwargs(self, '_x'.split()) # or 'x'.split() 
>
> You want the repr to be `Spam(x=3)`, but there’s no way to get that. If 
> you use _x you get `Spam(_x=3)`, which is wrong; if you use x you get an 
> AttributeError, which nobody wants from repr. 
>
> >> while for the other 20%, I think it might make it too easy to get 
> things wrong. 
> > 
> > Hmm. I kept it completely explicit - it will generate a repr that 
> > shows the exact attributes listed (and personally, I'd often write it 
> > as "quality recipe ham".split()), 
>
> Right, but that’s exactly what makes it easy to get wrong. If I add a new 
> param and attribute with a default value and don’t remember to also add it 
> to the repr, I’m now silently generating reprs that look right but aren’t. 
> If I have an attribute that’s computed and include it in repr by accident, 
> likewise. 
>
> >>> Bikeshedding opportunity: Should it be legal to omit the attrs 
> >>> parameter, and have it use __slots__ or fall back to dir(obj) ? 
> >> 
> >> This makes things even more dangerous. It’s very common for classes to 
> have attributes that aren’t part of the constructor call, or constructor 
> params that aren’t attributes, and this would give you the wrong answer. 
> >> 
> >> It would be nice if there were a safe way to get the 
> constructor-call-style repr. And I think there might be for 80% of the 
> types—and the rest can specify it manually and take the risk of getting it 
> wrong, probably. 
> >> 
> >> One option is the pickle/copy protocol. If the type uses one of the 
> newargs methods, you can use that to get the constructor arguments; if it 
> uses one of the other pickling methods (or can’t be pickled), this just 
> doesn’t work. 
> >> 
> >> You could also look at the inspect.signature of __init__ and/or 
> __new__. If every param has an attribute with the same name, use that; 
> otherwise, this doesn’t work. 
> >> 
> >> And if none of the automatic ways worked and you tried to use them 
> anyway, you get an error. 
> > 
> > This is definitely getting into the realm of magic. 
>
> I don’t think getnewargs is any more magical than dir is—and it’s correct, 
> and ties into a protocol already used for two other things in Python. 
>
> > The question is, 
> > is it worth the convenience? I'd be +0.25