[Tutor] method, type?

Cameron Simpson cs at zip.com.au
Thu Jan 7 19:54:13 EST 2016


On 08Jan2016 10:19, Steven D'Aprano <steve at pearwood.info> wrote:
>[...]
>> "factory" methods (typically called '.from_*') can be:

Maybe I should have said "often" instead of "typically", if I said "typically".  
I think they read well that way and there are several stdlib functions named 
this way as a precedent. I'm aiming for the notion "make a Foo from this or 
that or something else (3 distinct methods/functions, all taking arguments a 
bit different from the core __init__)".

>>     1. a normal function outside the class, or
>>     2. a class method (would allow subclassing.)
>
>"Factory methods" just means a method which you, the creator or author,
>thinks of as a factory. What's a factory? A function or method which
>takes a bunch of arguments and creates something useful.
>
>It's a pretty vague definition, because it's a pretty vague term.

It's mostly vague if you take the "everything is an object in Python" stance.  
Which is strictly true, but it is often useful to be thinking of a factory 
function as an alternative to the bare ClassName(args-for-__init__) 
constructor, where those arguments are not convenient. Such as Alex's 
JournalLineItem construction from a line of text.

[...]
>>     "alternative constructor" (what Petter Otten and Steven DAprano
>>     call it,)
>
>Alternative in the sense of "not the default", that is all.
>
>>     would be best placed immediately after __init__.
>
>*shrug*
>It doesn't matter where you put it inside the class. That is entirely a
>matter of personal taste.

I suggested this as a style thing (of course, inherently a matter of personal 
taste:-) I like functions with the same purpose to be textually close together.

[...]
>>     [Alan Gauld] recommends making it a factory
>>     function (defined at the module level, outside the class.)
>
>That's a matter of personal taste, and one which I happen to disagree
>with. Look at the design of the built-in classes like dict. We have
>dict.fromkeys(), not a global fromkeys() function.

I'm largely with Steven here rather than Alan, partly because a classmethod 
subclasses nicely (with all the caveats Alan alluded to - if you do this then 
your subclasses _may_ need to override the extra constructor just as they may 
need to override other methods), and partly because it keeps the constructor 
inside the class definition, which I find conceptually tidier.

>>     Steven DAprano calls it a Python3 regular function/ a Python2
>>     broken method and mentions the Descriptor protocol and how
>>     'methods' are initially simply functions that are then converted
>>     to methods (bound) as required. In my case it would be an
>>     'unbound' method which works in 3 but not in Python2.
>>
>> Cameron Simpson indicated that putting @staticmethod above my 'method'
>> would be OK (although not preferred.)  Present or absent, my method
>> still functions the same way.
>
>Only because you're just calling it from the class. As soon as you
>create an instance and call the method from that, you'll see why it
>is broken :-)

Aye. While we're on what staticmethod and classmethod accomplish, we could stop 
treating them like magic. Have you (alex) written any decorators? They are 
functions which accept a function definition and return a wrapper function with 
tweaked behaviour. So the notation:

  @foo
  def func1(blah):

defines "func1" and then calls "foo(func1)". "foo" returns a new function 
definition, and the class binds _that_ definition to its "func1" method.

So...

>> The table provided by Peter Otten (very helpful:)
>> -----------------------------------------------------------------
>> invoked with | @staticmethod  | @classmethod    | no decorator
>> ------------------------------------------------------------------
>> class        | args unchanged | class as 1st arg | args unchanged
>> instance     | args unchanged | class as 1st arg | inst as 1st arg
>> -------------------------------------------------------------------
>> It suggests that use of the @staticmethod serves to protect one should
>> the 'method' be called via an instance rather than the class.  Has it
>> any other effect?
>
>Yes, to confuse people into thinking they should be using staticmethod
>when what they really should use is classmethod :-)

Now consider what @staticmethod achieves: it causes a normal method to be 
called as though it were a global function i.e. without the normally implied 
"self" parameter. So we could write our own:

  def staticmethod(func):
    def method(self, *a, **kw):
      return func(*a, **kw)
  return method

As described above, this effectively installs the "method" function as the 
class's actual method, and that function's whole purpose is simply to _discard_ 
the self parameter and call the original function without "self".

Once that makes sense, you can them imagine writing @classmethod similarly:

  def classmethod(func):
    def method(self, *a, **kw):
      return func(type(self), *a, **kw)
  return method

This version discards "self" but passes in its type (== its class).

Now, both of these examples above are actually simplifications of what Python's 
inbuilt @staticmethod and @classmethod decorators do but they show that this 
isn't magic: it has simple and concrete actions with well defined effects.

Cheers,
Cameron Simpson <cs at zip.com.au>


More information about the Tutor mailing list