SoFunction
Updated on 2024-12-20

An in-depth analysis of function arguments and scopes in Python

Transfer parameters

Some brief key points when passing parameters to a function: the

  • Parameters are passed by automatically assigning objects to local variable names. All parameters are actually passed via pointers, and the objects passed as parameters are never automatically copied.
  • Assignment of parameter names inside a function does not affect the caller.
  • Changing the value of a function's mutable object parameter has an effect on the caller.

In fact, Python's parameter-passing model is quite similar to C's:.

Immutable parameters are passed "by value". Objects like integers and strings are passed by object reference rather than copy, but since it is impossible to change immutable objects in place anyway, the practical effect is much like creating a copy.
Mutable objects are passed via "pointers". This means that mutable objects can be modified in place inside a function.
>> Avoiding variable parameter modifications
There are many ways to avoid parameter modification: the

When passing parameters, pass a copy of.

L = [1,2]
changer(L[:])

Copying inside a function

def changer(b):
 b=b[:]

Converting mutable objects to immutable objects

L=[1,2]
changer(tuple(L))

>> Simulation of parameter outputs
There's a little trick for the return value of parameters: since return can return any kind of object, it can also return multiple values if those values are encapsulated in a tuple or other collection type.

def multiple(x,y):
 x = 2
 y = [2,4]
 return x,y #Return new values in a tuple

This code appears to return two values, but in fact there is only one: a tuple of 2 elements whose parentheses can be omitted.

Parameter-specific matching models

>>Basics
Outline of the matching model.

  • Position:Match from left to right.
  • Keyword arguments:Match by argument name. (The caller can define which function accepts the value, by using the variable name of the argument in the call, using syntax like name=value.)
  • Default Parameters:Defines parameter values for parameters that are not passed in as values.
  • Variable Parameters:Collect as many location or keyword based parameters as you like.
  • Variable Parameter Unwrapping:Pass any number of location or keyword based parameters.
  • Keyword-only parameter:Parameters must be passed by name. (Only exists in Python 3.0)

>>Match syntax

grammatical placement account for
func(value) caller General parameters:Matching by position.
func(name=value) caller Keyword arguments:Match by variable name.
func(*sequence) caller Pass all objects by name and as separate location-based parameters.
func(**dict) caller Pass all keywords/values in name pairs and as separate keyword arguments.
def func(name) function (math.) Regular Parameters:Match by position or variable name.
def func(name=value) function (math.) Default parameter values, if passed in the call.
def func(*name) function (math.) Matches and collects (in a tuple) all parameters containing a position.
def func(**name) function (math.) Matches and collects (in the dictionary) all arguments containing a position.
def func(*args,name) function (math.) Parameters must be passed in the call as per the keyword.
def func(*,name=value) function (math.)  Arguments must be passed by keyword in the call. (Python 3.0)

Corresponding notes.

In function calls (the first 4 rows of the table), matching is done simply by variable name position, but Python is told to match by variable name using the name=value form, which are called keyword arguments. Using *sequence or **dict in a call allows us to encapsulate any number of position-dependent or keyworded objects in a sequence or dictionary accordingly, and unwrap them into separate, individual arguments when passing them to the function.
At the head of a function, a simple variable name is matched by position or variable name (depending on how the caller passes the argument to it), but the name=value form defines the default argument value. The *name form collects any additional unmatched arguments into a tuple, and the **name form will phone additional keyword arguments into a dictionary. In Python 3.0 and later, any formal or default parameter name followed by *name or a separate * is a keyword-only parameter and must be passed by keyword when called.
>>Details
When using a mixed parameter model, Python will follow the following laws about order.

In a function call, parameters must appear in this order: any positional parameter (value), followed by any combination of a keyword parameter (name=value) and a *sequence form, followed by a **dict form.
In the function header, parameters must appear in this order: any general parameter (name), immediately followed by any default parameter (name=value), followed by the name (in Python 3.0 yes) form, followed by any name or name=value keyword-only parameter (in Python 3.0), followed by the ** name form.
In both the call and function headers, the **arg form must appear at the end if it appears.

