class methods vs. functions

B

beliavsky

What are the pros and cons of defining a method of a class versus defining
a function that takes an instance of the class as an argument? In the example
below, is it better to be able to write
z.modulus() or modulus(z)? Is there a difference between Python and C++ in
this regard?

from math import sqrt
class xy:
def __init__(self,x,y):
self.x = x
self.y = y
def modulus(self):
return sqrt(self.x**2 + self.y**2)

def modulus(z):
return sqrt(z.x**2 + z.y**2)

z = xy(3,4)
print z.modulus()
print modulus(z)
 
C

Christopher T King

What are the pros and cons of defining a method of a class versus defining
a function that takes an instance of the class as an argument? In the example
below, is it better to be able to write
z.modulus() or modulus(z)? Is there a difference between Python and C++ in
this regard?

The answer to this, as for most questions in computer science, is "it
depends". :) The answer (which is partially my opinion) has to do with
interfaces, and what you want to accomplish:

- Case 1 -

All of your objects conform to the 'xy' interface. This interface
requeries that an object has both .x and .y member variables. You can then
define a 'modulus' function that is defined to operate on any object
implementing the 'xy' interface:
def modulus(z):
return sqrt(z.x**2 + z.y**2)

Upside:

No matter what the object is, or from where it comes, so long as it
implements the 'xy' interface, you can now take the modulus of it.

Downside:

You can't take the modulus of anything that doesn't implement 'xy'.

- Case 2 -

Some of your objects implement the 'xy' interface, others don't (e.g. they
don't make sense with it). Either way, you want to be able to find their
modulus. You can do this by making all your objects conform to the
'modulus' interface. This interface requires that an object have a
..modulus() member function:
from math import sqrt
class xy:
def __init__(self,x,y):
self.x = x
self.y = y
def modulus(self):
return sqrt(self.x**2 + self.y**2)

Upside:

You can take the modulus of any object you create, regardless of whether
it can implement the 'xy' interface or not.

Downside:

If you aren't the designer of the object, you can't take the modulus of
it, even if it does implement the 'xy' interface.

So, the choice is up to you. You can always provide both, though:

def mymodulus(z):
try:
return z.modulus()
except AttributeError:
return modulus(z)

In case you can't tell, I'm as undecided on the issue as you ;)
 
E

Egbert Bouwman

From a technical point of view Christopher's explanation seems to be OK,
but my limited studies of object oriented programming taught me that
from outside an object you should not use its data-attributes directly.
Rather you should send the object messages, ie use methods of this object.
So z.modules() seems to be the better idea, and modules(z) the worse one.

I suppose that this means that you should treat each name foo of a
data-attribute as if it was called __foo.
egbert
 
J

Jacek Generowicz

What are the pros and cons of defining a method of a class versus defining
a function that takes an instance of the class as an argument? In the example
below, is it better to be able to write
z.modulus() or modulus(z)?

You're essentially comparing the message-passing paradigm to the
generic function paradigm.

One advantage of message-passing, is that it separates the messages
into namespaces, so you don't have to be as careful about chosing your
message names.

One disadvantage of message-passing, is that it separates the messages
into namespaces, so you are less careful about chosing your message
names, and end up confusing your clients. :)

One advantage of generic functions is that multiple dispatch is much
more natural (dare I say, easier?).

One disadvantage of generic functions is that, if you are not careful,
your code ends up distributed all over the shop.
 
J

Jacek Generowicz

Egbert Bouwman said:
but my limited studies of object oriented programming taught me that
from outside an object you should not use its data-attributes directly.
Rather you should send the object messages,

Message-passing, while being the most common style of OO, is not the
only style of OO.

You should use the object's interface. This may or may not be
implemented via messages.
ie use methods of this object. So z.modules() seems to be the
better idea, and modules(z) the worse one.

How do you know (without looking at the implementation of
"modules") that "modules(z)" is not "using a method" ?

This is a rhetorical question, which probably needs a bit of
comment. By "method" I don't necessarily mean "Python class or
instance method". You might like to think about "add(a,b)".
I suppose that this means that you should treat each name foo of a
data-attribute as if it was called __foo.

Why treat it as private, if it isn't marked as private? If it isn't
marked as private, then it's part of the interface. If it's part of
the interface, then it is intended for use by clients. If it's
intended for use by clients, then use it.

