#When to use getters/setters in Python?

1 messages · Page 1 of 1 (latest)

young current
#

so, you never NEED setters or getters, and I would say you don't really need to LOOK for reasons to use them (they definitely seem over-used rather than under-used IMO)

Usually I would use them if they're for accessing some object, BUT I also want to trigger some side effect, such as updating a counter

#

maybe a better example is that I want it to look like a class always has an object present, but in truth I only want to create the object if something is going to access it because it is expensive to create

#

but it's difficult for me to come up with good reasons to use it, and usually they end up making your code harder to read

#

so, you never NEED them. it's mostly about which version of the code helps a reader understand what is going on

#

the pros and cons of getter/setters:

Pros: less characters to type/read, callers think they're just accessing attributes, don't need to think about the function call and what's happening.

Cons: what's happening is probably more than the caller realizes

#

👍

#

no prob

lean current
#

In your own codebase there's only 1 reason to ever use them: if you've already got an attribute but you want to add behavior when getting/setting the value. Even that isn't really a reason since you could just refactor the codebase to use a method.

If you're working with a public/shared codebase you should be marking attributes that are not public with a leading underscore, and you should be using getters and setters to create read only properties and add validation where necessary. They make the code more "ergonomic" and intuitive than using functions.

lean current
#

Underscores signify intent and best practice. You'd never try to access a private attribute in C++, so just never try to access an attribute in Python that starts with an underscore. Just because you can ignore it doesn't mean there's no value in it.

lean current
#

Yeah that would be a private value since only the object it belongs to should ever assign its value. So if it were to be accessible outside of that object it should have a read only property

lean current
#

!exec ```py
class Constant:
"Blocks changing instance variables"
def set_name(self, owner, name):
self.name = name

def __set__(self, instance, value):
    if not hasattr(self, "value"):
        setattr(self, "value", value)
    else:
        print(f"Can't overwrite {self}.{self.name}, already set")

def __get__(self, instance, owner):
    return getattr(self, "value")

class ConstantMeta(type):
"Blocks changing class variables by explicitly setting the name of the variable"
def setattr(cls, name, value):
if name == "foo":
print(f"Can't overwrite {cls}.{name}, already set")
else:
super().setattr(name, value)

class Container(metaclass=ConstantMeta):
foo = Constant()
bizz = "buzz"
def init(self):
self.foo = "bar"

inst = Container()
Container.foo = 5 # >>> Can't overwrite <class 'main'.Container>.foo, already set
type.setattr(Container, "foo", "p0wn3d")
print(inst.foo) # >>> bar

covert fableBOT
lean current
#

Python doesn't have private variables because they aren't needed. What is needed is a convention for indicating what is public and not likely to change. For that convention Python suggests that users use an underscore to signify what may not be available publically in the future.

#

Your descriptor isn't correct, it's only storing 1 value for every subclass, you need to set the attr on instance not self.

#

Of course that won't work because of your private variable monster lol

lean current
#

Because of the dynamic nature of Python there's no way to actually do that, as I demonstrated by using a different metaclass to manually modify your class's attribute.

#

It's definitely a good exercise to learn about descriptors and metaclasses

#

Type is just a metaclass. It's the base metaclass so all other metaclasses inherit and modify it's behavior

#

So I can revert your behavior manually by directly calling type's behavior

#

All methods on classes are just functions. So where your metaclass has a setattr method that takes a cls, name, and value parameter, I can call directly into type's setattr with the three parameters I want to use, so I set cls to your class and it just works

#

Type is the default metaclass, so yes

lean current
#

What do you mean?

#

!exec ```py
class Example:
...

x = Example()
print(type(x))
print(type(type(x)))
print(type(Example))

covert fableBOT
#

@cursive mortar

Exec - No Code

NO PYTHON CODE BLOCK FOUND

The command format is as follows:

!exec ```py
YOUR CODE HERE
```

lean current
#

Example is an instance of type yeah

#

So you can access its meta class using dunder class

#

!exec ```py
class Example:
...

print(Example.class)

covert fableBOT
lean current
#

!exec ```py
class MCExample(type):
...
class Example(metaclass=MCExample):
...

print(Example.class)

covert fableBOT
lean current
#

Instances don't have an MRO so you access the type using dunder class. A class is an instance of the metaclass so it doesn't have a metaclass MRO. If you want the metaclass MRO you access the MRO of the class's metaclass

lean current
#

Yep

lean current
#

!exec ```py
import sys

#! Metaparser
with open(sys.argv[0], 'r') as meta:
skip = False
for line in meta.readlines():
if skip:
#print(line)
if ('#'+"! End metaparser") in line:
skip = False
continue
elif ('#'+"! Metaparser") in line:
#print(line)
skip = True
continue
#else:
# print(line)
if "type.setattr" in line:
if line[0] == '#' or line[1] == '#':
continue
raise RuntimeError("type.setattr is not allowed in this script")
#! End metaparser

class Constant:
"Blocks changing instance variables"
def set_name(self, owner, name):
self.name = name

def __set__(self, instance, value):
    if not hasattr(self, "value"):
        setattr(self, "value", value)
    else:
        print(f"Can't overwrite {instance}.{self.name}, already set")

def __get__(self, instance, owner):
    return getattr(self, "value")

class ConstantMeta(type):
"Blocks changing class variables by explicitly setting the name of the variable"
def setattr(cls, name, value):
if name == "foo":
print(f"Can't overwrite {cls}.{name}, already set")
else:
super().setattr(name, value)

class C(metaclass=ConstantMeta):
foo = Constant()
def init(self):
self.foo = "bar"

inst = C()

class Normal:
...

inst.class = Normal
inst.foo = "p0wn3d"
print(inst.foo)

covert fableBOT
lean current
#

Just needed three new lines, no need to modify the metaparser

#

There's also this solution

inst = C()
del C.__dict__["foo"].value
inst.foo = "p0wn3d"
print(inst.foo)
#

And I can even defeat the metaparser using obfuscation.

inst = C()
eval(0x747970652e5f5f736574617474725f5f28432c2027666f6f272c20277030776e33642729 .to_bytes(36, "big").decode(), globals())
print(inst.foo)
#

Another simple one

del Constant.__set__
inst = C()
inst.foo = "p0wn3d"
print(inst.foo)
#

That one will let me nuke any of your logic without modifying your code

vapid phoenix
#

Semi-related, if I’m motivated to grab a private value in a private class in even C# or Java I can do so via reflection. In probably all languages it’s possible if you’re determined to access something private.