Non-member vs member functions in Python

浪子不回头ぞ 提交于 2021-02-07 04:52:28

问题


I'm relatively new to Python and struggling to reconcile features of the language with habits I've picked up from my background in C++ and Java.

The latest issue I'm having has to do with encapsulation, specifically an idea best summed up by Item 23 of Meyer's "Effective C++":

Prefer non-member non-friend functions to member functions.

Ignoring the lack of a friend mechanism for a moment, are non-member functions considered preferable to member functions in Python, too?

An obligatory, asinine example:

class Vector(object):
    def __init__(self, dX, dY):
        self.dX = dX
        self.dY = dY

    def __str__(self):
        return "->(" + str(self.dX) + ", " + str(self.dY) + ")"

    def scale(self, scalar):
        self.dX *= scalar
        self.dY *= scalar

def scale(vector, scalar):
    vector.dX *= scalar
    vector.dY *= scalar

Given v = Vector(10, 20), we can now either call v.scale(2) or scale(v, 2) to double the magnitude of the vector.

Considering the fact that we're using properties in this case, which of the two options - if any - is better, and why?


回答1:


Interesting question.

You're starting from a different place than most questions coming from Java programmers, which tend to assume that you need classes when you mostly don't. Generally, in Python there's no point in having classes unless you're specifically doing data encapsulation.

Of course, here in your example you are actually doing that, so the use of classes is justified. Personally, I'd say that since you do have a class, then the member function is the best way to go: you're specifically doing an operation on that particular vector instance, so it makes sense for the function to be a method on Vector.

Where you might want to make it a standalone function (we don't really use the word "member" or "non-member") is if you need to make it work with multiple classes which don't necessarily inherit from each other or a common base. Thanks to duck-typing, it's fairly common practice to do this: specify that your function expects an object with a particular set of attributes or methods, and do something with those.




回答2:


A free function gives you the flexibility to use duck-typing for that first parameter as well.

A member function gives you the expressiveness of associating the functionality with the class.

Choose accordingly. Generally, functions are created equal, so they should all have the same assumptions about the interface of a class. Once you publish a free function scale, you are effectively advertising that .dX and .dY are part of the public interface of Vector. That is probably not what you want. You are doing this in exchange for the ability to reuse the same function with other objects that have a .dX and .dY. That is probably not going to be valuable to you. So in this case I would certainly prefer the member function.

For good examples of preferring a free function, we need look no further than the standard library: sorted is a free function, and not a member function of list, because conceptually you ought to be able to create the list that results from sorting any iterable sequence.




回答3:


Prefer non-member non-friend functions to member functions

This is a design philosophy and can and should be extended to all OOP Paradigm programming languages. If you understand the essence of this, the concept is clear

If you can do without requiring private/protected access to the members of a Class, your design do not have a reason to include the function, a member of the Class. To think this the Other way, when designing a Class, after you have enumerated all properties, you need to determine the minimal set of behaviors that would be sufficient enough to make the Class. Any member function that you can write using any of the available public methods/member functions should be made public.

How much is this applicable in Python

To some extent if you are careful. Python supports a weaker encapsulation compared to the other OOP Languages (like Java/C++) notably because there is no private members. (There is something called Private variables which a programmer can easily write by prefixing an '_' before the variable name. This becomes class private through a name mangling feature.). So if we literally adopt Scott Meyer's word totally considering there is a thin like between what should be accessed from Class and what should be from outside. It should be best left to the designer/programmer to decide whether a function should be an integral part of the Class or Not. One design principle we can easily adopt, "Unless your function required to access any of the properties of the class you can make it a non-member function".




回答4:


Look at your own example - the non-member function has to access the data members of the Vector class. This is not a win for encapsulation. This is especially so as it changes data members of the object passed in. In this case, it might be better to return a scaled vector, and leave the original unchanged.

Additionally, you will not realise any benefits of class polymorphism using the non-member function. For example, in this case, it can still only cope with vectors of two components. It would be better if it made use of a vector multiplication capability, or used a method to iterate over the components.

In summary:

  1. use member functions to operate on objects of classes you control;
  2. use non-member functions to perform purely generic operations which are implemented in terms of methods and operators which are themselves polymorphic.
  3. It is probably better to keep object mutation in methods.



回答5:


As scale relies on member-wise multiplication of a vector, I would consider implementing multiplication as a method and defining scale to be more general:

class Vector(object):
    def __init__(self, dX, dY):
        self._dX = dX
        self._dY = dY

    def __str__(self):
        return "->(" + str(self._dX) + ", " + str(self._dY) + ")"

    def __imul__(self, other):
        if other is Vector:
            self._dX *= other._dX
            self._dY *= other._dY
        else:
            self._dX *= other
            self._dY *= other

        return self

def scale(vector, scalar):
    vector *= scalar

Thus, the class interface is rich and streamlined while encapsulation is maintained.



来源:https://stackoverflow.com/questions/10072204/non-member-vs-member-functions-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!