[Tutor] How is "set(ls).add('a') evaluated? [Was: Re: A program that can check if all elements of the list are mutually disjoint]

dn PyTutor at DancesWithMice.info
Sun Jun 6 04:53:09 EDT 2021


On 06/06/2021 14.19, boB Stepp wrote:
> Ah, I finally see the light, dn!

Regret to advise that such wisdom came from @David, not
this-David?self.David aka "dn".


That said:-


> On Sat, Jun 5, 2021 at 9:08 PM boB Stepp <robertvstepp at gmail.com> wrote:
>> On Sat, Jun 5, 2021 at 8:57 PM David <bouncingcats at gmail.com> wrote:
>>> On Sun, 6 Jun 2021 at 11:37, boB Stepp <robertvstepp at gmail.com> wrote:
>>>
>>>> I have not played around with set's methods and operators to date, so
>>>> while trying to understand this code I tried out different things in
>>>> the interpreter.  Along the way I tried something and it surprised me:
>>>>
>>>> Python 3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928
>>>> 64 bit (AMD64)] on win32
>>>> Type "help", "copyright", "credits" or "license" for more information.
>>>>>>> ls = ["amba", "Joy", "Preet"]
>>>>>>> z = set(ls).add('a')
>>>>>>> z
>>>>>>> print(z)
>>>> None           # This surprised me.  I was expecting {'amba', 'Joy',
>>>> 'Preet', 'a'}.
>>>
>>> The set() object has an add() method that modifies
>>> its object and returns None.
>>
>> I understand this.  But for "set(ls).add('a')" I am thinking the
>> following sequence of events occur:
>>
>> 1)  "set(ls)" creates the set-type object "{'amba', 'Joy', 'Preet'}.
>> 2)  ".add('a')" method is called on this set object, resulting in the
>> new set object "{'amba', 'Joy', 'Preet', 'a'}
>> 3)  "z" is now assigned to point to this resultant object.
> 
> (3) is *not* correct.  "z" is assigned to what the add() method is
> returning, dn's point.
> 
>>>>> zz = set(ls).union('a')
>>>>> zz
>> {'amba', 'Joy', 'Preet', 'a'}
>>
>> Why are these different in their results?
> 
> Here according to the docs the union() method:
> 
> "Return a new set with elements from the set and all others."
> 
> Caught up in something elementary once again.  Sigh!

and later:-
> <Gripe>
> I find it really hard to remember which functions and methods return
> "None" and operate by side effect with those that return a new object.
> This is especially difficult for me as I so often have long breaks
> between using/studying Python.  I wish that Python had some sort of
> clever syntax that made it *obvious* which of the two possibilities
> will occur.
> </Gripe>
> ~(:>))


It's "pythonic"!
(hey, don't shoot me, I'm only the piano-player)


When we work with objects, we can access their attributes using "dotted
notation". (remember though: a method (function) is also an attribute)
Thus, we are able to access a value directly, without using "getters",
and to change its value without "setters". Because we are changing an
attribute's value, the modified-value is the result of such. Thus, a
return of None.

(see also "Functional Programming" and the concept of "side effects")


We need to 'tune in' to the documentation to settle such confusion
(thereafter clarifying any doubt by experimenting with the REPL). In
this case:

Python 3.9.5 (default, May 14 2021, 00:00:00)
[GCC 10.3.1 20210422 (Red Hat 10.3.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> help( set )

Help on class set in module builtins:

class set(object)
 |  set() -> new empty set object
 |  set(iterable) -> new set object
 |
 |  Build an unordered collection of unique elements.
 |
 |  Methods defined here:
 |
 |  __and__(self, value, /)
 |      Return self&value.
 |
 |  __contains__(...)
 |      x.__contains__(y) <==> y in x.
 |
...
 |  add(...)
 |      Add an element to a set.
 |
 |      This has no effect if the element is already present.
...
 |  __len__(self, /)
 |      Return len(self).
...
 |  update(...)
 |      Update a set with the union of itself and others.
...

- where we can see that the set method "and" will return a new set
(logical given the result will be neither self nor the other set),
whereas set.add() will modify the contents of this existing set (stated
more categorically in the explanation for set.update() ):

s = set()
s.add( 1 )    #no need for an assignment, ie LHS and RHS construct


Whereas functions, (built-in or otherwise) work with any provided
arguments, and return a result (in lieu of an explicit return-value,
this will be None).

if 1 in s: ...
l = len( s )
# which is a generic function implemented as
l = s.__len__()


Indeed:

s = set()

is a factory-function which creates (and outputs) a new set - and thus
also enables "s" to subsequently 'do stuff' with its attributes.


Functions normally run in their own "frame" and thus have their own
namespace. So, if a numerical value is named within a function, that
name will not be available after the return. (watch-out for the mutable
container exception!)

Conversely, objects form their own namespace, which lasts for the life
of the object. Thus, any change made within that namespace remains
accessible as the particular attribute.


Just to add to your gripe, there is (at least) one function which
provides a result AND a (by-design) internal (to the set) "side-effect":

s = { 1, 2 }
what = s.pop()

...from help( set )...
 |  pop(...)
 |      Remove and return an arbitrary set element.
 |      Raises KeyError if the set is empty.
...

Note the "and"!

What will be the value of "what"?
What will be the value of s?

Thus, as we "tune-in" to the documentation's idiom - words like
"remove", "and", and "return" become especially meaningful; in-turn
helping us to understand which operations will return a new object, and
which will alter some attribute of our current subject.
(or both...!)
-- 
Regards,
=dn


More information about the Tutor mailing list