9. ਕਲਾਸਾਂ

ਕਲਾਸਾਂ ਡਾਟਾ ਅਤੇ ਕਾਰਜਕੁਸ਼ਲਤਾ ਨੂੰ ਇਕੱਠੇ ਸੰਗਠਿਤ ਕਰਨ ਦਾ ਇੱਕ ਤਰੀਕਾ ਪ੍ਰਦਾਨ ਕਰਦੀਆਂ ਹਨ। ਇੱਕ ਨਵੀਂ ਕਲਾਸ ਬਣਾਉਣ ਨਾਲ ਵਸਤੂ ਦੀ ਇੱਕ ਨਵੀਂ ਕਿਸਮ ਬਣਦੀ ਹੈ, ਜਿਸ ਨਾਲ ਉਸ ਕਿਸਮ ਦੇ ਨਵੇਂ ਇੰਸਟੈਂਸ ਬਣਾਏ ਜਾ ਸਕਦੇ ਹਨ। ਹਰ ਕਲਾਸ ਇੰਸਟੈਂਸ ਨਾਲ ਉਸਦੀ ਸਥਿਤੀ ਬਣਾਈ ਰੱਖਣ ਲਈ ਗੁਣ ਜੋੜੇ ਜਾ ਸਕਦੇ ਹਨ। ਕਲਾਸ ਇੰਸਟੈਂਸਾਂ ਵਿੱਚ ਉਨ੍ਹਾਂ ਦੀ ਸਥਿਤੀ ਨੂੰ ਬਦਲਣ ਲਈ ਵਿਧੀਆਂ (methods) ਵੀ ਹੋ ਸਕਦੀਆਂ ਹਨ, ਜੋ ਕਲਾਸ ਵੱਲੋਂ ਪਰਿਭਾਸ਼ਿਤ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।

ਹੋਰ ਪ੍ਰੋਗਰਾਮਿੰਗ ਭਾਸ਼ਾਵਾਂ ਨਾਲ ਤੁਲਨਾ ਕਰਨ ਤੇ, Python ਦੀ ਕਲਾਸ ਪ੍ਰਣਾਲੀ ਘੱਟ ਤੋਂ ਘੱਟ ਨਵੇਂ ਸਿੰਟੈਕਸ ਅਤੇ ਅਰਥਾਤਮਕ ਨਿਯਮਾਂ ਨਾਲ ਕਲਾਸਾਂ ਪ੍ਰਦਾਨ ਕਰਦੀ ਹੈ। ਇਹ C++ ਅਤੇ Modula-3 ਵਿੱਚ ਮਿਲਣ ਵਾਲੀਆਂ ਕਲਾਸ ਪ੍ਰਣਾਲੀਆਂ ਦਾ ਮਿਲਾਪ ਹੈ। Python ਦੀਆਂ ਕਲਾਸਾਂ ਆਬਜੈਕਟ-ਓਰੀਐਂਟਿਡ ਪ੍ਰੋਗਰਾਮਿੰਗ ਦੀਆਂ ਸਾਰੀਆਂ ਮਿਆਰੀ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਪ੍ਰਦਾਨ ਕਰਦੀਆਂ ਹਨ। ਕਲਾਸ ਇਨਹੈਰਿਟੈਂਸ ਪ੍ਰਣਾਲੀ ਕਈ ਬੇਸ ਕਲਾਸਾਂ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ, ਡਿਰਾਈਵਡ ਕਲਾਸ ਆਪਣੀ ਬੇਸ ਕਲਾਸ ਜਾਂ ਕਲਾਸਾਂ ਦੀਆਂ ਕਿਸੇ ਵੀ ਮੈਥਡਾਂ ਨੂੰ ਓਵਰਰਾਈਡ ਕਰ ਸਕਦੀ ਹੈ ਅਤੇ ਕੋਈ ਮੈਥਡ ਉਸੇ ਨਾਮ ਵਾਲੀ ਬੇਸ ਕਲਾਸ ਦੀ ਮੈਥਡ ਨੂੰ ਕਾਲ ਕਰ ਸਕਦੀ ਹੈ। ਆਬਜੈਕਟ ਕਿਸੇ ਵੀ ਮਾਤਰਾ ਅਤੇ ਕਿਸਮ ਦਾ ਡਾਟਾ ਰੱਖ ਸਕਦੇ ਹਨ। ਮੌਡਿਊਲਾਂ ਵਾਂਗ, ਕਲਾਸਾਂ ਵੀ Python ਦੇ ਗਤੀਸ਼ੀਲ ਸੁਭਾਵ ਦਾ ਹਿੱਸਾ ਹਨ: ਉਹ ਰਨਟਾਈਮ ਦੌਰਾਨ ਬਣਦੀਆਂ ਹਨ ਅਤੇ ਬਣਨ ਤੋਂ ਬਾਅਦ ਵੀ ਸੋਧੀਆਂ ਜਾ ਸਕਦੀਆਂ ਹਨ।

C++ ਦੀ ਟਰਮੀਨੋਲੋਜੀ ਵਿੱਚ, ਆਮ ਤੌਰ 'ਤੇ ਕਲਾਸ ਮੈਂਬਰ (ਡਾਟਾ ਮੈਂਬਰ ਸਮੇਤ) public ਹੁੰਦੇ ਹਨ (ਹੇਠਾਂ ਪ੍ਰਾਈਵੇਟ ਵੇਰੀਏਬਲ ਵੇਖੋ), ਅਤੇ ਸਾਰੇ ਮੈਂਬਰ ਫੰਕਸ਼ਨ virtual ਹੁੰਦੇ ਹਨ। Modula-3 ਵਾਂਗ, ਵਿਧੀਆਂ ਦੇ ਅੰਦਰੋਂ ਆਬਜੈਕਟ ਦੇ ਮੈਂਬਰਾਂ ਨੂੰ ਹਵਾਲਾ ਦੇਣ ਲਈ ਕੋਈ ਸੰਖੇਪ ਰੂਪ ਨਹੀਂ ਹੈ। ਮੈਥਡ ਫੰਕਸ਼ਨ ਨੂੰ ਆਬਜੈਕਟ ਨੂੰ ਦਰਸਾਉਂਦੇ ਇੱਕ ਸਪਸ਼ਟ ਪਹਿਲੇ ਆਰਗੂਮੈਂਟ ਨਾਲ ਘੋਸ਼ਿਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਜੋ ਕਾਲ ਵੇਲੇ ਆਪਣੇ ਆਪ ਪ੍ਰਦਾਨ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। Smalltalk ਵਾਂਗ, ਕਲਾਸਾਂ ਖੁਦ ਵੀ ਆਬਜੈਕਟ ਹੁੰਦੀਆਂ ਹਨ। ਇਹ ਇੰਪੋਰਟ ਅਤੇ ਰੀਨੇਮਿੰਗ ਲਈ ਅਰਥਾਤਮਕ ਸਮਰਥਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ। C++ ਅਤੇ Modula-3 ਤੋਂ ਵੱਖਰਾ, ਬਿਲਟ-ਇਨ ਟਾਈਪਾਂ ਨੂੰ ਯੂਜ਼ਰ ਦੁਆਰਾ ਐਕਸਟੈਂਸ਼ਨ ਲਈ ਬੇਸ ਕਲਾਸ ਵਜੋਂ ਵਰਤਿਆ ਜਾ ਸਕਦਾ ਹੈ। ਇਸ ਤੋਂ ਇਲਾਵਾ, C++ ਵਾਂਗ ਖਾਸ ਸਿੰਟੈਕਸ ਵਾਲੇ ਬਹੁਤੇ ਬਿਲਟ-ਇਨ ਓਪਰੇਟਰ (ਅੰਕਗਣਿਤ ਓਪਰੇਟਰ, ਸਬਸਕ੍ਰਿਪਟਿੰਗ ਆਦਿ) ਕਲਾਸ ਇੰਸਟੈਂਸਾਂ ਲਈ ਮੁੜ ਪਰਿਭਾਸ਼ਿਤ ਕੀਤੇ ਜਾ ਸਕਦੇ ਹਨ।

(ਕਲਾਸਾਂ ਬਾਰੇ ਗੱਲ ਕਰਨ ਲਈ ਕੋਈ ਸਰਵਮਾਨਯ ਟਰਮੀਨੋਲੋਜੀ ਨਾ ਹੋਣ ਕਰਕੇ, ਮੈਂ ਕਦੇ-ਕਦੇ Smalltalk ਅਤੇ C++ ਦੇ ਸ਼ਬਦ ਵਰਤਾਂਗਾ। ਮੈਂ Modula-3 ਦੇ ਸ਼ਬਦ ਵਰਤ ਸਕਦਾ ਸੀ ਕਿਉਂਕਿ ਇਸਦੇ ਆਬਜੈਕਟ-ਓਰੀਐਂਟਿਡ ਨਿਯਮ C++ ਨਾਲੋਂ Python ਦੇ ਵਧੇਰੇ ਨੇੜੇ ਹਨ, ਪਰ ਮੈਨੂੰ ਲੱਗਦਾ ਹੈ ਕਿ ਬਹੁਤ ਘੱਟ ਪਾਠਕਾਂ ਨੇ ਇਸ ਬਾਰੇ ਸੁਣਿਆ ਹੋਵੇਗਾ।)

9.1. ਨਾਂਵਾਂ ਅਤੇ ਆਬਜੈਕਟਾਂ ਬਾਰੇ ਇੱਕ ਗੱਲ

ਵਸਤੂਆਂ ਦੀ ਵਿਲੱਖਣਤਾ ਹੁੰਦੀ ਹੈ, ਅਤੇ ਇੱਕੋ ਵਸਤੂ ਨੂੰ ਵੱਖ-ਵੱਖ scopes ਵਿੱਚ ਕਈ ਨਾਮ ਬੰਨ੍ਹੇ ਜਾ ਸਕਦੇ ਹਨ। ਇਸਨੂੰ ਹੋਰ ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ aliasing ਕਿਹਾ ਜਾਂਦਾ ਹੈ। Python 'ਤੇ ਪਹਿਲੀ ਨਜ਼ਰ 'ਚ ਇਹ ਆਮ ਤੌਰ 'ਤੇ ਧਿਆਨ ਵਿੱਚ ਨਹੀਂ ਆਉਂਦਾ, ਅਤੇ immutable ਬੁਨਿਆਦੀ ਕਿਸਮਾਂ (numbers, strings, tuples) ਨਾਲ ਕੰਮ ਕਰਦਿਆਂ ਇਸਨੂੰ ਸੁਰੱਖਿਅਤ ਤੌਰ 'ਤੇ ਨਜ਼ਰਅੰਦਾਜ਼ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਹਾਲਾਂਕਿ, mutable ਵਸਤੂਆਂ (ਜਿਵੇਂ ਕਿ lists, dictionaries ਅਤੇ ਹੋਰ ਬਹੁਤ ਸਾਰੀਆਂ ਕਿਸਮਾਂ) ਨਾਲ ਸੰਬੰਧਿਤ Python code ਦੇ semantics 'ਤੇ aliasing ਦਾ ਹੈਰਾਨ ਕਰਨ ਵਾਲਾ ਪ੍ਰਭਾਵ ਹੋ ਸਕਦਾ ਹੈ। ਇਹ ਆਮ ਤੌਰ 'ਤੇ ਪ੍ਰੋਗਰਾਮ ਦੇ ਫਾਇਦੇ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ, ਕਿਉਂਕਿ ਕੁਝ ਮਾਮਲਿਆਂ ਵਿੱਚ aliases pointers ਵਾਂਗ ਹੀ ਵਿਹਾਰ ਕਰਦੇ ਹਨ। ਉਦਾਹਰਨ ਲਈ, ਇੱਕ object pass ਕਰਨਾ ਸਸਤਾ ਹੁੰਦਾ ਹੈ ਕਿਉਂਕਿ implementation ਦੁਆਰਾ ਸਿਰਫ਼ ਇੱਕ pointer ਹੀ pass ਕੀਤਾ ਜਾਂਦਾ ਹੈ; ਅਤੇ ਜੇਕਰ ਕੋਈ function argument ਵਜੋਂ ਦਿੱਤੇ ਗਏ object ਨੂੰ ਸੋਧਦਾ ਹੈ, ਤਾਂ caller ਨੂੰ ਇਹ ਤਬਦੀਲੀ ਦਿਖਾਈ ਦੇਵੇਗੀ --- ਇਹ Pascal ਵਾਂਗ ਦੋ ਵੱਖ-ਵੱਖ argument passing mechanisms ਦੀ ਲੋੜ ਨੂੰ ਖਤਮ ਕਰਦਾ ਹੈ।

9.2. Python ਵਿੱਚ ਸਕੋਪ ਅਤੇ ਨੇਮਸਪੇਸ

ਕਲਾਸਾਂ ਦੀ ਜਾਣ-ਪਛਾਣ ਕਰਵਾਉਣ ਤੋਂ ਪਹਿਲਾਂ, ਮੈਨੂੰ ਤੁਹਾਨੂੰ Python ਦੇ ਸਕੋਪ ਨਿਯਮਾਂ ਬਾਰੇ ਕੁਝ ਦੱਸਣਾ ਪਵੇਗਾ। ਕਲਾਸ ਪਰਿਭਾਸ਼ਾਵਾਂ ਨੇਮਸਪੇਸ ਨਾਲ ਕੁਝ ਦਿਲਚਸਪ ਤਰੀਕੇ ਨਾਲ ਕੰਮ ਕਰਦੀਆਂ ਹਨ, ਅਤੇ ਜੋ ਕੁਝ ਹੋ ਰਿਹਾ ਹੈ ਉਸਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਸਮਝਣ ਲਈ ਤੁਹਾਨੂੰ ਸਕੋਪ ਅਤੇ ਨੇਮਸਪੇਸ ਦੀ ਕਾਰਵਾਈ ਨੂੰ ਜਾਣਨਾ ਲਾਜ਼ਮੀ ਹੈ। ਇਸ ਤੋਂ ਇਲਾਵਾ, ਇਸ ਵਿਸ਼ੇ ਬਾਰੇ ਗਿਆਨ ਕਿਸੇ ਵੀ ਉੱਨਤ Python ਪ੍ਰੋਗਰਾਮਰ ਲਈ ਲਾਭਦਾਇਕ ਹੈ।

ਆਓ ਕੁਝ ਪਰਿਭਾਸ਼ਾਵਾਂ ਨਾਲ ਸ਼ੁਰੂ ਕਰੀਏ।

