Mr_and_Mrs_D
Mr_and_Mrs_D

Reputation: 34026

How to set a class attribute from an instance method in python?

For previous versions of the question see the revision history - managed to come up with a minimal executable example that reproduces the problem

# module test.py
import shlex
from test2 import Parser

class Test(object):
    sync_client = None

    def __init__(self):
        self.__class__.sync_client = 1
        parser = Parser(description='desc') # was before the assignment - egal
        while True:
            cmd = shlex.split(raw_input('> ').strip())
            parser.parse_args(cmd)

    @classmethod
    def newRequestClient(cls):
        print cls.sync_client # None

if __name__ == "__main__":
    Test()

# module test2.py
import argparse

class Share(object):
    class _ShareAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            from test import Test
            print Test.sync_client # None
            Test.newRequestClient()

    def __call__(self, subparsers):
        parser_a = subparsers.add_parser('name')
        parser_a.add_argument(dest='dest', help='help2',
                              action=Share._ShareAction)

class Parser(argparse.ArgumentParser):
    def __init__(self, description, add_h=True):
        super(Parser, self).__init__(description=description, add_help=add_h)
        subparsers = self.add_subparsers(title='Commands')
        subparsers._parser_class = argparse.ArgumentParser
        Share()(subparsers)

Run test.py and type name 123 in the prompt to see the Nones printed. Well maybe it is something obvious - it's been hours :)

EDIT: For the reproducer posted here this:

if __name__ == "__main__":
    from test import Test
    Test()

works. Adding from sync import Sync (and variations) in my main did not help however. I finally "solved" it by:

class Share(Command):
    class _ShareAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            sync = sys.modules['__main__']
            print sync.Sync.sync_client # <SyncClient(SyncClient, started 6084)>
            sync.Sync.newRequestClient(host=namespace.host, repo=values)

But I do not yet fully understand why from sync import Sync did not work.

FINALLY: thanks to a comment by @MartijnPieters:

# module watcher.sync

if __name__ == "__main__":
    from watcher.sync import Sync
    Sync().main()

This looks ugly though so feel free to comment (or even add an answer) on how I could avoid it.

Upvotes: 2

Views: 3976

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1122302

You misspelled __init__:

def __int__(self):

You are missing an i and the method is simply not called. Correct the error and your test works.

Demo:

>>> class Test(object):
...     class_attr = None
...     def __init__(self):  # note the spelling
...         self.__class__.class_attr = 1
...     @staticmethod
...     def static_meth():
...         print Test.class_attr
...     @classmethod
...     def class_meth(cls):
...         print cls.class_attr
... 
>>> t = Test()
>>> Test.class_attr
1
>>> Test.static_meth()
1
>>> Test.class_meth()
1
>>> t.class_meth()
1
>>> t.static_meth()
1

In you updated code you have two issues:

First you create an instance of Parser before the class attribute has been set. The self.__class__.sync_client = 1 line simply hasn't executed yet when Parser.__init__ is being called.

You then confuse the main script and the test module. Python imports the main script as __main__, not test. If you move the Test class out to a separate module or use from __main__ import Test your code will work.

See Importing modules: __main__ vs import as module

Upvotes: 3

Related Questions