#When to use getters/setters in Python?
1 messages · Page 1 of 1 (latest)
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
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.
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.
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
!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
@lean current
Can't overwrite <class '__main__.Container'>.foo, already set
p0wn3d
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
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
What do you mean?
!exec ```py
class Example:
...
x = Example()
print(type(x))
print(type(type(x)))
print(type(Example))
@lean current
<class '__main__.Example'>
<class 'type'>
<class 'type'>
@cursive mortar
NO PYTHON CODE BLOCK FOUND
The command format is as follows:
!exec ```py
YOUR CODE HERE
```
Example is an instance of type yeah
So you can access its meta class using dunder class
!exec ```py
class Example:
...
print(Example.class)
@lean current
<class 'type'>
!exec ```py
class MCExample(type):
...
class Example(metaclass=MCExample):
...
print(Example.class)
@lean current
<class '__main__.MCExample'>
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
Yep
!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)
@lean current
p0wn3d
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
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.