ਇੱਕ ਨੇਮਸਪੇਸ ਨਾਮਾਂ ਅਤੇ ਆਬਜੈਕਟਾਂ ਵਿਚਕਾਰ ਇੱਕ ਮੈਪਿੰਗ ਹੁੰਦੀ ਹੈ। ਜ਼ਿਆਦਾਤਰ ਨੇਮਸਪੇਸ ਇਸ ਸਮੇਂ Python ਡਿਕਸ਼ਨਰੀਆਂ ਰਾਹੀਂ ਲਾਗੂ ਕੀਤੇ ਜਾਂਦੇ ਹਨ, ਪਰ ਇਹ ਗੱਲ ਆਮ ਤੌਰ 'ਤੇ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੀ (ਕਾਰਗੁਜ਼ਾਰੀ ਤੋਂ ਇਲਾਵਾ) ਅਤੇ ਭਵਿੱਖ ਵਿੱਚ ਬਦਲ ਵੀ ਸਕਦੀ ਹੈ। ਨੇਮਸਪੇਸਾਂ ਦੇ ਉਦਾਹਰਨ ਹਨ: ਬਿਲਟ-ਇਨ ਨਾਮਾਂ ਦਾ ਸਮੂਹ (ਜਿਸ ਵਿੱਚ abs() ਵਰਗੇ ਫੰਕਸ਼ਨ ਅਤੇ ਬਿਲਟ-ਇਨ ਐਕਸੈਪਸ਼ਨ ਨਾਮ ਸ਼ਾਮਲ ਹਨ), ਕਿਸੇ ਮੌਡਿਊਲ ਦੇ ਗਲੋਬਲ ਨਾਮ, ਅਤੇ ਕਿਸੇ ਫੰਕਸ਼ਨ ਕਾਲ ਦੇ ਸਥਾਨਕ ਨਾਮ। ਇੱਕ ਅਰਥ ਵਿੱਚ, ਕਿਸੇ ਆਬਜੈਕਟ ਦੇ ਐਟ੍ਰਿਬਿਊਟਾਂ ਦਾ ਸਮੂਹ ਵੀ ਇੱਕ ਨੇਮਸਪੇਸ ਬਣਾਉਂਦਾ ਹੈ। ਨੇਮਸਪੇਸਾਂ ਬਾਰੇ ਸਭ ਤੋਂ ਮਹੱਤਵਪੂਰਨ ਗੱਲ ਇਹ ਹੈ ਕਿ ਵੱਖ-ਵੱਖ ਨੇਮਸਪੇਸਾਂ ਦੇ ਨਾਮਾਂ ਵਿਚਕਾਰ ਕੋਈ ਸਿੱਧਾ ਸੰਬੰਧ ਨਹੀਂ ਹੁੰਦਾ। ਉਦਾਹਰਨ ਵਜੋਂ, ਦੋ ਵੱਖਰੇ ਮੌਡਿਊਲ maximize ਨਾਮ ਦਾ ਫੰਕਸ਼ਨ ਪਰਿਭਾਸ਼ਿਤ ਕਰ ਸਕਦੇ ਹਨ ਅਤੇ ਕੋਈ ਉਲਝਣ ਨਹੀਂ ਹੁੰਦੀ — ਵਰਤੋਂਕਾਰਾਂ ਨੂੰ ਮੌਡਿਊਲ ਦੇ ਨਾਮ ਨਾਲ ਇਸਦਾ ਹਵਾਲਾ ਦੇਣਾ ਪੈਂਦਾ ਹੈ।

ਵੈਸੇ, ਮੈਂ ਡੌਟ ਤੋਂ ਬਾਅਦ ਆਉਣ ਵਾਲੇ ਕਿਸੇ ਵੀ ਨਾਮ ਲਈ ਐਟ੍ਰਿਬਿਊਟ ਸ਼ਬਦ ਵਰਤਦਾ ਹਾਂ। ਉਦਾਹਰਨ ਵਜੋਂ, z.real ਐਕਸਪ੍ਰੈਸ਼ਨ ਵਿੱਚ real ਆਬਜੈਕਟ z ਦਾ ਇੱਕ ਐਟ੍ਰਿਬਿਊਟ ਹੈ। ਸਖ਼ਤੀ ਨਾਲ ਕਹੀਏ ਤਾਂ ਮੌਡਿਊਲਾਂ ਵਿੱਚ ਨਾਮਾਂ ਦੇ ਹਵਾਲੇ ਵੀ ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲੇ ਹੀ ਹੁੰਦੇ ਹਨ। modname.funcname ਵਿੱਚ modname ਇੱਕ ਮੌਡਿਊਲ ਆਬਜੈਕਟ ਹੈ ਅਤੇ funcname ਉਸਦਾ ਐਟ੍ਰਿਬਿਊਟ ਹੈ। ਇਸ ਮਾਮਲੇ ਵਿੱਚ ਮੌਡਿਊਲ ਦੇ ਐਟ੍ਰਿਬਿਊਟਾਂ ਅਤੇ ਉਸ ਵਿੱਚ ਪਰਿਭਾਸ਼ਿਤ ਗਲੋਬਲ ਨਾਮਾਂ ਵਿਚਕਾਰ ਸਿੱਧੀ ਮੈਪਿੰਗ ਹੁੰਦੀ ਹੈ ਅਤੇ ਉਹ ਇੱਕੋ ਨੇਮਸਪੇਸ ਸਾਂਝਾ ਕਰਦੇ ਹਨ। [1]

ਐਟ੍ਰਿਬਿਊਟ ਸਿਰਫ਼-ਪੜ੍ਹਨਯੋਗ ਜਾਂ ਲਿਖਣਯੋਗ ਹੋ ਸਕਦੇ ਹਨ। ਲਿਖਣਯੋਗ ਐਟ੍ਰਿਬਿਊਟਾਂ ਨੂੰ ਮੁੱਲ ਅਸਾਈਨ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਮੌਡਿਊਲ ਐਟ੍ਰਿਬਿਊਟ ਲਿਖਣਯੋਗ ਹੁੰਦੇ ਹਨ, ਉਦਾਹਰਨ ਵਜੋਂ ਤੁਸੀਂ modname.the_answer = 42 ਲਿਖ ਸਕਦੇ ਹੋ। ਲਿਖਣਯੋਗ ਐਟ੍ਰਿਬਿਊਟਾਂ ਨੂੰ del ਸਟੇਟਮੈਂਟ ਨਾਲ ਹਟਾਇਆ ਵੀ ਜਾ ਸਕਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, del modname.the_answer modname ਨਾਮਕ ਆਬਜੈਕਟ ਤੋਂ the_answer ਐਟ੍ਰਿਬਿਊਟ ਨੂੰ ਹਟਾ ਦੇਵੇਗਾ।

ਨੇਮਸਪੇਸ ਵੱਖ-ਵੱਖ ਸਮਿਆਂ 'ਤੇ ਬਣਾਏ ਜਾਂਦੇ ਹਨ ਅਤੇ ਉਨ੍ਹਾਂ ਦੀ ਉਮਰ ਵੀ ਵੱਖਰੀ ਹੁੰਦੀ ਹੈ। ਬਿਲਟ-ਇਨ ਨਾਮਾਂ ਵਾਲਾ ਨੇਮਸਪੇਸ Python ਇੰਟਰਪ੍ਰੀਟਰ ਦੇ ਸ਼ੁਰੂ ਹੋਣ ਸਮੇਂ ਬਣਦਾ ਹੈ ਅਤੇ ਕਦੇ ਮਿਟਾਇਆ ਨਹੀਂ ਜਾਂਦਾ। ਕਿਸੇ ਮੌਡਿਊਲ ਦਾ ਗਲੋਬਲ ਨੇਮਸਪੇਸ ਮੌਡਿਊਲ ਦੀ ਪਰਿਭਾਸ਼ਾ ਪੜ੍ਹੇ ਜਾਣ ਸਮੇਂ ਬਣਦਾ ਹੈ ਅਤੇ ਆਮ ਤੌਰ 'ਤੇ ਇੰਟਰਪ੍ਰੀਟਰ ਬੰਦ ਹੋਣ ਤੱਕ ਮੌਜੂਦ ਰਹਿੰਦਾ ਹੈ। ਇੰਟਰਪ੍ਰੀਟਰ ਦੇ ਉੱਚ-ਪੱਧਰੀ ਚਲਾਓ ਦੌਰਾਨ ਚਲਣ ਵਾਲੀਆਂ ਸਟੇਟਮੈਂਟਾਂ, ਭਾਵੇਂ ਉਹ ਸਕ੍ਰਿਪਟ ਫਾਈਲ ਤੋਂ ਪੜ੍ਹੀਆਂ ਗਈਆਂ ਹੋਣ ਜਾਂ ਇੰਟਰਐਕਟਿਵ ਤਰੀਕੇ ਨਾਲ, __main__ ਨਾਮਕ ਮੌਡਿਊਲ ਦਾ ਹਿੱਸਾ ਮੰਨੀਆਂ ਜਾਂਦੀਆਂ ਹਨ, ਇਸ ਲਈ ਉਹਨਾਂ ਦਾ ਆਪਣਾ ਗਲੋਬਲ ਨੇਮਸਪੇਸ ਹੁੰਦਾ ਹੈ। (ਬਿਲਟ-ਇਨ ਨਾਮ ਅਸਲ ਵਿੱਚ builtins ਮੌਡਿਊਲ ਵਿੱਚ ਮੌਜੂਦ ਹੁੰਦੇ ਹਨ।)

ਕਿਸੇ ਫੰਕਸ਼ਨ ਦਾ ਸਥਾਨਕ ਨੇਮਸਪੇਸ ਉਸ ਵੇਲੇ ਬਣਦਾ ਹੈ ਜਦੋਂ ਫੰਕਸ਼ਨ ਕਾਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਜਦੋਂ ਫੰਕਸ਼ਨ ਮੁੜਦਾ ਹੈ ਜਾਂ ਕੋਈ ਐਸੀ ਐਕਸੈਪਸ਼ਨ ਪੈਦਾ ਕਰਦਾ ਹੈ ਜੋ ਫੰਕਸ਼ਨ ਦੇ ਅੰਦਰ ਹੈਂਡਲ ਨਹੀਂ ਕੀਤੀ ਗਈ, ਤਾਂ ਇਹ ਮਿਟ ਜਾਂਦਾ ਹੈ। (ਅਸਲ ਵਿੱਚ, 'ਭੁੱਲ ਜਾਣਾ' ਇਸ ਪ੍ਰਕਿਰਿਆ ਦਾ ਵਧੀਆ ਵਰਣਨ ਹੈ।) ਬੇਸ਼ੱਕ, ਹਰ ਰਿਕਰਸਿਵ ਕਾਲ ਦਾ ਆਪਣਾ ਵੱਖਰਾ ਸਥਾਨਕ ਨੇਮਸਪੇਸ ਹੁੰਦਾ ਹੈ।

ਇੱਕ ਸਕੋਪ Python ਪ੍ਰੋਗਰਾਮ ਦਾ ਉਹ ਪਾਠਕ ਖੇਤਰ ਹੁੰਦਾ ਹੈ ਜਿੱਥੇ ਇੱਕ ਨੇਮਸਪੇਸ ਤੱਕ ਸਿੱਧੀ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇੱਥੇ "ਸਿੱਧੀ ਪਹੁੰਚ" ਦਾ ਅਰਥ ਹੈ ਕਿ ਕਿਸੇ ਨਾਂ ਦੇ ਬਿਨਾਂ ਯੋਗਤਾ ਵਾਲੇ ਹਵਾਲੇ ਦੁਆਰਾ ਉਸ ਨਾਂ ਨੂੰ ਨੇਮਸਪੇਸ ਵਿੱਚ ਲੱਭਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਜਾਂਦੀ ਹੈ।

ਹਾਲਾਂਕਿ ਸਕੋਪਾਂ ਦਾ ਨਿਰਧਾਰਣ ਸਥਿਰ ਤਰੀਕੇ ਨਾਲ ਹੁੰਦਾ ਹੈ, ਪਰ ਉਹਨਾਂ ਦੀ ਵਰਤੋਂ ਗਤੀਸ਼ੀਲ ਤਰੀਕੇ ਨਾਲ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਐਗਜ਼ਿਕਿਊਸ਼ਨ ਦੇ ਕਿਸੇ ਵੀ ਸਮੇਂ 3 ਜਾਂ 4 ਨੇਸਟਡ ਸਕੋਪ ਹੁੰਦੇ ਹਨ ਜਿਨ੍ਹਾਂ ਦੇ ਨੇਮਸਪੇਸ ਸਿੱਧੇ ਤੌਰ 'ਤੇ ਪਹੁੰਚਯੋਗ ਹੁੰਦੇ ਹਨ:

  • ਸਭ ਤੋਂ ਅੰਦਰਲਾ ਸਕੋਪ, ਜਿਸ ਦੀ ਪਹਿਲਾਂ ਖੋਜ ਕੀਤੀ ਜਾਂਦੀ ਹੈ, ਸਥਾਨਕ ਨਾਂਵਾਂ ਨੂੰ ਰੱਖਦਾ ਹੈ

  • ਘੇਰਨ ਵਾਲੇ ਫੰਕਸ਼ਨਾਂ ਦੇ ਸਕੋਪ, ਜਿਨ੍ਹਾਂ ਦੀ ਖੋਜ ਸਭ ਤੋਂ ਨੇੜਲੇ ਸਕੋਪ ਤੋਂ ਸ਼ੁਰੂ ਹੁੰਦੀ ਹੈ, ਗੈਰ-ਸਥਾਨਕ ਪਰ ਗੈਰ-ਗਲੋਬਲ ਨਾਂਵਾਂ ਨੂੰ ਰੱਖਦੇ ਹਨ

  • ਆਖਰੀ ਤੋਂ ਪਹਿਲਾਂ ਵਾਲਾ ਸਕੋਪ ਮੌਜੂਦਾ ਮੌਡਿਊਲ ਦੇ ਗਲੋਬਲ ਨਾਂਵਾਂ ਨੂੰ ਰੱਖਦਾ ਹੈ

  • ਸਭ ਤੋਂ ਬਾਹਰਲਾ ਸਕੋਪ (ਜਿਸ ਦੀ ਖੋਜ ਸਭ ਤੋਂ ਆਖਿਰ ਵਿੱਚ ਹੁੰਦੀ ਹੈ) ਬਿਲਟ-ਇਨ ਨਾਂਵਾਂ ਵਾਲਾ ਨੇਮਸਪੇਸ ਹੁੰਦਾ ਹੈ

