On Mon, Apr 20, 2020 at 3:13 PM Andrew Barnert <abarnert@yahoo.com> wrote:
> Sure, it’s a declarative format, it’s just that often it’s intended to be understood as representing an object graph.

I"m not sure the point here -- I was not getting onto detail nor expalingnoi myself well, but I think there are (kind of) three ways to "name" just one piece of data that came from a bunch of JSON:

- a key, as in a dict `data['this']`
- an attribute of an object: `data.this`
- a local variable: `this`

what I was getting at is that there may be a fine line between the dsta version and the object version, but that can go between those easily without typing all the names. It's only when you have it in a local variable that this whole idea starts to matter.

> In Python, you need to decide how you want to work with it, either as an object with attributes or a dict. But if you are getting it from JSON, it's a dict to begin with. So you can keep it as a dict, or populate an object with it. B ut populating that object can be automated:

an_instance = MyObject(**the_dict_from_JSON)

> But unless your object graph is flat, this doesn’t work.

Of course not -- that was just the most trivial example. but it's also trivial to unpack a whole graph, as long as you only want the standard types that JSON can store. Even if you want custom types, you can still build those from a dict of teh standrad tyupes.

And I've written (as have many others, I"m sure) code that does just that.

> It’s not like it’s _hard_ to write code to serialize and deserialize object graphs as JSON (although it’s hard enough that people keep proposing a __json__ method to go one way and then realizing they don’t have a proposal to go the other way…), but it’s not as trivial as just ** the dict into keywords.

sure. But what I'm suggesting is that even if you have a complex object graph, you should write (or use a library)  code to do that unpacking for you rather than, say:

blob = json.load(...)
people = []
for person in blob['people']
     name = person['name']
     phone_number = person['phone_number']
     # a bunch more
     people.append(Person(name=name,
                          phone_number=phone_number,
                          ...
                          ))

When you could write:

blob = json.load(...)
people = [Person(**person) for person in blob['people']]

Granted, I hope no one would write it quite that badly, but the point is that the proposal in hand is only helpful if the information is in local variables, and this kind of use case, that's probably not a good way to structure the code.

>> Apologies for my overly-fragmentary toy example.

well, better than my no example :-) -- but I wasn't referring to any particular example, I meant it generally -- again, this proposal is about using local variables to fill in function calls (or dict construction) with the same names, and I'm suggesting that if that is "data", then it probably shouldn't be in local variables anyway. And if it it came from JSON (or some such), then it took effort to put it in local variables ...

> Let’s say you have a function that makes an API request to some video site to get the local-language names of all movies of the user-chosen genre in the current year.

> If you’ve built an object model, it’ll look something like this:

    query = api.Query(genre=genre, year=datetime.date.today().year)
    response = api.query_movies(query)
    result = [movie.name[language] for movie in response.movies]

> If you’re treating the JSON as data instead, it’ll look something like this:

    query = {'query': {'genre': genre, 'year': datetime.date.today().year}}
    response = requests.post(api.query_movies_url, json=query).json
    result = [movie['name'][language] for movie in response.movies]

well, the query params and the result are not really the same, on the data vs code continuum. But...

> Either way, the problem is in that first line, and it’s the same problem. (And the existence of ** unpacking and the dict() constructor
> from keywords means that solving either one very likely solves the other nearly for free.)

Agreed. I do think that if this is going to happen at all, it should be a dict display feature (which would help with both "data" and "code"), not a function calling feature.

> Here I’ve got one local, `genre`. (I also included one global representing a global setting, just to show that they _can_ be reasonable as well, although I think a lot less often than locals, so ignore that.) I think it’s pretty reasonable that the local variable has the same name as the selector key/keyword. If I ask “why do I have to repeat myself with genre=genre or 'genre': genre”, what’s the answer?

Sure, but treating the result as data does not mean you have to treat the query as data as well, and you may have a query_params class that holds all that anyway :-) -- and even if they are locals -- where did they come from? (read from a config file? gotten from user input?)  if you were doing a full on JSON API, they may not have ever had to be in variables in the first place.

> If I have 38 locals for all 38 selectors in the API—or, worse, a dynamically-chosen subset of them—then “get rid of those locals” is almost surely the answer, but with just 1? Probably not. And maybe 3 or 4 is reasonable too—

right. but I don't think anyone is suggesting a language change for 1, or even 3-4 names (maybe 4...)

> And it’s clearly not an accident that the local and the selector have the same name. So, I think that case is real, and not dismissible.

I wasn't trying to dismiss it.
I'm not saying it never comes up in well designed code -- it sure does, but if there's a LOT of that, then maybe some refactoring is in order.

> Yes. And now that you point that out, thinking of how many people go to StackOverflow and python-list and so on looking for help with exactly that anti-pattern when they shouldn’t be doing it in the first place ...

Right. I think the data vs code distinction is a tough one in Python (maybe a tiny bit less than JS?) In more static languages, you can kind of decide based on what a pain it is to write the code -- if it's a serious pain, it's probably data :-)

> And if we go back a couple messages in this thread, I was suggesting that the anti-pattern was not using the same names in calling and function scope, but rather, using local names, when it really should be data: in a dict, or even a special object.

> there is definitely a risk that making this syntax easier could be an antipattern magnet. So, it’s not just whether the cases with 4 locals are important enough to overcome the cost of making Python syntax more complicated; the benefit has to _also_ overcome the cost of being a potential antipattern magnet. For me, this proposal is right on the border of being worth it (and I’m not sure which side it falls on), so that could be enough to change the answer,

Good point.

> But I don’t think it eliminates the rationale for the proposal, or even the rationale for using it with JSON-related stuff in particular.

Nor do I. However, as I think about it, where this may make the most sense might be for cases when you're making a complex call that has SOME arguments in the local namespace, with the some with other names, and some specified as literals. That's pretty common pattern in the call to setup() in setup.py files, for example.

-CHB





--
Christopher Barker, PhD

Python Language Consulting
  - Teaching
  - Scientific Software Development
  - Desktop GUI and Web Development
  - wxPython, numpy, scipy, Cython