Is this sound software engineering practice for class construction?

混江龙づ霸主 提交于 2020-04-30 08:00:24

问题


Is this a plausible and sound way to write a class where there is a syntactic sugar @staticmethod that is used for the outside to interact with? Thanks.

###scrip1.py###
import SampleClass.method1 as method1

output = method1(input_var)

###script2.py###
class SampleClass(object):

    def __init__(self):
        self.var1 = 'var1'
        self.var2 = 'var2'

    @staticmethod
    def method1(input_var):
        # Syntactic Sugar method that outside uses
        sample_class = SampleClass()
        result = sample_class._method2(input_var)
        return result

    def _method2(self, input_var):
        # Main method executes the various steps.
        self.var4 = self._method3(input_var)
        return self._method4(self.var4)

    def _method3(self):
        pass

    def _method4(self):
        pass

回答1:


Answering to both your question and your comment, yes it is possible to write such a code but I see no point in doing it:

class A:
    def __new__(cls, value):
        return cls.meth1(value)

    def meth1(value):
        return value + 1 

result = A(100)
print(result)

# output:
101

You can't store a reference to a class A instance because you get your method result instead of an A instance. And because of this, an existing __init__will not be called.

So if the instance just calculates something and gets discarded right away, what you want is to write a simple function, not a class. You are not storing state anywhere. And if you look at it:

result = some_func(value) 

looks exactly to what people expect when reading it, a function call.

So no, it is not a good practice unless you come up with a good use case for it (I can't remember one right now)

Also relevant for this question is the documentation here to understand __new__ and __init__ behaviour.

Regarding your other comment below my answer:

defining __init__ in a class to set the initial state (attribute values) of the (already) created instance happens all the time. But __new__ has the different goal of customizing the object creation. The instance object does not exist yet when __new__is run (it is a constructor function). __new__ is rarely needed in Python unless you need things like a singleton, say a class A that always returns the very same object instance (of A) when called with A(). Normal user-defined classes usually return a new object on instantiation. You can check this with the id() builtin function. Another use case is when you create your own version (by subclassing) of an immutable type. Because it's immutable the value was already set and there is no way of changing the value inside __init__ or later. Hence the need to act before that, adding code inside __new__. Using __new__ without returning an object of the same class type (this is the uncommon case) has the addtional problem of not running __init__.

If you are just grouping lots of methods inside a class but there is still no state to store/manage in each instance (you notice this also by the absence of self use in the methods body), consider not using a class at all and organize these methods now turned into selfless functions in a module or package for import. Because it looks you are grouping just to organize related code.

If you stick to classes because there is state involved, consider breaking the class into smaller classes with no more than five to 7 methods. Think also of giving them some more structure by grouping some of the small classes in various modules/submodules and using subclasses, because a long plain list of small classes (or functions anyway) can be mentally difficult to follow.

This has nothing to do with __new__ usage.

In summary, use the syntax of a call for a function call that returns a result (or None) or for an object instantiation by calling the class name. In this case the usual is to return an object of the intended type (the class called). Returning the result of a method usually involves returning a different type and that can look unexpected to the class user. There is a close use case to this where some coders return self from their methods to allow for train-like syntax:

 my_font = SomeFont().italic().bold()

Finally if you don't like result = A().method(value), consider an alias:

func = A().method
...
result = func(value)

Note how you are left with no reference to the A() instance in your code. If you need the reference split further the assignment:

a = A()
func = a.method
...
result = func(value)

If the reference to A() is not needed then you probably don't need the instance too, and the class is just grouping the methods. You can just write

func = A.method
result = func(value)

where selfless methods should be decorated with @staticmethod because there is no instance involved. Note also how static methods could be turned into simple functions outside classes.

Edit:

I have setup an example similar to what you are trying to acomplish. It is also difficult to judge if having methods injecting results into the next method is the best choice for a multistep procedure. Because they share some state, they are coupled to each other and so can also inject errors to each other more easily. I assume you want to share some data between them that way (and that's why you are setting them up in a class):

So this an example class where a public method builds the result by calling a chain of internal methods. All methods depend on object state, self.offset in this case, despite getting an input value for calculations.

Because of this it makes sense that every method uses self to access the state. It also makes sense that you are able to instantiate different objects holding different configurations, so I see no use here for @staticmethod or @classmethod.

Initial instance configuration is done in __init__ as usual.

# file: multistepinc.py

    def __init__(self, offset):
        self.offset = offset

    def result(self, value):
        return self._step1(value)

    def _step1(self, x):
        x = self._step2(x)
        return self.offset + 1 + x

    def _step2(self, x):
        x = self._step3(x)
        return self.offset + 2 + x

    def _step3(self, x):
        return self.offset + 3 + x

def get_multi_step_inc(offset):
    return MultiStepInc(offset).result

--------

# file: multistepinc_example.py

from multistepinc import get_multi_step_inc

# get the result method of a configured
# MultiStepInc instance
# with offset = 10.

# Much like an object factory, but you
# mentioned to prefer to have the result
# method of the instance
# instead of the instance itself.
inc10 = get_multi_step_inc(10)

# invoke the inc10 method
result = inc10(1)
print(result)

# creating another instance with offset=2
inc2 = get_multi_step_inc(2)

result = inc2(1)
print(result)

# if you need to manipulate the object
# instance
# you have to (on file top)

from multistepinc import MultiStepInc

# and then
inc_obj = MultiStepInc(5)

# ...
# ... do something with your obj, then
result = inc_obj.result(1)
print(result)

Outputs:

37
13
22


来源:https://stackoverflow.com/questions/56998151/is-this-sound-software-engineering-practice-for-class-construction

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