ਜੇ ਕਿਸੇ ਨਾਮ ਨੂੰ global ਘੋਸ਼ਿਤ ਕੀਤਾ ਗਿਆ ਹੋਵੇ, ਤਾਂ ਉਸ ਨਾਮ ਨਾਲ ਸੰਬੰਧਿਤ ਸਾਰੇ ਹਵਾਲੇ ਅਤੇ assignments ਸਿੱਧੇ ਮੋਡੀਊਲ ਦੇ global ਨਾਂਵਾਂ ਵਾਲੇ ਸਕੋਪ ਵਿੱਚ ਜਾਂਦੇ ਹਨ। ਸਭ ਤੋਂ ਅੰਦਰਲੇ ਸਕੋਪ ਤੋਂ ਬਾਹਰ ਮੌਜੂਦ ਵੇਰੀਏਬਲਾਂ ਨੂੰ ਮੁੜ ਬਾਈਂਡ ਕਰਨ ਲਈ nonlocal ਸਟੇਟਮੈਂਟ ਵਰਤੀ ਜਾ ਸਕਦੀ ਹੈ। ਜੇਕਰ ਉਨ੍ਹਾਂ ਨੂੰ nonlocal ਘੋਸ਼ਿਤ ਨਾ ਕੀਤਾ ਗਿਆ ਹੋਵੇ, ਤਾਂ ਉਹ ਸਿਰਫ਼-ਪੜ੍ਹਨਯੋਗ ਰਹਿੰਦੇ ਹਨ (ਅਜਿਹੇ ਵੇਰੀਏਬਲ ਵਿੱਚ ਲਿਖਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਨਾਲ ਅੰਦਰਲੇ ਸਕੋਪ ਵਿੱਚ ਇੱਕ ਨਵਾਂ ਸਥਾਨਕ ਵੇਰੀਏਬਲ ਬਣ ਜਾਂਦਾ ਹੈ ਅਤੇ ਬਾਹਰਲੇ ਸਕੋਪ ਵਾਲਾ ਇੱਕੋ ਹੀ ਨਾਮ ਵਾਲਾ ਵੇਰੀਏਬਲ ਅਣਬਦਲਿਆ ਰਹਿੰਦਾ ਹੈ)।

ਆਮ ਤੌਰ 'ਤੇ, ਸਥਾਨਕ ਸਕੋਪ (ਟੈਕਸਟੂਅਲ ਤੌਰ 'ਤੇ) ਮੌਜੂਦਾ ਫੰਕਸ਼ਨ ਦੇ ਸਥਾਨਕ ਨਾਮਾਂ ਨੂੰ ਹਵਾਲਾ ਦਿੰਦਾ ਹੈ। ਫੰਕਸ਼ਨਾਂ ਦੇ ਬਾਹਰ, ਸਥਾਨਕ ਸਕੋਪ ਗਲੋਬਲ ਸਕੋਪ ਵਾਂਗ ਹੀ ਮੋਡਿਊਲ ਦੇ ਨੇਮਸਪੇਸ ਨੂੰ ਹਵਾਲਾ ਦਿੰਦਾ ਹੈ। ਕਲਾਸ ਪਰਿਭਾਸ਼ਾਵਾਂ ਸਥਾਨਕ ਸਕੋਪ ਵਿੱਚ ਇੱਕ ਹੋਰ ਨੇਮਸਪੇਸ ਸ਼ਾਮਿਲ ਕਰਦੀਆਂ ਹਨ।

ਇਹ ਸਮਝਣਾ ਮਹੱਤਵਪੂਰਨ ਹੈ ਕਿ ਸਕੋਪ ਪਾਠਕ ਰੂਪ ਵਿੱਚ ਨਿਰਧਾਰਤ ਹੁੰਦੇ ਹਨ: ਕਿਸੇ ਮੋਡੀਊਲ ਵਿੱਚ ਪਰਿਭਾਸ਼ਿਤ ਫੰਕਸ਼ਨ ਦਾ ਗਲੋਬਲ ਸਕੋਪ ਉਸ ਮੋਡੀਊਲ ਦਾ namespace ਹੁੰਦਾ ਹੈ, ਭਾਵੇਂ ਫੰਕਸ਼ਨ ਨੂੰ ਕਿੱਥੋਂ ਜਾਂ ਕਿਸ alias ਰਾਹੀਂ ਕਾਲ ਕੀਤਾ ਜਾਵੇ। ਦੂਜੇ ਪਾਸੇ, ਨਾਮਾਂ ਦੀ ਅਸਲ ਖੋਜ ਚਲਾਉਣ ਸਮੇਂ run time ਗਤੀਸ਼ੀਲ ਤਰੀਕੇ ਨਾਲ ਕੀਤੀ ਜਾਂਦੀ ਹੈ --- ਹਾਲਾਂਕਿ, ਭਾਸ਼ਾ ਦੀ ਪਰਿਭਾਸ਼ਾ "compile" ਸਮੇਂ static name resolution ਵੱਲ ਵਿਕਸਿਤ ਹੋ ਰਹੀ ਹੈ, ਇਸ ਲਈ ਗਤੀਸ਼ੀਲ ਨਾਮ-ਨਿਰਣੇ ਉੱਤੇ ਨਿਰਭਰ ਨਾ ਕਰੋ! (ਅਸਲ ਵਿੱਚ, ਲੋਕਲ ਵੇਰੀਏਬਲ ਪਹਿਲਾਂ ਹੀ ਸਥਿਰ ਤਰੀਕੇ ਨਾਲ ਨਿਰਧਾਰਤ ਕੀਤੇ ਜਾਂਦੇ ਹਨ।)

Python ਦੀ ਇੱਕ ਖਾਸ ਵਿਸ਼ੇਸ਼ਤਾ ਇਹ ਹੈ ਕਿ -- ਜੇ global ਜਾਂ nonlocal ਸਟੇਟਮੈਂਟ ਪ੍ਰਭਾਵ ਵਿੱਚ ਨਾ ਹੋਵੇ -- ਤਾਂ ਨਾਮਾਂ ਲਈ ਕੀਤੀਆਂ ਅਸਾਈਨਮੈਂਟਾਂ ਹਮੇਸ਼ਾਂ ਸਭ ਤੋਂ ਅੰਦਰਲੇ ਸਕੋਪ ਵਿੱਚ ਜਾਂਦੀਆਂ ਹਨ। ਅਸਾਈਨਮੈਂਟਾਂ ਡਾਟਾ ਦੀ ਨਕਲ ਨਹੀਂ ਕਰਦੀਆਂ --- ਇਹ ਸਿਰਫ਼ ਨਾਮਾਂ ਨੂੰ ਆਬਜੈਕਟਾਂ ਨਾਲ ਬੰਨ੍ਹਦੀਆਂ ਹਨ। ਇਹੀ ਗੱਲ ਮਿਟਾਉਣ ਲਈ ਵੀ ਸੱਚ ਹੈ: del x ਸਟੇਟਮੈਂਟ ਲੋਕਲ ਸਕੋਪ ਦੁਆਰਾ ਦਰਸਾਏ namespace ਵਿੱਚੋਂ x ਦੀ ਬਾਈਂਡਿੰਗ ਹਟਾ ਦਿੰਦਾ ਹੈ। ਦਰਅਸਲ, ਨਵੇਂ ਨਾਮ ਜੋੜਣ ਵਾਲੀਆਂ ਸਾਰੀਆਂ ਕਾਰਵਾਈਆਂ ਲੋਕਲ ਸਕੋਪ ਦੀ ਵਰਤੋਂ ਕਰਦੀਆਂ ਹਨ; ਖਾਸ ਤੌਰ 'ਤੇ, import ਸਟੇਟਮੈਂਟ ਅਤੇ ਫੰਕਸ਼ਨ ਪਰਿਭਾਸ਼ਾਵਾਂ ਮੋਡੀਊਲ ਜਾਂ ਫੰਕਸ਼ਨ ਦੇ ਨਾਮ ਨੂੰ ਲੋਕਲ ਸਕੋਪ ਵਿੱਚ ਬੰਨ੍ਹਦੀਆਂ ਹਨ।

global ਸਟੇਟਮੈਂਟ ਇਹ ਦਰਸਾਉਣ ਲਈ ਵਰਤੀ ਜਾਂਦੀ ਹੈ ਕਿ ਕੁਝ ਵੇਰੀਏਬਲ ਗਲੋਬਲ ਸਕੋਪ ਵਿੱਚ ਮੌਜੂਦ ਹਨ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ ਉੱਥੇ ਹੀ ਮੁੜ ਬਾਈਂਡ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ। nonlocal ਸਟੇਟਮੈਂਟ ਇਹ ਦਰਸਾਉਂਦੀ ਹੈ ਕਿ ਕੁਝ ਵੇਰੀਏਬਲ ਕਿਸੇ ਘੇਰਨ ਵਾਲੇ ਸਕੋਪ ਵਿੱਚ ਮੌਜੂਦ ਹਨ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ ਉੱਥੇ ਹੀ ਮੁੜ ਬਾਈਂਡ ਕੀਤਾ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ।

9.2.1. ਸਕੋਪ ਅਤੇ ਨੇਮਸਪੇਸ ਦੀ ਉਦਾਹਰਨ

ਇਹ ਉਦਾਹਰਨ ਦਰਸਾਉਂਦੀ ਹੈ ਕਿ ਵੱਖ-ਵੱਖ ਸਕੋਪਾਂ ਅਤੇ ਨੇਮਸਪੇਸਾਂ ਦਾ ਹਵਾਲਾ ਕਿਵੇਂ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ global ਅਤੇ nonlocal ਵੇਰੀਏਬਲ ਬਾਈਂਡਿੰਗ ਨੂੰ ਕਿਵੇਂ ਪ੍ਰਭਾਵਿਤ ਕਰਦੇ ਹਨ:

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

ਉਦਾਹਰਨ ਕੋਡ ਦਾ ਆਉਟਪੁੱਟ ਇਹ ਹੈ:

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

ਧਿਆਨ ਦਿਓ ਕਿ local ਅਸਾਈਨਮੈਂਟ (ਜੋ ਮੂਲ ਵਰਤਾਰਾ ਹੈ) ਨੇ scope_test ਵਿੱਚ spam ਦੀ ਬਾਈਂਡਿੰਗ ਨਹੀਂ ਬਦਲੀ। nonlocal ਅਸਾਈਨਮੈਂਟ ਨੇ scope_test ਦੀ spam ਬਾਈਂਡਿੰਗ ਬਦਲ ਦਿੱਤੀ, ਜਦਕਿ global ਅਸਾਈਨਮੈਂਟ ਨੇ ਮੌਡਿਊਲ-ਪੱਧਰ ਦੀ ਬਾਈਂਡਿੰਗ ਬਦਲੀ।

ਤੁਸੀਂ ਇਹ ਵੀ ਦੇਖ ਸਕਦੇ ਹੋ ਕਿ global ਅਸਾਈਨਮੈਂਟ ਤੋਂ ਪਹਿਲਾਂ spam ਲਈ ਕੋਈ ਪਿਛਲੀ ਬਾਈਂਡਿੰਗ ਮੌਜੂਦ ਨਹੀਂ ਸੀ।

9.3. ਕਲਾਸਾਂ ਨਾਲ ਪਹਿਲੀ ਜਾਣ-ਪਛਾਣ

ਕਲਾਸਾਂ ਕੁਝ ਨਵਾਂ ਸਿੰਟੈਕਸ, ਤਿੰਨ ਨਵੀਆਂ ਵਸਤੂ ਕਿਸਮਾਂ ਅਤੇ ਕੁਝ ਨਵੇਂ ਅਰਥਾਤਮਕ ਨਿਯਮਾਂ ਦੀ ਜਾਣ-ਪਛਾਣ ਕਰਵਾਉਂਦੀਆਂ ਹਨ।

9.3.1. ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਦਾ ਸਿੰਟੈਕਸ

ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਦਾ ਸਭ ਤੋਂ ਸਰਲ ਰੂਪ ਇਸ ਤਰ੍ਹਾਂ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

ਕਲਾਸ ਪਰਿਭਾਸ਼ਾਵਾਂ, ਫੰਕਸ਼ਨ ਪਰਿਭਾਸ਼ਾਵਾਂ (def ਸਟੇਟਮੈਂਟਾਂ) ਵਾਂਗ, ਕੋਈ ਪ੍ਰਭਾਵ ਪੈਦਾ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਚਲਾਈਆਂ ਜਾਣੀਆਂ ਲਾਜ਼ਮੀ ਹਨ। (ਉਦਾਹਰਨ ਲਈ, ਤੁਸੀਂ ਕਿਸੇ if ਸਟੇਟਮੈਂਟ ਦੀ ਸ਼ਾਖਾ ਵਿੱਚ ਜਾਂ ਕਿਸੇ ਫੰਕਸ਼ਨ ਦੇ ਅੰਦਰ ਇੱਕ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਰੱਖ ਸਕਦੇ ਹੋ।)

ਅਮਲ ਵਿੱਚ, ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਦੇ ਅੰਦਰਲੇ ਸਟੇਟਮੈਂਟ ਆਮ ਤੌਰ 'ਤੇ ਫੰਕਸ਼ਨ ਪਰਿਭਾਸ਼ਾਵਾਂ ਹੁੰਦੀਆਂ ਹਨ, ਪਰ ਹੋਰ ਸਟੇਟਮੈਂਟ ਵੀ ਮਨਜ਼ੂਰ ਹਨ ਅਤੇ ਕਈ ਵਾਰ ਲਾਭਦਾਇਕ ਵੀ ਹੁੰਦੀਆਂ ਹਨ — ਇਸ ਬਾਰੇ ਅਸੀਂ ਬਾਅਦ ਵਿੱਚ ਮੁੜ ਗੱਲ ਕਰਾਂਗੇ। ਕਲਾਸ ਦੇ ਅੰਦਰਲੇ ਫੰਕਸ਼ਨ ਆਮ ਤੌਰ 'ਤੇ ਆਰਗੂਮੈਂਟ ਸੂਚੀ ਦੇ ਇੱਕ ਵਿਸ਼ੇਸ਼ ਰੂਪ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ, ਜੋ ਵਿਧੀਆਂ ਦੀ ਕਾਲਿੰਗ ਪਰੰਪਰਾ ਦੁਆਰਾ ਨਿਰਧਾਰਤ ਹੁੰਦਾ ਹੈ — ਇਸਦੀ ਵੀ ਵਿਆਖਿਆ ਅੱਗੇ ਕੀਤੀ ਜਾਵੇਗੀ।

ਜਦੋਂ ਕਿਸੇ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਵਿੱਚ ਦਾਖਲ ਹੁੰਦੇ ਹਾਂ, ਤਾਂ ਇੱਕ ਨਵਾਂ ਨੇਮਸਪੇਸ ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ ਅਤੇ ਇਸਨੂੰ ਸਥਾਨਕ ਸਕੋਪ ਵਜੋਂ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਸਥਾਨਕ ਵੇਰੀਏਬਲਾਂ ਨੂੰ ਕੀਤੀਆਂ ਸਾਰੀਆਂ ਅਸਾਈਨਮੈਂਟਾਂ ਇਸ ਨਵੇਂ ਨੇਮਸਪੇਸ ਵਿੱਚ ਜਾਂਦੀਆਂ ਹਨ।

