28

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?

8
  • 1
    vars(MyClass).keys() is dir(MyClass) Commented Jun 12, 2013 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('_')] Commented Jun 12, 2013 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. Commented Jun 12, 2013 at 20:57
  • 1
    Actually, the Pythonic way is to define a function: def public_vars(klass): return [f for f in vars(MyClass) if f[0] != '_'] Commented Jun 12, 2013 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 Commented Jun 12, 2013 at 21:39

4 Answers 4

22

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))]
Sign up to request clarification or add additional context in comments.

2 Comments

Isn't 'public' (the desired output) a callable here? Why is this a filtering criteria?
Please note that vars will filter the "magic" (aka d'under) methods, unlike dir. For "private" / s'under, the filter shown here is needed.
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.

6 Comments

out of curiosity: does import * just check for leading underscores in the same way as @Blender suggested that I do it?
@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.
I think it's the only way you add an implicit binding without using exec.
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 Got it. Edited the answer to remove ambiguity.
|
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.

Comments

0

A simple list comprehension should do it, although this will return a list and not a dict (similar to vars()):

def public_attr(classvar):
    return [a for a in dir(classvar) if not a.startswith("_")]

Now if we have a class similar to the one you proposed:

class MyClass(object):
    def __init__(self):
        print("init")

    def _private(self):
        print("private")

    def __gets_name_mangled(self):
        print("gets name mangled")

    def public(self):
        print("public")

This will result in:

>>> public_attr(MyClass())
init
['public']
>>> public_attr(MyClass)
['public']

Beware of calling it on a class instead of a class instance (the second test just above). Especially with current versions of Python, some attributes may get created only at runtime, like with dataclasses.field(default_factory) (since Python 3.7)..

2 Comments

I now see this was posted as a comment by @Blender on Jun 12, 2013 at 20:54.
Another possibility would be to use the private __dict__ as MyClass().__dict__.values(), but as noted here this is discouraged and would only work for class variables (not methods).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.