my limited studies of object oriented programming taught me

Be careful not to let Java or C++ (or anyone strongly influenced by
them) to shape your understanding of object oriented programming. You
would be doing yourself a disservice.
 
E

Egbert Bouwman

Why treat it as private, if it isn't marked as private? If it isn't
marked as private, then it's part of the interface. If it's part of
the interface, then it is intended for use by clients. If it's
intended for use by clients, then use it.

My impression is that you can hardly call this OOO:
Object Oriented Orthodoxy. For instance, from the GOF I learned:
- the object's internal state can not be accessed directly
- operations are the only way to change an object's internal state
- requests (or messages) are the only way to get an object
to execute an operation
- a signature consists of an operation's name, parameters and return value
- the interface to an object is the set of all signatures of its operations

It is not easy to find definitions of 'interface' in python books,
but I cannot find descriptions that contradict the GOF.

And now you suggest that everything that is accessible is part
of the interface. I think I prefer the orthodoxy.

I suppose that is what you mean when you say:
Be careful not to let Java or C++ (or anyone strongly influenced by
them) to shape your understanding of object oriented programming. You
would be doing yourself a disservice.
but I need more explanations, arguments, references.

You seem to encourage the following practice:because the internal state of c is not private,
so it is part of the interface, so it is intended for use,
so I should use it.

I finally got rid of my goto's, and now you do this to me.
egbert
 
S

Steven Bethard

Egbert Bouwman said:
From a technical point of view Christopher's explanation seems to be OK,
but my limited studies of object oriented programming taught me that
from outside an object you should not use its data-attributes directly.
Rather you should send the object messages, ie use methods of this object.
So z.modules() seems to be the better idea, and modules(z) the worse one.

This argument usually stems from the idea that only calls to methods
are "sent messages". This is true in a language like C++ or Java
where a class like:

class XY {
double x, y;
double modulus();
}

allocates space for two doubles (x and y) and then puts the modulus
method in its method lookup table. The concern is that, if the owner
of class XY determines that there is a better way of representing the
data portions of the class, e.g.:

class XY {
double radians, length;
double modulus();
}

then anyone who used the XY class's data-attributes directly will then
be in trouble (since they have been removed). This is why in a
language like C++ or Java you'll usually see these fields declared as
private, and thus only the methods are considered to be part of the
class's interface.

In Python, we can avoid some of this issue because of the getattr and
setattr type functionality. Imagine I have the class:

class xy:
def __init__(self,x,y):
self.x = x
self.y = y
def modulus(self):
return sqrt(self.x**2 + self.y**2)

And then imagine I've decided I would rather represent the class with
the radians, length formulation:

class xy:
def __init__(self, radians, length):
self.radians = radians
self.length = length
def modulus(self):
return ... # calculation in radians