ਜਦੋਂ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਆਮ ਤਰੀਕੇ ਨਾਲ (ਅੰਤ ਤੱਕ ਪਹੁੰਚ ਕੇ) ਸਮਾਪਤ ਹੁੰਦੀ ਹੈ, ਤਾਂ ਇੱਕ ਕਲਾਸ ਆਬਜੈਕਟ ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ। ਇਹ ਅਸਲ ਵਿੱਚ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਦੁਆਰਾ ਬਣਾਏ ਗਏ ਨੇਮਸਪੇਸ ਦੀ ਸਮੱਗਰੀ ਦੇ ਆਲੇ-ਦੁਆਲੇ ਇੱਕ ਰੈਪਰ ਹੁੰਦਾ ਹੈ। ਅਗਲੇ ਭਾਗ ਵਿੱਚ ਅਸੀਂ ਕਲਾਸ ਆਬਜੈਕਟਾਂ ਬਾਰੇ ਹੋਰ ਸਿੱਖਾਂਗੇ। ਮੂਲ ਸਥਾਨਕ ਸਕੋਪ (ਜੋ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਵਿੱਚ ਦਾਖਲ ਹੋਣ ਤੋਂ ਠੀਕ ਪਹਿਲਾਂ ਸਰਗਰਮ ਸੀ) ਮੁੜ ਬਹਾਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਕਲਾਸ ਆਬਜੈਕਟ ਨੂੰ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਦੇ ਸਿਰਲੇਖ ਵਿੱਚ ਦਿੱਤੇ ਕਲਾਸ ਨਾਂ ਨਾਲ ਇੱਥੇ ਜੋੜਿਆ ਜਾਂਦਾ ਹੈ (ClassName ਉਦਾਹਰਨ ਵਿੱਚ)।

9.3.2. ਕਲਾਸ ਆਬਜੈਕਟ

ਕਲਾਸ ਆਬਜੈਕਟ ਦੋ ਕਿਸਮ ਦੀਆਂ ਕਾਰਵਾਈਆਂ ਦਾ ਸਮਰਥਨ ਕਰਦੇ ਹਨ: ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲੇ ਅਤੇ ਇੰਸਟੈਂਸ਼ੀਏਸ਼ਨ।

ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲੇ Python ਵਿੱਚ ਸਾਰੇ ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲਿਆਂ ਲਈ ਵਰਤੇ ਜਾਣ ਵਾਲੇ ਮਿਆਰੀ ਸਿੰਟੈਕਸ obj.name ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ। ਵੈਧ ਐਟ੍ਰਿਬਿਊਟ ਨਾਂ ਉਹ ਸਾਰੇ ਨਾਂ ਹੁੰਦੇ ਹਨ ਜੋ ਕਲਾਸ ਆਬਜੈਕਟ ਬਣਾਉਣ ਵੇਲੇ ਕਲਾਸ ਦੇ ਨੇਮਸਪੇਸ ਵਿੱਚ ਮੌਜੂਦ ਸਨ। ਇਸ ਲਈ, ਜੇ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਇਸ ਤਰ੍ਹਾਂ ਹੋਵੇ:

class MyClass:
    """ਇੱਕ ਸਰਲ ਉਦਾਹਰਨ ਕਲਾਸ"""
    i = 12345

    def f(self):
        return 'hello world'

ਤਾਂ MyClass.i ਅਤੇ MyClass.f ਵੈਧ ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲੇ ਹਨ, ਜੋ ਕ੍ਰਮਵਾਰ ਇੱਕ ਪੂਰਨ ਅੰਕ ਅਤੇ ਇੱਕ ਫੰਕਸ਼ਨ ਵਸਤੂ ਵਾਪਸ ਕਰਦੇ ਹਨ। ਕਲਾਸ ਐਟ੍ਰਿਬਿਊਟਾਂ ਨੂੰ ਅਸਾਈਨ ਵੀ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ, ਇਸ ਲਈ ਤੁਸੀਂ ਅਸਾਈਨਮੈਂਟ ਰਾਹੀਂ MyClass.i ਦਾ ਮੁੱਲ ਬਦਲ ਸਕਦੇ ਹੋ। __doc__ ਵੀ ਇੱਕ ਵੈਧ ਐਟ੍ਰਿਬਿਊਟ ਹੈ ਜੋ ਕਲਾਸ ਨਾਲ ਸੰਬੰਧਿਤ ਡਾਕਸਟਰਿੰਗ "A simple example class" ਵਾਪਸ ਕਰਦਾ ਹੈ।

ਕਲਾਸ ਇੰਸਟੈਂਸ਼ੀਏਸ਼ਨ ਫੰਕਸ਼ਨ ਨੋਟੇਸ਼ਨ ਦੀ ਵਰਤੋਂ ਕਰਦੀ ਹੈ। ਸਿਰਫ਼ ਇਹ ਮੰਨ ਲਵੋ ਕਿ ਕਲਾਸ ਆਬਜੈਕਟ ਇੱਕ ਬਿਨਾਂ ਪੈਰਾਮੀਟਰ ਵਾਲਾ ਫੰਕਸ਼ਨ ਹੈ ਜੋ ਕਲਾਸ ਦਾ ਨਵਾਂ ਇੰਸਟੈਂਸ ਵਾਪਸ ਕਰਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ (ਉਪਰੋਕਤ ਕਲਾਸ ਮੰਨਦੇ ਹੋਏ):

x = MyClass()

ਇਹ ਕਲਾਸ ਦਾ ਇੱਕ ਨਵਾਂ ਇੰਸਟੈਂਸ ਬਣਾਉਂਦਾ ਹੈ ਅਤੇ ਇਸ ਵਸਤੂ ਨੂੰ ਸਥਾਨਕ ਵੇਰੀਏਬਲ x ਨਾਲ ਜੋੜ ਦਿੰਦਾ ਹੈ।

ਇੰਸਟੈਂਸ਼ੀਏਸ਼ਨ ਕਾਰਵਾਈ (ਕਲਾਸ ਆਬਜੈਕਟ ਨੂੰ "ਕਾਲ" ਕਰਨਾ) ਇੱਕ ਖਾਲੀ ਆਬਜੈਕਟ ਬਣਾਉਂਦੀ ਹੈ। ਬਹੁਤ ਸਾਰੀਆਂ ਕਲਾਸਾਂ ਐਸੀਆਂ ਵਸਤੂਆਂ ਬਣਾਉਣਾ ਪਸੰਦ ਕਰਦੀਆਂ ਹਨ ਜਿਨ੍ਹਾਂ ਦੇ ਇੰਸਟੈਂਸ ਕਿਸੇ ਖਾਸ ਸ਼ੁਰੂਆਤੀ ਸਥਿਤੀ ਅਨੁਸਾਰ ਸੰਰਚਿਤ ਕੀਤੇ ਗਏ ਹੋਣ। ਇਸ ਲਈ ਕੋਈ ਕਲਾਸ __init__() ਨਾਂ ਦੀ ਇੱਕ ਵਿਸ਼ੇਸ਼ ਵਿਧੀ ਪਰਿਭਾਸ਼ਿਤ ਕਰ ਸਕਦੀ ਹੈ, ਜਿਵੇਂ ਕਿ:

def __init__(self):
    self.data = []

ਜਦੋਂ ਕੋਈ ਕਲਾਸ __init__() ਵਿਧੀ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੀ ਹੈ, ਤਾਂ ਕਲਾਸ ਇੰਸਟੈਂਸ਼ੀਏਸ਼ਨ ਨਵੇਂ ਬਣੇ ਕਲਾਸ ਇੰਸਟੈਂਸ ਲਈ ਆਪਣੇ ਆਪ __init__() ਨੂੰ ਚਲਾਉਂਦੀ ਹੈ। ਇਸ ਉਦਾਹਰਨ ਵਿੱਚ ਇੱਕ ਨਵਾਂ ਅਤੇ ਸ਼ੁਰੂਆਤੀਕ੍ਰਿਤ ਇੰਸਟੈਂਸ ਇਸ ਤਰ੍ਹਾਂ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ:

x = MyClass()

ਬੇਸ਼ੱਕ, ਵੱਧ ਲਚਕਤਾ ਲਈ __init__() ਵਿਧੀ ਵਿੱਚ ਆਰਗੂਮੈਂਟ ਹੋ ਸਕਦੇ ਹਨ। ਇਸ ਸਥਿਤੀ ਵਿੱਚ ਕਲਾਸ ਇੰਸਟੈਂਸ਼ੀਏਸ਼ਨ ਓਪਰੇਟਰ ਨੂੰ ਦਿੱਤੇ ਗਏ ਆਰਗੂਮੈਂਟ __init__() ਨੂੰ ਭੇਜੇ ਜਾਂਦੇ ਹਨ। ਉਦਾਹਰਨ ਲਈ:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

9.3.3. ਇੰਸਟੈਂਸ ਆਬਜੈਕਟ

ਹੁਣ ਅਸੀਂ ਇੰਸਟੈਂਸ ਆਬਜੈਕਟਾਂ ਨਾਲ ਕੀ ਕਰ ਸਕਦੇ ਹਾਂ? ਇੰਸਟੈਂਸ ਆਬਜੈਕਟਾਂ ਦੁਆਰਾ ਸਮਝੀ ਜਾਣ ਵਾਲੀ ਇਕੱਲੀ ਕਾਰਵਾਈ ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲਾ ਹੈ। ਵੈਧ ਐਟ੍ਰਿਬਿਊਟ ਨਾਂਵਾਂ ਦੀਆਂ ਦੋ ਕਿਸਮਾਂ ਹਨ: data attributes ਅਤੇ methods।

ਡਾਟਾ ਐਟ੍ਰਿਬਿਊਟ Smalltalk ਵਿੱਚ "instance variables" ਅਤੇ C++ ਵਿੱਚ "data members" ਦੇ ਸਮਾਨ ਹੁੰਦੇ ਹਨ। ਡਾਟਾ ਐਟ੍ਰਿਬਿਊਟਾਂ ਨੂੰ ਪਹਿਲਾਂ ਤੋਂ ਘੋਸ਼ਿਤ ਕਰਨ ਦੀ ਲੋੜ ਨਹੀਂ ਹੁੰਦੀ; ਸਥਾਨਕ ਵੇਰੀਏਬਲਾਂ ਵਾਂਗ ਇਹ ਪਹਿਲੀ ਵਾਰ ਅਸਾਈਨ ਹੋਣ 'ਤੇ ਮੌਜੂਦਗੀ ਵਿੱਚ ਆ ਜਾਂਦੇ ਹਨ। ਉਦਾਹਰਨ ਵਜੋਂ, ਜੇ x ਉੱਪਰ ਬਣਾਇਆ ਗਿਆ MyClass ਦਾ ਇੰਸਟੈਂਸ ਹੈ, ਤਾਂ ਹੇਠਾਂ ਦਿੱਤਾ ਕੋਡ 16 ਮੁੱਲ ਪ੍ਰਿੰਟ ਕਰੇਗਾ ਅਤੇ ਬਾਅਦ ਵਿੱਚ ਕੋਈ ਨਿਸ਼ਾਨ ਨਹੀਂ ਛੱਡੇਗਾ:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter

ਇੰਸਟੈਂਸ ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲੇ ਦੀ ਦੂਜੀ ਕਿਸਮ method ਹੈ। Method ਇੱਕ ਐਸਾ ਫੰਕਸ਼ਨ ਹੈ ਜੋ ਕਿਸੇ ਆਬਜੈਕਟ ਨਾਲ ਸੰਬੰਧਿਤ ਹੁੰਦਾ ਹੈ।

ਕਿਸੇ ਇੰਸਟੈਂਸ ਆਬਜੈਕਟ ਦੇ ਵੈਧ ਮੈਥਡ ਨਾਮ ਉਸਦੀ ਕਲਾਸ 'ਤੇ ਨਿਰਭਰ ਕਰਦੇ ਹਨ। ਪਰਿਭਾਸ਼ਾ ਅਨੁਸਾਰ, ਕਲਾਸ ਦੇ ਉਹ ਸਾਰੇ ਐਟ੍ਰਿਬਿਊਟ ਜੋ ਫੰਕਸ਼ਨ ਆਬਜੈਕਟ ਹੁੰਦੇ ਹਨ, ਉਸਦੇ ਇੰਸਟੈਂਸਾਂ ਲਈ ਸੰਬੰਧਿਤ ਮੈਥਡ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੇ ਹਨ। ਇਸ ਲਈ ਸਾਡੀ ਉਦਾਹਰਨ ਵਿੱਚ x.f ਇੱਕ ਵੈਧ ਮੈਥਡ ਹਵਾਲਾ ਹੈ, ਕਿਉਂਕਿ MyClass.f ਇੱਕ ਫੰਕਸ਼ਨ ਹੈ, ਪਰ x.i ਨਹੀਂ, ਕਿਉਂਕਿ MyClass.i ਨਹੀਂ ਹੈ। ਪਰ x.f MyClass.f ਦੇ ਸਮਾਨ ਨਹੀਂ ਹੈ — ਇਹ ਇੱਕ ਮੈਥਡ ਆਬਜੈਕਟ ਹੈ, ਫੰਕਸ਼ਨ ਆਬਜੈਕਟ ਨਹੀਂ।

9.3.4. Method Objects

ਆਮ ਤੌਰ 'ਤੇ, ਕਿਸੇ method ਨੂੰ ਉਸਦੇ bind ਹੋਣ ਦੇ ਤੁਰੰਤ ਬਾਅਦ call ਕੀਤਾ ਜਾਂਦਾ ਹੈ:

x.f()

ਜੇ ਉੱਪਰ ਵਾਂਗ x = MyClass() ਹੋਵੇ, ਤਾਂ ਇਹ 'hello world' ਸਟ੍ਰਿੰਗ ਵਾਪਸ ਕਰੇਗਾ। ਪਰ ਮੈਥਡ ਨੂੰ ਤੁਰੰਤ ਕਾਲ ਕਰਨਾ ਲਾਜ਼ਮੀ ਨਹੀਂ ਹੈ। x.f ਇੱਕ ਮੈਥਡ ਆਬਜੈਕਟ ਹੈ ਅਤੇ ਇਸਨੂੰ ਸੰਭਾਲ ਕੇ ਬਾਅਦ ਵਿੱਚ ਵੀ ਕਾਲ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ:

xf = x.f
while True:
    print(xf())

ਇਹ ਅਨੰਤ ਸਮੇਂ ਤੱਕ hello world ਪ੍ਰਿੰਟ ਕਰਦਾ ਰਹੇਗਾ।

ਜਦੋਂ ਕੋਈ ਮੈਥਡ ਕਾਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਤਾਂ ਅਸਲ ਵਿੱਚ ਕੀ ਹੁੰਦਾ ਹੈ? ਤੁਸੀਂ ਧਿਆਨ ਦਿੱਤਾ ਹੋਵੇਗਾ ਕਿ ਉੱਪਰ x.f() ਬਿਨਾਂ ਕਿਸੇ ਆਰਗੂਮੈਂਟ ਦੇ ਕਾਲ ਕੀਤਾ ਗਿਆ ਸੀ, ਹਾਲਾਂਕਿ f() ਦੀ ਪਰਿਭਾਸ਼ਾ ਵਿੱਚ ਇੱਕ ਆਰਗੂਮੈਂਟ ਦਿੱਤਾ ਗਿਆ ਸੀ। ਫਿਰ ਉਹ ਆਰਗੂਮੈਂਟ ਕਿੱਥੇ ਗਿਆ? ਆਮ ਤੌਰ 'ਤੇ Python ਉਸ ਵੇਲੇ ਐਕਸੈਪਸ਼ਨ ਪੈਦਾ ਕਰਦਾ ਹੈ ਜਦੋਂ ਕਿਸੇ ਲੋੜੀਂਦੇ ਆਰਗੂਮੈਂਟ ਵਾਲੇ ਫੰਕਸ਼ਨ ਨੂੰ ਬਿਨਾਂ ਆਰਗੂਮੈਂਟ ਦੇ ਕਾਲ ਕੀਤਾ ਜਾਵੇ — ਭਾਵੇਂ ਉਹ ਆਰਗੂਮੈਂਟ ਵਰਤਿਆ ਨਾ ਵੀ ਜਾਵੇ...

