9

How do I make a python "constructor" "private", so that the objects of its class can only be created by calling static methods? I know there are no C++/Java like private methods in Python, but I'm looking for another way to prevent others from calling my constructor (or other method).

I have something like:

class Response(object):
    @staticmethod
    def from_xml(source):
        ret = Response()
        # parse xml into ret            
        return ret

    @staticmethod
    def from_json(source):
        # parse json
        pass

and would like the following behavior:

r = Response() # should fail
r = Response.from_json(source) # should be allowed

The reason for using static methods is that I always forget what arguments my constructors take - say JSON or an already parsed object. Even then, I sometimes forget about the static methods and call the constructor directly (not to mention other people using my code). Documenting this contract won't help with my forgetfulness. I'd rather enforce it with an assertion.

And contrary to some of the commenters, I don't think this is unpythonic - "explicit is better than implicit", and "there should be only one way to do it".

How can I get a gentle reminder when I'm doing it wrong? I'd prefer a solution where I don't have to change the static methods, just a decorator or a single line drop-in for the constructor would be great. A la:

class Response(object):
    def __init__(self):
        assert not called_from_outside()
Community
  • 1
  • 1
jdm
  • 9,470
  • 12
  • 58
  • 110
  • 2
    Possibly related: http://stackoverflow.com/q/8212053/3001761 – jonrsharpe Jul 30 '14 at 15:40
  • @jonrsharpe: Literally, yes, this is a duplicate. But that other question implied "I am a beginner, how do I do this thing I know from C++ in Python?", so all the answers were "don't do that" or "there are no private methods in Python". I like to think I know what I am doing, and I'm trying to create a clean API for my class that can't easily be misused. It's unlikely that I will ever get an answer for my problem in that other question. – jdm Jul 30 '14 at 15:45
  • 2
    Hence I'm not voting to close as duplicate. But the "all consenting adults" theme is strong in Python - it is difficult to do what you want *on purpose*, to discourage you from doing it! – jonrsharpe Jul 30 '14 at 15:48
  • Try this: http://stackoverflow.com/questions/8212053/private-constructor-in-python – moonbase3 Jul 30 '14 at 15:48
  • 1
    @stillthinkingaboutit that's exactly the question I linked to above – jonrsharpe Jul 30 '14 at 15:48
  • 1
    A *clean API* probably isn't as clean as you think if you have to do unpythonic stuff people won't expect or understand. It sounds like your real question is 'How do I make an API that works in this case' (with an explanation of the case). Good APIs should be as close to what the user expects as possible, and people don't expect this behavior in Python. (Not saying it's impossible this is the best route, just that it feels like your question is a level too low, and there may well be a better answer if you ask that one first). – Gareth Latty Jul 30 '14 at 15:51
  • One possible solution - raise error in `__init__`, then [create the object without calling `__init__`](http://stackoverflow.com/a/2169191/3001761). *Edit*: I see [Bruno got there first](http://stackoverflow.com/a/25041285/3001761) - that's probably the closest you'll get to what you want. – jonrsharpe Jul 30 '14 at 15:51
  • @Lattyware: Hmm, I thought this would be a *fairly* clean solution to the problem "How do I let users get a `Response` object from multiple sources, but discourage them from accidentially constructing one themselves?". Keep in mind that the alternatives would be: Subclasses (JSONResponse, XMLResponse), a ResponseFactory a la Java (*shudder*), or an additional flag parameter to the `__init__` which seems messy. Probably the most Pythonic alternative would be a global `def response_from_xml`, but that could get messy if I have a lot of such classes. – jdm Jul 30 '14 at 15:58
  • @jdm: just **why** would you prevent / discourage your users from constructing a `Response` by themselves ? What's the problem ? – bruno desthuilliers Jul 30 '14 at 16:03
  • Most classes similar to this I've seen, there was some combination of the class being hidden out of the way, the creation methods being taken off the class itself, and the constructor taking arguments in a way that would be generally inconvenient to provide directly. Some of them even do block direct creation; for example, you can't directly construct a `NoneType` instance. – user2357112 Jul 30 '14 at 16:09
  • @brunodesthuilliers: When I have classes representing parsed data, I often confuse what to call with the source data, and what to call with the parsed data. Is it `Data(source_string)` or `Data(parsed_object)` or even `Data(file_like_object)`? I'm trying to create an interface that makes the right usage obvious and the wrong usage hard. – jdm Jul 30 '14 at 16:14
  • Incidentally, I feel like everyone is being too hasty to say this is unpythonic. We use types with private constructors all the time and hardly notice. The `re` module's match objects can't be constructed directly. Most iterator types can't be constructed directly. We just hardly ever look at the types themselves, only dealing with the instances. Sure, these types are written in C, but from a user's perspective, it doesn't matter whether a type is written in C or Python, and the lack of a public constructor doesn't cause any major confusion. – user2357112 Jul 30 '14 at 16:26
  • Duplicate of [Private Constructor in Python](http://stackoverflow.com/questions/8212053/private-constructor-in-python) – Cimbali Jan 11 '17 at 15:42
  • @Cimbali: Please read the comments before marking as duplicate. Although the other question has almost the same title, it can be summarized as "I'm new to Python, where do I find private constructors?" - the answer is "there is no such thing in python". This question is: "I'm experienced in Python, I have a class where the contract is that you must instantiate it a specific way only. How do enforce that constraint in code?" – jdm Jan 11 '17 at 15:50
  • @jdm Hmm, that's not how this works. Both questions are exact duplicates, and as it stands they would gain to be merged. Next time, you could open a bounty on the other question to ask for more elaborate answers. – Cimbali Jan 11 '17 at 15:53
  • @Cimbali, No! :-) This question is definitely not an exact duplicate. I was aware of the other question when I wrote this 2 1/2 years ago, and thought "that's all very well, but I want to know something different". I could rewrite this question as "How can I make a constructor throw an exception, unless it is called from a static method?" or "How can I write a decorator that prevents others from calling my `__init__`?" - but that would be really obstruse. People would tell me I'm suffering from the AB problem and close this even faster! So I wrote it this way and gave some motivation. – jdm Jan 11 '17 at 16:09
  • Furthermore, the answer I was looking for would be wrong and inappropriate for the other question (I'll should use this as an opportunity to write it up!). Basically I wrote a decorator that uses `inspect` to see if the calling frame was in a method of this class, and if not, raises an exception. Great as a unobstrusive drop-in to enforce a certain coding rule, but a bad thing to tell to a newcomer who just wants to know about encapsulation and double underscores in Python! – jdm Jan 11 '17 at 16:14
  • @Cimbali And don't get me wrong, I see your point. I edited the question and hope it is now distinct enough - without invalidating the already existing answers. Now I've spent way more time on this than I should have :-). – jdm Jan 11 '17 at 17:15

6 Answers6

22

I think this is what you're looking for - but it's kind of unpythonic as far as I'm concerned.

class Foo(object):
    def __init__(self):
        raise NotImplementedError()

    def __new__(cls):
        bare_instance = object.__new__(cls)
        # you may want to have some common initialisation code here
        return bare_instance


    @classmethod
    def from_whatever(cls, arg):
        instance = cls.__new__(cls)
        instance.arg = arg
        return instance

Given your example (from_json and from_xml), I assume you're retrieving attribute values from either a json or xml source. In this case, the pythonic solution would be to have a normal initializer and call it from your alternate constructors, i.e.:

class Foo(object):
    def __init__(self, arg):
        self.arg = arg

    @classmethod
    def from_json(cls, source):
        arg = get_arg_value_from_json_source(source)
        return cls(arg)

    @classmethod
    def from_xml(cls, source):
        arg = get_arg_value_from_xml_source(source)
        return cls(arg)

Oh and yes, about the first example: it will prevent your class from being instantiated in the usual way (calling the class), but the client code will still be able to call on Foo.__new__(Foo), so it's really a waste of time. Also it will make unit testing harder if you cannot instantiate your class in the most ordinary way... and quite a few of us will hate you for this.

Jean-François Corbett
  • 37,420
  • 30
  • 139
  • 188
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • 1
    The second snippet is the way I would implement this - it doesn't do anything unexpected, and allows the user to create the instance directly if they already have `arg`. – jonrsharpe Jul 30 '14 at 15:59
  • First, thanks! About my motivation: I'm not trying to absolutely *prevent* the user from creating instances, that would be impossible, and that's not my style. But I try to make it obvious that if you have the raw JSON, then calling `Foo(source)` is wrong. As a user, I often confuse methods taking the raw source, and those taking parsed data. – jdm Jul 30 '14 at 16:04
  • @jdm Assuming it's easy enough to differentiate your raw source from your parsed data, I don't really see the difference between them getting a 'you are passing the wrong thing here' error to 'this constructor doesn't exist, use a class method' - if anything, I'd say the former is clearer. – Gareth Latty Jul 30 '14 at 16:05
  • 3
    Then just document it. And if your users have the same problem as you do (and I can feel your pain) then well, we will all be sorry but that's life. But really, the "normal initializer + alternate constructors" *is* the pythonic way, and as a Python programmer that's what *I* do expect. – bruno desthuilliers Jul 30 '14 at 16:06
15

I'd recommend turning the factory methods into module-level factory functions, then hiding the class itself from users of your module.

def one_constructor(source):
    return _Response(...)

def another_constructor(source):
    return _Response(...)

class _Response(object):
    ...

You can see this approach used in modules like re, where match objects are only constructed through functions like match and search, and the documentation doesn't actually name the match object type. (At least, the 3.4 documentation doesn't. The 2.7 documentation incorrectly refers to re.MatchObject, which doesn't exist.) The match object type also resists direct construction:

>>> type(re.match('',''))()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot create '_sre.SRE_Match' instances

but unfortunately, the way it does so relies upon the C API, so it's not available to ordinary Python code.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 1
    Of all the approaches I saw in my research, I thought this was the cleanest. The sub-answer, which is "Look at how the re module does it," means a lot. – John McGehee Sep 24 '15 at 17:13
4

Good discussion in the comments.

For the minimal use case you describe,

class Response(object):

    def __init__(self, construct_info = None):
        if construct_info is None: raise ValueError, "must create instance using from_xml or from_json"
        # etc

    @staticmethod
    def from_xml(source):
         info = {}  # parse info into here
         return Response(info)

    @staticmethod
    def from_json(source):
         info = {}  # parse info into here
         return Response(info)

It can be gotten around by a user who passes in a hand-constructed info, but at that point they'll have to read the code anyway and the static method will provide the path of least resistance. You can't stop them, but you can gently discourage them. It's Python, after all.

theodox
  • 12,028
  • 3
  • 23
  • 36
1

This might be achievable through metaclasses, but is heavily discouraged in Python. Python is not Java. There is no first-class notion of public vs private in Python; the idea is that users of the language are "consenting adults" and can use methods however they like. Generally, functions that are intended to be "private" (as in not part of the API) are denoted by a single leading underscore; however, this is mostly just convention and there's nothing stopping a user from using these functions.

In your case, the Pythonic thing to do would be to default the constructor to one of the available from_foo methods, or even to create a "smart constructor" that can find the appropriate parser for most cases. Or, add an optional keyword arg to the __init__ method that determines which parser to use.

Mark Whitfield
  • 2,470
  • 1
  • 12
  • 12
  • Well, I'm trying to create an API that is self-documenting, and to some extent foolproof to use. I'd rather not have a magic constructor that guesses what content-type I have, but have the user of my class explicitly decide what "constructor" to call. I like to think that this *is* pythonic (explicit is better than implicit), although static methods are not too widespread in python. – jdm Jul 30 '14 at 15:51
1

An alternative API (and one I've seen far more in Python APIs) if you want to keep it explicit for the user would be to use keyword arguments:

class Foo(object):
    def __init__(self, *, xml_source=None, json_source=None):
        if xml_source and json_source:
            raise ValueError("Only one source can be given.")
        elif xml_source:
            from_xml(xml_source)
        elif json_source:
            from_json(json_source)
        else:
            raise ValueError("One source must be given.")

Here using 3.x's * to signify keyword-only arguments, which helps enforce the explicit API. In 2.x this is recreatable with kwargs.

Naturally, this doesn't scale well to lots of arguments or options, but there are definitely cases where this style makes sense. (I'd argue bruno desthuilliers probably has it right for this case, from what we know, but I'll leave this here as an option for others).

Community
  • 1
  • 1
Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
0

The following is similar to what I ended up doing. It is a bit more general then what was asked in the question.

I made a function called guard_call, that checks if the current method is being called from a method of a certain class.

This has multiple uses. For example, I used the Command Pattern to implement undo and redo, and used this to ensure that my objects were only ever modified by command objects, and not random other code (which would make undo impossible).

In this concrete case, I place a guard in the constructor ensuring only Response methods can call it:

class Response(object):
    def __init__(self):
        guard_call([Response])
        pass

    @staticmethod
    def from_xml(source):
        ret = Response()
        # parse xml into ret
        return ret

For this specific case, you could probably make this a decorator and remove the argument, but I didn't do that here.

Here is the rest of the code. It's been a long time since I tested it, and can't guarentee that it works in all edge cases, so beware. It is also still Python 2. Another caveat is that it is slow, because it uses inspect. So don't use it in tight loops and when speed is an issue, but it might be useful when correctness is more important than speed.

Some day I might clean this up and release it as a library - I have a couple more of these functions, including one that asserts you are running on a particular thread. You may snear at the hackishness (it is hacky), but I did find this technique useful to smoke out some hard to find bugs, and to ensure my code still behaves during refactorings, for example.

from __future__ import print_function
import inspect

# http://stackoverflow.com/a/2220759/143091        
def get_class_from_frame(fr):
    args, _, _, value_dict = inspect.getargvalues(fr)
    # we check the first parameter for the frame function is
    # named 'self'
    if len(args) and args[0] == 'self':
        # in that case, 'self' will be referenced in value_dict
        instance = value_dict.get('self', None)
        if instance:
            # return its class
            return getattr(instance, '__class__', None)
    # return None otherwise
    return None

def guard_call(allowed_classes, level=1):
    stack_info = inspect.stack()[level + 1]
    frame = stack_info[0]
    method = stack_info[3]
    calling_class = get_class_from_frame(frame)
    # print ("calling class:", calling_class)
    if calling_class:
        for klass in allowed_classes:
            if issubclass(calling_class, klass):
                return

    allowed_str = ", ".join(klass.__name__ for klass in allowed_classes)
    filename = stack_info[1]
    line = stack_info[2]
    stack_info_2 = inspect.stack()[level]
    protected_method = stack_info_2[3]
    protected_frame = stack_info_2[0]
    protected_class = get_class_from_frame(protected_frame)

    if calling_class:
        origin = "%s:%s" % (calling_class.__name__, method)
    else:
        origin = method

    print ()
    print ("In %s, line %d:" % (filename, line))
    print ("Warning, call to %s:%s was not made from %s, but from %s!" %
           (protected_class.__name__, protected_method, allowed_str, origin))
    assert False

r = Response() # should fail
r = Response.from_json("...") # should be allowed
jdm
  • 9,470
  • 12
  • 58
  • 110