Does _private makes refactoring harder?

EDIT

2025-11-21 22:37 Coordinated Universal Time

TL;DR: I propose to add a keyword to mark fields and classes as private. My proposal for the keyword name is local, or what else.

Why:

  1. Speed. Python in recent releases is focusing on speed. If an attribute can be marked as local, Python can be sure this is “really” private, and can do some optimizations.

  2. It’s granular. Now, if you want to make attributes “private”, you should use @dataclass(frozen=True). This is all or nothing. You can’t choose to make only some attributes “private” and some other “public”. Furthermore, in a future, you could potentially have a @dataclass(locals=true) to have all members local, if you really want that.

  3. It will be easier to refactor a class. You don’t have to rename all the occurrences of the field, just add or remove local

  4. it will give to people that fights against the mantra that Python is “not good for corporate code” another strong point against this.

Why local:

  1. IMO more pythonic that private
  2. open to other future uses. Note the word “future”. My proposal is restricted to classes only for now.
    Examples:
local:
    a = 1
    print(a)  # prints 1

print(a)  # NameError: name 'a' is not defined


##########################################################################

for local k, local v in d.items()
    print(k, v)

print(k, v)  # NameError

##########################################################################

# file my_useless_module.py

local import math
local from datetime import datetime

local x = 2

now = datetime.now()
sqrt2 = math.sqrt(2)

local def f():  pass

local class A:  pass

# If you import `my_useless_module`, only `now` and `sqrt2` are exposed,
# without the need of underscores, `__all__` or `del`

##########################################################################

# alternative to the previous idea

local a = 2

print(a)  # prints 2

def f():
    print(a)

f()  # NameError: name 'a' is not defined

##########################################################################

def g():
    local a = 3
    
    print(a)
    
    def h()
        print(a)
    
    h()

g()  # prints 3 and then raises NameError: name 'a' is not defined

Original post (2025 remastered)


When you write a class, “private” fields and methods are prepended with underscore. Python borrowed that from Unix, if my memory it’s not wrong.

In theory, you can write a class without “private” fields and methods, not adding any underscore as prefix. If, in a future, you want to turn them private, you can use @property to restrict the accessibility to, for example, read-only. Or you can simply add the underscore and turn it private.

But in the real world, no one do that. People prefers to write _field from the start, to say loudly “Hey, don’t touch it. It’s a bad, bad idea”. This is impossible using a field without underscore from the start and then making it private with @property or adding an underscore after.

Why? Because it’s more simple to mark internal fields and methods as private with the underscore instead of writing them all without underscore. If you do this, and later a lot of people that uses your code need to use a field that’s private, the dev can make it public without any problem.

On the contrary, if you write all the fields without underscore, people can use them all without any fear. They don’t see any underscore and they think “Hey, I can use it”. But, if at a certain point you decide to render one of them private, using a read-only @property or adding an underscore to make it completely private, it will be a big problem: the code of the people that used that field will break.

So, in practice, no one writes all the fields without the underscore and use later @property or adds the underscore. Furthermore, @property is incompatible with the fantastic @dataclass.

That’s why IMO Python should introduce the local, private or whatelse keyword. It’s not only easier this way to refactor a class, but also give to people that fights against the mantra that Python is “not good for corporate code” another strong point against this.

Yeah, this is yet-another-discussion-about-private.

I am unsure what situations you are thinking of where @property and an underscore are competing with each other?

2 Likes

I don’t really think a keyword is justified, and if we assume that we only need private fields, won’t some typing.Private work as a hint. Python is never really private, so only adding hints (or _<name> )should be enough, because if someone wants get that field, they will be able to anyways.

1 Like

Nothing is really private ;-)

You can access private fields in any other language, and Python is no exception. For example, you can use @dataclass(frozen=True), but you can still mutate the class in some way.

You can also mutate immutable CPython objects. Try ctypes.pythonapi ;-)

Furthermore, in a world with IDE and debuggers, it’s a really little benefit the ability to read and write a private field.