ਅਸਲ ਵਿੱਚ, ਤੁਸੀਂ ਸ਼ਾਇਦ ਇਸਦਾ ਜਵਾਬ ਅੰਦਾਜ਼ਾ ਲਾ ਲਿਆ ਹੋਵੇਗਾ। ਮੈਥਡਾਂ ਦੀ ਖਾਸ ਗੱਲ ਇਹ ਹੈ ਕਿ ਇੰਸਟੈਂਸ ਆਬਜੈਕਟ ਆਪਣੇ ਆਪ ਫੰਕਸ਼ਨ ਦੇ ਪਹਿਲੇ ਆਰਗੂਮੈਂਟ ਵਜੋਂ ਭੇਜਿਆ ਜਾਂਦਾ ਹੈ। ਸਾਡੀ ਉਦਾਹਰਨ ਵਿੱਚ x.f() ਦੀ ਕਾਲ ਬਿਲਕੁਲ MyClass.f(x) ਦੇ ਬਰਾਬਰ ਹੈ। ਆਮ ਤੌਰ 'ਤੇ, n ਆਰਗੂਮੈਂਟਾਂ ਵਾਲੇ ਮੈਥਡ ਨੂੰ ਕਾਲ ਕਰਨਾ ਉਸ ਸੰਬੰਧਿਤ ਫੰਕਸ਼ਨ ਨੂੰ ਕਾਲ ਕਰਨ ਦੇ ਬਰਾਬਰ ਹੈ ਜਿਸਦੇ ਆਰਗੂਮੈਂਟਾਂ ਦੀ ਸੂਚੀ ਦੇ ਸ਼ੁਰੂ ਵਿੱਚ ਇੰਸਟੈਂਸ ਆਬਜੈਕਟ ਜੋੜਿਆ ਗਿਆ ਹੋਵੇ।

ਆਮ ਤੌਰ 'ਤੇ ਮੈਥਡ ਇਸ ਤਰ੍ਹਾਂ ਕੰਮ ਕਰਦੇ ਹਨ। ਜਦੋਂ ਕਿਸੇ ਇੰਸਟੈਂਸ ਦੇ ਗੈਰ-ਡਾਟਾ ਐਟ੍ਰਿਬਿਊਟ ਦਾ ਹਵਾਲਾ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਉਸਦੀ ਕਲਾਸ ਵਿੱਚ ਖੋਜ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਜੇਕਰ ਉਹ ਨਾਮ ਕਿਸੇ ਐਸੇ ਕਲਾਸ ਐਟ੍ਰਿਬਿਊਟ ਨੂੰ ਦਰਸਾਉਂਦਾ ਹੋਵੇ ਜੋ ਇੱਕ ਫੰਕਸ਼ਨ ਆਬਜੈਕਟ ਹੈ, ਤਾਂ ਇੰਸਟੈਂਸ ਆਬਜੈਕਟ ਅਤੇ ਫੰਕਸ਼ਨ ਆਬਜੈਕਟ ਦੋਵਾਂ ਦੇ ਹਵਾਲੇ ਮਿਲਾ ਕੇ ਇੱਕ ਮੈਥਡ ਆਬਜੈਕਟ ਬਣਾਇਆ ਜਾਂਦਾ ਹੈ। ਜਦੋਂ ਇਸ ਮੈਥਡ ਆਬਜੈਕਟ ਨੂੰ ਆਰਗੂਮੈਂਟਾਂ ਨਾਲ ਕਾਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਇੰਸਟੈਂਸ ਆਬਜੈਕਟ ਅਤੇ ਦਿੱਤੇ ਗਏ ਆਰਗੂਮੈਂਟਾਂ ਤੋਂ ਇੱਕ ਨਵੀਂ ਆਰਗੂਮੈਂਟ ਸੂਚੀ ਬਣਾਈ ਜਾਂਦੀ ਹੈ ਅਤੇ ਫੰਕਸ਼ਨ ਆਬਜੈਕਟ ਨੂੰ ਇਸ ਨਵੀਂ ਸੂਚੀ ਨਾਲ ਕਾਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।

9.3.5. ਕਲਾਸ ਅਤੇ ਇੰਸਟੈਂਸ ਵੇਰੀਏਬਲ

ਆਮ ਤੌਰ 'ਤੇ, ਇੰਸਟੈਂਸ ਵੇਰੀਏਬਲ ਹਰ ਇੰਸਟੈਂਸ ਲਈ ਵਿਲੱਖਣ ਡਾਟਾ ਸੰਭਾਲਣ ਲਈ ਹੁੰਦੇ ਹਨ ਅਤੇ ਕਲਾਸ ਵੇਰੀਏਬਲ ਉਹਨਾਂ ਐਟ੍ਰਿਬਿਊਟਾਂ ਅਤੇ ਵਿਧੀਆਂ ਲਈ ਹੁੰਦੇ ਹਨ ਜੋ ਕਲਾਸ ਦੇ ਸਾਰੇ ਇੰਸਟੈਂਸਾਂ ਦੁਆਰਾ ਸਾਂਝੇ ਕੀਤੇ ਜਾਂਦੇ ਹਨ:

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'

ਜਿਵੇਂ ਕਿ ਨਾਂਵਾਂ ਅਤੇ ਆਬਜੈਕਟਾਂ ਬਾਰੇ ਇੱਕ ਗੱਲ ਵਿੱਚ ਚਰਚਾ ਕੀਤੀ ਗਈ ਹੈ, ਸਾਂਝਾ ਡਾਟਾ mutable ਆਬਜੈਕਟਾਂ ਜਿਵੇਂ ਲਿਸਟਾਂ ਅਤੇ ਡਿਕਸ਼ਨਰੀਆਂ ਨਾਲ ਕਈ ਵਾਰ ਹੈਰਾਨੀਜਨਕ ਨਤੀਜੇ ਪੈਦਾ ਕਰ ਸਕਦਾ ਹੈ। ਉਦਾਹਰਨ ਵਜੋਂ, ਹੇਠਾਂ ਦਿੱਤੇ ਕੋਡ ਵਿੱਚ tricks ਲਿਸਟ ਨੂੰ ਕਲਾਸ ਵੇਰੀਏਬਲ ਵਜੋਂ ਨਹੀਂ ਵਰਤਣਾ ਚਾਹੀਦਾ ਕਿਉਂਕਿ ਇੱਕੋ ਲਿਸਟ ਸਾਰੇ Dog ਇੰਸਟੈਂਸਾਂ ਵੱਲੋਂ ਸਾਂਝੀ ਕੀਤੀ ਜਾਵੇਗੀ:

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

ਕਲਾਸ ਦੇ ਸਹੀ ਡਿਜ਼ਾਇਨ ਵਿੱਚ ਇਸਦੀ ਬਜਾਏ ਇੱਕ ਇੰਸਟੈਂਸ ਵੇਰੀਏਬਲ ਵਰਤਣਾ ਚਾਹੀਦਾ ਹੈ:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']

9.4. ਕੁਝ ਵਾਧੂ ਟਿੱਪਣੀਆਂ

ਜੇਕਰ ਇੱਕੋ ਐਟ੍ਰਿਬਿਊਟ ਨਾਂ ਇੰਸਟੈਂਸ ਅਤੇ ਕਲਾਸ ਦੋਵਾਂ ਵਿੱਚ ਮੌਜੂਦ ਹੋਵੇ, ਤਾਂ ਐਟ੍ਰਿਬਿਊਟ ਖੋਜ ਦੌਰਾਨ ਇੰਸਟੈਂਸ ਨੂੰ ਤਰਜੀਹ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ:

>>> class Warehouse:
...    purpose = 'storage'
...    region = 'west'
...
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east

