0

I receive a namespace object from command line arguments. And I don't want to modify it. Can I do that or do you have some ideas?

# -*- coding: utf-8 -*-

import argparse

def parse_args():
    parser = argparse.ArgumentParser(description='This script is ...')
    parser.add_argument('--confdir', type=str, required=True)
    parser.add_argument('--outdir', type=str, required=True)
    return parser.parse_args()

if __name__ == '__main__':
    mutable_namespace = parse_args()

    # I want to prevent overwrite like that.
    mutable_namespace.confdir = "xxx"
4
  • 1
    Lock a variable in Python? Commented Dec 18, 2015 at 4:40
  • 3
    Just... don't modify it? There's nothing like const in Python. The usual philosophy is that if you don't want something done, you just don't do it. It's why we don't have private, either. Commented Dec 18, 2015 at 4:44
  • Thank you for the adivice. I see, my idea is not appropriate in Python. I understood very well. I'll change the way. Thank you very much. Commented Dec 19, 2015 at 10:03
  • related: How to make an immutable object in Python? Commented Dec 19, 2015 at 19:48

2 Answers 2

5

I initially proposed the custom Namespace class, but I like this idea of copying args to a NamedTuple better.

Namedtuple

Another option is to copy the values from args to an immutable object/class. A named tuple might do the job nicely.

Create a namespace

In [1157]: dest=['x','y']
In [1158]: args=argparse.Namespace()
In [1159]: for name in dest:
   ......:     setattr(args, name, 23)
   ......:     
In [1160]: args
Out[1160]: Namespace(x=23, y=23)

now define a namedtuple

In [1161]: from collections import namedtuple
In [1163]: Foo = namedtuple('Foo',dest)

You could also get the tuple names from the Namespace itself (after parsing)

Foo = namedtuple('Foo',vars(args).keys())

create such a tuple with values from args:

In [1165]: foo=Foo(**vars(args))
In [1166]: foo
Out[1166]: Foo(x=23, y=23)
In [1167]: foo.x
Out[1167]: 23

and it is immutable:

In [1168]: foo.x=34
... 
AttributeError: can't set attribute

Such a namedtuple cannot be used as a Namespace, since setattr(foo,'x',34) produces the same error.

A clean way to do all of this is to wrap it all in a function:

def do_parse():
   parser = ....
   Foo = namedtuple(...)
   args = parser.parse_args()
   foo = Foo(**vars(args))
   return foo

The calling code never sees the mutable args, just the immutable foo.

Custom Namespace class

To build on Ingaz answer, argparse can use your own Namespace class.

https://docs.python.org/3/library/argparse.html#the-namespace-object

class MyNamespace(argparse.Namespace):
    pass
    <customize one or more methods>

anamespace = MyNamespace()
args = parser.parse_args(namespace=anamespace)

Now args and anamespace reference the same MyNamespace object. As long as getattr(anamespace, adest) and setattr(anamespace, adest, avalue) work, argparse can use this namespace object.

Now, can you allow setattr(anamespace, 'string', 'value'), but disallow anamespace.string = value? I think you can, but it will require a good understanding of how the latter expression works. It may just require customizing .__setattr__, but I haven't studied this aspect of Python in a while.

By design it is possible, and even acceptable to 'monkey patch' the argparse namespace - with a custom class like this.

Sign up to request clarification or add additional context in comments.

4 Comments

As long as you're going to all this trouble, you may as well subclass argparse.ArgumentParser to wrap its parse_args to do the rewrap as an immutable namespace; easier to do that and make your work reusable trivially (users only change the initialization line for the parser and otherwise use it like normal) than to make special do_parse for each use case.
It depends on whether you need to do this kind of thing repeatedly, or just once or twice. Subclass ArgumentParser if you need to reuse some new feature repeatedly. You can still wrap the parser definition/use in a function to cleanly separate parsing from use.
Thank you very much. I understood there's nothing like 'const' and my way is not appropriate in Python. This is the way in another languages, I think. I'll follow the Python's way. Thank you for your answer, I learned a lot.
@ShadowRanger: a more reusable solution is to create a function that converts a Bunch-like object such as argparse.Namespace() into a named tuple (it should be straightforward, to create a shallow, non-recursive version).
1

You can redefine __setattr__ in your mutable_namespace:

class NotMutableException(Exception):pass

class SomeObject(object):
    def init(self):
        self.x = 10
        self.y = 20

some_obj = SomeObject()
some_obj.z = 30

def not_setattr(self, name, value):
    raise NotMutableException

type(some_obj).__setattr__ = not_setattr

some_obj.a = 1000

3 Comments

If you do this to argparse's Namespace, you just broke argparse for anything else in the code that might use it in the future. Thought I'd give you a heads up. Monkeypatching core classes to disable them is a big no no.
@ShadowRanger: OP idea is strange from the beginning. So it have equally strange answers.
It's not that outlandish of an idea. You can define your own namespace class. As long as the parser is allowed to fetch and set attributes with getattr and setattr, it should work.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.