Python internally uses the following steps to match parameters before assignment: the

  • Assign non-keyword parameters by position.
  • Assign keyword arguments by matching variable names.
  • Other additional non-keywords are assigned to the *name tuple.
  • Other additional keyword arguments are assigned to the **name dictionary.
  • Assign with default values to parameters that are not assigned in the header.
  • After this, Python checks to make sure that only one value has been passed in for each parameter. If this is not the case, an error will occur. When all the matches are complete, Python assigns the objects passed to the parameter names to them.

>> Examples of keyword parameters and default parameters
If you don't use any special matching syntax, Python defaults to matching variable names from left to right by position.

def f(a,b,c):
 print(a,b,c)

f(1,2,3)   #Prints 1,2,3

Keyword parameters

Keyword arguments allow matching by variable name, not by position.

f(c=3,b=2,a=1) #Prints 1,2,3

default parameter

Default parameters allow the creation of optional arguments to a function. If no value is passed in, the parameters are assigned default values before the function is run.

def f(a,b=2,c=3):
 print(a,b,c)

f(1)    #Prints 1,2,3
f(1,4)   #Prints 1,4,3
f(1,c=6)   #Prints 1,2,6

Mix of keyword parameters and default parameters

def func(spam,eggs,totast=0,ham=0):
 print((spam,eggs,totast=0,ham=0))
func(1,2)     #Ouput:(1,2,0,0)
func(1,ham=1,eggs=0)  #Ouput:(1,0,0,1)
func(spam=1,eggs=0)   #Ouput:(1,0,0,0)
func(toast=1,eggs=2,spam=3) #Ouput:(3,2,1,0)
func(1,2,3,4)    #Ouput:(1,2,3,4)

Examples of >> arbitrary parameters
The last two matching extensions, * and **, are what allow functions to support taking an arbitrary number of arguments.

Collecting parameters

Collect mismatched positional arguments in a tuple in a function definition.

def f(*args):print(args)

When this function is called, Python collects all position-dependent arguments into a new tuple and assigns this tuple to the variable args. it is therefore a general tuple object, capable of indexing or iteration.

The ** feature is similar, but it only works for keyword arguments. Pass these keyword arguments to a new dictionary that will later be able to be processed by the usual dictionary tools. In this case, ** allows keyword arguments to be converted to dictionaries, which you are able to step through or iterate through the dictionary later using key calls.

def f(a,*pargs,**kargs):print(a,pargs,kargs)

f(1,2,3,x=1,y=2)  #Prints:1 (2,3) {'x':2,'y':1}

unpacking parameter

In the latest version of Python, we are able to use * syntax when calling functions. In this case, it means the opposite of the function definition. It will unwrap the set of arguments instead of creating the set of arguments.

def func(a,b,c,d):print(a,b,c,d)
args=(1,2)
args+=(3,4)
func(*args)   #Prints 1,2,3,4

Similarly, at function call time, ** unpacks a dictionary in the form of a key/value pair, making it a separate keyword argument.

args={'a':1,'b':2,'c':3}
args['d']=4
func(**args)   #Prints 1,2,3,4

Note: Don't confuse the syntax of */** in the header of a function and */** at the function call:in the header, it means to collect arbitrarily many arguments, and at the call, it unwraps arbitrarily many arguments.

Application function generalization

if <test>:
 action,args=func1,(1,)
else:
 action,args=func2,(1,2,3)
...

action(*args)

>>Python3.0 Keyword-Only Parameters
Python 3.0 generalizes the ordering rules in function headers, allowing us to specify keyword-only arguments - that is, arguments that must be passed only by keyword and will not be populated by a positional argument.