Now I have to make Christopher's decision. If I believe that users of
my class never access the x and y variables, only the modulus method,
(i.e. if I envision this class with Christopher's "Case 2" interface)
then I'm done. On the other hand, if I believe that x and y are part
of the interface I've presented to users of the class, (i.e.
Christopher's "Case 1" interface), I need to continue to provide
access to these "attributes". In Python, we can continue to make such
attributes available, even if we don't actually have an 'x' or 'y'
field in the class anymore, using the getattr/setattr functionality:

def __getattr__(self, name):
if name == 'x':
return ... # calculation of x from radians and length
if name == 'y':
return ... # calculation of y from radians and length
def __setattr__(self, name):
... # set radians or length based on x or y

So if you decide that your data attributes need to change, you haven't
destroyed compatibility with users of your class, even if your data
attributes were publicly available. Continuing to support the older
attributes isn't necessarily trivial, but at least it's /possible/
(unlike C++ or Java).

Steve
 
C

Christopher T King

My impression is that you can hardly call this OOO:
Object Oriented Orthodoxy. For instance, from the GOF I learned:
- the object's internal state can not be accessed directly
- operations are the only way to change an object's internal state
- requests (or messages) are the only way to get an object
to execute an operation
- a signature consists of an operation's name, parameters and return value
- the interface to an object is the set of all signatures of its operations

Replace .x with .getx() and .y with .gety() if it suits you, but often in
the case where an object is mostly used as a container (similar to a C
struct), direct access to its attributes is considered the norm:

a = 3+5j
a.real # --> 3.0
a.imag # --> 5.0

If you so choose, you can make .x and .y read-only with a little
__setattr__ magic (there's probably an easier way to do this in new-style
classes), as they are in the complex type:

a.real = 4 # --> AttributeError

Sure, it may not fit with "proper OO methodology", but it's a good way of
making visible the fact that "this object is a container, I'm retrieving
one of the variables it contains". If the interface is well-defined, it
doesn't matter how you access it, so long as you follow the interface's
design.
 
E

Egbert Bouwman

So if you decide that your data attributes need to change, you haven't
destroyed compatibility with users of your class, even if your data
attributes were publicly available. Continuing to support the older
attributes isn't necessarily trivial, but at least it's /possible/
(unlike C++ or Java).

Yes, I see that in real life access to the internal state of an
object is sometimes necessary. And Python allows it.

But what should i think of systems in which the users actually
have to use that licence ?

egbert
 
E

Egbert Bouwman

but often in
the case where an object is mostly used as a container (similar to a C
struct), direct access to its attributes is considered the norm:


Sure, it may not fit with "proper OO methodology", but it's a good way of
making visible the fact that "this object is a container, I'm retrieving
one of the variables it contains". If the interface is well-defined, it
doesn't matter how you access it, so long as you follow the interface's
design.
I am beginning to see the light. Thank you all.
egbert
 
J

Jacek Generowicz

My impression is that you can hardly call this OOO:
Object Oriented Orthodoxy. For instance, from the GOF I learned:

Aaah, the dear GOF book. You do realize that the GOF book is, to a
lange extent, a book about the shortcomings of C++, don't you?

For example, think about how you would implement the Decorator and the
State patterns in Python.

I would advise against basing your understanding of what object
oriented programming is about, on the GOF book.
- the object's internal state can not be accessed directly

This is blatantly false, as your code snippet below clearly
demonstrates. (You probably meant "should not" rather that "can not".
The "should not" is a dogma. Understand its motivation, and decide for
yourself to what extent you want to buy it.)
- operations are the only way to change an object's internal state

Ditto. (... "are the only way" -> "should be the only way" ...)
- requests (or messages) are the only way to get an object
to execute an operation

Read up about properties in Python.

Strictly speaking, it's not an object which executes an operation;
rather methods carry out operations on objects. And it's certainly not
true that messages are the only way to get a method to carry out an
operation on objects ... put differently: generic functions exist.
It is not easy to find definitions of 'interface' in python books,

How about "Anything that does not start with an underscore" ?

(One can argue about the details of this proto-definition, but in terms
of value per word, it's pretty good, I'd say.)
And now you suggest that everything that is accessible is part
of the interface.

No. I suggest that everything that is part of the interface is part of
the interface. Put in other terms, everything that is not marked as
private, is part of the interface (or in some languages, everything
that is marked as public, is part of the interface --- though "private"
and "public" might not be the words which are used to describe a
similar concept in those languages). Some languages (notably C++ and
Java) consider that marking things as private is not good enough (I
guess that the assumption is that people who program in those languages
are too stupid to understand the importance of interfaces and to
respect them), and such languages attempt to make the private parts
inaccessible.

Encapsulation and access restriction are orthogonal concepts, which
have been conflated in Java and C++. That doesn't make them any less
orthogonal, as concepts ... so don't make the (all too common mistake)
of believing that they are actually one and the same.
I think I prefer the orthodoxy.

Into bondage, are you ? :)
I suppose that is what you mean when you say:
but I need more explanations, arguments, references.

Well, in terms of explanations and arguments, I was steering you
towards a discussion of the generic-function OO paradigm ... but you
seem not to have followed that up.

In terms of references ... try looking into some languages which
support OO, which predate Java and C++ (or which are otherwise not
influenced by them, or, at least, look very different from them.) You
could try Smalltalk, or Common Lisp, for example.
You seem to encourage the following practice:
Absolutely.

the internal state of c is not private,

This is not generally true, but it is true in this case.

For example, I would discourage the following:

class C:
def __init__(self):
self._x = 3

c = C()
c._x = 9

Note that I would not forbid it. In fact, I would encourage it, in some
situations. For example

a) You are debugging your application, or investigating its behaviour

