2

Codes below defined Duck class as composited from Bill class and Tail class. My questions is that as for the method about() inside Duck class definition, why can one write bill.description and tail.length? Is self omitted here? If yes, when can I omit self? Can I omit them inside __init__ method?

class Bill(): 
    def __init__(self, description):
        self.description = description 
class Tail(): 
    def __init__(self, length): 
        self.length = length 
class Duck(): 
    def __init__(self, bill, tail): 
        self.bill = bill
        self.tail = tail 
    def about(self): 
        print('This duck has a', bill.description, 'bill and a', tail.length, 'tail')
tail = Tail('long')
bill = Bill('wide orange') 
duck = Duck(bill, tail)
duck.about()

The output are as follows, enter image description here

Nicholas
  • 2,560
  • 2
  • 31
  • 58
  • 5
    It actually uses names `tail` and `bill` from global scope. Try changing creation to `tail1 = Tail('long')` - NameError will be raised. – Łukasz Rogalski Jun 22 '16 at 08:53
  • Why do you want to omit self? Its Python language specific construct used in objects to refer to themselves. If you dont use it, you are referring to global context. – Wax Cage Jun 22 '16 at 08:55
  • @WaxCage Because I know that if I wrote self.bill.description and self.tail.length in about() method, my code would work, too. But I was surprised to see that I could even omit self! – Nicholas Jun 22 '16 at 09:08
  • I hope you already understood, that self CANNOT be omitted! Calling variable with and without self have different meaning and refers to different values. If you didn't understand from already given answers, i can explain in detail. Do you know OOP well? – Wax Cage Jun 22 '16 at 09:15

4 Answers4

4

In Python explicit is better than implicit and self cannot ever be omitted when accessing member variables.

In your case you actually use names from global scope. To trigger an error simply assign the created objects to other names:

tail1 = Tail('long')
bill1 = Bill('another')
duck = Duck(tail1, bill1)
duck.about()
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
4

To make a long story short: you MUST use self. whenever you want to access an attribute (data attribute, property or method) of the current instance.

For a somewhat more detailed answer: when using def in a class statement, what you create is not a "method" but a plain function. When looked up a class or instance attribute (Duck.about or duck.about) this function will be wrapped (together with the class and instance on which it's looked up) in a callable method object that will take care of automagically inject the instance (or class) object as first parameter to the function call. You can read the whole details here : https://wiki.python.org/moin/FromFunctionToMethod

As other already mentionned, your code snippet only accidentally "works" because it ends up looking global names that happens to be defined, and breaks as soon as these names are not defined:

# tail = Tail('long')
# bill = Bill('wide orange') 
duck = Duck(Bill('wide orange'), Tail('long'))
duck.about()

=> crashes with a NameError

You'd also get unexpected results if rebinding these global names ie:

tail = Tail('long')
bill = Bill('wide orange') 
duck1 = Duck(bill, tail)

tail = Tail('short')
bill = Bill('norvegian blue') 
duck2 = Duck(bill, tail)

duck1.about()

=> Duh, why does it prints "short norvegian blue" ???

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Because global variable bill is now tagged at object Bill('norvegian blue'). One more question, is the rule of namespace inside a class definition the same as that inside a function definition? For instance, if I define a variable inside a class, then it is recognized as local? – Nicholas Jun 22 '16 at 09:58
  • the `class` statement defines a namespace but this namespace only lives until the `class` statement is executed (which is as soon as the code exists the `class` statement's block) so functions defined inside the `class` cannot directly access other names defined within the `class` statement block. But since names defined in this namespace become attributes of the class (unless some custom metaclass remove them from the attributes dict) these names can be accessed thru `self.__class__.` or (if no instance attribute shadows them) directly as `self.. This is all documented... – bruno desthuilliers Jun 22 '16 at 10:06
2

You can write that because you have defined a global variable bill and tail, which is being referenced here.

...
    def about(self): 
        print('This duck has a', bill.description, 'bill and a', tail.length, 'tail')
                         # these ^^^^                            ^^^^

# refer to these vvvv
tail = Tail('long')
bill = Bill('wide orange') 

So it's not doing what you think. If you want to refer to the bill and tail properties of your current object, you cannot omit self, it has to be self.bill and self.tail. Your code will break if you rename or remove those global variables.

deceze
  • 510,633
  • 85
  • 743
  • 889
1

You should avoid doing that. bill in about() will refer to bill in the outer scope, in this case the global one. So when you change the value of that global variable, the output of about() is also affected:

>>> tail = Tail('long')
>>> bill = Bill('wide orange')
>>> duck = Duck(bill, tail)
>>> duck.about()
This duck has a wide orange bill and a long tail
>>> bill = Bill('DIFFERENT')
>>> duck.about()
This duck has a DIFFERENT bill and a long tail

In order to reference the bill and tail objects that were passed to Duck() and are stored within the instance, you always need to use self.bill and self.tail.

poke
  • 369,085
  • 72
  • 557
  • 602