Syntactically, keyword-only parameters are encoded as named arguments that appear after *args in the argument list. All such arguments must be passed in the call using keyword syntax.

We can also use a * character in the argument list to indicate that a function will not accept a variable-length argument list, but will still expect all arguments following the * to be passed as keywords.

def kwonly(a,*,b,c):
 print(a,b,c)
kwonly(1,c=3,b=2) #Prints:1,2,3
kwonly(c=3,b=2,a=1) #Prints:1,2,3
kwonly(1,2,3)  #Error!

In the above code, b and c must be passed as per the keyword and no other additional positional passes are allowed.

Also, the default function is still valid for keyword-only arguments, so, in effect, keyword-only arguments with defaults are optional, but those without defaults really become essential keyword-only arguments to the function.

Sorting Rules Finally, note that the keyword-only argument must be specified after a single asterisk, not two - named arguments cannot appear after any keyword form of **args, and a ** cannot appear alone in an argument list. Both of these practices will produce errors.

def kwonly(a,**pargs,b,c)  #Error!
def kwonly(a,**,b,c)   #Error!

This means that in the head of a function, the keyword-only argument must be written before the **args arbitrary keyword form and after the *args arbitrary positional form.

In fact, a similar ordering rule holds in function calls: when passing keyword-only arguments, they must appear before a **args form. keyword-only arguments can be written before or after *args, and may be contained in **args:.

def f(a,*b,c=6,**d):print(a,b,c,d)

f(1,*(2,3),**dict(x=4,y=5))  #Prints:1 (2,3) 6 {'x':4,'y':5}
f(1,*(2,3),**dict(x=4,y=5),c=7) #Error!
f(1,*(2,3),c=7,**dict(x=4,y=5)) #Prints:1 (2,3) 7 {'x':4,'y':5}
f(1,c=7,*(2,3),**dict(x=4,y=5)) #Prints:1 (2,3) 7 {'x':4,'y':5}
f(1,*(2,3),**dict(x=4,y=5,c=7)) #Prints:1 (2,3) 7 {'x':4,'y':5}

Python Scopes

When a Python program uses only variable names, Python creates, changes, or looks up variable names in what is called a namespace (a place where variable names are stored). That is, the location in the code where the variable name is assigned determines the extent to which the variable name can be accessed, i.e., in which namespace it exists.

In addition to packing the program, functions add an extra namespace layer to the program:By default, all variable names for a function are associated with the function's namespace. This means.