b) The author of C hadn't anticipated some perfectly valid use you wish
to make of C, and didn't provide you with an adequate interface
 
G

Greg Ewing

Egbert said:
My impression is that you can hardly call this OOO:
Object Oriented Orthodoxy. For instance, from the GOF I learned:
- the object's internal state can not be accessed directly
- operations are the only way to change an object's internal state

What you're missing is that, in Python, attribute access
*is* an operation, on the same footing as calling a method.
The object can be programmed to respond to it in any
desired way. So, exposing an attribute as part of the
public interface in no way limits the current or future
implementation.

This is in contrast to languages such as C++ and Java,
where attribute access cannot be overridden, so you
are forced to use accessor methods for everything in
order to keep interface and implementation separate.
The rules as you have learned them were designed for
those languages.
 
J

Jacek Generowicz

Egbert Bouwman said:
Yes, I see that in real life access to the internal state of an
object is sometimes necessary.

Let's just think about this statement. In real life, access to the
internal state of an object is _always_ necessary. What would be the
point of the internal state, if there were no means of accessing it ?

(Yes, I'm being pedantic, but we are talking about programming
computers, and computers are pedantic in the extreme ... so you'd
better be used to expressing yourself accurately :)
And Python allows it.

So does C++:

class A {
public:
int i;
};

A a;
a.i;

OK, this goes against C++ dogma, so you might prefer this:

class A {
private:
int i;
public:
int getx() { return this->i; }
void seti(int i) { this->i = i; }
};

A a;
a.i;

You're still accessing the internal state. And it doesn't have to be
that trivial:

class complex {
private:
double r;
double i;
public:
double get_modulus() {
return sqrt(i*i + r*r);
}
...
};


C c;
c.get_modulus();

You're still accessing internal state. The whole point of internal
state is that it be accessed somehow; if you couldn't access it, it
would be a waste of memory. Now, you might wish to debate _how_ it
should or should not be acessed ...
But what should i think of systems in which the users actually
have to use that licence ?

Maybe you want to refine your question.

As I said elsewhere ... you should probably think about the point of
interfaces (others have addressed this point upthread); you should
understand the concept of data abstraction. If you understand that,
you should be able to judge for yourself what is a good idea and what
is not. Dogmas are for people who can't think for themseleves.

I guess I could summarize my message thus:

Don't just repeat words; understand the concepts behind the words.
 
A

A B Carter

from math import sqrt
class xy:
def __init__(self,x,y):
self.x = x
self.y = y
def modulus(self):
return sqrt(self.x**2 + self.y**2)

def modulus(z):
return sqrt(z.x**2 + z.y**2)

z = xy(3,4)
print z.modulus()
print modulus(z)

In the example given the use of a function is IMHO a straightforward
mistake. The rivers will not run with blood if you do this, but it is
nonetheless a clear violation of basic OO design.

By using a function you're implying that it will work, if not with all
objects then with some wide range of semi-generic or base objects. But
most objects will not have x and y attributes. In other cases an
object may have such attributes but store string values or possess
attributes that could be sensibly used as arguments to a modulus
function but have the wrong names. A fundamental insight of the OO
approach is the benefit of associating a data structure with a set of
methods that work with that data structure. By incorporating modulus
as a method in the xy class you avoid the problems mentioned above.

It makes sense to have functions that work with all objects, such as
the id() function or they work with basic Python types such as numeric
values. A modulus function would be fine if it took two numeric values
and returned the square root of the sum of their squares, but it would
then be just the math.hypot function.

Python does place a premium on rapid prototyping and in consequence
possesses an OO implementation that doesn't get in the way of coding.
The joy of Python is that you can usually concentrate on the problem
and not be distracted by the need to satisfy additional requirements
of OO purity. However Python is an OO language; all of the standard
concepts still apply and all of the usual guidelines should still be
followed. You still write to the interface, you still encapsulate your
data and you still associate methods with data structures. This isn't
a matter of OO orthodoxy but of practical design that led to the
development of OO.

A B Carter
 
B

beliavsky

Egbert Bouwman said:
My impression is that you can hardly call this OOO:
Object Oriented Orthodoxy. For instance, from the GOF I learned:

Excuse me, but what is "GOF"?

I thought of Good Old Fortran, but them I am an odd duck :).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top