I proposed private (as I’m sure a ton of other people suggested before) because is ubiquitous in a lot of other languages. But maybe it’s too much restricted to classes.

As an alternative, I propose local. It will align with global and nonlocal keywords and maybe it can be used also outside classes.

Can you show an actual example where enforcing private fields actually helps with anything? If someone touches a field starting with an underscore and something breaks, they get to keep both pieces.

3 Likes

I think that local could also be a great addition for other possible uses:

local:
    a = 1
    print(a)  # prints 1

print(a)  # NameError: name 'a' is not defined

##########################################################################

local a = 2

print(a)  # prints 2

def f():
    print(a)

f()  # NameError: name 'a' is not defined

##########################################################################

def g():
    local a = 3
    
    print(a)
    
    def h()
        print(a)
    
    h()

g()  # prints 3 and then raises NameError: name 'a' is not defined

I’m not proposing to add there features now, but I think local is more pythonic and it can be used in a future also in other contexts, like the ones I provided. For now, I restrict my proposal to classes.

I think local is more pythonic than private. Anyway, I already explained the use cases. I quote myself:

I add that the refactoring is easier, of course, because you have not to rename the field or method. You have just to remove the local keyword. Done.

Sorry, I don’t understand “they get to keep both pieces.”. Can you explain it?

I missed some posts:

Yes, a typing.Private will work for the refactoring problem, but:

  1. it’s restricted to the typing world. It’s not open to other uses, as I posted.
  2. it gives to you no possibility to enforce runtime checks.

I already explained it:

Yes. I have no idea what you mean with that paragraph. Is your suggestion that people use _private when they should use @property? The other way around? That they should be doing that?

I am going to be honest, half your post is incomprehensible to me. It would really benefit from code examples that show of the problems you are seeing and your suggestions.

1 Like

Your post was not very clear before the edits, and I still do not understand the argument involving @property.

In any reasonable code editor, renaming a field or method is trivial. If it was private before, you don’t even need an IDE that can find references, all edits can be done in one file.

Is there any actual corporation which rejected Python solely because of not having enforced private fields? That does not seem very likely, I imagine there are many arguments that would have discouraged them from choosing Python (such as the broken packaging ecosystem or speed) before reaching such a minute detail.

It’s an idiom. If I tell you not to do something, but you ignore my warning and do it, and something goes wrong, you get to suffer the consequences for it, and you cannot blame me or demand a fix from me for the mess you made.

Well, so any reasonable IDE have a debugger :wink: Personally, I never printed or logged the entire __dict__ of an object to see if there’s some problems in its private fields too. I used the debugger.

Of course not. As I said, it’s another argument against the mantra. I don’t think that proposing to the core devs to make it possible to use also let in the same proposal is a good idea ;)

I don’t know nothing about it, apart some Pypi packages that were hacked and malicious code was injected. AFAIK, now you have to use a token to publish on Pypi, and many packaging systems now encourages the use of pylock.toml or other specific lockfiles.

Faster Python group is already working on it. I don’t have the luck to be a Dutch ;-)

There’s always bigger fish to fry ;-). Core devs are already working on all the problems you listed, so let’s fry this fish. It will not slow down any other bigger ones.

Now I understand. Yeah, it’s their business if they use a private method, and they can do that also with local, as I said before. No one said the contrary.

But a lot of people likes compile and runtime errors, and inexperienced devs are not so skilled to manipulate a “really” private field. So, in companies, expert devs prefer to use languages like Java that prevent newbies to mess up things.

I’m not saying Python must turn its ideology. On the contrary, I really dislike the paternalism of the Java ecosystem – even if things are changing also there. local will be completely optional. If people want to use it, they can use it. If not, they can continue with underscore.

Ok, maybe I was not so clear. I try to rewrite the paragraph about @property:

In theory, you can write a class without “private” fields and methods, not adding any underscore as prefix. If, in a future, you want to turn them private, you can use @property to restrict the accessibility to, for example, read-only. Or you can simply add the underscore and turn it private.

But in the real world, no one do that. People prefers to write _field from the start, to say loudly “Hey, don’t touch it. It’s a bad, bad idea”. This is impossible using a field without underscore from the start and then making it private with @property or adding an underscore after.

