Encapsulation in Python

sohcahtoa82 at gmail.com sohcahtoa82 at gmail.com
Mon Mar 14 14:11:52 EDT 2016


On Friday, March 11, 2016 at 6:39:53 PM UTC-8, Rick Johnson wrote:
> On Friday, March 11, 2016 at 9:48:22 AM UTC-6, Ian wrote:
> > On Thu, Mar 10, 2016 at 5:45 PM, Rick Johnson
> > The honorable Rick Johnson wrote:
> > > Many times, i would have preferred to define my module space
> > > across multiple files, multiple files that could share state
> > > without resorting to the yoga-style "import contortions",
> > > and/or the dreaded "circular import nightmares" that plague
> > > our community today.
> >
> > Sounds to me like you're blaming Python for your own poor design.
> > Possible solutions:
> >
> > 1) Don't do that. If your module is too big for one file, then it's
> > likely poorly organized and doesn't all belong in one module anyway.
> 
> Apparently you've missed the many heated arguments between
> Chris Angelico and myself concerning the size of source
> files. I have *ALWAYS* taken the position that source files
> should be kept as small as possible, but that position is
> more relevant in Python, were modules are defined by a
> single source file. I don't take the Java position that a
> module can only contain one class, but i like to keep the
> number of classes (IN MY SOURCE FILES) small.
> 
> At run-time, i don't care how large a "module namespace" may
> be. Sometimes a module namespace will be small, with only a
> few exposed symbols, but sometimes, a module namespace will
> expose thousands of symbols. The size of a "run-time module"
> is irrelevant in most languages, but here in Python, were
> module namespace is defined by the contents of *ONE SINGLE
> SOURCE FILE*, the whole ballgame is changed. If i need to
> create a module that contains many exposed symbols in
> Python, i'm forced to do one of the following:
> 
>   (1) Write all my code in a single *GIGANTIC* file...
> 
> or
> 
>   (2) Create one file that will be the "mother-ship module",
>   and N files that will be the "satellite modules", and from
>   inside the mother-ship, import all the symbols from all
>   the satellites. Ha, but in reality, it's not that simple,
>   because state does not "magically" travel between modules!
> 
>     ##########
>     # foo.py #
>     ##########
>     FOO_SHARED_STATE = "foo"
>     import _fooSatilite1
>     _fooSatilite1.FOO_SHARED_STATE = FOO_SHARED_STATE
>     from _fooSatilite1 import *
>     import _fooSatilite2
>     _fooSatilite2.FOO_SHARED_STATE = FOO_SHARED_STATE
>     from _fooSatilite2 import *
>     print 'This is the mother-ship called foo'
>     ...
> 
>     ####################
>     # _fooSatilite1.py #
>     ####################
>     from _fooConstants import *
>     print 'This is foo-fighter1, reporting for duty'
>     print FOO_SHARED_STATE
>     ...
> 
>     ####################
>     # _fooSatilite2.py #
>     ####################
>     from _fooConstants import *
>     print 'This is foo-fighter2, reporting for duty'
>     print FOO_SHARED_STATE
>     ...
> 
> But i find both to be absurd. Writing all code in a single
> file might be fine for a toy module that contains a handful
> of functions or classes or vars, but once we start creating 
> anything in the "professional size range", it will become 
> an "editing nightmare" of epic proportions!
> 
> But option two is no better, because once we cut and paste
> portions of the code into satellite files, we lose the
> ability to "easily share state". Then we're forced to start
> "hacking at the weeds" with import contortions and monkey
> patches, all the while, fearing the dreaded circular import.
> 
> 
> NO, THIS IS INSANITY!  WHAT WE NEED IS AN OPTION 3!
> 
>  (3) Allow a programmer to define module space at will
> 
>     ##########
>     # foo.py #
>     ##########
>     module foo
>     FOO_SHARED_STATE = "foo"
>     print 'This is the mother-ship called foo'
>     ...
> 
>     ####################
>     # _fooSatilite1.py #
>     ####################
>     module foo
>     print 'This is foo-fighter1, reporting for duty'
>     print FOO_SHARED_STATE # NO MP REQUIRED!
>     ...
> 
>     ####################
>     # _fooSatilite2.py #
>     ####################
>     module foo
>     print 'This is foo-fighter2, reporting for duty'
>     print FOO_SHARED_STATE # NO MP REQUIRED!
>     ...
> 
> No need for import contortions, no need for monkey patching,
> but most importantly, no need for professional programmers
> to feel as though they are penchant little children, who
> need their "module diapers" wrapped for them by mommy
> Python, who, after popping one too many "mother's little
> helpers", has a nasty habit of wrapping our diapers too damn
> tight -- what a drag, it is, getting old...
> 
> > 2) Clearly define which module is to be imported first. Then follow
> > it. When module b is imported, it should import module a. When module
> > a is imported, it *shouldn't* import module b until it's defined all
> > of its own members first. If module a depends on anything from module
> > b at import time, then refactor so it doesn't. This doesn't require
> > "contortions".
> 
> That sounds simple in theory, but it breaks down quickly
> when you create "professional sized programs"
> 
> > 3) Just put all your damn shared state in a class. There's nothing
> > stopping you from spreading out your class method definitions over
> > multiple files if you really want to, and it solves your import issue
> > by allowing everything to be imported before you even begin to set up
> > the shared state.
> 
> Your words tell me that you have not written anything substantial.
> 
> > And you're suggesting that public attributes and properties do not
> > expose an interface? Rubbish.
> 
> No, what i'm suggesting, is that, from the POV of a dir
> listing, they *ALL* look the same.
> 
> > I don't care how easy they are to *create*. Code is read much more
> > often than it is written, and excessive boilerplate damages
> > readability.
> 
> I understand the importance of readability, but is usability
> of no concern to you? In fact, i would argue that, when
> *ROCK SOLID* professional interfaces are combined with
> proper documentation and strict adherence to style guides,
> there is no need to read source code.
> 
> The only person who should be reading source, is a
> maintenance programmer. And if the code was designed
> properly, his job would be mostly just adding a feature here
> or there, or repairing a small bug here or there -- any code
> monkey can do that!
> 
> > >   (2) Properties and attributes encourage vague naming
> > >   schemes. When i read code, i find the code more
> > >   comprehensible when the symbols give me clues as to what
> > >   is going on. So if i read code like: `re.groups`,
> > >   befuddlement sets in. What is "groups"? A function
> > >   object? An attribute? "getCapturingGroupCount" would be a
> > >   better name (but that's semantics)
> >
> > Actually, it would be wrong. Match.groups doesn't return a count.
> 
> Who said anything about match objects?
> 
> ############################################################
> # BEGIN INTERACTIVE SESSION
> ############################################################
> py> import re
> py> prog = re.compile(r'(Ian) (\w+)')
> py> prog?
> ['findall', 'finditer', 'flags', 'groupindex', 'groups', 'match', 'pattern', 'scanner', 'search', 'split', 'sub', 'subn']
> py> prog.groups
> 2
> py> prog.groups?type
> <type 'int'>
> ############################################################
> # END INTERACTIVE SESSION
> ############################################################
> 
> Looks like an integer to me. (Well, i'll admit, first you'll
> have to parse my mini-language syntax, but it's highly
> intuitive -- more than a Python dir listing, i'll tell you
> that much!)
> 
> > >   In pure OOP,  methods
> > >   are the only communication meduim, so we're more likely to
> > >   write "getBlah" and "setBlah", and use verbs for
> > >   procedural names -- these naming conventions are more
> > >   comprehensible to the user.
> >
> > Good names for methods are *descriptive* verb phrases. Good names for
> > attributes and properties are *descriptive* nouns or noun phrases. Do
> > you really believe that verbs are somehow inherently easier to
> > understand?
> 
> When i see "get_width" or "set_width", i know that the first is
> returning the width, and the second will set the width. And
> anyone with an IQ over room temperature, can surmise that
> set_width takes at least one argument.
> 
> However, when i see "width", how the hell am i supposed to
> know if it is a read-only attribute, a property, or a method?
> Must i read source code just to find this out? Sure, if the
> author included a useful doc string, i could pull it up, but
> to me, this is a giant waste of time. Self documenting names
> are the *KEY* to short learning curves.

I don't think you'll find anyone that disagrees with you here.

If you're seeing a method "width", then whoever wrote that method is a terrible programmer.  Method names should *always* contain some sort of verb.  As you just said, self-documenting names are the key to short learning curves.

IMO, if you intend an attribute to be read-only, then you should use a getter, even in Python, and of course prefix the actual value with an underscore to show it should not be accessed publicly.  Of course, a programmer still can, but that's fine.  Let them shoot themselves in the foot if that's what they want to do.  It should not be the job of the module author to make sure users of the module behave.

The only time you should see "someObject.width" is if width is both readable and writable.  Of course, Using @property to add some logic to the setting/getting is fine.



More information about the Python-list mailing list