Refactoring Dilemma

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Kamilche

    Refactoring Dilemma

    '''
    I'm in the middle of a refactoring dilemma.
    I have several singletons that I'm turning into modules, for ease of
    access.
    The usual method is noted as 'Module 1' below.
    The new method is noted as 'Module 2'.
    Is there any reason NOT to do this that I may be unaware of?
    It's easier than remembering to declare global variables at the top of
    the function.
    '''

    # ----------- Module 1.py ------------
    # Normal module processing
    var = 0

    def MyRoutine():
    global var
    var = 1

    MyRoutine()
    print var


    # ----------- Module 2.py ------------
    # 'Self' module processing
    import sys
    var = 0
    self = sys.modules[__name__]

    def MyRoutine():
    self.var = 1

    MyRoutine()
    print var

  • George Sakkis

    #2
    Re: Refactoring Dilemma

    Kamilche wrote:
    '''
    I'm in the middle of a refactoring dilemma.
    I have several singletons that I'm turning into modules, for ease of
    access.
    The usual method is noted as 'Module 1' below.
    The new method is noted as 'Module 2'.
    Is there any reason NOT to do this that I may be unaware of?
    It's easier than remembering to declare global variables at the top of
    the function.
    '''
    >
    # ----------- Module 1.py ------------
    # Normal module processing
    var = 0
    >
    def MyRoutine():
    global var
    var = 1
    >
    MyRoutine()
    print var
    >
    >
    # ----------- Module 2.py ------------
    # 'Self' module processing
    import sys
    var = 0
    self = sys.modules[__name__]
    >
    def MyRoutine():
    self.var = 1
    >
    MyRoutine()
    print var

    What's wrong with
    <code>
    def MyRoutine():
    return 1

    var = MyRoutine()
    </code>
    ?

    George

    Comment

    • Carl Banks

      #3
      Re: Refactoring Dilemma

      Kamilche wrote:
      '''
      I'm in the middle of a refactoring dilemma.
      I have several singletons that I'm turning into modules, for ease of
      access.
      The usual method is noted as 'Module 1' below.
      The new method is noted as 'Module 2'.
      Is there any reason NOT to do this that I may be unaware of?
      It's easier than remembering to declare global variables at the top of
      the function.
      '''
      >
      # ----------- Module 1.py ------------
      # Normal module processing
      var = 0
      >
      def MyRoutine():
      global var
      var = 1
      >
      MyRoutine()
      print var
      >
      >
      # ----------- Module 2.py ------------
      # 'Self' module processing
      import sys
      var = 0
      self = sys.modules[__name__]
      >
      def MyRoutine():
      self.var = 1
      >
      MyRoutine()
      print var
      I don't see any major problem with it. In fact, I think it's a very
      good idea to do this, rather than use global statements, when using
      module as a singleton class.

      I recently made the same transition myself, though I solved it a bit
      differently. I used a decorator to pass the module as the first
      argument (actually it passes a proxy to module's global dict, since the
      module itself isn't accesible from the function object).

      def modmethod(func) :
      class modproxy(object ):
      __getattribute_ _ = func.func_globa ls.__getitem__
      __setattr__ = func.func_globa ls.__setitem__
      self = modproxy()
      def call_with_modul e(*args,**kwarg s):
      return func(self,*args ,**kwargs)
      call_with_modul e.func_name = func.func_name
      return call_with_modul e

      @modmethod
      def MyRoutine(self) :
      self.var = 1

      MyRoutine()
      print var

      One problem with my decorator is it makes stack traces a little
      bloated. (Also attribute errors are raised as KeyError, but that's
      easily fixable.) Other than that, I've been running it for awhile
      without any problems. I doubt your approach would have many problems,
      either.


      Carl Banks

      Comment

      • Carl Banks

        #4
        Re: Refactoring Dilemma

        George Sakkis wrote:
        Kamilche wrote:
        '''
        I'm in the middle of a refactoring dilemma.
        I have several singletons that I'm turning into modules, for ease of
        access.
        The usual method is noted as 'Module 1' below.
        The new method is noted as 'Module 2'.
        Is there any reason NOT to do this that I may be unaware of?
        It's easier than remembering to declare global variables at the top of
        the function.
        '''

        # ----------- Module 1.py ------------
        # Normal module processing
        var = 0

        def MyRoutine():
        global var
        var = 1

        MyRoutine()
        print var


        # ----------- Module 2.py ------------
        # 'Self' module processing
        import sys
        var = 0
        self = sys.modules[__name__]

        def MyRoutine():
        self.var = 1

        MyRoutine()
        print var
        >
        >
        What's wrong with
        <code>
        def MyRoutine():
        return 1
        >
        var = MyRoutine()
        </code>
        ?

        Kamilche simplified things in his example that obscured the main use
        case. Short story is, var is considered a state of the module, and
        MyRoutine() is designed to be called from outside the module to modify
        the state. Your suggestion can only modify the state of the module
        from within, so it won't do.

        More detailed discussion:

        A common piece of advice when people ask "how can I implement a
        singleton in Python" is to use a module. A module, after all, is a
        singleton object.

        But there's a big problem using a module as a singleton: you need to
        use then global statement to rebind module-level variables. Which
        means that if you want your module to have lots of modifyable state,
        most likely you'll have to use a bunch global statements. And that's a
        major pain in the neck when you have to do it a lot and in many
        functions.

        In these cases, it would be nice if a module could access its own state
        in the same way that an instance does, that is, as an attribute. My
        approach of using a decorator to pass in a module proxy, and Kamilche's
        approach of binding the module object to self, both accomplish this.
        Internally, the module behaves very much like a class instance.
        Functions in the module act almost exactly like bound methods, and
        module-level variables act like instance attributes. The difference
        between writing a module and a singleton class thus becomes mostly a
        matter of indentation.

        In fact, when I made a major switch from using singleton classes to
        modules, I was able to change it by dedenting once, and pasting a
        @modmethod decoration above each method, with very few other changes.

        Carl Banks

        Carl Banks

        Comment

        • George Sakkis

          #5
          Re: Refactoring Dilemma

          Carl Banks wrote:
          Kamilche wrote:
          '''
          I'm in the middle of a refactoring dilemma.
          I have several singletons that I'm turning into modules, for ease of
          access.
          The usual method is noted as 'Module 1' below.
          The new method is noted as 'Module 2'.
          Is there any reason NOT to do this that I may be unaware of?
          It's easier than remembering to declare global variables at the top of
          the function.
          '''

          # ----------- Module 1.py ------------
          # Normal module processing
          var = 0

          def MyRoutine():
          global var
          var = 1

          MyRoutine()
          print var


          # ----------- Module 2.py ------------
          # 'Self' module processing
          import sys
          var = 0
          self = sys.modules[__name__]

          def MyRoutine():
          self.var = 1

          MyRoutine()
          print var
          >
          I don't see any major problem with it. In fact, I think it's a very
          good idea to do this, rather than use global statements, when using
          module as a singleton class.
          >
          I recently made the same transition myself, though I solved it a bit
          differently. I used a decorator to pass the module as the first
          argument (actually it passes a proxy to module's global dict, since the
          module itself isn't accesible from the function object).
          >
          def modmethod(func) :
          class modproxy(object ):
          __getattribute_ _ = func.func_globa ls.__getitem__
          __setattr__ = func.func_globa ls.__setitem__
          self = modproxy()
          def call_with_modul e(*args,**kwarg s):
          return func(self,*args ,**kwargs)
          call_with_modul e.func_name = func.func_name
          return call_with_modul e
          >
          @modmethod
          def MyRoutine(self) :
          self.var = 1
          >
          MyRoutine()
          print var
          This looks quite hackish, both the implementation and the usage; most
          people would get confused when they didn't find var's assignment at
          global scope. I prefer the the simple global statements if they aren't
          that many, otherwise the assignment of the module to self is also fine.

          George

          Comment

          • Carl Banks

            #6
            Re: Refactoring Dilemma


            George Sakkis wrote:
            Carl Banks wrote:
            I don't see any major problem with it. In fact, I think it's a very
            good idea to do this, rather than use global statements, when using
            module as a singleton class.

            I recently made the same transition myself, though I solved it a bit
            differently. I used a decorator to pass the module as the first
            argument (actually it passes a proxy to module's global dict, since the
            module itself isn't accesible from the function object).

            def modmethod(func) :
            class modproxy(object ):
            __getattribute_ _ = func.func_globa ls.__getitem__
            __setattr__ = func.func_globa ls.__setitem__
            self = modproxy()
            def call_with_modul e(*args,**kwarg s):
            return func(self,*args ,**kwargs)
            call_with_modul e.func_name = func.func_name
            return call_with_modul e

            @modmethod
            def MyRoutine(self) :
            self.var = 1

            MyRoutine()
            print var
            >
            This looks quite hackish, both the implementation and the usage;
            Once again, the function MyRoutine is intended to be called from
            another module. The usage I have here is just an example demonstrating
            that var is in the module's dict. Please keep this in mind when
            criticizing usage. The intended usage would be something like this
            (from another module):

            from xxx import yyy
            yyy.MyRoutine()

            As far as the caller is concerned, yyy could be a module using globals,
            a module with this "hack", or a class instance, or something else. It
            doesn't matter; usage is the same in all three cases.

            As for the implementation. ..
            most
            people would get confused when they didn't find var's assignment at
            global scope. I prefer the the simple global statements if they aren't
            that many, otherwise the assignment of the module to self is also fine.
            For ordinary modules that might have one or two serial number counters
            or debug flags or whatnot, I agree. For modules that have lots of
            state and act more like class instances than modules, the massive
            amounts of globals are ugly and error prone. In that case you should
            either use a regular class, or tolerate the "hack"; don't use a bunch
            of globals.

            I think a simple comment at the top could take care of any confusion
            about what's happening. Since it mimics how a class works, it won't be
            anything completely new.

            YMMV.

            Carl Banks

            Comment

            • Carl Banks

              #7
              Re: Refactoring Dilemma

              George Sakkis wrote:
              I prefer the the simple global statements if they aren't
              that many, otherwise the assignment of the module to self is also fine.
              I replied to this article, and then canceled the reply (but that never
              works), thinking you were criticizing the general concept of "module as
              a singleton" and not just my approach. I should learn to read. I
              apologize for the misunderstandin g.

              I do agree my implementation is a little hacky, although IMHO it feels
              to fit in with the way classes work better, which appeals to me but
              probably not most people :). For one thing, the methods have the
              explicit self argument, like regular classes. For another, it uses a
              decorator to affect the binding of the modules. In regular classes,
              functions are instance methods by default, and become class methods or
              static methods with a decorator. I like to think of modules as having
              a special metaclass that treats functions as static methods by default,
              and uses a decorator to get an instance method. (Even though that
              isn't remotely how it works, it fits how it would work if you were
              implementing modules based on classes.)

              Having said that, I've no problem with Kamilche's approach. The real
              evil in my mind is using lots of global statements, and anything you
              can do to stay away from that is a good thing.


              Carl Banks

              Comment

              • Steven Bethard

                #8
                Re: Refactoring Dilemma

                Kamilche wrote:
                Is there any reason NOT to do this that I may be unaware of?
                [snip]
                # ----------- Module 2.py ------------
                # 'Self' module processing
                import sys
                var = 0
                self = sys.modules[__name__]
                >
                def MyRoutine():
                self.var = 1
                >
                MyRoutine()
                print var
                Looks basically fine to me, but I'd probably write it as::

                _self = __import__(__na me__)

                Then you don't need to import sys. I'd also use a leading underscore
                for "self" so that it's clearly marked as a module-internal attribute.

                STeVe

                Comment

                Working...