Reputation: 55
Sorry for this newbie question guys, I am sure the answer is simple but I still don't get it.
Despite some reading, Object Oriented Programming still has darkness I do not understand. It is the second time I see that it is necessary to create a intermediate instance of a class to call a method. Check the example below from the urllib
doc:
import urllib
opener = urllib.FancyURLopener({})
f = opener.open("http://www.python.org/")
f.read()
I don't get why it needs to create the opener
instance to call the open
method. I don't get why the following code doesn't work :
import urllib
f = urllib.FancyURLopener.open("http://www.python.org/")
f.read()
I get an error when trying to call urllib.FancyURLopener.open
:
TypeError: unbound method open() must be called with FancyURLopener instance as first argument (got str instance instead)
Could you guys bring lights in this shadows ? Thanks a lot !
Upvotes: 0
Views: 160
Reputation: 488263
You're not creating an "intermediate instance" of the class here, you're creating an instance (call it a "primary" instance if you like) of the class.
Here, FancyURLopener
is the name of the class itself. The class is a type:
>>> import urllib
>>> type(urllib.FancyURLopener)
<type 'classobj'>
(the fact that it's a <type 'classobj'>
instead of <type 'type'>
tells you that this is one of Python2's "old style" classes, but that's a subject for another question entirely :-) ). Because it's a type, to actually use it, you generally have to make an object of that type. Kind of similar to: to use some integers, you have to make integers, not just refer to the int
type.
A class-type has (i.e., lets you refer to) a bunch of "class methods", which are really just "functions that take a state-holder variable", which we normally spell self
. So you create an instance of the type:
>>> x = urllib.FancyURLopener()
and now you can invoke functions with x.open(...)
which really means: urllib.FancyURLopener.open(x, ...)
. This allows the code inside urllib.FancyURLopener
to stash all kinds of state inside x
. Want to make HTTP connections use persistence? Keep some state inside x
as to whether the connection is still open. Want to manipulate some web-server cookies? Keep some state inside x
to help track them down. Anything you (the writer of urllib) want, you can wedge it into x
. When your functions are called you get the caller's x
as your self
, and self.whatever
is the state stashed in x
.
(Python actually exposes all this supposedly-hidden state to the caller, so people use _spam
and __eggs
member names to help keep them out of the way, but again that's getting away from the simple "why".)
Upvotes: 1
Reputation: 386010
urllib has many different types of openers. Well, ok, it has a couple, not many, but that's beside the point. If you call urllib.open
the way you want, how is urllib supposed to know which opener you intend to use?
It knows this because your "intermediate step" created an instance of a particular type of opener. If you create an instance of FancyURLOpener
, python will know that that is the particular type of opener that you want. If you want to use URLOpener
instead, create an instance of that, and then you can use it instead.
Don't think of this as an intermediate step, think of it as the step -- the way to tell python "this is the specific type of url opener I want to use".
Now, of course, python could have been written in a non-OO way and had you tell the open method what type of opener you wanted to use. For example, a non-OO way might have you do this: f = urllib.open({}, type="Fancy")
. That could work, right? Many people write code in that style and it works, so why bother with objects?
Using an object oriented method means you can create your own opener with its own special properties. For example, the documentation for the fancy version says it "[provides] default handling for the following HTTP response codes: 301, 302, 303, 307 and 401". What if you also want default handling of 404? You might call this the CJOpener, but in order to use it you would have to modify urllib
itself to take "CJ" as well as "Fancy" as that last parameter. Do you really want to be modifying the built-in python code?
Classes and objects are the solution. The class defines the interface -- the standard way that an opener should work. You are free to create your own variations, and all you have to do is create a subclass, and then create an instance of that subclass. Once you do that, any python code anywhere in the world that knows how to work with FancyURLOpener
and URLOpener
instantly knows how to work with your opener, too.
So, the advantage of this intermediate step is a convenient way to tell python which variation of a common API you want to use. All programs that know how to use this API will also know how to use your version of the API too (assuming you don't break the standard API).
Upvotes: 0