Write a short property definition in Python

The code that normally defines a property with @ property is long

In 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.

Create a helper function that defines a property

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_propertyCould significantly reduce the amount of code,nameOremailI feel redundant when I write multiple times.

Create a decorator for the constructor

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_propertiesYou 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.

Completed form

To make it a little more convenient, make the following changes:

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

Write a short property definition in Python
Write A * (A-star) algorithm in Python
Write a pie chart in Python
Write a vim plugin in Python
Write a depth-first search in Python
Write the test in a python docstring
Write a Caesar cipher program in Python
Write a simple greedy algorithm in Python
Write a simple Vim Plugin in Python 3
Write Python in MySQL
Write Pandoc filters in Python
Create a function in Python
Create a dictionary in Python
Write a super simple molecular dynamics program in python
Write beta distribution in Python
Write python in Rstudio (reticulate)
I want to write in Python! (2) Let's write a test
Make a bookmarklet in Python
Write a log-scale histogram on the x-axis in python
Draw a heart in Python
Maybe in a python (original title: Maybe in Python)
How to write a Python class
Write a table-driven test in C
[python] Manage functions in a list
Function argument type definition in python
Write JSON Schema in Python DSL
Create a DI Container in Python
Write an HTTP / 2 server in Python
Draw a scatterplot matrix in python
Write AWS Lambda function in Python
ABC166 in Python A ~ C problem
Create a binary file in Python
Write selenium test code in python
Solve ABC036 A ~ C in Python
Implementing a simple algorithm in Python 2
Create a Kubernetes Operator in Python
Solve ABC037 A ~ C in Python
Run a simple algorithm in Python
Draw a CNN diagram in Python
Create a random string in Python
Write C unit tests in Python
Schedule a Zoom meeting in Python
Write a batch script with Python3.5 ~
When writing a program in Python
I get a can't set attribute when using @property in python
Write documentation in Sphinx with Python Livereload
Spiral book in Python! Python with a spiral book! (Chapter 14 ~)
Solve ABC175 A, B, C in Python
Use print in a Python2 lambda expression
A simple HTTP client implemented in Python
Do a non-recursive Euler Tour in Python
I made a payroll program in Python!
Try sending a SYN packet in Python
Try drawing a simple animation in Python
Create a simple GUI app in Python
Create a JSON object mapper in Python
Draw a heart in Python Part 2 (SymPy)
[Python] [Windows] Take a screen capture in Python
Write O_SYNC file in C and Python
Run the Python interpreter in a script
How to get a stacktrace in python