1"use strict";(self.webpackChunkblog_2=self.webpackChunkblog_2||[]).push([[4019],{65227:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>h,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var s=t(42464),i=t(74848),a=t(28453);const o={title:"The Prebound Method and Sentinel Object Pattern in Python",date:new Date("2024-01-08T22:40:32.169Z"),description:"An overview of the two patterns in Python",tags:["CS","software"]},r=void 0,l={authorsImageUrls:[]},c=[{value:"Motivation",id:"motivation",level:2},{value:"The Prebound Method Pattern",id:"the-prebound-method-pattern",level:2},{value:"The Sentinel Object Pattern",id:"the-sentinel-object-pattern",level:2},{value:"Conclusion",id:"conclusion",level:2}];function d(e){const n={a:"a",code:"code",h2:"h2",p:"p",pre:"pre",...(0,a.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h2,{id:"motivation",children:"Motivation"}),"\n",(0,i.jsxs)(n.p,{children:["This article reflects on the blog posts, ",(0,i.jsx)(n.a,{href:"https://python-patterns.guide/python/prebound-methods/",children:"The Prebound Method Pattern"})," and ",(0,i.jsx)(n.a,{href:"https://python-patterns.guide/python/sentinel-object/",children:"The Sentinel Object Pattern"}),", by Brandon Rhodes. I'll briefly summarize the patterns and discuss my thoughts on them."]}),"\n",(0,i.jsx)(n.h2,{id:"the-prebound-method-pattern",children:"The Prebound Method Pattern"}),"\n",(0,i.jsxs)(n.p,{children:["This pattern can be observed when using built-in functions such as ",(0,i.jsx)(n.code,{children:"random"})," and ",(0,i.jsx)(n.code,{children:"logging"}),". Instead of needing to create a new instance of the class, we can simply call the function directly. This is possible because a default instance is created within the module, and the instance method is assigned to the module's global namespace."]}),"\n",(0,i.jsx)(n.p,{children:"For example, a logger could be created as follows:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'class Logger:\n def __init__(self, name):\n self.name = name\n\n def log(self, message):\n print(f"{self.name}: {message}")\n\n_default_logger = Logger("default")\nlog = _default_logger.log\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"log"})," method is assigned to the module's global namespace, allowing it to be called directly. This is a simple example, but it's useful when you want to create and use a default instance of a class without needing to create a new one."]}),"\n",(0,i.jsx)(n.p,{children:"This supports the usage of the logger as follows:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'import logger\n\nlogger.log("Hello World")\n'})}),"\n",(0,i.jsxs)(n.p,{children:["This pattern isn't super common, but it's a neat trick to know about. It feels a bit like the ",(0,i.jsx)(n.a,{href:"https://refactoring.guru/design-patterns/singleton",children:"singleton"})," pattern. However, since we don't restrict the number of instances that can be created, it's not truly a singleton."]}),"\n",(0,i.jsx)(n.h2,{id:"the-sentinel-object-pattern",children:"The Sentinel Object Pattern"}),"\n",(0,i.jsxs)(n.p,{children:["This pattern highlights that, despite Python's support for ",(0,i.jsx)(n.code,{children:"None"}),", we can sometimes provide a more meaningful value to represent a missing value. This is particularly useful when we need to differentiate between a missing value and a valid one."]}),"\n",(0,i.jsxs)(n.p,{children:["To illustrate with a similar example from the original article, consider the context of open-source software, where we might want to specify the type of license. We could use ",(0,i.jsx)(n.code,{children:"None"})," to represent an unspecified license type. However, a valid alternative could be to assign it to a ",(0,i.jsx)(n.code,{children:"License"}),' object that clearly indicates the type of license (e.g., "not specified" or ',(0,i.jsx)(n.a,{href:"https://choosealicense.com/licenses/unlicense/",children:'"unlicensed"'}),", which may mean different things)."]}),"\n",(0,i.jsxs)(n.p,{children:["To give a similar example provided in the original article, suppose in the context of open source software, we want to provide a value of the license type. We can use ",(0,i.jsx)(n.code,{children:"None"})," to represent the case where the license type is not specified. However, a valid alternative could be assigning it to a ",(0,i.jsx)(n.code,{children:"License"}),' object that clearly indicates the type of license (whether it is "not specified" or "unlicensed", they may mean different things).']}),"\n",(0,i.jsx)(n.p,{children:"Here's an example of the pattern in action:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'class License:\n def __init__(self, name):\n self.name = name\n\n def get_name(self):\n return self.name\n\nclass Package:\n def __init__(self, name, license):\n self.name = name\n self.license = license\n\n# INSTEAD OF\npackages = [\n Package("dummy1", None),\n Package("dummy2", License("BSD")),\n]\n\nfor package in packages:\n if package.license is None:\n print("not specified")\n else:\n print(package.license.get_name())\n\n# WE CAN USE\nUNLICENSED = License("unlicensed")\nNOT_SPECIFIED = License("not specified")\n\npackages = [\n Package("dummy1", UNLICENSED),\n Package("dummy2", NOT_SPECIFIED),\n Package("dummy3", License("BSD")),\n]\n\nfor package in packages:\n print(package.license.get_name())\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The advantage here is replacing ",(0,i.jsx)(n.code,{children:"None"})," with more explicit values, which documents the intent more clearly. This may also reduce the need for ",(0,i.jsx)(n.code,{children:"None"})," checks in the code."]}),"\n",(0,i.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,i.jsx)(n.p,{children:"The two patterns are subtle but can be quite useful in certain cases. It's good to be aware of them and use them when appropriate. The original articles are also worth reading for more detailed explanations."})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},28453:(e,n,t)=>{t.d(n,{R:()=>o,x:()=>r});var s=t(96540);const i={},a=s.createContext(i);function o(e){const n=s.useContext(a);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(a.Provider,{value:n},e.children)}},42464:e=>{e.exports=JSON.parse('{"permalink":"/blog/prebound-sentinel-pattern-in-python","source":"@site/blog/prebound-sentinel-pattern-in-python/index.md","title":"The Prebound Method and Sentinel Object Pattern in Python","description":"An overview of the two patterns in Python","date":"2024-01-08T22:40:32.169Z","tags":[{"inline":true,"label":"CS","permalink":"/blog/tags/cs"}
1,{"inline":true,"label":"software","permalink":"/blog/tags/software"}],"readingTime":2.765,"hasTruncateMarker":false,"authors":[],"frontMatter":{"title":"The Prebound Method and Sentinel Object Pattern in Python","date":"2024-01-08T22:40:32.169Z","description":"An overview of the two patterns in Python","tags":["CS","software"]},"unlisted":false,"prevItem":{"title":"Easy vs Right","permalink":"/blog/easy-vs-right"},"nextItem":{"title":"Software Problems - Exceptions","permalink":"/blog/software-problems-exceptions"}}')}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.