24

I would like to call a method to give me a dict of all of the "non-private" (I use the term "private" somewhat loosely here since it does not really exist in Python) and non-builtin attributes (i.e. those that do not begin with a single or double underscore) on a class. Something like vars(MyClass) that would return only the "public" attributes on that class.

I'm aware that

from M import * 

does not import objects whose name starts with an underscore. (http://www.python.org/dev/peps/pep-0008/#id25) How does import implement that? Via a builtin function or just by checking for underscores? What is the pythonic way to do this?

Example:

class MyClass(object):
    def __init__(self):
        do_stuff()
    def _private(self):
        print 'private'
    def __gets_name_mangled(self:
        print 'becomes _MyClass__gets_name_mangled()'
    def public(self):
        print 'public'

If I do

vars(MyClass).keys()

I get

['_MyClass__gets_name_mangled', '__module__', '_private', '__doc__', '__dict__', '__weakref__', 'public', '__init__']

How can I get only

['public']

Or do I just need to check for underscores myself? It just seems like there would be a pythonic way to do this.

For more on underscores and double underscores, see: What is the meaning of a single- and a double-underscore before an object name?

Community
  • 1
  • 1
andy
  • 1,399
  • 3
  • 12
  • 32
  • 1
    `vars(MyClass).keys()` is `dir(MyClass)` – Elazar Jun 12 '13 at 20:54
  • 9
    I don't know of any function that does this. There's always: `[f for f in dir(MyClass) if not f.startswith('_')]` – Blender Jun 12 '13 at 20:54
  • 3
    @Elazar if my understanding is correct, dir(MyClass) will return attributes of classes that are subclassed by MyClass (if MyClass happened to subclass something) in addition to those defined inside MyClass, while vars(MyClass) only returns those attributes defined inside MyClass. Subtle difference. But original question stands. – andy Jun 12 '13 at 20:57
  • Yes, you are right. And I believe @Blender gave you the only answer there is. – Elazar Jun 12 '13 at 20:58
  • @Blender that is certainly a way to do it...I suppose if it's the only way, then it must be the Pythonic way (lest the Pythonic way be "tuck tail and retreat"). In which case I'd give you credit for an answer if you posted as an answer... – andy Jun 12 '13 at 21:01
  • 1
    Actually, the Pythonic way is to define a function: `def public_vars(klass): return [f for f in vars(MyClass) if f[0] != '_']` – Steven Rumbalski Jun 12 '13 at 21:13
  • 3
    Double underscore **doesn't mean private**. It means "use name mangling" which is just a mechanism for this class to keep an attribute that is distinct from the same attribute in any subclasses – John La Rooy Jun 12 '13 at 21:39
  • @gnibbler. good point. i updated the question for clarity. – andy Jun 12 '13 at 21:42

3 Answers3

17

With a dict comprehension that filters vars()

{ k:v for k,v in vars(myObject).items() if not k.startswith('_') }

Moved into a function that returns a list of attributes that are not 'soft private' or callables. You can return values if you like by changing to dict comprehension as above

def list_public_attributes(input_var):
    return [k for k, v in vars(input_var).items() if
            not (k.startswith('_') or callable(v))]
kert
  • 2,161
  • 21
  • 22
  • 1
    Isn't 'public' (the desired output) a callable here? Why is this a filtering criteria? – lightsong Jan 25 '20 at 20:49
  • Please note that `vars` _will_ filter the "magic" (aka d'under) methods, unlike `dir`. For "private" / s'under, the filter shown here is needed. – abe Apr 09 '20 at 17:27
4

Actually, it would be unpythonic for such function to exists - because "officially" there is no private or protected fields/properties in Python.

While it makes sense to throw away module attributes with leading underscores (which are usually some implementation details) during import * from some module*, it is not useful in context of any other object.

So, if you need to list only "public" methods/attributes of an object, just iterate through result of dir and drop names with leading underscores.


* "during import * from some module'"

Usually it is not the best practice. Consider the next example:

module A has a1 and a2 defined

module B has b1 and b2 defined

This code in module C works as expected:

from A import a1, a2
from B import *

Imagine we add function a1 in module B. Now suddenly module C is broken, although we haven't touched it.

Roman Bodnarchuk
  • 29,461
  • 12
  • 59
  • 75
  • out of curiosity: does import * just check for leading underscores in the same way as @Blender suggested that I do it? – andy Jun 12 '13 at 21:04
  • @andy - import * is considered bad practice because you can never be sure of what you exactly import, which in turn can create name conflicts and possible unintended use of methods which have been imported using the *. It is always better to explicityly import every method separately, or to simply refer to them from the common namespace. – kramer65 Jun 12 '13 at 21:16
  • I think it's the only way you add an implicit binding without using `exec`. – Elazar Jun 12 '13 at 21:24
  • I see. You're saying _calling_ `import *` is bad practice (that makes sense). I thought you were saying that the _implementation_ of `import *` (ignoring attributes with leading underscores) was bad practice (because it followed the public/private paradigm). Thanks for clarifying. – andy Jun 12 '13 at 21:26
  • @andy Got it. Edited the answer to remove ambiguity. – Roman Bodnarchuk Jun 12 '13 at 21:30
  • @Elazar Yep, this is a nice observation. – Roman Bodnarchuk Jun 12 '13 at 21:30
1

I'm using this function:

def print_all_public_fields(obj):
    print(obj)
    for a in dir(obj):
        if not a.startswith('_') and not a.isupper():
            print('\t%s = %s' % (a, getattr(obj, a)))

I know it's not exactly what you want, but maybe it'll give you some idea.

omikron
  • 2,745
  • 1
  • 25
  • 34