{"id":2728,"date":"2021-10-28T15:48:09","date_gmt":"2021-10-28T15:48:09","guid":{"rendered":"https:\/\/www.pythontutorial.net\/?page_id=2728"},"modified":"2025-03-28T03:27:20","modified_gmt":"2025-03-28T03:27:20","slug":"python-descriptors","status":"publish","type":"page","link":"https:\/\/www.pythontutorial.net\/python-oop\/python-descriptors\/","title":{"rendered":"Python Descriptors"},"content":{"rendered":"\n<p><strong>Summary<\/strong>: in this tutorial, you&#8217;ll learn about Python descriptors, how descriptors work, and how to apply them more effectively.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='introduction-to-the-python-descriptors'>Introduction to the Python descriptors <a href=\"#introduction-to-the-python-descriptors\" class=\"anchor\" id=\"introduction-to-the-python-descriptors\" title=\"Anchor for Introduction to the Python descriptors\">#<\/a><\/h2>\n\n\n\n<p>Suppose you have a <a href=\"https:\/\/www.pythontutorial.net\/python-oop\/python-class\/\">class<\/a> <code>Person<\/code> with two <a href=\"https:\/\/www.pythontutorial.net\/python-oop\/python-instance-variables\/\">instance attributes<\/a> <code>first_name<\/code> and <code>last_name<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Person<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__init__<\/span><span class=\"hljs-params\">(self, first_name, last_name)<\/span>:<\/span>\n        self.first_name = first_name\n        self.last_name = last_name<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And you want the <code>first_name<\/code> and <code>last_name<\/code> attributes to be non-empty strings. These plain attributes cannot guarantee this.<\/p>\n\n\n\n<p>To enforce the data validity, you can use <a href=\"https:\/\/www.pythontutorial.net\/python-oop\/python-properties\/\">property<\/a> with a getter and setter methods, like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Person<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__init__<\/span><span class=\"hljs-params\">(self, first_name, last_name)<\/span>:<\/span>\n        self.first_name = first_name\n        self.last_name = last_name\n\n<span class=\"hljs-meta\">    @property<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">first_name<\/span><span class=\"hljs-params\">(self)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">return<\/span> self._first_name\n\n<span class=\"hljs-meta\">    @first_name.setter<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">first_name<\/span><span class=\"hljs-params\">(self, value)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">if<\/span> <span class=\"hljs-keyword\">not<\/span> isinstance(value, str):\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">'The first name must a string'<\/span>)\n\n        <span class=\"hljs-keyword\">if<\/span> len(value) == <span class=\"hljs-number\">0<\/span>:\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">'The first name cannot be empty'<\/span>)\n\n        self._first_name = value\n\n<span class=\"hljs-meta\">    @property<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">last_name<\/span><span class=\"hljs-params\">(self)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">return<\/span> self._last_name\n\n<span class=\"hljs-meta\">    @last_name.setter<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">last_name<\/span><span class=\"hljs-params\">(self, value)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">if<\/span> <span class=\"hljs-keyword\">not<\/span> isinstance(value, str):\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">'The last name must a string'<\/span>)\n\n        <span class=\"hljs-keyword\">if<\/span> len(value) == <span class=\"hljs-number\">0<\/span>:\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">'The last name cannot be empty'<\/span>)\n\n        self._last_name = value<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this <code>Person<\/code> class, the getter returns the attribute value while the setter validates it before assigning it to the attribute.<\/p>\n\n\n\n<p>This code works perfectly fine. However, it is redundant because the validation logic validates the first and last names is the same.<\/p>\n\n\n\n<p>Also, if the class has more attributes that require a non-empty string, you need to duplicate this logic in other properties. In other words, this validation logic is not reusable.<\/p>\n\n\n\n<p>To avoid duplicating the logic, you may have a method that validates data and reuse this method in other properties. This approach will enable reusability. However, Python has a better way to solve this by using descriptors.<\/p>\n\n\n\n<p>First, define a descriptor class that implements three methods <code>__set_name__<\/code>, <code>__get__<\/code>, and <code>__set__<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RequiredString<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__set_name__<\/span><span class=\"hljs-params\">(self, owner, name)<\/span>:<\/span>\n        self.property_name = name\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__get__<\/span><span class=\"hljs-params\">(self, instance, owner)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">if<\/span> instance <span class=\"hljs-keyword\">is<\/span> <span class=\"hljs-literal\">None<\/span>:\n            <span class=\"hljs-keyword\">return<\/span> self\n\n        <span class=\"hljs-keyword\">return<\/span> instance.__dict__&#91;self.property_name] <span class=\"hljs-keyword\">or<\/span> <span class=\"hljs-literal\">None<\/span>\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__set__<\/span><span class=\"hljs-params\">(self, instance, value)<\/span>:<\/span>\n        <span class=\"hljs-keyword\">if<\/span> <span class=\"hljs-keyword\">not<\/span> isinstance(value, str):\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">f'The <span class=\"hljs-subst\">{self.property_name}<\/span> must be a string'<\/span>)\n\n        <span class=\"hljs-keyword\">if<\/span> len(value) == <span class=\"hljs-number\">0<\/span>:\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">f'The <span class=\"hljs-subst\">{self.property_name}<\/span> cannot be empty'<\/span>)\n\n        instance.__dict__&#91;self.property_name] = value<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Second, use the <code>RequiredString<\/code> class in the <code>Person<\/code> class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Person<\/span>:<\/span>\n    first_name = RequiredString()\n    last_name = RequiredString()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If you assign an empty string or a non-string value to the <code>first_name<\/code> or <code>last_name<\/code> attribute of the <code>Person<\/code> class, you&#8217;ll get an error.<\/p>\n\n\n\n<p>For example, the following attempts to assign an empty string to the <code>first_name<\/code> attribute:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-keyword\">try<\/span>:\n    person = Person()\n    person.first_name = <span class=\"hljs-string\">''<\/span>\n<span class=\"hljs-keyword\">except<\/span> ValueError <span class=\"hljs-keyword\">as<\/span> e:\n    print(e)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Error:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">The first_name must be a string<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Also, you can use the <code>RequiredString<\/code> class in any class with attributes that require a non-empty string value.<\/p>\n\n\n\n<p>Besides the <code>RequiredString<\/code>, you can define other descriptors to enforce other data types like age, email, and phone. And this is just a simple application of the descriptors.<\/p>\n\n\n\n<p>Let&#8217;s understand how descriptors work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='descriptor-protocol'>Descriptor protocol <a href=\"#descriptor-protocol\" class=\"anchor\" id=\"descriptor-protocol\" title=\"Anchor for Descriptor protocol\">#<\/a><\/h2>\n\n\n\n<p>In Python, the descriptor protocol consists of three methods:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>__get__<\/code> gets an attribute value<\/li>\n\n\n\n<li><code>__set__<\/code> sets an attribute value<\/li>\n\n\n\n<li><code>__delete__<\/code> deletes an attribute<\/li>\n<\/ul>\n\n\n\n<p>Optionally, a descriptor can have the <code>__set_name__<\/code> method that sets an attribute on an instance of a class to a new value.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='what-is-a-descriptor'>What is a descriptor <a href=\"#what-is-a-descriptor\" class=\"anchor\" id=\"what-is-a-descriptor\" title=\"Anchor for What is a descriptor\">#<\/a><\/h2>\n\n\n\n<p>A descriptor is an object of a class that implements one of the methods specified in the descriptor protocol.<\/p>\n\n\n\n<p>Descriptors have two types: data descriptor and non-data descriptor.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A data descriptor is an object of a class that implements the <code>__set__<\/code> and\/or <code>__delete__<\/code> method.<\/li>\n\n\n\n<li>A non-data descriptor is an object that implements the <code>__get__<\/code> method only.<\/li>\n<\/ol>\n\n\n\n<p>The descriptor type specifies the <a href=\"https:\/\/www.pythontutorial.net\/python-oop\/python-data-descriptors\/\">property lookup resolution that we&#8217;ll cover in the next tutorial<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='how-descriptors-work'>How descriptors work <a href=\"#how-descriptors-work\" class=\"anchor\" id=\"how-descriptors-work\" title=\"Anchor for How descriptors work\">#<\/a><\/h2>\n\n\n\n<p>The following modifies the <code>RequiredString<\/code> class to include the <code>print<\/code> statements that print out the arguments.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RequiredString<\/span>:<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__set_name__<\/span><span class=\"hljs-params\">(self, owner, name)<\/span>:<\/span>\n        print(<span class=\"hljs-string\">f'__set_name__ was called with owner=<span class=\"hljs-subst\">{owner}<\/span> and name=<span class=\"hljs-subst\">{name}<\/span>'<\/span>)\n        self.property_name = name\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__get__<\/span><span class=\"hljs-params\">(self, instance, owner)<\/span>:<\/span>\n        print(<span class=\"hljs-string\">f'__get__ was called with instance=<span class=\"hljs-subst\">{instance}<\/span> and owner=<span class=\"hljs-subst\">{owner}<\/span>'<\/span>)\n        <span class=\"hljs-keyword\">if<\/span> instance <span class=\"hljs-keyword\">is<\/span> <span class=\"hljs-literal\">None<\/span>:\n            <span class=\"hljs-keyword\">return<\/span> self\n\n        <span class=\"hljs-keyword\">return<\/span> instance.__dict__&#91;self.property_name] <span class=\"hljs-keyword\">or<\/span> <span class=\"hljs-literal\">None<\/span>\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__set__<\/span><span class=\"hljs-params\">(self, instance, value)<\/span>:<\/span>\n        print(<span class=\"hljs-string\">f'__set__ was called with instance=<span class=\"hljs-subst\">{instance}<\/span> and value=<span class=\"hljs-subst\">{value}<\/span>'<\/span>)\n\n        <span class=\"hljs-keyword\">if<\/span> <span class=\"hljs-keyword\">not<\/span> isinstance(value, str):\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">f'The <span class=\"hljs-subst\">{self.property_name}<\/span> must a string'<\/span>)\n\n        <span class=\"hljs-keyword\">if<\/span> len(value) == <span class=\"hljs-number\">0<\/span>:\n            <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">f'The <span class=\"hljs-subst\">{self.property_name}<\/span> cannot be empty'<\/span>)\n\n        instance.__dict__&#91;self.property_name] = value\n\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">Person<\/span>:<\/span>\n    first_name = RequiredString()\n    last_name = RequiredString()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='the-__set_name__-method'>The __set_name__ method <a href=\"#the-__set_name__-method\" class=\"anchor\" id=\"the-__set_name__-method\" title=\"Anchor for The __set_name__ method\">#<\/a><\/h3>\n\n\n\n<p>When you compile the code, you&#8217;ll see that Python creates the descriptor objects for <code>first_name<\/code> and <code>last_name<\/code> and automatically call the <code>__set_name__<\/code> method of these objects. Here&#8217;s the output:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">__set_name__ was called with owner=&lt;class '__main__.Person'&gt; and name=first_name\n__set_name__ was called with owner=&lt;class '__main__.Person'&gt; and name=last_name<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this example, the owner argument of <code>__set_name__<\/code> is set to the <code>Person<\/code> class in the <code>__main__<\/code> module, and the <code>name<\/code> argument is set to the <code>first_name<\/code> and <code>last_name<\/code> attribute accordingly.<\/p>\n\n\n\n<p>It means that Python automatically calls the <code>__set_name__<\/code> when the owning class <code>Person<\/code> is created. The following statements are equivalent:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">first_name = RequiredString()<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>and<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">first_name.__set_name__(Person, <span class=\"hljs-string\">'first_name'<\/span>)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Inside, the <code>__set_name__<\/code> method, we assign the <code>name<\/code> argument to the <code>property_name<\/code> instance attribute of the <code>descriptor<\/code> object so that we can access it later in the <code>__get__<\/code> and <code>__set__<\/code> method:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">self.property_name = name<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>first_name<\/code> and <code>last_name<\/code> are the <a href=\"https:\/\/www.pythontutorial.net\/python-oop\/python-class-variables\/\">class variables<\/a> of the <code>Person<\/code> class. If you look at the <code>Person.__dict__<\/code> class attribute, you&#8217;ll see two descriptor objects <code>first_name<\/code> and <code>last_name<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-keyword\">from<\/span> pprint <span class=\"hljs-keyword\">import<\/span> pprint\n\npprint(Person.__dict__)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Output:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">mappingproxy({<span class=\"hljs-string\">'__dict__'<\/span>: &lt;attribute <span class=\"hljs-string\">'__dict__'<\/span> of <span class=\"hljs-string\">'Person'<\/span> objects&gt;,\n            <span class=\"hljs-string\">'__doc__'<\/span>: <span class=\"hljs-literal\">None<\/span>,\n            <span class=\"hljs-string\">'__module__'<\/span>: <span class=\"hljs-string\">'__main__'<\/span>,\n            <span class=\"hljs-string\">'__weakref__'<\/span>: &lt;attribute <span class=\"hljs-string\">'__weakref__'<\/span> of <span class=\"hljs-string\">'Person'<\/span> objects&gt;,\n            <span class=\"hljs-string\">'first_name'<\/span>: &lt;__main__.RequiredString object at <span class=\"hljs-number\">0x0000019D6AB947F0<\/span>&gt;,\n            <span class=\"hljs-string\">'last_name'<\/span>: &lt;__main__.RequiredString object at <span class=\"hljs-number\">0x0000019D6ACFBE80<\/span>&gt;})<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='the-__set__-method'>The __set__ method <a href=\"#the-__set__-method\" class=\"anchor\" id=\"the-__set__-method\" title=\"Anchor for The __set__ method\">#<\/a><\/h3>\n\n\n\n<p>Here&#8217;s the <code>__set__<\/code> method of the <code>RequiredString<\/code> class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__set__<\/span><span class=\"hljs-params\">(self, instance, value)<\/span>:<\/span>\n    print(<span class=\"hljs-string\">f'__set__ was called with instance=<span class=\"hljs-subst\">{instance}<\/span> and value=<span class=\"hljs-subst\">{value}<\/span>'<\/span>)\n\n    <span class=\"hljs-keyword\">if<\/span> <span class=\"hljs-keyword\">not<\/span> isinstance(value, str):\n        <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">f'The <span class=\"hljs-subst\">{self.property_name}<\/span> must be a string'<\/span>)\n\n    <span class=\"hljs-keyword\">if<\/span> len(value) == <span class=\"hljs-number\">0<\/span>:\n        <span class=\"hljs-keyword\">raise<\/span> ValueError(<span class=\"hljs-string\">f'The <span class=\"hljs-subst\">{self.property_name}<\/span> cannot be empty'<\/span>)\n\n    instance.__dict__&#91;self.property_name] = value<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>When you assign the new value to a descriptor, Python calls <code>__set__<\/code> method to set the attribute on an instance of the owner class to the new value. For example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">person = Person()\nperson.first_name = <span class=\"hljs-string\">'John'<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Output:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">__set__ was called <span class=\"hljs-keyword\">with<\/span> instance=&lt;__main__.Person object at <span class=\"hljs-number\">0x000001F85F7167F0<\/span>&gt; <span class=\"hljs-keyword\">and<\/span> value=John<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this example, the <code>instance<\/code> argument is <code>person<\/code> object and the value is the string <code>'John'<\/code>. Inside the <code>__set__<\/code> method, we raise a <code>ValueError<\/code> if the new value is not a string or if it is an empty string.<\/p>\n\n\n\n<p>Otherwise, we assign the value to the instance attribute <code>first_name<\/code> of the <code>person<\/code> object:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">instance.__dict__&#91;self.property_name] = value<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note that Python uses <code>instance.__dict__<\/code> dictionary to store instance attributes of the <code>instance<\/code> object.<\/p>\n\n\n\n<p>Once you set the <code>first_name<\/code> and <code>last_name<\/code> of an instance of the <code>Person<\/code> object, you&#8217;ll see the instance attributes with the same names in the instance&#8217;s <code>__dict__<\/code>. For example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">person = Person()\nprint(person.__dict__)  <span class=\"hljs-comment\"># {}<\/span>\n\nperson.first_name = <span class=\"hljs-string\">'John'<\/span>\nperson.last_name = <span class=\"hljs-string\">'Doe'<\/span>\n\nprint(person.__dict__) <span class=\"hljs-comment\"># {'first_name': 'John', 'last_name': 'Doe'}<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Output:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">{}\n{'first_name': 'John', 'last_name': 'Doe'}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id='the-__get__-method'>The __get__ method <a href=\"#the-__get__-method\" class=\"anchor\" id=\"the-__get__-method\" title=\"Anchor for The __get__ method\">#<\/a><\/h3>\n\n\n\n<p>The following shows the <code>__get__<\/code> method of the <code>RequiredString<\/code> class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title\">__get__<\/span><span class=\"hljs-params\">(self, instance, owner)<\/span>:<\/span>\n    print(<span class=\"hljs-string\">f'__get__ was called with instance=<span class=\"hljs-subst\">{instance}<\/span> and owner=<span class=\"hljs-subst\">{owner}<\/span>'<\/span>)\n    <span class=\"hljs-keyword\">if<\/span> instance <span class=\"hljs-keyword\">is<\/span> <span class=\"hljs-literal\">None<\/span>:\n        <span class=\"hljs-keyword\">return<\/span> self\n\n    <span class=\"hljs-keyword\">return<\/span> instance.__dict__&#91;self.property_name] <span class=\"hljs-keyword\">or<\/span> <span class=\"hljs-literal\">None<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Python calls the <code>__get__<\/code> method of the <code>Person<\/code>&#8216;s object when you access the <code>first_name<\/code> attribute. For example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">person = Person()\n\nperson.first_name = <span class=\"hljs-string\">'John'<\/span>\nprint(person.first_name)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Output:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">__set__ was called with instance=&lt;__main__.Person object at 0x000001F85F7167F0&gt; and value=John\n__get__ was called with instance=&lt;__main__.Person object at 0x000001F85F7167F0&gt; and owner=&lt;class '__main__.Person'&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>__get__<\/code> method returns the descriptor if the <code>instance<\/code> is <code>None<\/code>. For example, if you access the <code>first_name<\/code> or <code>last_name<\/code> from the <code>Person<\/code> class, you&#8217;ll see the descriptor object:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"Python\" data-shcb-language-slug=\"python\"><span><code class=\"hljs language-python\">print(Person.first_name)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Python<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">python<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Output:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"plaintext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">&lt;__main__.RequiredString object at 0x000001AF1DA147F0&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">plaintext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If the <code>instance<\/code> is not <code>None<\/code>, the <code>__get__()<\/code> method returns the value of the attribute with the name <code>property_name<\/code> of the <code>instance<\/code> object.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id='summary'>Summary <a href=\"#summary\" class=\"anchor\" id=\"summary\" title=\"Anchor for Summary\">#<\/a><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Descriptors are objects of class that implements one of the method in the descriptor protocol including <code>__set__<\/code>, <code>__get__<\/code>, <code>__del__<\/code><\/li>\n<\/ul>\n<div class=\"helpful-block-content\" data-title=\"\">\n\t<header>\n\t\t<div class=\"wth-question\">Was this tutorial helpful ?<\/div>\n\t\t<div class=\"wth-thumbs\">\n\t\t\t<button\n\t\t\t\tdata-post=\"2728\"\n\t\t\t\tdata-post-url=\"https:\/\/www.pythontutorial.net\/python-oop\/python-descriptors\/\"\n\t\t\t\tdata-post-title=\"Python Descriptors\"\n\t\t\t\tdata-response=\"1\"\n\t\t\t\tclass=\"wth-btn-rounded wth-yes-btn\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\txmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t\tclass=\"feather feather-thumbs-up block w-full h-full\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3\"\n\t\t\t\t\t><\/path>\n\t\t\t\t<\/svg>\n\t\t\t\t<span class=\"sr-only\"> Yes <\/span>\n\t\t\t<\/button>\n\n\t\t\t<button\n\t\t\t\tdata-response=\"0\"\n\t\t\t\tdata-post=\"2728\"\n\t\t\t\tdata-post-url=\"https:\/\/www.pythontutorial.net\/python-oop\/python-descriptors\/\"\n\t\t\t\tdata-post-title=\"Python Descriptors\"\n\t\t\t\tclass=\"wth-btn-rounded wth-no-btn\"\n\t\t\t>\n\t\t\t\t<svg\n\t\t\t\t\txmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\tstroke-width=\"2\"\n\t\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t\t>\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17\"\n\t\t\t\t\t><\/path>\n\t\t\t\t<\/svg>\n\t\t\t\t<span class=\"sr-only\"> No <\/span>\n\t\t\t<\/button>\n\t\t<\/div>\n\t<\/header>\n\n\t<div class=\"wth-form hidden\">\n\t\t<div class=\"wth-form-wrapper\">\n\t\t\t<div class=\"wth-title\"><\/div>\n\t\t\t<textarea class=\"wth-message\"><\/textarea>\n\t\t\t<input type=\"button\" name=\"wth-submit\" class=\"wth-btn wth-btn-submit\" id=\"wth-submit\" \/>\n\t\t\t<input type=\"button\" class=\"wth-btn wth-btn-cancel\" value=\"Cancel\" \/>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Summary: in this tutorial, you&#8217;ll learn about Python descriptors, how descriptors work, and how to apply them more effectively. Introduction to the Python descriptors # Suppose you have a class Person with two instance attributes first_name and last_name: And you want the first_name and last_name attributes to be non-empty strings. These plain attributes cannot guarantee [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":417,"menu_order":38,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-2728","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages\/2728","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/comments?post=2728"}],"version-history":[{"count":1,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages\/2728\/revisions"}],"predecessor-version":[{"id":7181,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages\/2728\/revisions\/7181"}],"up":[{"embeddable":true,"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/pages\/417"}],"wp:attachment":[{"href":"https:\/\/www.pythontutorial.net\/wp-json\/wp\/v2\/media?parent=2728"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}