ਡਾਟਾ ਐਟ੍ਰਿਬਿਊਟਾਂ ਨੂੰ ਵਿਧੀਆਂ ਅਤੇ ਕਿਸੇ ਵਸਤੂ ਦੇ ਆਮ ਵਰਤੋਂਕਾਰਾਂ ("ਕਲਾਇੰਟਾਂ") ਦੁਆਰਾ ਵੀ ਹਵਾਲਾ ਦਿੱਤਾ ਜਾ ਸਕਦਾ ਹੈ। ਦੂਜੇ ਸ਼ਬਦਾਂ ਵਿੱਚ, ਕਲਾਸਾਂ ਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਅਮੂਰਤ ਡਾਟਾ ਕਿਸਮਾਂ ਲਾਗੂ ਕਰਨ ਲਈ ਵਰਤਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ। ਅਸਲ ਵਿੱਚ Python ਵਿੱਚ ਡਾਟਾ ਲੁਕਾਉਣ ਨੂੰ ਲਾਜ਼ਮੀ ਬਣਾਉਣ ਦਾ ਕੋਈ ਤਰੀਕਾ ਨਹੀਂ ਹੈ — ਇਹ ਸਾਰਾ ਕੁਝ ਰਿਵਾਜਾਂ ਉੱਤੇ ਆਧਾਰਿਤ ਹੈ। (ਦੂਜੇ ਪਾਸੇ, C ਵਿੱਚ ਲਿਖਿਆ Python ਇੰਪਲੀਮੈਂਟੇਸ਼ਨ ਲੋੜ ਪੈਣ 'ਤੇ ਅੰਦਰੂਨੀ ਵੇਰਵਿਆਂ ਨੂੰ ਪੂਰੀ ਤਰ੍ਹਾਂ ਲੁਕਾ ਸਕਦਾ ਹੈ ਅਤੇ ਵਸਤੂ ਤੱਕ ਪਹੁੰਚ ਨੂੰ ਨਿਯੰਤਰਿਤ ਕਰ ਸਕਦਾ ਹੈ; ਇਹ C ਵਿੱਚ ਲਿਖੀਆਂ Python ਐਕਸਟੈਂਸ਼ਨਾਂ ਦੁਆਰਾ ਵਰਤਿਆ ਜਾ ਸਕਦਾ ਹੈ।)

Clients ਨੂੰ data attributes ਦੀ ਵਰਤੋਂ ਸਾਵਧਾਨੀ ਨਾਲ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ, ਕਿਉਂਕਿ ਉਹ ਆਪਣੇ attributes ਵਿੱਚ ਤਬਦੀਲੀਆਂ ਕਰਕੇ methods ਦੁਆਰਾ ਬਣਾਈ ਰੱਖੀਆਂ ਗਈਆਂ invariants ਨੂੰ ਖਰਾਬ ਕਰ ਸਕਦੇ ਹਨ। ਧਿਆਨ ਦਿਓ ਕਿ ਜੇ ਨਾਂਵਾਂ ਦਾ ਟਕਰਾਅ ਨਾ ਹੋਵੇ ਤਾਂ clients instance object ਵਿੱਚ ਆਪਣੇ data attributes ਵੀ ਜੋੜ ਸਕਦੇ ਹਨ ਬਿਨਾਂ methods ਦੀ ਵੈਧਤਾ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕੀਤੇ। ਇੱਥੇ ਵੀ ਇੱਕ ਵਧੀਆ naming convention ਬਹੁਤ ਸਾਰੀਆਂ ਮੁਸ਼ਕਲਾਂ ਤੋਂ ਬਚਾ ਸਕਦਾ ਹੈ।

Methods ਦੇ ਅੰਦਰੋਂ data attributes (ਜਾਂ ਹੋਰ methods) ਦਾ ਹਵਾਲਾ ਦੇਣ ਲਈ ਕੋਈ ਸੰਖੇਪ ਰੂਪ ਨਹੀਂ ਹੈ। ਅਸਲ ਵਿੱਚ ਇਹ methods ਦੀ ਪੜ੍ਹਨਯੋਗਤਾ ਵਧਾਉਂਦਾ ਹੈ ਕਿਉਂਕਿ method ਨੂੰ ਦੇਖਦੇ ਸਮੇਂ local variables ਅਤੇ instance variables ਵਿੱਚ ਉਲਝਣ ਦੀ ਸੰਭਾਵਨਾ ਨਹੀਂ ਰਹਿੰਦੀ।

ਅਕਸਰ ਕਿਸੇ ਮੈਥਡ ਦੇ ਪਹਿਲੇ ਆਰਗੂਮੈਂਟ ਨੂੰ self ਕਿਹਾ ਜਾਂਦਾ ਹੈ। ਇਹ ਸਿਰਫ਼ ਇੱਕ ਰਿਵਾਜ ਹੈ; self ਨਾਮ ਦਾ Python ਲਈ ਕੋਈ ਖਾਸ ਅਰਥ ਨਹੀਂ ਹੈ। ਹਾਲਾਂਕਿ, ਜੇ ਤੁਸੀਂ ਇਸ ਰਿਵਾਜ ਦੀ ਪਾਲਣਾ ਨਹੀਂ ਕਰਦੇ, ਤਾਂ ਤੁਹਾਡਾ ਕੋਡ ਹੋਰ Python ਪ੍ਰੋਗਰਾਮਰਾਂ ਲਈ ਘੱਟ ਸਮਝਣਯੋਗ ਹੋ ਸਕਦਾ ਹੈ ਅਤੇ ਸੰਭਵ ਹੈ ਕਿ ਕੋਈ class browser ਪ੍ਰੋਗਰਾਮ ਵੀ ਇਸੇ ਰਿਵਾਜ 'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੋਵੇ।

ਕਲਾਸ ਐਟ੍ਰਿਬਿਊਟ ਵਜੋਂ ਮੌਜੂਦ ਹਰ ਫੰਕਸ਼ਨ ਆਬਜੈਕਟ ਉਸ ਕਲਾਸ ਦੇ ਇੰਸਟੈਂਸਾਂ ਲਈ ਇੱਕ ਮੈਥਡ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦਾ ਹੈ। ਇਹ ਲਾਜ਼ਮੀ ਨਹੀਂ ਕਿ ਫੰਕਸ਼ਨ ਦੀ ਪਰਿਭਾਸ਼ਾ ਪਾਠਕ ਰੂਪ ਵਿੱਚ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਦੇ ਅੰਦਰ ਹੀ ਹੋਵੇ; ਕਲਾਸ ਦੇ ਅੰਦਰ ਕਿਸੇ ਫੰਕਸ਼ਨ ਆਬਜੈਕਟ ਨੂੰ ਸਥਾਨਕ ਵੇਰੀਏਬਲ ਨਾਲ ਜੋੜਨਾ ਵੀ ਠੀਕ ਹੈ। ਉਦਾਹਰਨ ਲਈ:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1

    def g(self):
        return 'hello world'

    h = g

ਹੁਣ f, g ਅਤੇ h ਸਾਰੇ C ਕਲਾਸ ਦੇ ਐਟ੍ਰਿਬਿਊਟ ਹਨ ਜੋ ਫੰਕਸ਼ਨ ਆਬਜੈਕਟਾਂ ਵੱਲ ਸੰਕੇਤ ਕਰਦੇ ਹਨ, ਅਤੇ ਇਸ ਲਈ ਇਹ ਸਾਰੇ C ਦੇ ਇੰਸਟੈਂਸਾਂ ਲਈ ਮੈਥਡ ਹਨ। h ਬਿਲਕੁਲ g ਦੇ ਬਰਾਬਰ ਹੈ। ਧਿਆਨ ਰਹੇ ਕਿ ਇਹ ਤਰੀਕਾ ਆਮ ਤੌਰ 'ਤੇ ਪ੍ਰੋਗਰਾਮ ਪੜ੍ਹਨ ਵਾਲੇ ਨੂੰ ਉਲਝਾਉਂਦਾ ਹੈ।

ਮੈਥਡ self ਆਰਗੂਮੈਂਟ ਦੇ ਮੈਥਡ ਐਟ੍ਰਿਬਿਊਟਾਂ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਹੋਰ ਮੈਥਡਾਂ ਨੂੰ ਕਾਲ ਕਰ ਸਕਦੇ ਹਨ:

class Bag:
    def __init__(self):
        self.data = []

    def add(self, x):
        self.data.append(x)

    def addtwice(self, x):
        self.add(x)
        self.add(x)

ਮੈਥਡ ਆਮ ਫੰਕਸ਼ਨਾਂ ਵਾਂਗ ਹੀ ਗਲੋਬਲ ਨਾਂਵਾਂ ਦਾ ਹਵਾਲਾ ਦੇ ਸਕਦੇ ਹਨ। ਕਿਸੇ ਮੈਥਡ ਨਾਲ ਸੰਬੰਧਿਤ ਗਲੋਬਲ ਸਕੋਪ ਉਹ ਮੌਡਿਊਲ ਹੁੰਦਾ ਹੈ ਜਿਸ ਵਿੱਚ ਉਸਦੀ ਪਰਿਭਾਸ਼ਾ ਕੀਤੀ ਗਈ ਹੋਵੇ। (ਕਲਾਸ ਨੂੰ ਕਦੇ ਵੀ ਗਲੋਬਲ ਸਕੋਪ ਵਜੋਂ ਨਹੀਂ ਵਰਤਿਆ ਜਾਂਦਾ।) ਹਾਲਾਂਕਿ ਮੈਥਡਾਂ ਵਿੱਚ ਗਲੋਬਲ ਡਾਟਾ ਵਰਤਣ ਦੀ ਲੋੜ ਕਦੇ-ਕਦੇ ਹੀ ਪੈਂਦੀ ਹੈ, ਪਰ ਗਲੋਬਲ ਸਕੋਪ ਦੇ ਕਈ ਵੈਧ ਉਪਯੋਗ ਹਨ। ਉਦਾਹਰਨ ਵਜੋਂ, ਗਲੋਬਲ ਸਕੋਪ ਵਿੱਚ ਇੰਪੋਰਟ ਕੀਤੇ ਫੰਕਸ਼ਨ ਅਤੇ ਮੌਡਿਊਲ ਮੈਥਡਾਂ ਦੁਆਰਾ ਵਰਤੇ ਜਾ ਸਕਦੇ ਹਨ, ਜਿਵੇਂ ਕਿ ਉਸੇ ਸਕੋਪ ਵਿੱਚ ਪਰਿਭਾਸ਼ਿਤ ਫੰਕਸ਼ਨ ਅਤੇ ਕਲਾਸਾਂ। ਆਮ ਤੌਰ 'ਤੇ, ਮੈਥਡ ਵਾਲੀ ਕਲਾਸ ਵੀ ਇਸੇ ਗਲੋਬਲ ਸਕੋਪ ਵਿੱਚ ਪਰਿਭਾਸ਼ਿਤ ਹੁੰਦੀ ਹੈ ਅਤੇ ਅਗਲੇ ਭਾਗ ਵਿੱਚ ਅਸੀਂ ਦੇਖਾਂਗੇ ਕਿ ਮੈਥਡ ਆਪਣੀ ਹੀ ਕਲਾਸ ਦਾ ਹਵਾਲਾ ਕਿਉਂ ਦੇਣਾ ਚਾਹੁੰਦਾ ਹੈ।

ਹਰ ਮੁੱਲ ਇੱਕ ਆਬਜੈਕਟ ਹੁੰਦਾ ਹੈ ਅਤੇ ਇਸ ਲਈ ਉਸਦੀ ਇੱਕ ਕਲਾਸ (ਜਿਸਨੂੰ ਉਸਦੀ ਟਾਈਪ ਵੀ ਕਿਹਾ ਜਾਂਦਾ ਹੈ) ਹੁੰਦੀ ਹੈ। ਇਹ ਜਾਣਕਾਰੀ object.__class__ ਵਿੱਚ ਸਟੋਰ ਹੁੰਦੀ ਹੈ।

9.5. ਇਨਹੈਰਿਟੈਂਸ

ਬੇਸ਼ੱਕ, ਜੇਕਰ ਕੋਈ ਭਾਸ਼ਾਈ ਵਿਸ਼ੇਸ਼ਤਾ ਵਿਰਾਸਤ ਦਾ ਸਮਰਥਨ ਨਾ ਕਰੇ ਤਾਂ ਉਸਨੂੰ "ਕਲਾਸ" ਕਹਿਣਾ ਉਚਿਤ ਨਹੀਂ ਹੋਵੇਗਾ। ਇੱਕ ਡਿਰਾਈਵਡ ਕਲਾਸ ਦੀ ਪਰਿਭਾਸ਼ਾ ਦਾ ਸਿੰਟੈਕਸ ਇਸ ਤਰ੍ਹਾਂ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

BaseClassName ਨਾਮ ਉਸ ਨੇਮਸਪੇਸ ਵਿੱਚ ਪਰਿਭਾਸ਼ਿਤ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਜੋ ਡਿਰਾਈਵਡ ਕਲਾਸ ਦੀ ਪਰਿਭਾਸ਼ਾ ਵਾਲੇ ਸਕੋਪ ਤੋਂ ਪਹੁੰਚਯੋਗ ਹੋਵੇ। ਬੇਸ ਕਲਾਸ ਦੇ ਨਾਮ ਦੀ ਥਾਂ ਹੋਰ ਮਨਚਾਹੇ ਐਕਸਪ੍ਰੈਸ਼ਨ ਵੀ ਵਰਤੇ ਜਾ ਸਕਦੇ ਹਨ। ਇਹ ਉਦਾਹਰਨ ਵਜੋਂ ਉਸ ਵੇਲੇ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ ਜਦੋਂ ਬੇਸ ਕਲਾਸ ਕਿਸੇ ਹੋਰ ਮੌਡਿਊਲ ਵਿੱਚ ਪਰਿਭਾਸ਼ਿਤ ਹੋਵੇ:

class DerivedClassName(modname.BaseClassName):

ਡਿਰਾਈਵਡ ਕਲਾਸ ਦੀ ਪਰਿਭਾਸ਼ਾ ਦਾ ਐਗਜ਼ਿਕਿਊਸ਼ਨ ਬੇਸ ਕਲਾਸ ਵਾਂਗ ਹੀ ਹੁੰਦਾ ਹੈ। ਜਦੋਂ ਕਲਾਸ ਆਬਜੈਕਟ ਬਣਾਈ ਜਾਂਦੀ ਹੈ ਤਾਂ ਬੇਸ ਕਲਾਸ ਨੂੰ ਯਾਦ ਰੱਖਿਆ ਜਾਂਦਾ ਹੈ। ਇਹ ਐਟ੍ਰਿਬਿਊਟ ਹਵਾਲਿਆਂ ਨੂੰ ਹੱਲ ਕਰਨ ਲਈ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ। ਜੇਕਰ ਲੋੜੀਂਦਾ ਐਟ੍ਰਿਬਿਊਟ ਕਲਾਸ ਵਿੱਚ ਨਾ ਮਿਲੇ ਤਾਂ ਖੋਜ ਬੇਸ ਕਲਾਸ ਵਿੱਚ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਜੇਕਰ ਬੇਸ ਕਲਾਸ ਖੁਦ ਕਿਸੇ ਹੋਰ ਕਲਾਸ ਤੋਂ ਡਿਰਾਈਵ ਕੀਤੀ ਗਈ ਹੋਵੇ ਤਾਂ ਇਹ ਨਿਯਮ ਰਿਕਰਸਿਵ ਤਰੀਕੇ ਨਾਲ ਲਾਗੂ ਹੁੰਦਾ ਹੈ।

ਡਿਰਾਈਵਡ ਕਲਾਸਾਂ ਦੀ ਇੰਸਟੈਂਸ਼ੀਏਸ਼ਨ ਵਿੱਚ ਕੁਝ ਖਾਸ ਨਹੀਂ ਹੁੰਦਾ। DerivedClassName() ਕਲਾਸ ਦਾ ਨਵਾਂ ਇੰਸਟੈਂਸ ਬਣਾਉਂਦਾ ਹੈ। ਵਿਧੀ ਹਵਾਲਿਆਂ ਦਾ ਹੱਲ ਇਸ ਤਰ੍ਹਾਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ ਕਿ ਸੰਬੰਧਿਤ ਕਲਾਸ ਐਟ੍ਰਿਬਿਊਟ ਨੂੰ ਲੱਭਿਆ ਜਾਂਦਾ ਹੈ ਅਤੇ ਲੋੜ ਪੈਣ 'ਤੇ ਬੇਸ ਕਲਾਸਾਂ ਦੀ ਲੜੀ ਵਿੱਚ ਹੇਠਾਂ ਤੱਕ ਖੋਜ ਕੀਤੀ ਜਾਂਦੀ ਹੈ। ਜੇਕਰ ਇਸ ਖੋਜ ਦਾ ਨਤੀਜਾ ਇੱਕ ਫੰਕਸ਼ਨ ਵਸਤੂ ਹੋਵੇ ਤਾਂ ਵਿਧੀ ਹਵਾਲਾ ਵੈਧ ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ।

Derived classes ਆਪਣੀਆਂ base classes ਦੀਆਂ methods ਨੂੰ override ਕਰ ਸਕਦੀਆਂ ਹਨ। ਕਿਉਂਕਿ ਇੱਕੋ object ਦੀਆਂ ਹੋਰ methods ਨੂੰ call ਕਰਦੇ ਸਮੇਂ methods ਨੂੰ ਕੋਈ ਵਿਸ਼ੇਸ਼ ਅਧਿਕਾਰ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹੁੰਦੇ, ਇਸ ਲਈ base class ਦੀ ਕੋਈ method ਜੋ ਉਸੇ base class ਦੀ ਦੂਜੀ method ਨੂੰ call ਕਰਦੀ ਹੈ, ਆਖਿਰਕਾਰ derived class ਦੀ override ਕੀਤੀ method ਨੂੰ call ਕਰ ਸਕਦੀ ਹੈ। (C++ programmers ਲਈ: Python ਵਿੱਚ ਸਾਰੀਆਂ methods ਅਸਲ ਵਿੱਚ virtual ਹੁੰਦੀਆਂ ਹਨ।)

Derived class ਵਿੱਚ overriding method ਕਈ ਵਾਰ base class ਦੀ method ਨੂੰ ਸਿਰਫ਼ ਬਦਲਣ ਦੀ ਬਜਾਏ ਉਸਦਾ ਵਿਸਥਾਰ ਕਰਨਾ ਚਾਹੁੰਦੀ ਹੈ। Base class ਦੀ method ਨੂੰ ਸਿੱਧਾ call ਕਰਨ ਦਾ ਇੱਕ ਆਸਾਨ ਤਰੀਕਾ ਹੈ: BaseClassName.methodname(self, arguments)। ਇਹ ਕਈ ਵਾਰ clients ਲਈ ਵੀ ਲਾਭਦਾਇਕ ਹੁੰਦਾ ਹੈ। (ਧਿਆਨ ਰਹੇ ਕਿ ਇਹ ਤਦ ਹੀ ਕੰਮ ਕਰੇਗਾ ਜੇਕਰ base class global scope ਵਿੱਚ BaseClassName ਵਜੋਂ ਪਹੁੰਚਯੋਗ ਹੋਵੇ।)

Python ਵਿੱਚ ਦੋ ਬਿਲਟ-ਇਨ ਫੰਕਸ਼ਨ ਹਨ ਜੋ inheritance ਨਾਲ ਕੰਮ ਕਰਦੇ ਹਨ:

  • ਇੰਸਟੈਂਸ ਦੀ ਕਿਸਮ ਜਾਂਚਣ ਲਈ isinstance() ਵਰਤੋ। isinstance(obj, int) ਤਦ ਹੀ True ਹੋਵੇਗਾ ਜੇਕਰ obj.__class__ int ਹੋਵੇ ਜਾਂ int ਤੋਂ ਡਿਰਾਈਵ ਕੀਤੀ ਕੋਈ ਕਲਾਸ ਹੋਵੇ।

  • ਕਲਾਸ ਵਿਰਾਸਤ ਦੀ ਜਾਂਚ ਕਰਨ ਲਈ issubclass() ਵਰਤੋ। issubclass(bool, int) ਦਾ ਨਤੀਜਾ True ਹੈ ਕਿਉਂਕਿ bool, int ਦੀ ਸਬਕਲਾਸ ਹੈ। ਹਾਲਾਂਕਿ issubclass(float, int) ਦਾ ਨਤੀਜਾ False ਹੈ ਕਿਉਂਕਿ float, int ਦੀ ਸਬਕਲਾਸ ਨਹੀਂ ਹੈ।

9.5.1. ਮਲਟੀਪਲ ਇਨਹੈਰਿਟੈਂਸ

Python ਮਲਟੀਪਲ ਇਨਹੈਰਿਟੈਂਸ ਦਾ ਸਮਰਥਨ ਵੀ ਕਰਦਾ ਹੈ। ਕਈ ਬੇਸ ਕਲਾਸਾਂ ਵਾਲੀ ਕਲਾਸ ਪਰਿਭਾਸ਼ਾ ਇਸ ਤਰ੍ਹਾਂ ਦਿਖਾਈ ਦਿੰਦੀ ਹੈ:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

ਜ਼ਿਆਦਾਤਰ ਮਾਮਲਿਆਂ ਵਿੱਚ, ਸਧਾਰਣ ਤਰੀਕੇ ਨਾਲ ਤੁਸੀਂ ਬੇਸ ਕਲਾਸਾਂ ਤੋਂ ਵਿਰਾਸਤ ਵਿੱਚ ਮਿਲੇ ਐਟ੍ਰਿਬਿਊਟਾਂ ਦੀ ਖੋਜ ਨੂੰ depth-first ਅਤੇ ਖੱਬੇ ਤੋਂ ਸੱਜੇ ਵੱਲ ਹੋਣ ਵਾਲੀ ਖੋਜ ਵਜੋਂ ਸਮਝ ਸਕਦੇ ਹੋ, ਜਿਸ ਵਿੱਚ ਇੱਕੋ ਕਲਾਸ ਨੂੰ ਦੋ ਵਾਰ ਨਹੀਂ ਖੰਗਾਲਿਆ ਜਾਂਦਾ ਜੇਕਰ ਇਨਹੈਰਿਟੈਂਸ ਲੜੀ ਵਿੱਚ ਓਵਰਲੈਪ ਹੋਵੇ। ਇਸ ਲਈ ਜੇਕਰ ਕੋਈ ਐਟ੍ਰਿਬਿਊਟ DerivedClassName ਵਿੱਚ ਨਾ ਮਿਲੇ, ਤਾਂ ਉਸਦੀ ਖੋਜ Base1 ਵਿੱਚ ਕੀਤੀ ਜਾਂਦੀ ਹੈ, ਫਿਰ (ਰਿਕਰਸਿਵ ਤਰੀਕੇ ਨਾਲ) Base1 ਦੀਆਂ ਬੇਸ ਕਲਾਸਾਂ ਵਿੱਚ, ਅਤੇ ਜੇ ਉੱਥੇ ਵੀ ਨਾ ਮਿਲੇ ਤਾਂ Base2 ਵਿੱਚ ਖੋਜ ਕੀਤੀ ਜਾਂਦੀ ਹੈ, ਅਤੇ ਇਹ ਪ੍ਰਕਿਰਿਆ ਅੱਗੇ ਵੀ ਜਾਰੀ ਰਹਿੰਦੀ ਹੈ।

ਅਸਲ ਵਿੱਚ ਇਹ ਇਸ ਤੋਂ ਕੁਝ ਵੱਧ ਜਟਿਲ ਹੈ। super() ਨੂੰ ਸਹਿਯੋਗੀ ਕਾਲਾਂ ਦਾ ਸਮਰਥਨ ਦੇਣ ਲਈ method resolution order ਗਤੀਸ਼ੀਲ ਤਰੀਕੇ ਨਾਲ ਬਦਲਦਾ ਹੈ। ਕੁਝ ਹੋਰ multiple inheritance ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ ਇਸ ਪਹੁੰਚ ਨੂੰ call-next-method ਕਿਹਾ ਜਾਂਦਾ ਹੈ ਅਤੇ ਇਹ single inheritance ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ ਮਿਲਣ ਵਾਲੀ super ਕਾਲ ਨਾਲੋਂ ਵੱਧ ਸ਼ਕਤੀਸ਼ਾਲੀ ਹੈ।

ਗਤੀਸ਼ੀਲ ਕ੍ਰਮਬੱਧਤਾ ਲਾਜ਼ਮੀ ਹੈ ਕਿਉਂਕਿ multiple inheritance ਦੇ ਸਾਰੇ ਮਾਮਲਿਆਂ ਵਿੱਚ ਇੱਕ ਜਾਂ ਵੱਧ diamond ਸੰਬੰਧ ਹੁੰਦੇ ਹਨ (ਜਿੱਥੇ ਘੱਟੋ-ਘੱਟ ਇੱਕ parent class ਤੱਕ ਸਭ ਤੋਂ ਹੇਠਲੀ ਕਲਾਸ ਤੋਂ ਕਈ ਰਸਤਿਆਂ ਰਾਹੀਂ ਪਹੁੰਚਿਆ ਜਾ ਸਕਦਾ ਹੈ)। ਉਦਾਹਰਨ ਲਈ, ਸਾਰੀਆਂ ਕਲਾਸਾਂ object ਤੋਂ ਵਿਰਾਸਤ ਲੈਂਦੀਆਂ ਹਨ, ਇਸ ਲਈ multiple inheritance ਵਿੱਚ object ਤੱਕ ਪਹੁੰਚਣ ਲਈ ਇੱਕ ਤੋਂ ਵੱਧ ਰਸਤੇ ਹੁੰਦੇ ਹਨ। ਬੇਸ ਕਲਾਸਾਂ ਨੂੰ ਇੱਕ ਤੋਂ ਵੱਧ ਵਾਰ ਐਕਸੈੱਸ ਕਰਨ ਤੋਂ ਬਚਾਉਣ ਲਈ, ਗਤੀਸ਼ੀਲ ਐਲਗੋਰਿਦਮ ਖੋਜ ਕ੍ਰਮ ਨੂੰ ਇਸ ਤਰੀਕੇ ਨਾਲ ਰੇਖੀਕ੍ਰਿਤ ਕਰਦਾ ਹੈ ਕਿ ਹਰ ਕਲਾਸ ਵਿੱਚ ਦਿੱਤਾ ਗਿਆ ਖੱਬੇ ਤੋਂ ਸੱਜੇ ਕ੍ਰਮ ਕਾਇਮ ਰਹੇ, ਹਰ parent ਨੂੰ ਸਿਰਫ਼ ਇੱਕ ਵਾਰ ਕਾਲ ਕੀਤਾ ਜਾਵੇ, ਅਤੇ ਕ੍ਰਮ monotonic ਰਹੇ (ਅਰਥਾਤ ਕੋਈ ਕਲਾਸ ਆਪਣੇ parent ਦੀ ਪ੍ਰਾਥਮਿਕਤਾ ਕ੍ਰਮ ਨੂੰ ਬਦਲੇ ਬਿਨਾਂ subclass ਬਣ ਸਕੇ)। ਇਹ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਮਿਲ ਕੇ ਭਰੋਸੇਯੋਗ ਅਤੇ ਵਿਸਤਾਰਯੋਗ multiple inheritance ਕਲਾਸਾਂ ਬਣਾਉਣ ਨੂੰ ਸੰਭਵ ਬਣਾਉਂਦੀਆਂ ਹਨ। ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ The Python 2.3 Method Resolution Order ਵੇਖੋ।

ਕੁਝ ਮਾਮਲਿਆਂ ਵਿੱਚ multiple inheritance ਦੀ ਆਗਿਆ ਨਹੀਂ ਹੁੰਦੀ। ਵੇਰਵਿਆਂ ਲਈ Multiple inheritance ਵੇਖੋ।

9.6. ਪ੍ਰਾਈਵੇਟ ਵੇਰੀਏਬਲ

Python ਵਿੱਚ ਐਸੇ "private" ਇੰਸਟੈਂਸ ਵੇਰੀਏਬਲ ਮੌਜੂਦ ਨਹੀਂ ਹਨ ਜਿਨ੍ਹਾਂ ਤੱਕ ਸਿਰਫ਼ ਆਬਜੈਕਟ ਦੇ ਅੰਦਰੋਂ ਹੀ ਪਹੁੰਚ ਕੀਤੀ ਜਾ ਸਕੇ। ਹਾਲਾਂਕਿ ਇੱਕ ਰਿਵਾਜ ਹੈ ਜਿਸਦੀ ਪਾਲਣਾ ਜ਼ਿਆਦਾਤਰ Python ਕੋਡ ਕਰਦਾ ਹੈ: ਅੰਡਰਸਕੋਰ ਨਾਲ ਸ਼ੁਰੂ ਹੋਣ ਵਾਲੇ ਨਾਮ (ਜਿਵੇਂ _spam) ਨੂੰ API ਦਾ ਗੈਰ-ਜਨਤਕ ਹਿੱਸਾ ਮੰਨਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ (ਭਾਵੇਂ ਉਹ ਫੰਕਸ਼ਨ, ਮੈਥਡ ਜਾਂ ਡਾਟਾ ਮੈਂਬਰ ਹੋਵੇ)। ਇਸਨੂੰ ਇੰਪਲੀਮੈਂਟੇਸ਼ਨ ਦੀ ਅੰਦਰੂਨੀ ਜਾਣਕਾਰੀ ਮੰਨਿਆ ਜਾਣਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਇਹ ਬਿਨਾਂ ਸੂਚਨਾ ਦੇ ਬਦਲ ਸਕਦੀ ਹੈ।

ਕਿਉਂਕਿ ਕਲਾਸ-ਪ੍ਰਾਈਵੇਟ ਮੈਂਬਰਾਂ ਲਈ ਇੱਕ ਵੈਧ ਉਪਯੋਗ-ਕੇਸ ਹੈ (ਖਾਸ ਕਰਕੇ ਸਬ-ਕਲਾਸਾਂ ਦੁਆਰਾ ਪਰਿਭਾਸ਼ਿਤ ਨਾਵਾਂ ਨਾਲ ਨਾਮਾਂ ਦੇ ਟਕਰਾਅ ਤੋਂ ਬਚਣ ਲਈ), ਇਸ ਤਰ੍ਹਾਂ ਦੇ ਮਕੈਨਿਜ਼ਮ ਲਈ ਸੀਮਤ ਸਹਾਇਤਾ ਹੈ, ਜਿਸਨੂੰ name mangling ਕਿਹਾ ਜਾਂਦਾ ਹੈ। __spam (ਘੱਟੋ-ਘੱਟ ਦੋ ਅੱਗੇਲੇ ਅੰਡਰਸਕੋਰ, ਵੱਧ ਤੋਂ ਵੱਧ ਇੱਕ ਪਿੱਛਲਾ ਅੰਡਰਸਕੋਰ) ਨੂੰ ਟੈਕਸਟੂਅਲ ਤੌਰ 'ਤੇ _classname__spam ਨਾਲ ਬਦਲ ਦਿੱਤਾ ਜਾਂਦਾ ਹੈ, ਜਿੱਥੇ classname ਉਹ ਮੌਜੂਦਾ ਕਲਾਸ ਦਾ ਨਾਮ ਹੈ ਜਿਸ ਤੋਂ ਅੱਗੇਲੇ ਅੰਡਰਸਕੋਰ(ਆਂ) ਹਟਾ ਦਿੱਤੇ ਗਏ ਹਨ। ਇਹ ਮੈਂਗਲਿੰਗ ਆਈਡੈਂਟਿਫਾਇਰ ਦੀ ਵਾਕ-ਰਚਨਾਤਮਕ ਸਥਿਤੀ ਦੀ ਪਰਵਾਹ ਕੀਤੇ ਬਿਨਾਂ ਕੀਤੀ ਜਾਂਦੀ ਹੈ, ਜਦ ਤੱਕ ਇਹ ਕਿਸੇ ਕਲਾਸ ਦੀ ਪਰਿਭਾਸ਼ਾ ਦੇ ਅੰਦਰ ਆਉਂਦਾ ਹੈ।

See also

ਵੇਰਵਿਆਂ ਅਤੇ ਵਿਸ਼ੇਸ਼ ਮਾਮਲਿਆਂ ਲਈ private name mangling specifications ਵੇਖੋ।

ਨੇਮ ਮੈਂਗਲਿੰਗ ਸਬਕਲਾਸਾਂ ਨੂੰ ਮੈਥਡ ਓਵਰਰਾਈਡ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ ਬਿਨਾਂ ਕਲਾਸ ਦੇ ਅੰਦਰਲੇ ਮੈਥਡ ਕਾਲਾਂ ਨੂੰ ਪ੍ਰਭਾਵਿਤ ਕੀਤੇ। ਉਦਾਹਰਨ ਲਈ:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

ਉਪਰੋਕਤ ਉਦਾਹਰਨ ਤਦ ਵੀ ਕੰਮ ਕਰੇਗੀ ਜੇ MappingSubclass ਆਪਣਾ __update ਪਛਾਣਕਰਤਾ ਪਰਿਭਾਸ਼ਿਤ ਕਰੇ, ਕਿਉਂਕਿ Mapping ਕਲਾਸ ਵਿੱਚ ਇਹ _Mapping__update ਅਤੇ MappingSubclass ਕਲਾਸ ਵਿੱਚ _MappingSubclass__update ਨਾਲ ਬਦਲਿਆ ਜਾਂਦਾ ਹੈ।

ਧਿਆਨ ਦਿਓ ਕਿ ਮੈਂਗਲਿੰਗ ਦੇ ਨਿਯਮ ਮੁੱਖ ਤੌਰ 'ਤੇ ਅਣਚਾਹੀਆਂ ਟਕਰਾਵਾਂ ਤੋਂ ਬਚਣ ਲਈ ਬਣਾਏ ਗਏ ਹਨ; ਫਿਰ ਵੀ ਕਿਸੇ ਐਸੇ ਵੇਰੀਏਬਲ ਤੱਕ ਪਹੁੰਚਣਾ ਜਾਂ ਉਸਨੂੰ ਸੋਧਣਾ ਸੰਭਵ ਹੈ ਜਿਸਨੂੰ ਪ੍ਰਾਈਵੇਟ ਮੰਨਿਆ ਜਾਂਦਾ ਹੈ। ਇਹ ਕੁਝ ਵਿਸ਼ੇਸ਼ ਸਥਿਤੀਆਂ ਵਿੱਚ, ਜਿਵੇਂ ਡੀਬੱਗਰ ਵਿੱਚ, ਲਾਭਦਾਇਕ ਵੀ ਹੋ ਸਕਦਾ ਹੈ।

ਧਿਆਨ ਦਿਓ ਕਿ exec() ਜਾਂ eval() ਨੂੰ ਦਿੱਤਾ ਗਿਆ ਕੋਡ ਕਾਲ ਕਰਨ ਵਾਲੀ ਕਲਾਸ ਦੇ ਨਾਮ ਨੂੰ ਮੌਜੂਦਾ ਕਲਾਸ ਨਹੀਂ ਮੰਨਦਾ। ਇਹ global ਸਟੇਟਮੈਂਟ ਦੇ ਪ੍ਰਭਾਵ ਵਾਂਗ ਹੈ, ਜਿਸਦਾ ਪ੍ਰਭਾਵ ਵੀ ਸਿਰਫ਼ ਉਸ ਕੋਡ ਤੱਕ ਸੀਮਿਤ ਹੁੰਦਾ ਹੈ ਜੋ ਇਕੱਠੇ byte-compile ਕੀਤਾ ਗਿਆ ਹੋਵੇ। ਇਹੀ ਸੀਮਾ getattr(), setattr() ਅਤੇ delattr() 'ਤੇ ਵੀ ਲਾਗੂ ਹੁੰਦੀ ਹੈ, ਅਤੇ __dict__ ਨੂੰ ਸਿੱਧਾ ਹਵਾਲਾ ਦੇਣ ਸਮੇਂ ਵੀ।

9.7. ਕੁਝ ਹੋਰ ਲਾਭਦਾਇਕ ਜਾਣਕਾਰੀਆਂ

ਕਈ ਵਾਰ Pascal ਦੇ "record" ਜਾਂ C ਦੇ "struct" ਵਰਗੀ ਡਾਟਾ ਟਾਈਪ ਲਾਭਦਾਇਕ ਹੁੰਦੀ ਹੈ, ਜੋ ਕੁਝ ਨਾਮਿਤ ਡਾਟਾ ਆਈਟਮਾਂ ਨੂੰ ਇਕੱਠਾ ਰੱਖਦੀ ਹੈ। ਇਸ ਉਦੇਸ਼ ਲਈ Python ਵਿੱਚ ਆਮ ਤਰੀਕਾ dataclasses ਦੀ ਵਰਤੋਂ ਕਰਨਾ ਹੈ:

from dataclasses import dataclass

@dataclass
class Employee:
    name: str
    dept: str
    salary: int
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000

Python ਕੋਡ ਦਾ ਕੋਈ ਹਿੱਸਾ ਜੋ ਕਿਸੇ ਖਾਸ abstract data type ਦੀ ਉਮੀਦ ਕਰਦਾ ਹੈ, ਉਸਨੂੰ ਅਕਸਰ ਐਸੀ ਕਲਾਸ ਦਿੱਤੀ ਜਾ ਸਕਦੀ ਹੈ ਜੋ ਉਸ ਡਾਟਾ ਟਾਈਪ ਦੀਆਂ ਮੈਥਡਾਂ ਦੀ ਨਕਲ ਕਰਦੀ ਹੋਵੇ। ਉਦਾਹਰਨ ਲਈ, ਜੇ ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਫੰਕਸ਼ਨ ਹੈ ਜੋ file object ਤੋਂ ਡਾਟਾ ਫਾਰਮੈਟ ਕਰਦਾ ਹੈ, ਤਾਂ ਤੁਸੀਂ read() ਅਤੇ readline() ਮੈਥਡਾਂ ਵਾਲੀ ਕਲਾਸ ਬਣਾ ਸਕਦੇ ਹੋ ਜੋ ਡਾਟਾ ਸਟ੍ਰਿੰਗ ਬਫ਼ਰ ਤੋਂ ਪ੍ਰਾਪਤ ਕਰੇ ਅਤੇ ਉਸਨੂੰ ਆਰਗੂਮੈਂਟ ਵਜੋਂ ਭੇਜ ਸਕਦੇ ਹੋ।

Instance method objects ਦੇ ਆਪਣੇ ਐਟ੍ਰਿਬਿਊਟ ਵੀ ਹੁੰਦੇ ਹਨ। m.__self__ ਉਹ ਇੰਸਟੈਂਸ ਆਬਜੈਕਟ ਹੈ ਜਿਸ ਨਾਲ m() ਮੈਥਡ ਸੰਬੰਧਿਤ ਹੈ, ਅਤੇ m.__func__ ਉਸ ਮੈਥਡ ਨਾਲ ਸੰਬੰਧਿਤ function object ਹੈ।

9.8. ਇਟਰੇਟਰ

ਹੁਣ ਤੱਕ ਤੁਸੀਂ ਸ਼ਾਇਦ ਧਿਆਨ ਦਿੱਤਾ ਹੋਵੇਗਾ ਕਿ ਜ਼ਿਆਦਾਤਰ ਕੰਟੇਨਰ ਆਬਜੈਕਟਾਂ ਉੱਤੇ for ਸਟੇਟਮੈਂਟ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਲੂਪ ਚਲਾਇਆ ਜਾ ਸਕਦਾ ਹੈ:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

ਪਹੁੰਚ ਦਾ ਇਹ ਤਰੀਕਾ ਸਪਸ਼ਟ, ਸੰਖੇਪ ਅਤੇ ਸੁਵਿਧਾਜਨਕ ਹੈ। ਇਟਰੇਟਰਾਂ ਦੀ ਵਰਤੋਂ Python ਵਿੱਚ ਹਰ ਥਾਂ ਮਿਲਦੀ ਹੈ ਅਤੇ ਇਹ ਭਾਸ਼ਾ ਨੂੰ ਇੱਕਸਾਰ ਬਣਾਉਂਦੀ ਹੈ। ਅੰਦਰੂਨੀ ਤੌਰ 'ਤੇ for ਸਟੇਟਮੈਂਟ ਕੰਟੇਨਰ ਆਬਜੈਕਟ ਉੱਤੇ iter() ਨੂੰ ਕਾਲ ਕਰਦੀ ਹੈ। ਇਹ ਫੰਕਸ਼ਨ ਇੱਕ ਇਟਰੇਟਰ ਆਬਜੈਕਟ ਵਾਪਸ ਕਰਦਾ ਹੈ ਜੋ __next__() ਮੈਥਡ ਪਰਿਭਾਸ਼ਿਤ ਕਰਦਾ ਹੈ। ਇਹ ਮੈਥਡ ਕੰਟੇਨਰ ਦੇ ਐਲਿਮੈਂਟ ਇੱਕ-ਇੱਕ ਕਰਕੇ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ। ਜਦੋਂ ਹੋਰ ਐਲਿਮੈਂਟ ਨਹੀਂ ਰਹਿੰਦੇ, ਤਾਂ __next__() StopIteration ਐਕਸੈਪਸ਼ਨ ਪੈਦਾ ਕਰਦਾ ਹੈ, ਜੋ for ਲੂਪ ਨੂੰ ਸਮਾਪਤ ਕਰਨ ਲਈ ਕਹਿੰਦੀ ਹੈ। ਤੁਸੀਂ __next__() ਮੈਥਡ ਨੂੰ next() ਬਿਲਟ-ਇਨ ਫੰਕਸ਼ਨ ਰਾਹੀਂ ਕਾਲ ਕਰ ਸਕਦੇ ਹੋ; ਹੇਠਾਂ ਦਿੱਤੀ ਉਦਾਹਰਨ ਇਹ ਦਿਖਾਉਂਦੀ ਹੈ ਕਿ ਇਹ ਸਭ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

Iterator protocol ਦੇ ਅੰਦਰੂਨੀ ਕਾਰਜ-ਤਰੀਕੇ ਨੂੰ ਸਮਝਣ ਤੋਂ ਬਾਅਦ, ਆਪਣੀਆਂ classes ਵਿੱਚ iterator behavior ਜੋੜਨਾ ਆਸਾਨ ਹੋ ਜਾਂਦਾ ਹੈ। ਇੱਕ __iter__() method ਪਰਿਭਾਸ਼ਿਤ ਕਰੋ ਜੋ ਐਸਾ object ਵਾਪਸ ਕਰੇ ਜਿਸ ਵਿੱਚ __next__() method ਹੋਵੇ। ਜੇ class ਖੁਦ __next__() ਪਰਿਭਾਸ਼ਿਤ ਕਰਦੀ ਹੈ, ਤਾਂ __iter__() ਸਿਰਫ਼ self ਵਾਪਸ ਕਰ ਸਕਦੀ ਹੈ:

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

9.9. ਜਨਰੇਟਰ

Generators ਇਟਰੇਟਰ ਬਣਾਉਣ ਲਈ ਇੱਕ ਸਰਲ ਅਤੇ ਸ਼ਕਤੀਸ਼ਾਲੀ ਸਾਧਨ ਹਨ। ਇਹ ਆਮ ਫੰਕਸ਼ਨਾਂ ਵਾਂਗ ਲਿਖੇ ਜਾਂਦੇ ਹਨ, ਪਰ ਜਦੋਂ ਵੀ ਡਾਟਾ ਵਾਪਸ ਕਰਨਾ ਹੋਵੇ ਤਾਂ yield ਸਟੇਟਮੈਂਟ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ। ਹਰ ਵਾਰ ਜਦੋਂ next() ਕਾਲ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਜਨਰੇਟਰ ਉੱਥੋਂ ਹੀ ਮੁੜ ਚਲਣਾ ਸ਼ੁਰੂ ਕਰਦਾ ਹੈ ਜਿੱਥੇ ਉਹ ਪਹਿਲਾਂ ਰੁਕਿਆ ਸੀ (ਇਹ ਸਾਰੇ ਡਾਟਾ ਮੁੱਲ ਅਤੇ ਆਖਰੀ ਚਲਾਈ ਗਈ ਸਟੇਟਮੈਂਟ ਨੂੰ ਯਾਦ ਰੱਖਦਾ ਹੈ)। ਹੇਠਾਂ ਦਿੱਤੀ ਉਦਾਹਰਨ ਦਿਖਾਉਂਦੀ ਹੈ ਕਿ ਜਨਰੇਟਰ ਬਣਾਉਣਾ ਕਿੰਨਾ ਆਸਾਨ ਹੈ:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
>>> for char in reverse('golf'):
...     print(char)
...
f
l
o
g

ਜੋ ਕੁਝ ਵੀ generators ਨਾਲ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ, ਉਹ ਪਿਛਲੇ ਭਾਗ ਵਿੱਚ ਵਰਣਿਤ class-based iterators ਨਾਲ ਵੀ ਕੀਤਾ ਜਾ ਸਕਦਾ ਹੈ। Generators ਨੂੰ ਇੰਨਾ ਸੰਖੇਪ ਬਣਾਉਣ ਵਾਲੀ ਗੱਲ ਇਹ ਹੈ ਕਿ __iter__() ਅਤੇ __next__() methods ਆਪਣੇ ਆਪ ਬਣ ਜਾਂਦੀਆਂ ਹਨ।

ਇੱਕ ਹੋਰ ਮਹੱਤਵਪੂਰਨ ਵਿਸ਼ੇਸ਼ਤਾ ਇਹ ਹੈ ਕਿ ਕਾਲਾਂ ਦੇ ਦਰਮਿਆਨ ਸਥਾਨਕ ਵੇਰੀਏਬਲ ਅਤੇ ਐਗਜ਼ਿਕਿਊਸ਼ਨ ਸਥਿਤੀ ਆਪਣੇ ਆਪ ਸੰਭਾਲੀ ਜਾਂਦੀ ਹੈ। ਇਸ ਨਾਲ ਫੰਕਸ਼ਨ ਲਿਖਣਾ ਆਸਾਨ ਹੋ ਜਾਂਦਾ ਹੈ ਅਤੇ self.index ਅਤੇ self.data ਵਰਗੇ ਇੰਸਟੈਂਸ ਵੇਰੀਏਬਲਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵਾਲੀ ਪਹੁੰਚ ਨਾਲੋਂ ਕਾਫ਼ੀ ਵੱਧ ਸਪਸ਼ਟ ਬਣ ਜਾਂਦਾ ਹੈ।

Methods ਦੇ ਆਪਣੇ ਆਪ ਬਣਨ ਅਤੇ program state ਸੰਭਾਲੇ ਜਾਣ ਤੋਂ ਇਲਾਵਾ, ਜਦੋਂ generators ਸਮਾਪਤ ਹੁੰਦੇ ਹਨ ਤਾਂ ਉਹ ਆਪਣੇ ਆਪ StopIteration raise ਕਰਦੇ ਹਨ। ਇਹ ਸਾਰੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਮਿਲ ਕੇ iterators ਬਣਾਉਣ ਨੂੰ ਆਮ function ਲਿਖਣ ਜਿੰਨਾ ਆਸਾਨ ਬਣਾ ਦਿੰਦੀਆਂ ਹਨ।

9.10. ਜਨਰੇਟਰ ਐਕਸਪ੍ਰੈਸ਼ਨ

ਕੁਝ ਸਰਲ ਜਨਰੇਟਰਾਂ ਨੂੰ list comprehension ਵਰਗੇ ਸਿੰਟੈਕਸ ਨਾਲ, ਪਰ square brackets ਦੀ ਥਾਂ parentheses ਦੀ ਵਰਤੋਂ ਕਰਕੇ, ਸੰਖੇਪ ਐਕਸਪ੍ਰੈਸ਼ਨ ਵਜੋਂ ਲਿਖਿਆ ਜਾ ਸਕਦਾ ਹੈ। ਇਹ ਐਕਸਪ੍ਰੈਸ਼ਨ ਉਹਨਾਂ ਸਥਿਤੀਆਂ ਲਈ ਬਣਾਈਆਂ ਗਈਆਂ ਹਨ ਜਿੱਥੇ ਜਨਰੇਟਰ ਨੂੰ ਕਿਸੇ ਬਾਹਰੀ ਫੰਕਸ਼ਨ ਦੁਆਰਾ ਤੁਰੰਤ ਵਰਤਿਆ ਜਾਣਾ ਹੋਵੇ। ਜਨਰੇਟਰ ਐਕਸਪ੍ਰੈਸ਼ਨ ਪੂਰੀ ਜਨਰੇਟਰ ਪਰਿਭਾਸ਼ਾ ਨਾਲੋਂ ਵੱਧ ਸੰਖੇਪ ਹੁੰਦੇ ਹਨ, ਪਰ ਘੱਟ ਲਚਕਦਾਰ ਹੁੰਦੇ ਹਨ ਅਤੇ ਸਮਾਨ list comprehension ਨਾਲੋਂ ਘੱਟ ਮੈਮੋਰੀ ਵਰਤਦੇ ਹਨ।

ਉਦਾਹਰਨਾਂ:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> unique_words = set(word for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

>>> x = [[1,2,3], [], [4, 5]]
>>> g = (*i for i in x)
>>> list(g)
[1, 2, 3, 4, 5]

ਜ਼ਿਆਦਾਤਰ ਮਾਮਲਿਆਂ ਵਿੱਚ, ਜਨਰੇਟਰ ਐਕਸਪ੍ਰੈਸ਼ਨਾਂ ਨੂੰ parentheses ਵਿੱਚ ਲਪੇਟਣਾ ਲਾਜ਼ਮੀ ਹੁੰਦਾ ਹੈ। ਹਾਲਾਂਕਿ ਇੱਕ ਵਿਸ਼ੇਸ਼ ਮਾਮਲੇ ਵਿੱਚ, ਜਦੋਂ ਇਹ ਕਿਸੇ ਫੰਕਸ਼ਨ ਨੂੰ ਇਕੱਲੇ ਆਰਗੂਮੈਂਟ ਵਜੋਂ ਦਿੱਤੀਆਂ ਜਾਣ (ਜਿਵੇਂ ਉੱਪਰ sum, set, max ਅਤੇ list ਦੀਆਂ ਉਦਾਹਰਨਾਂ ਵਿੱਚ), ਤਾਂ ਵਾਧੂ parentheses ਦੀ ਲੋੜ ਨਹੀਂ ਹੁੰਦੀ। ਅਰਥਾਤ ਹੇਠਾਂ ਦਿੱਤੇ ਦੋਵੇਂ ਕੋਡ ਅਰਥਕ ਤੌਰ 'ਤੇ ਇਕੋ ਜਿਹੇ ਹਨ:

>>> f(x for x in y)
>>> f((x for x in y))

ਹੇਠਾਂ ਦਿੱਤੀਆਂ ਉਦਾਹਰਨਾਂ ਵੀ ਇਸੇ ਤਰ੍ਹਾਂ ਹਨ:

>>> f(*x for x in y)
>>> f((*x for x in y))

ਫੁੱਟਨੋਟ