[Tutor] Why do I not get an error when I mistakenly type "humdrum.sigh_strenght" instead of the correct "humdrum.sigh_strength"?

Steven D'Aprano steve at pearwood.info
Sun Jan 24 21:21:30 EST 2016


On Fri, Jan 22, 2016 at 10:14:57PM -0600, boB Stepp wrote:
> On Thu, Jan 21, 2016 at 4:57 AM, Steven D'Aprano <steve at pearwood.info> wrote:

> > def spam(x, y):
> >     ...
> >
> > spam.extra_info = "whatever"
> 
> A new thing that I did not suspect I could do.  This bothers me for two reasons:
> 
>     1)  It does not seem right adding attributes to functions outside
> of its definition.

Well, unfortunately there's no syntax for adding attributes to a 
function *inside* its definition. If you put it inside the body of the 
function, it won't get run until you call the function:

def spam(arg):
    spam.widget = some_widget

spam.widget  # Fails because the function hasn't been run yet.


If you put it before the function definition, the function doesn't 
exist yet:

spam.widget = some_widget  # Fails because spam doesn't exist.
def spam(arg): 
    ...

So the only thing left is to put it *after* the definition.

In practice, I would create a decorator that adds the extra attribute, 
so you can write:


def add_widget(the_widget):
    def decorator(func):
        func.widget = the_widget
        return func
    return decorator

@add_widget(some_widget)
def spam(arg):
    ...



 
>     2)  spam.extra_info appears to be global:

`spam` is global, but only because you created it in the global 
namespace. You could have put it in a class, or nested inside another 
function. `extra_info` is not global, it is attached firmly to `spam`, 
no different from the 30 or so existing attributes of functions:

py> dir(lambda:None)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', 
'__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', 
'__eq__', '__format__', '__ge__', '__get__', '__getattribute__', 
'__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', 
'__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', 
'__qualname__', '__reduce__', '__reduce_ex__', '__repr__', 
'__setattr__', '__sizeof__', '__str__', '__subclasshook__']

or for that matter nearly any object. Even None has 20+ attributes.

You'll notice that *by default* all of the function attributes are 
__dunder__ names, which mean that they are used (somewhere) by the 
Python implementation. E.g. the __code__ object contains the actual 
byte-code of the function. But you can add your own, if you have a 
reason to do so.

It would have been very easy for the core developers to prohibit the 
addition of arbitrary attributes to functions: just don't give functions 
a __dict__ attribute. And indeed, in the earliest versions of Python, 
such as version 1.5, functions didn't have a __dict__ and you couldn't 
add attributes to them. So it is a deliberate choice to allow adding 
attributes to functions, but not strings, ints, floats etc.


[...]
> And I imagine I am being dense about something that is quite obvious
> to you:  How is this a useful feature to have?  What does it give me
> that is more useful than just saying something like:
> 
> just_another_global variable = "whatever"
> 
> ?


It keeps the function's data close to the function. What if you have two 
functions, spam and eggs, that both want to claim the name "widget" for 
their internal data? Do they just stomp all over each other's data? Or 
do you call them "spam_widget" and "eggs_widget"?

If you can write spam_widget, what's wrong with writing spam.widget 
instead?



> And what bothered me about my original example that started this
> thread is that when my typo
> 
> humdrum.sigh_strenght = 'high'
> 
> was accepted and did not generate an error, it seemed to mean to me
> that I was violating my object's data encapsulation.

That's a matter of opinion.

Encapsulation just means that data and code that operates on that 
data are bundled together in some sense. Encapsulation isn't even 
*necessarily* to do with objects. Ancient 1970s BASICs even had a 
primitive form of encapsulation, with the DATA statement. You could 
encapsulate data in the same file as the BASIC code that operated on 
that data.

One question is whether objects are *open* or *closed* to modifications. 
Python defaults to them being open unless there are good reasons for 
them to be closed. Other languages, like Java, default to them being 
closed: you can't easily add new attributes to Java objects after 
creation.


> It just seems to
> me that I should not be able to arbitrarily add new attributes from
> outside the class definition that created my object.  That seems
> similar to having a class Dog, to which from outside the class'
> definition, I decide to add a new Dog attribute that all dogs in this
> class can now have a tail sticking out of their noses.

That would be:

lassie = Dog()
lassie.nose.tail = make_tail()

Yes, that would be a silly thing to do. But how about this?

lassie = Dog()
lassie.collar = Collar()

I don't think that collar should be an attribute of all dogs. I know 
that Gaspode wouldn't be caught dead wearing a collar:

http://wiki.lspace.org/mediawiki/index.php/Gaspode

and I'm pretty sure that neither White Fang nor Buck would have collars:

https://en.wikipedia.org/wiki/White_Fang
https://en.wikipedia.org/wiki/The_Call_of_the_Wild

"collar" is not an attribute of the Dog class, but it might be an 
attribute of individual dogs.



-- 
Steve


More information about the Tutor mailing list