Why? Because it’s more simple to mark internal fields and methods as private with the underscore instead of writing them all without underscore. If you do this, and later a lot of people that uses your code need to use a field that’s private, the dev can make it public without any problem.

On the contrary, if you write all the fields without underscore, people can use them all without any fear. They don’t see any underscore and they think “Hey, I can use it”. But, if at a certain point you decide to render one of them private, using a read-only @property or adding an underscore to make it completely private, it will be a big problem: the code of the people that used that field will break.

So, in practice, no one writes all the fields without the underscore and use later @property or adds the underscore. Furthermore, @property is incompatible with the fantastic @dataclass.

End of rewriting. What’s the point? The point is that it will be simpler with a local keyword. You can write all the fields and methods without any underscore, you have just to add local to the private ones. If you want to make it public, you have only to remove local. If you want to make it private, accepting the risk that people using you API could egg your house, you can simply add local.

And much more, as I wrote in the thread and summarized in the first post.

Ok, I now understand the argument. However:

  • I doubt that the public → property conversion doesn’t happen. I personally have done it for a public package. It might not be prevalent, but I would judge that that is probably more because it’s not that common to have a public field you want to make private without being willing to break compatibility.
  • AFAICT, this is an argument for making everything _sunder private, no?

Additionally, you have no defined what your proposed local does inside a class. You have shown what it does in a normal execution environment, but that does not at all translate to what behavior you expect inside a class. Currently all you have shown is introducing “block scopes”, and that is a common proposal that has been met with “meh” to “no” in the past.

A single leading underscore followed by more characters does not mean ‘private’. Semantically, it means “This name is not part of the supported interface; use at your own risk as it meaning and existence can change.” Functionally, it means “Not imported by from module import *.” import * is intended to import the supported interface of a module, so its omission therefrom means “not part of the supported interface”. Python core developers don’t care is users use them anyway as long as they do not request support.

Having a (somewhat) enforced private designation is a different issue and has been proposed before and rejected. Any repeated proposal should address the rejection reasons.

Note: I don’t understand the connection between refactoring and the proposal.

2 Likes

I’ll try to be more clear and less verbose.

If you name a property _x, and lately you want to make it public, you have to rename it or write a @property. A @property doesn’t play very nicely with dataclasses.

If you have to rename it, you have to rename it in all your code. This is not a problem if your classes and subclasses are in one file only, but it is if you have in multiple files. In this case, an IDE it’s really needed.

With a local keyword, it will be more simple. You can write local x, and if lately you want to make it public, you have only to remove local. It plays well with dataclasses and doesn’t need refactoring.

Ok, but I don’t really know where to look for.

I was thinking about two options:

  1. local will make the member private, and stop. so local self.x = None will be in practice identical to local self._x = None with no @property, and local def f(self) will be identical to def _f(self)

pros: dead simple

cons: you can’t define a getter, a setter or a deleter without using a different name. Probably you can do it with different keywords – but I don’t like it – or with annotations – that Python doesn’t have. Anyway, annotations or other keywords are outside my proposal.

  1. local will act as an alias. local self.x = None doesn’t create a “real” field named x, but a field named _x. The same for methods. If you want a “completely” private field, you can write local self._x = None. It will mapped to a field named __x. In theory you can also add yet another keyword, but I think it’s too much; so, outside my proposal again.

pros: this way you can add getter, setter and deleter with the same name of the alias.

cons: it will create a “magical” self._x. Not explicit and potentially confusing for people that will introspect the object. Furthermore, the creation of another field named _x should be disallowed, and the same for methods.

That’s really interesting. Can you link me the project?

Well, no. If you start to make all your properties private, you have to provide a @property for every public field, and it’s very boring, especially with dataclasses.

To be clear, I added the examples in the first post only to justify the choice of the name local for the new keyword.

I think local is better than private because it’s open to possible other uses. That doesn’t imply we need the other uses, or that they will be ever implemented. They are completely outside my proposal, and they are there only for brainstorming.

