@ property
is longIn Python, you can define properties by using the property decorator as follows. As an example, a User class with properties named name and email can be defined as follows:
class User:
def __init__(self, *, name=None, email=None):
self.__name = name
self.__email = email
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
@property
def email(self):
return self.__email
@email.setter
def email(self, email):
self.__email = email
You can write the property value as user.name
and the value setting as user.name =" Taro "
.
user = User(name="taro", email="[email protected]")
print(user.name) # => "taro"
user.name = "Taro"
print(user.name) # => "Taro"
There is nothing that feels annoying to the user of the User class, but from the perspective of the user class definer, it is annoying because you have to write as many similar codes as there are properties.
Therefore, define the following helper function.
def define_property(self, name, value=None):
# "_User__name"Name after mangling.
field_name = "_{}__{}".format(self.__class__.__name__, name)
#Set the initial value.
setattr(self, field_name, value)
# getter/Generate setter,Define properties.
getter = lambda _: getattr(self, field_name)
setter = lambda _, value: setattr(self, field_name, value)
setattr(self.__class__, name, property(getter, setter))
With this, the definition of the User class can be shortened as follows.
class User:
def __init__(self, *, name=None, email=None):
define_property(self, "name", name)
define_property(self, "email", email)
define_property
Could significantly reduce the amount of code,name
Oremail
I feel redundant when I write multiple times.
So let's create a decorator for the constructor.
def define_properties(*names):
def decorator(constructor):
def wrapper(self, **kwargs):
for name in names:
define_property(self, name, kwargs.get(name))
constructor(self, **kwargs)
return wrapper
return decorator
define_properties
You can use to rewrite the user class as follows:
class User:
@define_properties("name", "email")
def __init__(self):
pass
Redundancy is gone. It will be completed with a little more improvement.
To make it a little more convenient, make the following changes:
define_property
.`True``` for
`` readable``` to define it as readable.`True``` for
`writable``` to define it as writable. define_properties
.accessible
as readable and writable. readable
as readable. writable
as writable. `readable
and` `` writable``` are defined as readable and writable. define_properties
, define a readable / writable property with the name specified in the keyword argument of the constructor.The modified code is as follows.
define_property.py
def define_property(self, name, value=None, readable=True, writable=True):
# "_User__name"Name after mangling.
field_name = "_{}__{}".format(self.__class__.__name__, name)
#Set the initial value.
setattr(self, field_name, value)
# getter/Generate setter,Define properties.
getter = (lambda self: getattr(self, field_name)) if readable else None
setter = (lambda self, value: setattr(self, field_name, value)) if writable else None
setattr(self.__class__, name, property(getter, setter))
def define_properties(constructor=None, *, accessible=(), readable=(), writable=()):
if callable(constructor):
def wrapper(self, *args, **kwargs):
for name, value in kwargs.items():
define_property(self, name, value)
constructor(self, *args, **kwargs)
return wrapper
else:
to_set = lambda x: set(x) if any(isinstance(x, type_) for type_ in (set, list, tuple)) else {x}
accessibles = to_set(accessible)
readables = accessibles | to_set(readable)
writables = accessibles | to_set(writable)
def decorator(constructor):
def wrapper(self, *args, **kwargs):
for name in (readables | writables):
readable = name in readables
writable = name in writables
initial_value = kwargs.get(name, None)
define_property(self, name, initial_value, readable, writable)
constructor_kwargs = dict([(key, kwargs[key]) for key in (constructor.__kwdefaults__ or {}) if key in kwargs])
constructor(self, *args, **constructor_kwargs)
return wrapper
return decorator
The test code is as follows.
define_property_test.rb
import unittest
from define_property import *
class DefinePropertyTest(unittest.TestCase):
def test_initial_value(self):
class User:
def __init__(self, *, name=None, email=None):
define_property(self, "name", name)
define_property(self, "email", email)
user = User()
taro = User(name="taro", email="[email protected]")
self.assertEqual(None, user.name)
self.assertEqual(None, user.email)
self.assertEqual("taro", taro.name)
self.assertEqual("[email protected]", taro.email)
def test_accessor(self):
class User:
def __init__(self, *, name=None):
define_property(self, "name", name)
taro = User(name="taro")
self.assertEqual("taro", taro.name)
taro.name = "Taro"
self.assertEqual("Taro", taro.name)
def test_readable(self):
class User:
def __init__(self, *, name=None, email=None):
define_property(self, "name", name, readable=True)
define_property(self, "email", email, readable=False)
taro = User(name="taro", email="[email protected]")
taro.name
with self.assertRaises(AttributeError):
taro.email
def test_writable(self):
class User:
def __init__(self, *, name=None, email=None):
define_property(self, "name", name, writable=True)
define_property(self, "email", email, writable=False)
taro = User(name="taro", email="[email protected]")
taro.name = "Taro"
with self.assertRaises(AttributeError):
taro.email = "[email protected]"
def test_not_accessible(self):
class User:
def __init__(self, *, name=None, email=None):
define_property(self, "name", name, readable=False)
define_property(self, "email", email, readable=False)
taro = User(name="taro", email="[email protected]")
with self.assertRaises(AttributeError):
taro.name
with self.assertRaises(AttributeError):
taro.email
def test_access_by_other_method(self):
class User:
def __init__(self, *, name=None, email=None):
define_property(self, "name", name)
def get_name(self):
return self.__name
taro = User(name="taro")
self.assertEqual("taro", taro.get_name())
class DefinePropertiesTest(unittest.TestCase):
def test_no_arguments(self):
class User:
@define_properties
def __init__(self, *, name=None, email=None):
pass
with self.assertRaises(AttributeError):
User().name
User(name="taro").name
with self.assertRaises(AttributeError):
User(name="taro").email
User(name="taro", email="[email protected]").email
def test_initial_value(self):
class User:
@define_properties(accessible=("name", "email"))
def __init__(self, *, name=None, email=None):
if name != None:
self.__name = self.name.upper()
if email != None:
self.__email = self.email.upper()
user = User()
self.assertEqual(None, user.name)
self.assertEqual(None, user.email)
taro = User(name="taro", email="[email protected]")
self.assertEqual("TARO", taro.name)
self.assertEqual("[email protected]", taro.email)
def test_accessible_with_no_arguments(self):
class User:
@define_properties(accessible=())
def __init__(self):
pass
user = User()
with self.assertRaises(AttributeError):
user.name
def test_accessible_with_string_argument(self):
class User:
@define_properties(accessible="name")
def __init__(self):
pass
user = User()
self.assertEqual(None, user.name)
user.name = "jiro"
self.assertEqual("jiro", user.name)
user = User(name="taro")
self.assertEqual("taro", user.name)
user.name = "jiro"
self.assertEqual("jiro", user.name)
def test_accessible_with_tuple_argument(self):
class User:
@define_properties(accessible=("name", "email"))
def __init__(self):
pass
user = User()
self.assertEqual(None, user.name)
self.assertEqual(None, user.email)
user.name = "jiro"
user.email = "[email protected]"
self.assertEqual("jiro", user.name)
self.assertEqual("[email protected]", user.email)
user = User(name="taro", email="[email protected]")
self.assertEqual("taro", user.name)
self.assertEqual("[email protected]", user.email)
user.name = "jiro"
user.email = "[email protected]"
self.assertEqual("jiro", user.name)
self.assertEqual("[email protected]", user.email)
def test_readable_with_no_arguments(self):
class User:
@define_properties(readable=())
def __init__(self):
pass
user = User()
with self.assertRaises(AttributeError):
user.name
def test_readable_with_string_argument(self):
class User:
@define_properties(readable="name")
def __init__(self):
pass
user = User()
self.assertEqual(None, user.name)
with self.assertRaises(AttributeError):
user.name = "jiro"
user = User(name="taro")
self.assertEqual("taro", user.name)
with self.assertRaises(AttributeError):
user.name = "jiro"
def test_readable_with_tuple_argument(self):
class User:
@define_properties(readable=("name", "email"))
def __init__(self):
pass
user = User()
self.assertEqual(None, user.name)
self.assertEqual(None, user.email)
with self.assertRaises(AttributeError):
user.name = "jiro"
with self.assertRaises(AttributeError):
user.email = "[email protected]"
user = User(name="taro", email="[email protected]")
self.assertEqual("taro", user.name)
self.assertEqual("[email protected]", user.email)
with self.assertRaises(AttributeError):
user.name = "jiro"
with self.assertRaises(AttributeError):
user.email = "[email protected]"
def test_writable_with_no_arguments(self):
class User:
@define_properties(writable=())
def __init__(self):
pass
user = User()
with self.assertRaises(AttributeError):
user.name
user.name = "taro"
def test_writable_with_string_argument(self):
class User:
@define_properties(writable="name")
def __init__(self):
pass
user = User()
with self.assertRaises(AttributeError):
user.name
user.name = "jiro"
user = User(name="taro")
with self.assertRaises(AttributeError):
user.name
user.name = "jiro"
def test_writable_with_tuple_argument(self):
class User:
@define_properties(writable=("name", "email"))
def __init__(self):
pass
user = User()
with self.assertRaises(AttributeError):
user.name
with self.assertRaises(AttributeError):
user.email
user.name = "jiro"
user.email = "[email protected]"
user = User(name="taro", email="[email protected]")
with self.assertRaises(AttributeError):
user.name
with self.assertRaises(AttributeError):
user.email
user.name = "jiro"
user.email = "[email protected]"
if __name__ == "__main__":
unittest.main()
Recommended Posts