A variable defined within a def can be used in code within the def, but cannot be applied outside the function.
The names of variables within a def do not conflict with the names of variables outside the def. A variable X assigned outside the def is a completely different variable from the variable X assigned within this def.
>> Law of Scope
All of the code we write before we start writing functions sits at the top level of a module (i.e., it's not nested within a def), so the variable names we use either exist in the module file itself or are pre-defined by Python's built-in. Functions define local scopes, while modules define global scopes. The relationship between these two scopes is as follows.

Embedded modules are global scopes Each module is a global scope (that is, a namespace for variables created at the top level of the module file). To the outside of the module, the module's global variables become attributes of this module's objects, but are able to be used like simple variables within this module.
Global scoping is limited to a single file. Global here means that variable names at the top level of a file are global only to the code inside that file. There is no global scope in Python based on a single, all-encompassing scenario file.
Each call to the function creates a new local scope.
Assigned variable names are local unless declared as global or non-local variables
All variable names can be categorized as local, global, or built-in
>> Variable Name Resolution: The LEGB Principle
Python's variable name resolution mechanism, sometimes referred to as LEGB's Law, searches four scopes when unauthenticated variable names are used in functions:.

  • Local scope (L)
  • Local scope (E) of def or lambda in the previous level of structure (actually the case of function nesting)
  • Global scope (G)
  • And finally, the built-in scope (B)

Python looks for variables in the above four scopes in order and stops at the first place where it can find the variable name; if it doesn't find it in any of the four scopes, Python reports an error.

It is important to emphasize here that the four scopes above are the search process for code in a function, i.e., the ability to use variables in the previous level directly in a function!

s=10
def times(x,y):
 x=s
 return x*y

times(3,4) #return 40 not 12

>>Built-in scopes
Built-in scopes are implemented through a standard module called builtin, but the variable name itself is not put into a built-in scope, so the file must be imported in order to use it. In Python 3.0, you can see what variables are predefined using the following code.

import builtins
dir(builtins)

Therefore, there are in fact two ways to refer to a built-in function: through the benefits of the LEGB law, or by manually importing the builtin module. The second of these methods is useful in complex tasks where some local variables may override a built-in variable or function. Again, the LEGB law only makes the first place it finds a variable name effective!

global statement

The global statement is a namespace declaration that tells the Python interpreter that it intends to generate one or more global variables, that is, variable names that exist in the internal scope (namespace) of the entire module. About global variable names.

A global variable is the name of a variable located at the top level inside the module file.
Global variables must be declared if they are assigned inside a function.
Global variable names can be referenced inside a function without being declared.
The global statement contains the keyword global followed by one or more variable names separated by commas. When assigned or referenced in the function topic, all listed variable names are mapped to the scope of the entire module. Example.

X=88
def func():
 global X
 X = 99

func()
print(X) #Prints 99

Scopes and Nested Functions

This section is about the E layer of the LEGB lookup law, which includes local scopes inside arbitrary nested functions. Nested scopes are sometimes called statically nested scopes. In effect, a nesting is a syntactically nested scope that corresponds to a nested structure in the physical structure of the program's source code.

>> Details of nested scopes
For a function.

A reference (X) is first searched for the variable name X in the local (in-function) scope; then it is searched in the local scope of the function in which the code is syntactically nested, from the inside to the outside; then it is searched in the current global scope (the module file); and finally it is searched in the built-in scope (the module builder). Global declarations will be searched directly from the global (module file) scope. In fact, it will start from the place where X is referenced, and search online layer by layer until the first X is found.
By default, an assignment (X=value) creates or modifies the current scope of the variable name X. If X is declared as a global variable inside a function, it creates or modifies the scope of variable name X for the entire module. On the other hand, if X is declared nonlocal inside the function, the assignment modifies the name X in the local scope of the nearest nested function.
>> Nested scopes example

X = 99
def f1():
 X = 88
 def f2():
 print(X)
 f2()
f1() #Prints 88:enclosing def local

The first thing to note is that the code above is legal; a def is a simple executable statement that can appear wherever any other statement can appear, including nested within another def. In the code, f2 is a function defined in f1. In this case, f2 is a temporary function that exists only during the internal execution of f1 (and is visible only to the code in f1). The X within f2 is automatically mapped to the X of f1 by the LEGB lookup rule.

It is worth noting that this nested scope lookup is valid even after the nested function has returned.

X = 99
def f1():
 X = 88
 def f2():
 print(X) #Remember X in enclosing def scope
 return f2 #Return f2 but don't call it

action = f1() #Make return function
action() #Call it now:Prints 88

In the above code, no matter how many times the action function is called, the return value is 88. f2 remembers the X in the nested scope in f1, even though f1 is no longer active at this point.

factory function

This behavior is sometimes called a closure or factory function - a function that remembers the value of a variable in a nested scope, even though that scope may no longer exist. In general, it's better to use classes to record state information, but factory functions like this provide an alternative. Specific example.

def maker(N):
 def action(X):
 return X ** N
 return action

f=maker(2) #Pass 2 to N
f(3) #Pass 3 to X,N remembers 2: 3**2,Return 9
f(4) #return 4**2

g=maker(3) #g remembers 3,f remembers 2
g(3) #return 27
f(3) #return 9

As can be seen from the above code, the f and g functions each record a different value of N, i.e., they record a different state, and each assignment to this factory function yields a collection of state information, and each function has its own state information, maintained by the variable N in the maker.

Scoping vs. default parameters with loop variables

There is a noteworthy special case in the laws already given: if lambda or def is defined in a function, nested in a loop, and the nested function references a variable in an upper scope that is changed by the loop, all functions generated in that loop will have the same value-the value of the variable that was referenced at the time the last value of the referenced variable at the time of completion in the last loop. Specific example.

def makeActions():
 acts=[]
 for i in range(5): #Tries to remember each i
 (lambda x: i ** x) #All remember same last it
 return acts

Despite being an attempt to create a list of functions such that each function has a different state value, the fact is that the functions in this list all have the same state value of 4. Because variables in nested scopes are looked up when the nested function is called, they actually remember the same value (the value of the loop variable in the last loop iteration).

In order for this type of code to work, the current value must be passed to the nested scope's variables using default arguments. Because the default parameter is evaluated when the nested function is created (rather than when it is called later), each function remembers the value of its own variable i.

def makeActions():
 acts=[]
 for i in range(5): #Use default instead
 (lambda x,i=i: i ** x) #Remember current i
 return acts
{

nonlocal statement

In fact, in Python 3.0, we can also modify nested scope variables, as long as we declare them in a nonlocal statement. With this statement, a nested def has read and write access to the names in the nested function. nonlocal applies to a name in the scope of a nested function, not to all global module scopes outside of the def-they may exist only in a nested function and cannot be created by the created by the first assignment in a nested def.

In other words, nonlocal, i.e., it allows assignments to name variables in nested function scopes and restricts such name-scope lookups to nested def.

>>nonlocal basis

def func():
 nonlocal name1,name2...

This statement allows a nested function to modify one or more names defined in the scope of a syntactically nested function. In Python, when a function def is nested within another function, the nested function can refer to various variables defined in the previous function, but cannot modify them. In Python 3.0, declaring nested scopes in a nonlocal statement enables the nested function to assign values and, as a result, to modify such names.

In addition to allowing modification of names in nested defs, the nonlocal statement also speeds up referencing - like the global statement, nonlocal makes the lookup of the names listed in the statement start in the scope of the nested defs, rather than in the local scope of the declared function, which means "skip my local scope altogether". That is, nonlocal also means "skip my local scope altogether".

In fact, by the time the nonlocal statement is executed, the names listed in nonlocal must have been defined in advance in a nested def; otherwise, an error will be generated. The direct effect is similar to that of global: global means that the names are in a higher-level module, nonlocal means that they are in a higher-level def function. nonlocal is even stricter-scope lookups are limited to nested defs only. that is, the That is, nonlocal can only occur in nested defs, not in the global scope of a module or in built-in scopes outside of def.

When used within a function, both global and nonlocal statements restrict the lookup rules in some way: the

global makes the scope lookup start at the nested module's scope and allows assignments to names there. If the name does not exist in the module, the scope lookup continues to the built-in scope, but assignments to global names always create or modify them in the module scope.
The nonlocal restriction of scope lookups to just nested defs requires that the names already exist there and allows assignments to them. Scope lookups do not continue to global or built-in scopes.
>>nonlocal applications
Use nonlocal to make changes

def tester(start):
 state = start #each call gets its own state
 def nested(label):
 nonlocal state #remember state in enclosing scope
 print(label,state)
 state+=1 #Allowed to change it if onolocal
 return nested


F = tester(0) #Increments state on each call
F('spam') #Prints:spam 0
F('ham') #Prints:ham 1
F('eggs') #Prints:eggs 2

Boundary situation

When executing a nonlocal statement, the nonlocal name must already have been assigned in a nested def scope or you will get an error.
The nonlocal restricted scope lookup is only for nested defs. nonlocal does not look in the global scope of nested modules or in built-in scopes outside of all defs.