About namespaces, ty for the links. I read the PEP 359 and at the beginning, the rejection note was obscure to me:

This PEP was withdrawn at Guido’s request [2]. Guido didn’t like it, and in particular didn’t like how the property use-case puts the instance methods of a property at a different level than other instance methods and requires fixed names for the property functions.

But after I clicked the link in the rejection note, I understood:

Guido van Rossum guido at python.org
Tue Apr 18 10:36:21 CEST 2006

[…]

2: the dict-alike is ordered

[…]

Unfortunately I don’t think you can implement this without
significantly slowing things down. […]

So I’m against making the class dict an ordereddict by default […]

PS: if this change to the class statement were to happen, the crowd
discussing the ‘make’ PEP (which I don’t like but don’t have the power
to stop being discussed) should take notice. Or perhaps they could run
with the idea and make it the focal point of their proposal. Or
whatever. :slight_smile:

Steven Bethard steven.bethard at gmail.com
Tue Apr 18 18:54:33 CEST 2006

If you pronounce it dead, I can make sure the discussion stops. :slight_smile:

Guido van Rossum guido at python.org
Tue Apr 18 20:43:53 CEST 2006

Let’s kill it.

So, if I understood well, the make proposal for a namespace required an ordered dict for storing the map name → var of the objects in the namespace. This is no more a problem from Python 3.6+ (thanks Pypy).

PS: much more ideas – let me restate that’s they are only brainstorming and outside the proposal:

for local k, local v in d.items()
    print(k, v)

print(k, v)  # NameError

Also, an alternative to one of my ideas:

# file my_useless_module.py
local import math
local from datetime import datetime

local x = 2

now = datetime.now()
sqrt2 = math.sqrt(2)

local def f():  pass

local class A:  pass

If you import my_useless_module, only now and sqrt2 are exposed, without the need of underscores, __all__ or del

If you are saying “exposed”, do you mean literally, at runtime? So does my_useless_module.A raise an exception? Or do you just mean as a convention as a more convient way of documenting this?

Again, please be more clear with semantics. Ideally, produce semantically identical python code that doesn’t rely on this feature, or point out where new runtime semantics are needed and be precise about what does are.

To be honest, I’m not so interested in this particular idea I proposed. I proposed it only to show yet another possible use of a local keyword.

My main proposal is the local for classes. And I’m really interested about past conversations about I can’t find.

Anyway, this is the code you requested:

# file my_useless_module.py

import math
from datetime import datetime

x = 2

now = datetime.now()
sqrt2 = math.sqrt(2)

def f():  pass

class A:  pass

del math
del datetime
del x
del f
del A

or, more pragmatically:

# file my_useless_module.py

import math as _math
from datetime import datetime as _datetime

_x = 2

now = _datetime.now()
sqrt2 = _math.sqrt(2)

def _f():  pass

class _A:  pass

__all__ = ["now", "sqrt2"]

