Why Functions Exist — The Core Idea ☕Functions क्यों होते हैं — मूल विचार ☕为什么需要函数 — 核心思想 ☕
A function is that coffee machine. Write the logic once, give it a name, and call it whenever you need it. The rest of your code stops caring how it works — only what it returns.
Function वही कॉफ़ी मशीन है। Logic एक बार लिखें, उसे नाम दें, और जब भी ज़रूरत हो call करें। बाक़ी code को फ़र्क नहीं पड़ता कि यह कैसे काम करता है — बस यह कि यह क्या लौटाता है।
函数 就是那台咖啡机。把逻辑写一次,起个名字,需要时就调用它。其余代码不再关心它如何工作 — 只关心它返回什么。
In Python, a function is a named, reusable block of code. It takes zero or more inputs (called arguments), does some work, and can give back a result with return. Every serious program is built from many small functions that call each other.
Python में function code का एक नाम वाला, दोबारा इस्तेमाल होने वाला हिस्सा है। यह शून्य या ज़्यादा inputs (जिन्हें arguments कहते हैं) लेता है, कुछ काम करता है, और return से result वापस दे सकता है। हर बड़ा program कई छोटे functions से बनता है जो एक-दूसरे को call करते हैं।
在 Python 中,函数 是一段有名字、可复用的代码。它接收零个或多个输入(叫作 实参,arguments),做一些工作,并可以用 return 返回结果。每个正经的程序都由许多互相调用的小函数组成。
The caller only sees the arguments it sends and the value it gets back. Everything inside the box stays private.
Caller को सिर्फ़ भेजे गए arguments और वापस मिली value दिखती है। डिब्बे के अंदर का सब कुछ निजी रहता है।
调用者只看到它送进去的实参和拿回来的值。盒子里的一切都是私有的。
Don't Repeat Yourself. If you copy-paste the same three lines into five places, that is a function waiting to be born. Functions make change cheap and bugs rare: fix the logic once, and every caller gets the fix.
Don't Repeat Yourself — ख़ुद को न दोहराएँ। अगर आप वही तीन lines पाँच जगह copy-paste करते हैं, तो वहाँ एक function बनने का इंतज़ार कर रहा है। Functions बदलाव को सस्ता और bugs को कम बनाते हैं: logic एक बार ठीक करें, और हर caller को सुधार मिल जाता है।
Don't Repeat Yourself — 不要重复自己。如果你把同样的三行代码复制粘贴到五个地方,那里就有一个等着诞生的函数。函数让修改变得便宜、bug 变得罕见:逻辑只改一次,每个调用者都得到修复。
Look for the interactive boxes: the Function Call Stepper (Section 02), the Argument Matching Machine (06), the Packing Machine for *args/**kwargs (09), the Reference Visualizer (10), the Mutable Default Trap Lab (11) and the Logger Call Builder (12). Change the values, press Play, and watch Python work step by step. Errors match real Python.
Interactive डिब्बे खोजें: Function Call Stepper (Section 02), Argument Matching Machine (06), *args/**kwargs के लिए Packing Machine (09), Reference Visualizer (10), Mutable Default Trap Lab (11) और Logger Call Builder (12)। Values बदलें, Play दबाएँ, और Python को कदम-दर-कदम काम करते देखें। Errors असली Python जैसे ही हैं।
请留意这些互动框:Function Call Stepper(第 02 节)、Argument Matching Machine(06)、用于 *args/**kwargs 的 Packing Machine(09)、Reference Visualizer(10)、Mutable Default Trap Lab(11)和 Logger Call Builder(12)。修改数值,按下播放,一步步看 Python 如何工作。错误信息与真实 Python 一致。
The Anatomy of a Function — def, Parameters, return 🔬Function की बनावट — def, Parameters, return 🔬函数的结构 — def、形参、return 🔬
Every Python function has the same skeleton. Learn it once and you can read all of them.
हर Python function का ढाँचा एक जैसा होता है। इसे एक बार सीख लें, तो आप सभी पढ़ सकेंगे।
每个 Python 函数都有相同的骨架。学会一次,就能读懂所有函数。
Minimal Example — Greeting a Userसबसे छोटा उदाहरण — User का स्वागत最小示例 — 问候用户
def greet(name):
# Body indented with 4 spaces
message = f"Hello, {name}!"
return message
# Calling the function
result = greet("Aarav")
print(result)
A parameter is the variable name in the function definition (name above). An argument is the actual value you pass when calling it ("Aarav"). People mix these up in casual talk, but interviewers love this difference.
Parameter function की definition में लिखा variable का नाम है (ऊपर name)। Argument वह असली value है जो call करते समय दी जाती है ("Aarav")। आम बातचीत में लोग इन्हें मिला देते हैं, पर interview लेने वालों को यह अंतर बहुत पसंद है।
形参(parameter)是函数定义里的变量名(上面的 name)。实参(argument)是调用时传入的实际值("Aarav")。日常交流中人们常混用,但面试官很爱考这个区别。
Functions Without return — What Actually Happensreturn के बिना Functions — असल में क्या होता है没有 return 的函数 — 实际发生了什么
def shout(word):
print(word.upper() + "!")
# No return statement
result = shout("hello")
print("result is:", result)
print("type is:", type(result))
print shows something on the screen. return hands a value back to the caller so it can be stored, passed on or used. A function that only prints cannot be chained or tested.
print screen पर कुछ दिखाता है। return caller को value वापस देता है ताकि उसे रखा, आगे भेजा या इस्तेमाल किया जा सके। जो function सिर्फ़ print करता है, उसे न जोड़ा जा सकता है, न test किया जा सकता है।
print 在屏幕上显示内容。return 把值交还给调用者,让它可以被保存、传递或使用。只会 print 的函数无法串联使用,也无法测试。
Returning Several Valuesकई Values लौटाना返回多个值
A function can return only one object — but that object can be a tuple. Write values with commas after return, and unpack them at the call site.
Function सिर्फ़ एक object लौटा सकता है — पर वह object एक tuple हो सकता है। return के बाद values को commas से लिखें, और call करने की जगह उन्हें अलग-अलग नामों में खोल लें।
函数只能返回一个对象 — 但这个对象可以是 tuple。在 return 后用逗号写多个值,再在调用处解包。
def min_max(numbers):
return min(numbers), max(numbers) # really ONE tuple
pair = min_max([4, 9, 1, 7])
print(pair, type(pair).__name__)
low, high = min_max([4, 9, 1, 7]) # unpack it into two names
print("low =", low, "| high =", high)
Try It — Function Call Stepperकरके देखें — Function Call Stepper动手试试 — 函数调用步进器
Watch what really happens when you call a function. A new frame (a private workspace for the function's variables) is pushed onto the call stack, the body runs, and return hands a value back before the frame disappears. Try the print vs return tab to see why printing is not returning.
देखें कि function call करने पर असल में क्या होता है। Call stack पर एक नया frame (function के variables का निजी कार्यस्थान) रखा जाता है, body चलती है, और frame के गायब होने से पहले return value वापस देता है। print vs return tab आज़माएँ और देखें कि print करना return करना क्यों नहीं है।
看看调用函数时究竟发生了什么。一个新的 帧(存放函数变量的私有工作区)被压入 调用栈,函数体运行,return 在帧消失之前把值交回。试试 print vs return 标签页,看看为什么打印不等于返回。
Positional Arguments — Order Is Everything 🔢Positional Arguments — क्रम ही सब कुछ है 🔢位置参数 — 顺序就是一切 🔢
When you call a function with values but no names, Python matches them by position — first argument to first parameter, second to second, and so on. Swap the order and you get a bug, not an error.
जब आप बिना नामों के values देकर function call करते हैं, तो Python उन्हें स्थान से मिलाता है — पहला argument पहले parameter को, दूसरा दूसरे को, और आगे भी ऐसे ही। क्रम उलट दें तो error नहीं, bug मिलता है।
当你只传值、不写名字调用函数时,Python 按位置 匹配 — 第一个实参给第一个形参,第二个给第二个,依此类推。顺序一换,得到的是 bug,而不是报错。
def divide(numerator, denominator):
return numerator / denominator
# Positional — order matters
print(divide(10, 2)) # 10 / 2 = 5.0 correct
print(divide(2, 10)) # 2 / 10 = 0.2 silent bug!
Python does not check whether your values make sense — only that the count matches. Wrong-order positional arguments give wrong answers with zero warnings. This is why keyword arguments exist.
Python यह नहीं जाँचता कि आपकी values का कोई मतलब बनता है या नहीं — सिर्फ़ यह कि गिनती मेल खाती है। ग़लत क्रम के positional arguments बिना किसी चेतावनी के ग़लत जवाब देते हैं। इसीलिए keyword arguments बने हैं।
Python 不检查你的值 是否合理 — 只检查数量是否对得上。顺序错了的位置参数会在没有任何警告的情况下给出错误答案。这就是 关键字参数 存在的原因。
Keyword Arguments — Name Them and Sleep Well 🏷️Keyword Arguments — नाम दें और चैन से सोएँ 🏷️关键字参数 — 写上名字,睡个好觉 🏷️
You can pass arguments by name instead of position with the parameter=value syntax. Keyword arguments explain themselves and are safe from the order-swap bug above.
parameter=value लिखकर आप arguments को स्थान की जगह नाम से दे सकते हैं। Keyword arguments ख़ुद अपना मतलब बताते हैं और ऊपर वाले क्रम-उलटने के bug से सुरक्षित हैं।
用 parameter=value 语法,可以 按名字 而不是按位置传参。关键字参数自带说明,也不会出现上面那种顺序颠倒的 bug。
def divide(numerator, denominator):
return numerator / denominator
# Keyword calls — order does not matter
print(divide(numerator=10, denominator=2)) # 5.0
print(divide(denominator=2, numerator=10)) # 5.0 — same!
# Mix positional then keyword (positional MUST come first)
print(divide(10, denominator=2)) # 5.0 OK
# Keyword before positional is a SyntaxError — Python refuses to run the file
try:
compile("divide(numerator=10, 2)", "<test>", "eval")
except SyntaxError as e:
print("SyntaxError:", e.msg)
If a function takes more than two arguments, or any argument is a True/False flag, use keyword arguments at the call site. Compare send_email("a@b", "hi", True, False) with send_email(to="a@b", subject="hi", html=True, urgent=False). Only the second is readable six months later.
अगर function दो से ज़्यादा arguments लेता है, या कोई argument True/False flag है, तो call करते समय keyword arguments इस्तेमाल करें। send_email("a@b", "hi", True, False) की तुलना send_email(to="a@b", subject="hi", html=True, urgent=False) से करें। छह महीने बाद सिर्फ़ दूसरा पढ़ा जा सकेगा।
如果函数接收超过 两个 参数,或者有 True/False 开关参数,调用时就用关键字参数。比较 send_email("a@b", "hi", True, False) 和 send_email(to="a@b", subject="hi", html=True, urgent=False)。六个月后只有第二个还能看懂。
Default Arguments — Sensible Fallbacks 🎛️Default Arguments — समझदार विकल्प 🎛️默认参数 — 合理的后备值 🎛️
You can give a parameter a default value in the definition with parameter=default. If the caller does not supply it, Python uses the default. This makes functions flexible without forcing callers to set every option.
Definition में parameter=default लिखकर आप parameter को default value दे सकते हैं। Caller अगर उसे न दे, तो Python default इस्तेमाल करता है। इससे functions लचीले बनते हैं और caller को हर विकल्प भरना नहीं पड़ता।
在定义中用 parameter=default 给形参一个 默认值。调用者不提供时,Python 就用默认值。这让函数更灵活,调用者不必设置每个选项。
def connect(host, port=8080, timeout=30, use_ssl=True):
print(f"Connecting to {host}:{port}")
print(f" timeout={timeout}s, ssl={use_ssl}")
# Only the required argument — rest use defaults
connect("api.example.com")
# Override just the port
connect("api.example.com", 9000)
# Override port and timeout, keep ssl=True
connect("api.example.com", 9000, 60)
def f(a=1, b) is a SyntaxError: parameter without a default follows parameter with a default.
Default वाले parameters, बिना default वालों के बाद आने चाहिए। def f(a=1, b) एक SyntaxError है: parameter without a default follows parameter with a default।
带默认值的形参必须写在不带默认值的形参 之后。def f(a=1, b) 是 SyntaxError:parameter without a default follows parameter with a default。
def line runs — not on every call. This causes the famous mutable-default trap (Section 11).
Defaults एक बार बनते हैं, जब def line चलती है — हर call पर नहीं। इसी से मशहूर mutable-default जाल बनता है (Section 11)।
默认值只在 def 这一行运行时计算 一次 — 而不是每次调用都算。这就是著名的可变默认值陷阱的来源(第 11 节)。
Calling Default Arguments by Keyword — The Skip Trick ⏭️Default Arguments को Keyword से Call करना — छोड़ने की तरकीब ⏭️用关键字调用默认参数 — 跳过技巧 ⏭️
Here defaults and keyword calls combine into a superpower. What if you want to keep the defaults for port and timeout, but change only use_ssl? Without keywords you would have to repeat every earlier value. With them, you jump straight to the one you care about.
यहाँ defaults और keyword calls मिलकर ख़ास ताक़त बनते हैं। अगर आप port और timeout के defaults रखना चाहें, पर सिर्फ़ use_ssl बदलना चाहें तो? Keywords के बिना आपको पहले की हर value दोहरानी पड़ती। उनके साथ आप सीधे उसी पर पहुँचते हैं जो आपको चाहिए।
在这里,默认值和关键字调用合在一起成了超能力。如果你想保留 port 和 timeout 的默认值,只改 use_ssl 呢?没有关键字,你得把前面的每个值都重写一遍。有了关键字,你可以直接跳到你关心的那一个。
| CallCall调用 | What Happenedक्या हुआ结果 |
|---|---|
connect("h", 8080, 30, False) | Had to repeat every defaultहर default दोहराना पड़ा不得不重复每个默认值 |
| Fragileनाज़ुक脆弱 | Change one default → every call site is now out of dateएक default बदला → हर call पुरानी हो गई改一个默认值 → 每个调用处都过时了 |
| Unreadableपढ़ने में कठिन难以阅读 | What does the False mean?False का मतलब क्या है?False 是什么意思? |
| CallCall调用 | What Happenedक्या हुआ结果 |
|---|---|
connect("h", use_ssl=False) | Only override what changesसिर्फ़ जो बदले, वही बदलें只覆盖需要改的 |
| Robustमज़बूत稳健 | New optional params at the end never break callersअंत में जुड़े नए वैकल्पिक params callers को नहीं तोड़ते在末尾新增可选参数不会破坏调用者 |
| Self-documentingख़ुद समझाने वाला自带说明 | The False has a nameFalse का एक नाम हैFalse 有了名字 |
def connect(host, port=8080, timeout=30, use_ssl=True):
print(f"{host}:{port} ssl={use_ssl} t={timeout}")
# Keep port & timeout defaults, only change ssl
connect("api.example.com", use_ssl=False)
# Skip port, change timeout only
connect("api.example.com", timeout=60)
# Any combination — order does not matter
connect("api.example.com", use_ssl=False, timeout=60, port=443)
Look at almost any function in pandas, requests or sklearn — many take 10 to 30 parameters, nearly all with defaults. You never pass them by position. You override two or three by name and leave the rest at their defaults. This pattern makes big APIs usable.
pandas, requests या sklearn का लगभग कोई भी function देखें — कई 10 से 30 parameters लेते हैं, लगभग सभी defaults के साथ। आप उन्हें कभी स्थान से नहीं देते। आप दो-तीन को नाम से बदलते हैं और बाक़ी defaults पर छोड़ देते हैं। यही तरीका बड़े APIs को इस्तेमाल लायक बनाता है।
看看 pandas、requests 或 sklearn 里几乎任何函数 — 很多都有 10 到 30 个形参,几乎全都有默认值。你从不按位置传它们,而是按名字覆盖两三个,其余保持默认。正是这种模式让庞大的 API 变得好用。
Try It — Argument Matching Machineकरके देखें — Argument Matching Machine动手试试 — 参数匹配机
Edit the def line or the call, then press Call it. Each argument you pass is a card; each parameter is a slot. Watch positional cards fill slots in order, keyword cards jump to the slot with the same name, and defaults fill whatever is left. Try the ✗ chips to see every error Python can raise.
def line या call बदलें, फिर Call करें दबाएँ। हर argument एक card है; हर parameter एक slot। देखें कि positional cards slots को क्रम से भरते हैं, keyword cards उसी नाम वाले slot पर कूदते हैं, और बचा हुआ defaults भरते हैं। Python के हर error को देखने के लिए ✗ वाले chips आज़माएँ।
修改 def 行或调用,然后按 调用。你传的每个实参是一张卡片,每个形参是一个槽。看位置卡片 按顺序 填槽,关键字卡片跳到 同名 的槽,剩下的由默认值填充。试试带 ✗ 的按钮,看看 Python 会抛出的每种错误。
Variable-Length Arguments — *args 🎒बदलती संख्या के Arguments — *args 🎒可变长度参数 — *args 🎒
sum_all function. Some callers pass 2 numbers. Some pass 5. Some pass 200. You cannot list 200 parameters. You need a bag that catches "however many numbers they gave me" and lets you loop through them. That bag is *args.sum_all function लिख रहे हैं। कुछ callers 2 numbers देते हैं, कुछ 5, कुछ 200। आप 200 parameters नहीं लिख सकते। आपको एक थैला चाहिए जो "जितने भी numbers दिए गए" उन्हें पकड़ ले और आपको उन पर loop चलाने दे। वह थैला *args है।sum_all 函数。有的调用者传 2 个数,有的传 5 个,有的传 200 个。你不可能列出 200 个形参。你需要一个袋子,接住 "他们给我的所有数字",让你可以遍历它们。这个袋子就是 *args。A single asterisk * before a parameter tells Python: "catch all remaining positional arguments and pack them into a tuple". The name args is only a convention — *numbers works too. The magic is the *.
Parameter से पहले एक asterisk * Python को बताता है: "बचे हुए सारे positional arguments पकड़ो और उन्हें tuple में पैक करो"। नाम args सिर्फ़ परंपरा है — *numbers भी चलता है। असली जादू * में है।
形参前加一个星号 * 是在告诉 Python:"接住所有剩下的 位置 参数,打包成 tuple"。名字 args 只是惯例 — *numbers 也可以。真正的魔法在 *。
def sum_all(*args):
print("args is:", args)
print("type is:", type(args))
total = 0
for n in args:
total += n
return total
print(sum_all(1, 2, 3))
print(sum_all(10, 20, 30, 40, 50))
print(sum_all()) # zero args is fine — empty tuple
Mixing Regular Parameters with *argsसामान्य Parameters के साथ *args普通形参与 *args 混用
def log_event(level, *messages):
for msg in messages:
print(f"[{level}] {msg}")
log_event("INFO", "server started", "port 8080 open", "ready")
Unpacking — the Other Side of * and **Unpacking — * और ** का दूसरा पहलू解包 — * 和 ** 的另一面
The same stars work at the call site in reverse. * spreads a list or tuple into positional arguments, and ** spreads a dict into keyword arguments. Defining and calling are mirror images.
वही stars call की जगह उल्टा काम करते हैं। * list या tuple को positional arguments में फैला देता है, और ** dict को keyword arguments में। Define करना और call करना एक-दूसरे का प्रतिबिंब हैं।
同样的星号在 调用处 反向工作。* 把 list 或 tuple 展开成位置参数,** 把 dict 展开成关键字参数。定义和调用互为镜像。
def describe(name, age, city):
print(f"{name}, {age}, from {city}")
values = ["Meera", 31, "Pune"]
describe(*values) # * unpacks a list into positions
options = {"city": "Delhi", "age": 25, "name": "Kabir"}
describe(**options) # ** unpacks a dict into keywords
print(*[1, 2, 3], sep=" | ") # works with built-ins too
Keyword Arguments Bag — **kwargs 🗃️Keyword Arguments का थैला — **kwargs 🗃️关键字参数袋 — **kwargs 🗃️
A double asterisk ** is the cousin of *args. It catches all remaining keyword arguments and packs them into a dictionary. Your function can then accept an open-ended set of named options without declaring each one.
दोहरा asterisk **, *args का भाई है। यह बचे हुए सारे keyword arguments पकड़कर एक dictionary में पैक करता है। तब आपका function हर एक को घोषित किए बिना नाम वाले विकल्पों का खुला सेट ले सकता है।
双星号 ** 是 *args 的兄弟。它接住所有剩下的 关键字 参数,打包成 字典。这样你的函数不必逐个声明,也能接受任意多个具名选项。
def create_user(username, **kwargs):
print(f"Creating user: {username}")
print("kwargs is:", kwargs)
print("type is: ", type(kwargs))
for key, value in kwargs.items():
print(f" {key} = {value}")
create_user(
"priya_92",
email="priya@example.com",
age=28,
country="India",
is_admin=False
)
| Featureविशेषता特点 | *args | **kwargs |
|---|---|---|
| Catchesपकड़ता है接收 | Extra positional argumentsअतिरिक्त positional arguments多余的 位置 参数 | Extra keyword argumentsअतिरिक्त keyword arguments多余的 关键字 参数 |
| Packed intoकिसमें पैक打包成 | tuple | dict |
| Common nameआम नाम常用名 | args (convention)args (परंपरा)args(惯例) | kwargs (convention)kwargs (परंपरा)kwargs(惯例) |
| Access patternपढ़ने का तरीका访问方式 | for x in args: | for k, v in kwargs.items(): |
Place in defdef में जगह在 def 中的位置 | Before keyword-only params and **kwargsKeyword-only params और **kwargs से पहले在仅关键字形参和 **kwargs 之前 | Must be the last parameterआख़िरी parameter होना ज़रूरी必须是 最后一个 形参 |
The Full Parameter Order — Putting It All Together 🧱Parameters का पूरा क्रम — सब एक साथ 🧱完整的形参顺序 — 全部组合起来 🧱
When a function uses every kind of parameter at once, Python enforces a strict order in the definition. Here is the full sequence. Every part is optional, but the parts you use must appear in this order.
जब कोई function हर तरह के parameter एक साथ इस्तेमाल करता है, तो Python definition में सख़्त क्रम लागू करता है। यह रहा पूरा क्रम। हर हिस्सा वैकल्पिक है, पर जो हिस्से आप इस्तेमाल करें, वे इसी क्रम में आने चाहिए।
当一个函数同时使用各种形参时,Python 在定义中强制要求严格的顺序。下面是完整的顺序。每一部分都是可选的,但用到的部分必须按这个顺序出现。
/सिर्फ़-positional parameters, फिर /仅位置形参,然后 /len(obj, /).len(obj, /) जैसे built-ins में आम।len(obj, /)。*args (or a bare *)*args (या अकेला *)*args(或单独的 *)* catches nothing — it only marks where keyword-only parameters begin.* कुछ नहीं पकड़ता — वह सिर्फ़ बताता है कि keyword-only parameters कहाँ से शुरू होते हैं।* 什么也不接收 — 它只标记仅关键字形参从哪里开始。*args or *. Callers must pass these by name. They may have defaults or not, in any order.*args या * के बाद का सब कुछ। Callers को इन्हें नाम से देना होगा। इनके defaults हो भी सकते हैं और नहीं भी, किसी भी क्रम में।*args 或 * 之后的形参。调用者必须按名字传入。它们可以有默认值也可以没有,顺序随意。**kwargs — variable keyword**kwargs — बदलते keywords**kwargs — 可变关键字def register(username, email, role="user", *permissions, **profile):
print(f"username : {username}")
print(f"email : {email}")
print(f"role : {role}")
print(f"permissions : {permissions}")
print(f"profile : {profile}")
register(
"rahul_dev", # username (positional)
"rahul@example.com", # email (positional)
"admin", # role (overrides default)
"read", "write", "delete", # *permissions -> tuple
country="India", # **profile -> dict
joined="2024-01-15"
)
Keyword-Only and Positional-Only Parametersसिर्फ़-Keyword और सिर्फ़-Positional Parameters仅关键字与仅位置形参
Two markers give you extra control. Anything after *args (or a bare *) must be passed by name. Anything before / must be passed by position. The logger in Section 12 uses the first trick for level.
दो निशान आपको अतिरिक्त नियंत्रण देते हैं। *args (या अकेले *) के बाद का सब कुछ नाम से देना होगा। / से पहले का सब कुछ स्थान से देना होगा। Section 12 का logger level के लिए पहला तरीका इस्तेमाल करता है।
两个标记给你更多控制。*args(或单独的 *)之后的形参必须 按名字 传入。/ 之前的形参必须 按位置 传入。第 12 节的日志函数就用第一种技巧来处理 level。
# Parameters AFTER *args (or a bare *) are keyword-only
def order(item, *extras, size="regular"):
print(f"{size} {item} with {extras}")
order("tea", "sugar", "milk") # size keeps its default
order("tea", "sugar", size="large") # size only by name
# Parameters BEFORE / are positional-only (Python 3.8+)
def power(base, exp, /, *, verbose=False):
result = base ** exp
if verbose:
print(f"{base} ** {exp} = {result}")
return result
print(power(2, 10))
power(2, 10, verbose=True)
try:
power(base=2, exp=10)
except TypeError as e:
print("TypeError:", e)
Write def f(**kw, x) and Python refuses to even read the file: arguments cannot follow var-keyword argument. Two stars (def f(*a, *b)) fail too. But def f(*args, x, y) is valid — x and y simply become keyword-only. The order is not a style guide — it is grammar.
def f(**kw, x) लिखें तो Python file पढ़ने से ही मना कर देता है: arguments cannot follow var-keyword argument। दो stars (def f(*a, *b)) भी विफल होते हैं। पर def f(*args, x, y) सही है — x और y बस keyword-only बन जाते हैं। यह क्रम style का सुझाव नहीं — व्याकरण है।
写 def f(**kw, x),Python 连文件都不肯读:arguments cannot follow var-keyword argument。两个星号(def f(*a, *b))同样失败。但 def f(*args, x, y) 是 合法 的 — x 和 y 只是变成了仅关键字形参。这个顺序不是风格建议 — 而是语法。
Try It — Packing Machineकरके देखें — Packing Machine动手试试 — 打包机
Same machine, now with bags. Arguments that find no named slot drop into the *args bag (a tuple) or the **kwargs bag (a dict). A * or ** at the call site does the opposite: it unpacks a list or dict into separate arguments. Try the register chips to see all four kinds at once.
वही मशीन, अब थैलों के साथ। जिन arguments को कोई नाम वाला slot नहीं मिलता, वे *args थैले (एक tuple) या **kwargs थैले (एक dict) में गिरते हैं। Call में * या ** उल्टा करता है: list या dict को अलग-अलग arguments में खोल देता है। चारों तरह एक साथ देखने के लिए register वाले chips आज़माएँ।
同一台机器,现在多了袋子。找不到具名槽的实参会掉进 *args 袋(一个 tuple)或 **kwargs 袋(一个 dict)。调用 处的 * 或 ** 正好相反:把 list 或 dict 拆成单独的实参。试试 register 按钮,一次看到全部四种。
How Arguments Are Passed — Object References 🔗Arguments कैसे दिए जाते हैं — Object References 🔗参数如何传递 — 对象引用 🔗
Python does neither pure "pass by value" nor pure "pass by reference". It does what is often called pass-by-object-reference (or "pass by assignment"). The name inside the function points to the same object the caller passed. Whether changes leak out depends on what you do with it — and whether the object can change at all.
Python न पूरी तरह "pass by value" करता है, न पूरी तरह "pass by reference"। यह वह करता है जिसे अक्सर pass-by-object-reference (या "pass by assignment") कहते हैं। Function के अंदर का नाम उसी object की ओर इशारा करता है जो caller ने दिया। बदलाव बाहर दिखेंगे या नहीं, यह इस पर निर्भर है कि आप उसके साथ क्या करते हैं — और क्या वह object बदल भी सकता है।
Python 既不是纯粹的 "值传递",也不是纯粹的 "引用传递"。它采用的方式通常叫 对象引用传递(pass-by-object-reference,也叫 "赋值传递")。函数里的名字指向调用者传入的 同一个对象。修改会不会泄漏出去,取决于你怎么使用它 — 以及这个对象究竟能不能被修改。
Python passes a reference to the object, not a copy of it. What happens next depends on whether you mutate that object in place, or rebind the local name to a new object.
Python object का reference देता है, उसकी copy नहीं। आगे क्या होगा, यह इस पर निर्भर है कि आप उस object को उसी जगह बदलते (mutate) हैं, या local नाम को किसी नए object से जोड़ते (rebind) हैं।
Python 传递的是对象的 引用,而不是副本。接下来会发生什么,取决于你是 原地修改(mutate)这个对象,还是把局部名字 重新绑定(rebind)到新对象上。
Case 1 — Unchangeable Objects (int, str, tuple, float, bool)मामला 1 — न बदलने वाले Objects (int, str, tuple, float, bool)情况 1 — 不可变对象(int、str、tuple、float、bool)
def try_change(x):
print(f" inside before: x = {x}, id = {id(x)}")
x = x + 100 # rebinds LOCAL x to a new int
print(f" inside after : x = {x}, id = {id(x)}")
number = 5
print(f"before call: number = {number}, id = {id(number)}")
try_change(number)
print(f"after call : number = {number}, id = {id(number)}")
Look at the ids. The parameter x started out pointing at the very same integer as number. But x = x + 100 created a new integer and moved the local name x to it. The caller's number was never touched, because integers cannot change — you cannot modify 5 in place, only create a new object. (Your id numbers will be different; only which ones match matters.)
Ids देखें। Parameter x शुरू में बिल्कुल उसी integer की ओर इशारा करता था जिसकी ओर number। पर x = x + 100 ने एक नया integer बनाया और local नाम x को उस पर ले गया। Caller का number कभी छुआ नहीं गया, क्योंकि integers बदल नहीं सकते — आप 5 को उसी जगह नहीं बदल सकते, सिर्फ़ नया object बना सकते हैं। (आपके id numbers अलग होंगे; मायने सिर्फ़ यह रखता है कि कौन-से मेल खाते हैं।)
看看这些 id。形参 x 一开始指向的就是 number 指向的那个整数。但 x = x + 100 创建了一个 新 整数,并把局部名字 x 移了过去。调用者的 number 从未被碰过,因为整数不可变 — 你不能原地修改 5,只能创建新对象。(你的 id 数值会不同;重要的只是哪些相同。)
Case 2 — Changeable Objects (list, dict, set, your own classes)मामला 2 — बदलने वाले Objects (list, dict, set, आपकी अपनी classes)情况 2 — 可变对象(list、dict、set、自定义类)
def add_item(cart):
print(f" inside before: cart = {cart}, id = {id(cart)}")
cart.append("laptop") # MUTATES the object in place
print(f" inside after : cart = {cart}, id = {id(cart)}")
my_cart = ["book", "pen"]
print(f"before call: cart = {my_cart}, id = {id(my_cart)}")
add_item(my_cart)
print(f"after call : cart = {my_cart}, id = {id(my_cart)}")
A function that takes a list and calls .append() on it quietly changes the caller's list. There is no warning, no return, no signal — the change just happens. Two weeks later someone cannot work out why their list has extra items.
जो function list लेकर उस पर .append() चलाता है, वह चुपचाप caller की list बदल देता है। न चेतावनी, न return, न कोई संकेत — बदलाव बस हो जाता है। दो हफ़्ते बाद कोई समझ नहीं पाता कि उसकी list में अतिरिक्त items क्यों हैं।
一个接收 list 并对它调用 .append() 的函数,会悄悄修改调用者的 list。没有警告,没有返回值,没有任何信号 — 修改就这么发生了。两周后,有人怎么也想不明白自己的 list 里为什么多了东西。
Case 3 — Changeable Object, but Rebinding Instead of Mutatingमामला 3 — बदलने वाला Object, पर बदलने की जगह Rebinding情况 3 — 可变对象,但重新绑定而不是修改
def replace_cart(cart):
cart = ["phone", "charger"] # REBINDS local name to a NEW list
print(f" inside: cart = {cart}")
my_cart = ["book", "pen"]
replace_cart(my_cart)
print(f"after : cart = {my_cart}") # unchanged!
Same type (a list, which can change), but a completely different result. The assignment cart = [...] did not modify the caller's list — it only made the local name cart point at a brand-new list. The original survived untouched.
वही type (list, जो बदल सकती है), पर बिल्कुल अलग result। Assignment cart = [...] ने caller की list नहीं बदली — इसने सिर्फ़ local नाम cart को एक बिल्कुल नई list की ओर मोड़ दिया। मूल list अछूती रही।
同样的类型(可变的 list),结果却完全不同。赋值 cart = [...] 并没有修改调用者的 list — 它只是让局部名字 cart 指向一个全新的 list。原来的 list 完好无损。
| Object TypeObject का Type对象类型 | What the Function DoesFunction क्या करता है函数内的操作 | Does the Caller See It?क्या Caller को दिखता है?调用者能看到吗? |
|---|---|---|
| int, str, float, bool, tuple | Anything (they cannot be changed)कुछ भी (वे बदल नहीं सकते)任何操作(它们不可修改) | Noनहीं否 |
| list, dict, set | .append(), .pop(), [i] = v, .update() | Yes — mutation leaksहाँ — mutation रिसता है是 — 修改会泄漏 |
| list, dict, set | cart = [...] or d = {...} (rebinding)cart = [...] या d = {...} (rebinding)cart = [...] 或 d = {...}(重新绑定) | No — only the local name movesनहीं — सिर्फ़ local नाम हटता है否 — 只移动了局部名字 |
| Your own class instanceआपकी अपनी class का instance自定义类的实例 | Attribute assignment: obj.attr = valueAttribute assignment: obj.attr = value属性赋值:obj.attr = value | Yesहाँ是 |
One fine point: a tuple cannot change, but a list inside a tuple still can. t[0].append(1) works when t = ([],).
एक बारीक बात: tuple बदल नहीं सकता, पर tuple के अंदर रखी list अब भी बदल सकती है। t = ([],) हो तो t[0].append(1) चलता है।
一个细节:tuple 不可修改,但 tuple 里面 的 list 仍然可以修改。当 t = ([],) 时,t[0].append(1) 可以正常执行。
Try It — Reference Visualizerकरके देखें — Reference Visualizer动手试试 — 引用可视化
Names are tags; values are objects. Step through each case and watch the arrows. Passing an argument sticks a second tag on the same object. Then it depends: mutating the object is seen by everyone, rebinding the tag is not.
नाम tags हैं; values objects हैं। हर मामले को कदम-दर-कदम चलाएँ और तीर देखें। Argument देने पर उसी object पर दूसरा tag चिपकता है। फिर यह निर्भर करता है: object को बदलना सबको दिखता है, tag को हटाना नहीं।
名字是 标签;值是 对象。逐步执行每种情况,观察箭头。传参就是在 同一个 对象上再贴一个标签。接下来就看情况了:修改 对象大家都看得到,重新绑定 标签则不会。
If a function takes a list or dict and you don't want the caller to see changes, copy it at the top: data = data.copy() for a shallow copy, or copy.deepcopy(data) if it contains nested lists or dicts. Or accept a tuple instead of a list — a tuple cannot be changed at all.
अगर function list या dict लेता है और आप नहीं चाहते कि caller को बदलाव दिखें, तो शुरू में ही copy बनाएँ: shallow copy के लिए data = data.copy(), या अंदर nested lists या dicts हों तो copy.deepcopy(data)। या list की जगह tuple लें — tuple बिल्कुल नहीं बदलता।
如果函数接收 list 或 dict,而你不希望调用者看到修改,就在开头复制一份:浅拷贝用 data = data.copy();如果里面有嵌套的 list 或 dict,用 copy.deepcopy(data)。或者改为接收 tuple — tuple 根本无法修改。
The Mutable Default Argument Trap 💥बदलने वाले Default Argument का जाल 💥可变默认参数陷阱 💥
This is the most famous Python gotcha. Almost every Python developer meets it at least once — often in production.
यह Python की सबसे मशहूर पकड़ है। लगभग हर Python developer कम से कम एक बार इससे टकराता है — अक्सर production में।
这是 Python 最著名的坑。几乎每个 Python 开发者都至少踩过一次 — 而且往往是在生产环境里。
# The trap:
def append_to(item, target=[]): # mutable default!
target.append(item)
return target
print(append_to(1))
print(append_to(2))
print(append_to(3))
print("stored default:", append_to.__defaults__)
Each call was expected to return a fresh [item]. Instead, the same list is reused and grows across calls. Why? Because default values are evaluated once, when the def line runs. That one list object is stored in append_to.__defaults__ — the last line proves it — and every call that does not pass target changes that same object.
हर call से नई [item] लौटने की उम्मीद थी। इसकी जगह वही list बार-बार इस्तेमाल होती है और हर call के साथ बढ़ती जाती है। क्यों? क्योंकि default values एक बार, def line चलने पर बनती हैं। वह एक list object append_to.__defaults__ में रखा जाता है — आख़िरी line इसका सबूत है — और target न देने वाला हर call उसी object को बदलता है।
本来期望每次调用都返回一个新的 [item]。结果同一个 list 被反复使用,越变越长。为什么?因为默认值 只在 def 这一行运行时计算一次。那个 list 对象存放在 append_to.__defaults__ 里 — 最后一行就是证据 — 每次不传 target 的调用,修改的都是这同一个对象。
None as a Markerहल — None को निशान बनाएँ解决办法 — 用 None 作标记Set the default to None and create a fresh list inside the body. A new list is made on every call, and the trap is gone.
Default को None रखें और body के अंदर नई list बनाएँ। हर call पर नई list बनती है, और जाल ख़त्म।
把默认值设为 None,在函数体里创建新的 list。每次调用都会得到一个新 list,陷阱就消失了。
# The correct pattern:
def append_to(item, target=None):
if target is None:
target = [] # fresh list every call
target.append(item)
return target
print(append_to(1))
print(append_to(2))
print(append_to(3))
print("stored default:", append_to.__defaults__)
Try It — Mutable Default Trap Labकरके देखें — Mutable Default Trap Lab动手试试 — 可变默认值陷阱实验室
Press Call a few times. In the trap version, look inside the function's hidden drawer (__defaults__) and watch the one default list grow — and every earlier result change with it. Then switch to the fixed version and repeat.
Call कुछ बार दबाएँ। Trap वाले रूप में function की छिपी दराज़ (__defaults__) में देखें और एक default list को बढ़ते देखें — और उसके साथ पहले के हर result को बदलते। फिर fixed रूप पर जाएँ और दोहराएँ।
多按几次 调用。在 陷阱 版本中,看看函数的隐藏抽屉(__defaults__),观察 那一个 默认 list 不断变长 — 之前的每个结果也跟着变。然后切换到 修复 版本再试一次。
A Complete Real-World Example — Building a Flexible Logger 📝एक पूरा असली उदाहरण — लचीला Logger बनाना 📝完整的实战示例 — 构建灵活的日志函数 📝
Let's combine everything: a required parameter, *args, keyword-only parameters with defaults, keyword calls and **kwargs.
अब सब कुछ जोड़ते हैं: एक ज़रूरी parameter, *args, defaults वाले keyword-only parameters, keyword calls और **kwargs।
把所有内容组合起来:一个必需形参、*args、带默认值的仅关键字形参、关键字调用以及 **kwargs。
import datetime
def log(message, *tags, level="INFO", timestamp=True, **metadata):
"""
Log a message with optional tags, level, timestamp, and metadata.
- message : required, the log body
- *tags : any number of string tags
- level : keyword-only, with a default (INFO, WARN, ERROR)
- timestamp : keyword-only, whether to add the time
- **metadata: any extra key=value context (user_id, request_id, ...)
"""
parts = []
if timestamp:
now = datetime.datetime.now().strftime("%H:%M:%S")
parts.append(f"[{now}]")
parts.append(f"[{level}]")
if tags:
parts.append("#" + " #".join(tags))
parts.append(message)
if metadata:
meta_str = " ".join(f"{k}={v}" for k, v in metadata.items())
parts.append(f"| {meta_str}")
print(" ".join(parts))
# Simple call — only the required argument
log("Server started")
# With tags (variable positional)
log("Cache miss", "redis", "user-profile")
# Override a default by KEYWORD — timestamp keeps its default
log("Login failed", level="WARN")
# Everything at once
log(
"Payment declined",
"stripe", "billing", # *tags
level="ERROR", # overrides default
timestamp=True, # same as the default
user_id=4821, # **metadata
request_id="req-9f3a",
amount=2500
)
The time in brackets is the moment the code ran, so yours will be different.
Brackets में समय वह पल है जब code चला, इसलिए आपका अलग होगा।
方括号里的时间是代码运行的时刻,所以你的会不同。
No call passes True or "WARN" as a mystery positional argument. In fact it cannot: level and timestamp come after *tags, so they are keyword-only. Every optional value has a name at the call site. Mature Python libraries use this pattern — learn it and copy it.
कोई भी call True या "WARN" को रहस्यमय positional argument की तरह नहीं देता। असल में दे ही नहीं सकता: level और timestamp *tags के बाद आते हैं, इसलिए वे keyword-only हैं। Call में हर वैकल्पिक value का नाम है। परिपक्व Python libraries यही तरीका अपनाती हैं — इसे सीखें और अपनाएँ।
没有任何调用把 True 或 "WARN" 当作含义不明的位置参数传入。事实上也 不可能:level 和 timestamp 在 *tags 之后,所以是仅关键字形参。调用处的每个可选值都有名字。成熟的 Python 库都用这种模式 — 学会它,照着用。
Try It — Logger Call Builderकरके देखें — Logger Call Builder动手试试 — 日志调用构建器
Fill in the form and the lab writes the call for you, in the clean keyword style. The call is bound to the real log() signature and printed. Try the chips: passing "WARN" by position, or misspelling level, shows two classic *args/**kwargs traps.
Form भरें और lab आपके लिए साफ़ keyword शैली में call लिख देता है। Call असली log() signature से जोड़ा जाता है और print होता है। Chips आज़माएँ: "WARN" को स्थान से देना, या level की ग़लत spelling, *args/**kwargs के दो मशहूर जाल दिखाते हैं।
填写表单,实验室就会用干净的关键字风格帮你写出调用。调用会绑定到真实的 log() 签名并打印出来。试试这些按钮:按位置传入 "WARN",或把 level 拼错,会展示 *args/**kwargs 的两个经典陷阱。
Golden Rules 🏆सुनहरे नियम 🏆黄金法则 🏆
[], not {}, not set(). Use None as a marker and create the list or dict inside the body. This one rule removes a whole family of bugs.[], न {}, न set()। None को निशान बनाएँ और list या dict body के अंदर बनाएँ। यह एक नियम bugs के पूरे परिवार को ख़त्म करता है।[],不要 {},也不要 set()。用 None 作标记,在函数体里创建 list 或 dict。这一条规则就能消灭一整类 bug。f(x, y, True, False, None) is unreadable six months later. f(x, y, save=True, verbose=False, on_error=None) is not.f(x, y, True, False, None) छह महीने बाद समझ नहीं आता। f(x, y, save=True, verbose=False, on_error=None) आता है।f(x, y, True, False, None) 六个月后就看不懂了,f(x, y, save=True, verbose=False, on_error=None) 则不会。prints cannot be combined, tested or reused. return makes your function a tool. print makes it a dead end.print करता है, उसे न जोड़ा जा सकता है, न test किया जा सकता है, न दोबारा इस्तेमाल। return आपके function को औज़ार बनाता है। print उसे बंद गली।print 的函数无法组合、测试或复用。return 让函数成为工具,print 让它成为死胡同。def is fixed: positional-only → / → regular → with defaults → *args (or *) → keyword-only → **kwargs. Breaking it is a SyntaxError. Remember: only **kwargs must always be last.def में parameters का क्रम तय है: सिर्फ़-positional → / → सामान्य → defaults वाले → *args (या *) → keyword-only → **kwargs। इसे तोड़ना SyntaxError है। याद रखें: सिर्फ़ **kwargs को हमेशा आख़िर में होना है।def 中形参的顺序是固定的:仅位置 → / → 普通 → 带默认值 → *args(或 *)→ 仅关键字 → **kwargs。打乱就是 SyntaxError。记住:只有 **kwargs 必须永远在最后。verbose or level after * (or *args), so callers must name them. The logger in Section 12 does exactly this.verbose या level जैसे विकल्प * (या *args) के बाद रखें, ताकि callers को उनका नाम लिखना ही पड़े। Section 12 का logger बिल्कुल यही करता है।verbose 或 level 这类选项放在 *(或 *args)之后,调用者就 必须 写出名字。第 12 节的日志函数正是这样做的。*args only when the count really is open-ended (like sum-style or print-style functions). If you always expect 3 arguments, declare 3 parameters — clearer signatures make better APIs.*args तभी इस्तेमाल करें जब संख्या सच में तय न हो (जैसे sum या print जैसे functions)। अगर आप हमेशा 3 arguments की उम्मीद करते हैं, तो 3 parameters लिखें — साफ़ signatures बेहतर APIs बनाते हैं।*args(比如 sum 或 print 这类函数)。如果你总是期望 3 个参数,就声明 3 个形参 — 清晰的签名才是好 API。**kwargs for pass-through and extension — for example, to forward options to a lower-level function. Don't use it instead of a well-designed signature: it hides what the function accepts, and typos pass silently.**kwargs को आगे भेजने और विस्तार के लिए इस्तेमाल करें — जैसे निचले function को विकल्प आगे भेजना। अच्छे signature की जगह इसे न लगाएँ: यह छिपा देता है कि function क्या लेता है, और typos चुपचाप निकल जाते हैं।**kwargs 做透传和扩展 — 例如把选项转发给更底层的函数。不要用它代替精心设计的签名:它会隐藏函数接受什么参数,拼写错误也会悄悄通过。Quick Checkछोटी जाँच小测验
Seven questions on functions. Pick an answer to see why.
Functions पर सात सवाल। जवाब चुनें और कारण देखें।
关于函数的七道题。选择答案查看解释。
A function is a named machine: arguments go in, the body runs in its own frame, and return sends one value back. Match arguments by position or by name, give options sensible unchangeable defaults, use *args/**kwargs for open-ended input, and remember that the caller sees any change you make to a shared list or dict.
Function एक नाम वाली मशीन है: arguments अंदर जाते हैं, body अपने frame में चलती है, और return एक value वापस भेजता है। Arguments को स्थान से या नाम से मिलाएँ, विकल्पों को समझदार न बदलने वाले defaults दें, खुले input के लिए *args/**kwargs इस्तेमाल करें, और याद रखें कि साझा list या dict में किया गया हर बदलाव caller को दिखता है।
函数是一台有名字的机器:实参进去,函数体在自己的帧里运行,return 送回一个值。按位置或按名字匹配实参,给选项合理的 不可变 默认值,用 *args/**kwargs 接收不定数量的输入,并记住:你对共享 list 或 dict 的任何修改,调用者都看得到。