Ok, but these two examples have widely different semantics. That’s my point. You are constantly waving a magic wand “well, I want useful semantics”. Which semantics do you want? Are the attributes still accessible from outside the module via my_useless_module._math or not?What if there is a conflict attribute name? del mathwouldn't work because then you can't accessmathfrom inside any function inside ofmy_useless_module` either.

If you are not interested in nailing down semantics for the global case, then nail down semantics for the class case. You clearly have some concrete idea of how you want this to behave, but I still don’t know what it is. You are just saying “mark as private” as if that’s a well-defined concept - it’s not. We can’t read your mind.

From my point of view…

A big portion of refactoring activities is moving and renaming things. In general, moving things and renaming things is the same operation anyway (just like the mv command on Linux). I can’t really picture many refactoring operations that do not involve some kind of renaming.

I do not think I ever saw this suggestion before, where one would wrap _x with a property to make it public instead of renaming _x to x. I think that if I were to do code review for such a change I would reject it.

If _x was indeed not part of the public API then there is likely no reason against renaming it to x. Renaming x to _x is likely not a backwards-incompatible change.

This kind of refactoring is so basic. Tools have been able to do this kind of refactoring for decades.

2 Likes

##########################################################################
But you can read my posts ;-) I quote myself:
##########################################################################

##########################################################################
Now answering to sinoroc:
##########################################################################

##########################################################################
Yeah, I completely agree. It’s also funny. It’s like tidy up your house after a crazy party.
##########################################################################

##########################################################################
Not saying that. I’m saying that you can name your private members local x or local fun() instead of _x and _fun(). If you want to make them public, you have just to remove local.
##########################################################################

Yeah, it was already said, and my reply was: if so, also debugging a private member and modify it has powerful tools that exists from a lot of time.

I would like also that local will make the variable “really” private. Terry said there was already discussed – and I don’t find it suprising… But I found only this:

https://discuss.python.org/search?q=private%20%23ideas%20in%3Atitle%20order%3Alatest

I quote some of the posts I find they have some good counterpoints:

This is true. The fact that _x is not special at all and you can access it easily as x is good if you want a fast debug and test. And it’s true you can workaround bugs more easily. I remember I’ve done it for a public py library. I had to read a private member to patch my app (app is so cool to say instead of program now… maybe because is Unix like. Only three letters).

But it’s really easy to read and modify also a “real” private member. That’s a piece of cake.

Ok. But these proposals don’t work. You have not thought these through.

class A:
    def __init__(self):
        local self.x = 1

    def foo(self):
        print(self.x)    # (1) How does this statement know what to access?
        print(getattr(self, 'x')) # What happens here?
        print(self._x)  # What happens with this statement?

    def __eq__(self, other):
        if isinstance(other, A):
            return self.x == other.x # What does this statement access?
        elif isinstance(other, SomeOtherClass):
            return self.x == other.x + 4 # What about this one?
        else: ...

a = A()
# What about these?
print(a.x)
print(a._x)
print(getattr(a, 'x'))
print(getattr(a, '_x'))
print(a.__dict__['x'])
print(a.__dict__['_x'])

# What about subclasses?
class B(A):
     def __init__(self):
        super().__init__()
        # Which of these are this legal? What do they mean? How do they influence `A`'s 'x'?
        local self.x = 2
        local super().x = 3
       print(self.x)
       print(self._x)

While answering each individual question would be interesting, you should make sure that your proposal is well defined enough that the answers to the above are obvious. Especially question 1.

(Some of the questions are answered by you saying “It creates an attribute called _x instead”. But if you do that, that just means people from outside the class still have the exact same access potential as before, so it has no benefits at all in keeping things private. It still requires the exact same social contract to be in place to make refactoring simple)

I thought about some other advantages of having a local keyword:

  1. Speed. Python in recent releases is focusing on speed. If an attribute can be marked as local, Python can be sure this is “really” private, and can apply speed optimizations.

  2. It’s granular. Now, if you want to make attributes “private”, you should use @dataclass(frozen=True).

This is all or nothing. You can’t choose to make only some attributes “private” and some other “public”.

Furthermore, you could potentially have a @dataclass(locals=true) to have all members local, if you really want that. (EDIT I previously wrote local=True. This will impossible if local will be an hard keyword).

Maybe you mean the second proposal, not both. The first is very simple:

The second one is probably the one you’re referring with your questions. Indeed, it’s tricky. I personally strongly prefer the first one. Moreover, I explained it very badly. That’s why you didn’t understand it. Let me rephrase:

Re-reading that, it seems that both x and _x can be used. That was not my idea. My idea is that, internally, Python will create a member named _x. But the coder can use only x to access or modify it. That’s way I said

This is because, with the second proposal:

class ClashOfPrivates
    def __init__(self, *args, **kwargs):
        local self.x = None
        self._x = "FUBAR"

both local self.x and self._x will create internally the same field with the name _x.

That’s why I put that in the cons of the second proposal, and it’s a big con. With the second proposal the above code will raise an exception, and it will be hard to understand.

That’s why I prefer the first proposal. Even if it’s less powerful, it’s more easy, less “magical” and you don’t need to know implementation details.