개요
본 글은 Python v3.13 공식 Tutorial의 4.8. Defining Functions ~ 4.10. Intermezzo: Coding Style 의 모든 내용을 보다 이해하기 쉽게 정리한 글이다.
1. 함수 정의
def fib(n): # write Fibonacci series less than n """Print a Fibonacci series less than n.""" a, b = 0, 1 while a < n: print(a, end=' ') a, b = b, a+b print()
def: 함수 정의를 한다는 키워드이다.
fib: 함수의 이름을 설정한다.
(): 함수의 parameter를 list 형식으로 넣는다.
:: 이 콜론 다음으로 함수의 Body가 이어진다. 함수 작성시 Identation (들여쓰기)가 필수적으로 요구된다.
2. Symbol Table
symbol table이란 어떤 object에 대해 name과 데이터의 주소를 저장하고 있는 dictionary이다.
- 함수를 실행하면 그 함수의 새 symbol table을 만든다.
- 변수를 참조할 때는 local symbol table → 그 함수를 감싸는 함수의 symbol table → global symbol table → built-in name의 table 순서로 찾는다.
그래서 global variable이나 감싸는 함수의 variable의 값이 할당되지 않을 수 있다.
- 다른 함수가 call하거나 자기 자신이 재귀적으로 call할 때도 그 call마다 local symbol table을 만든다.
- 함수도 symbol table에서 object이기 때문에 다른 name이 같은 함수 object를 가리킬 수도 있다.
fib
<function fib at 10042ed0>
f = fib f(100)
0 1 1 2 3 5 8 13 21 34 55 89
3. Return
return statement를 통해 함수내 어떤 value를 내보낼 수 있다.3.1. return이 없는 함수
앞서 보인
fib 함수는 값을 return statement가 없다. 하지만 python에서는 return이 없더라도 항상 return을 하고 그 값은 None이다. None은 interpreter에서 안 보이게 되기 때문에 억지로 None이 return 됨을 보고 싶다면 print를 쓸 수 있다.print(fib(0))
None
3.2. Method
def fib2(n): # return Fibonacci series up to n """Return a list containing the Fibonacci series up to n.""" result = [] a, b = 0, 1 while a < n: result.append(a) # see below a, b = b, a+b return result f100 = fib2(100) # call it f100 # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
method는 함수이며 이름은 obj.methodname 로 정해진다. 이때 obj는 어떤 object이고 str, int와 같이 expression일 수 있다. 위 예시에서는 result.append(a) 라는 statement가 list object인 result 의 method를 call한 것이다.- object의 type마다 서로 다른 method를 가지고 있다.
예를 들어 list object에는 append가 있지만 어떤 object에는 append가 없을 수 있다.
- object type이 다르지만 서로 name이 같아도 ambiguity를 일으키지 않는다.
class를 통해 직접 object type과 method를 정의할 수 있다.
예를 들어 temp라는 type의 object를 만들고 method로 append를 추가해도 list의 append와 충돌하지 않는다.
4. Argument
4.1. Default Argument Values
함수를 정의할 때 여러개의 argument를 둘 수 있으며 argument마다 기본 값을 설정할 수 있다.
def으로 정의할 때
retries=4 와 같이 기본값을 설정하는 것이다.def ask_ok(prompt, retries=4, reminder='Please try again!'): while True: reply = input(prompt) if reply in {'y', 'ye', 'yes'}: return True if reply in {'n', 'no', 'nop', 'nope'}: return False retries = retries - 1 if retries < 0: raise ValueError('invalid user response') print(reminder)
기본값을 설정해두면 함수 호출시 argument의 값을 명시하지 않아도 호출할 수 있다. 예를 들어 아래와 같이 3개를 모두 명시하지 말고 1개나 2개만 명시할 수 있다.
- 필수 argument만 전달:
ask_ok('Do you really want to quit?')
- 선택 argument 1개 전달:
ask_ok('OK to overwrite the file?', 2)
- 모든 argument 전달:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
4.1.1. 주의할 점
argument의 기본값은 함수가 정의될 떄 딱 한 번만 정해진다. 예를 들어 아래 코드에서는 함수가 정의될 때 i=5였기 때문에 arg=5로 확정된다. 따라서 i=6을 거쳐도 f를 호출시
5가 출력된다.i = 5 def f(arg=i): print(arg) i = 6 f()
만일 기본값이 list, dictionary, class의 instance와 같이 mutable하다면 함수 호출시 모두 같은 object를 공유하게 된다. 예컨대 아래와 같이 L=[]로 기본값을 정해두면 호출 때마다 같은 list에 값을 추가하므로 오른쪽과 같이 출력된다.
def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print(f(3))
[1] [1, 2] [1, 2, 3]
기본값을 공유하기 싫다면 아래와 같이 수정할 수 있다.
def f(a, L=None): if L is None: L = [] L.append(a) return L
4.2. Keyword Arguments
함수 호출시
kwarg=value 꼴로 argument를 전달할 수 있다. def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'): print("-- This parrot wouldn't", action, end=' ') print("if you put", voltage, "volts through it.") print("-- Lovely plumage, the", type) print("-- It's", state, "!")
예를 들어 위 함수를 아래처럼 호출할 수 있다.
parrot(1000) # 1 positional argument parrot(voltage=1000) # 1 keyword argument parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments parrot('a million', 'bereft of life', 'jump') # 3 positional arguments parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
이때 아래와 같은 특징이 있다.
- 모든 keyword arguments는 함수의 argument 중 하나와 반드시 맞아야 한다. (아래 예시에서
parrot에 없는actorargument를 전달하면 안 된다.)
- keyword argument 끼리는 순서가 상관이 없다.
- argument가 중복되면 안 된다.
- non-keyword argument가 keyword argument 다음에 와서는 안된다.
parrot() # required argument missing parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument parrot(110, voltage=220) # duplicate value for the same argument parrot(actor='John Cleese') # unknown keyword argument
4.3. Arbitrary Argument: *args, **kwargs
4.3.1. Arbitrary Positional Argument: *args
*name 형식으로 argument를 두어 임의의 개수의 argument를 호출할 수 있다. 이때 아래와 같은 특징이 있다.- 여러 argument는 tuple로 보내진다.
*arg다음의 parameter는 keyword-only arguments 이다.
def concat(*args, sep="/"): return sep.join(args) concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
4.3.2. Arbitrary Keyword Argument: **kwargs
*name은 tuple로 받지만 **name은 dictionary로 받는다. 마찬가지로 임의 개수의 argument를 keyword로 전달할 수 있다. 이때 *name 이후에 **name이 봐야한다. 또한 dictionary 내 argument의 순서는 호출시 표기한 순서와 같다.def cheeseshop(kind, *arguments, **keywords): print("-- Do you have any", kind, "?") print("-- I'm sorry, we're all out of", kind) for arg in arguments: print(arg) print("-" * 40) for kw in keywords: print(kw, ":", keywords[kw]) cheeseshop("Limburger", "It's very runny, sir.", "It's really very, VERY runny, sir.", shopkeeper="Michael Palin", client="John Cleese", sketch="Cheese Shop Sketch")
-- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir. ---------------------------------------- shopkeeper : Michael Palin client : John Cleese sketch : Cheese Shop Sketch
4.4. Special parameters
가독성과 성능을 위해 선택적으로
/ 와 *를 써서 argument를 어떻게 함수에게 전달할지의 방법을 제한시킬 수 있다.def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): ----------- ---------- ---------- | | | | Positional or keyword | | - Keyword only -- Positional only
4.4.1. Positional-or-Keyword Arguments
함수를 정의할 때
/ 와 * 가 없다면 position으로 전달하거나 keyword로 전달할 수 있다. 지금까지 살펴본 것과 같다.4.4.2. Positional-Only Parameters
/ 이전에 놓인 parameter는 반드시 position으로만 전달되고 keyword로 전달할 수 없다.4.4.3. Keyword-Only Arguments
* 후에 놓인 parameter는 반드시 keyword로만 전달되고 position으로 전달할 수 없다.4.4.4. 사용 예시
def standard_arg(arg): print(arg) def pos_only_arg(arg, /): print(arg) def kwd_only_arg(*, arg): print(arg) def combined_example(pos_only, /, standard, *, kwd_only): print(pos_only, standard, kwd_only)
/ 와 *를 두지 않으면 어떤 제약도 없다.standard_arg(2)
2
standard_arg(arg=2)
2
positional only argument 자리에 keyword로 전달하면 오류가 난다.
pos_only_arg(1)
1
pos_only_arg(arg=1)
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'
keyword only argument 자리에 position으로 전달하면 오류가 난다.
kwd_only_arg(3)
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
kwd_only_arg(arg=3)
3
4.4.5. Positional argument와 **kwrds 의 충돌
아래의 왼쪽 예시처럼
**kwds에서 positional argument에 이미 있는 값을 전달하려고 하면 에러가 난다. 먼저 정의되고 전달된 positional argument가 먼저 저장되기 때문이다.이 문제를 오른쪽 예시처럼
/을 두어 name을 positional only argument로 두면 오류가 발생하지 않는다.def foo(name, **kwds): return 'name' in kwds foo(1, **{'name': 2})
Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: foo() got multiple values for argument 'name'
def foo(name, /, **kwds): return 'name' in kwds foo(1, **{'name': 2})
True
4.4.6. 정리
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
- positional-only를 쓰면 좋은 상황
- parameter name이 어떠한 의미가 없다.
- user에게 parameter name를 보이게 하고 싶지 않다.
- argument의 순서를 더 명확히 정하고 싶다.
- positional parameter와 arbitrary keywords를 둘 다 사용하려 한다.
- API를 위한 함수이다. 나중에 parameter name이 바뀌어 API가 망가지는 것을 방지할 수 있다.
- keyword-only를 쓰면 좋은 상황
- parameter name에 어떤 의미가 있다.
- user가 position으로 argument를 전달하게 하고 싶지 않다.
- 이름을 둬서 함수 정의를 보다 명료하게 하고 싶다.
4.5. Unpacking Argument: *args, **kwargs
똑같은
*와 ** 기호를 사용하기 때문에 Arbitrary Argument: *args, **kwargs 부분과 헷갈릴 수 있다. 명료하게 하자면 이전까지는 함수를 정의할 때 *를 사용한 것이고 이 부분은 함수를 호출할 때 *를 쓰는 것이다.argument가 이미 list, tuple, dictionary로 되어 있을 때 unpack해서 함수를 호출하고 싶다면 list 또는 tuple일 때는
*를 사용하고 dictionary일 때는 **를 통해 unpack할 수 있다. (dictionary에서 *를 사용하면 key만 전달되고 keyword argument가 아니라 positional argument로 작동한다.)def f(arg1, arg2, arg3): print(arg1, arg2, arg3) args = (1, 2, 3) f(*args)
1 2 3
def f(arg1, arg2, arg3): print(arg1, arg2, arg3) args = {'arg1': 1, 'arg2': 2, 'arg3': 3} f(**args)
1 2 3
5. Lambda Expressions
이름이 없고 간단한 함수를
lambda keyword로 만들 수 있다. lambda {argument list}: {return value} 와 같은 형식으로 쓴다. 이때 아래와 같은 특징이 있다. - Lambda 함수는 일반적인
def function으로 완전히 대체 가능하다. 단순히 좀 더 간결히 쓸 수 있는 것만이 존재 이유이다.
- 본문을 한 줄만 쓸 수 있도록 제한 돼 있다. 여러 줄이 필요하면 일반
def함수를 쓰자.
- 일반
def함수처럼 Lambda 함수도 자신을 감싸는 함수의 variable을 참조할 수 있다.
예를 들어 아래와 같이 두 값을 더하는 함수를 만드는함수를 보자. lambda 함수를 감싸는 make_incrementor 함수의 n을 참조할 수 있는 모습이다. 이처럼 함수를 return할 때 lambda를 유용하게 쓸 수 있다.
def make_incrementor(n): return lambda x: x + n f = make_incrementor(42) f(10)
52
다른 예시로는 함수를 argument로 전달할 때도 쓸 수 있다.
list.sort()는 sorting key function을 필요로 하는데 이 key를 lambda 함수로 전달할 수 있다.pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')] pairs.sort(key=lambda pair: pair[1]) pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
또 자주 쓰는 예시는 아래와 같다. pandas dataframe에서 table을 불러올 때 여러 column의 converters를 정의하거나 dataframe을 이미 불러오고 나서 각 행마다의 변환을 쉽게 할 수 있다.
converters = { 'distance': lambda x: float(x) / 1_000, 'time' : lambda x: float(x) / 3_600, } df = pd.read_csv(path, converters=converters)
df['lat_lng'] = df.apply(lambda row: (df['lat'], df['lng']), axis=1)
참고로 위 코드에서 axis=1로 해야 row들에 적용하고 axis=0으로 하면 column들에 적용한다.
6. Documentation Strings
6.1. 정의
함수 Body의 첫 줄에 string literal이 올 때 이 string literal을 documentation string 또는 docstring이라고 한다. doctring은 ide이나 다른 프로그램에서 해당 함수에 대한 설명을 읽어올 때 관습적으로 사용된다.
string literal이란 ‘로 감싸거나 “로 감싸거나 “””로 감싼 텍스트를 말한다. “””로 감싸면 line break도 기록되기 때문에 주로 “””로 감싼다.
6.2. 관습
- 첫 번째 줄은 함수의 목적을 짧게 요약하고 대문자로 시작한다. 첫 번째 줄에서 type을 명시하진 않는다.
- 더 많은 설명이 필요하면 두 번째 줄은 비워둬서 요약과 상세 설명을 시각적으로 구분시킨다.
- 그 이후부터는 호출 방식, 각 argument의 type 등 다양한 내용을 담는다.
이때 여러 줄에 걸쳐 작성한다면 docstring의 identation은 첫 번째 줄 그 다음에 오는 비어 있지 않은 첫 줄의 identation으로 결정된다. 즉, Do noting,…이 첫 번째 줄이고 그 다음으로 non blank인 줄은 No, …이다. 이 줄의 Identation을 doctring 전체의 identation으로 설정한다.
def my_function(): """Do nothing, but document it. No, really, it doesn't do anything. """ pass print(my_function.__doc__)
Do nothing, but document it. No, really, it doesn't do anything.
6.3. Annotations
Annotation은 type에 대한 메타 데이터로 선택적으로 추가할 수 있고 함수 자체에는 아무런 변화도 가져오지 않는다. argument와 return 값의 type을 보다 구체적으로 명시하고 싶을 때 사용한다. 명시하면 dictionary 형태로 함수의
__annotations__ 속성에 저장된다.- Parameter annotation은 parameter 이름 다음에
:str, int, float과 같은 expression을 남긴다. 아래 예시에서= 'eggs'는 default argument value로 annotation과는 별개이다.
- Return annotation은 parameter list와 def 선언의
:사이에->과 expression을 적어 정의한다.
def f(ham: str, eggs: str = 'eggs') -> str: print("Annotations:", f.__annotations__) print("Arguments:", ham, eggs) return ham + ' and ' + eggs f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>} Arguments: spam eggs 'spam and eggs'
7. Coding Style
길고 복잡한 코드를 작성할 때 보다 간결하고 format을 맞춰 적는 것은 다른 사람이나 몇 달 뒤 내가 코드를 볼 때 더 잘 이해할 수 있다. PEP 8 에는 Python 개발자가 언젠가는 반드시 읽어야 하는 스타일 가이드가 쓰여 있다. 그중 가장 중요한 몇 가지는 아래와 같다.
- 4-space identation(들여쓰기)를 하라. tab을 쓰지 말라.
그보다 적은 space는 더 깊은 nesting을 할 수 있지만 읽기 어렵고 그보다 많아도 역시 코드를 읽기 어려워진다. tab 문자는 혼동을 일으킨다.
(최신 IDE에서는 tab만으로 4-space identation이 자동으로 되기는 한다.)
- 79 글자를 넘지 않도록 줄바꿈을 하라.
side-by-side 화면 등에서 보다 코드가 잘 보인다.
- 함수, class, 함수 내의 큰 덩어리의 코드를 분리하기 위해 blank lines를 쓰라.
- 가능하다면 주석은 코드 옆에다 쓰지 말고 주석줄만 따로 둬서 써라.
- docstrings을 써라.
- Operator와 comma 다음에는 space를 둬라.
+,-,*,/,=와 같은 operator 양 옆에 space- comma 다음에 space
- 단
(),{},[]와 같은 괄호에서는 바로 space를 두지 말라. ✅(1, 2)❌( 1, 2 ) - 예시:
a = f(1, 2) + g(3, 4)
- class와 function의 이름을 일관적으로 작명해라.
- class는
UpperCamelCase을 사용하는 것이 관례이고, - function이나 method는
lowercase_with_underscores를 쓰는 것이 관례이다.
- class의 첫 번째 method는 항상 이름을
self로 둬라.
- 여러 언어의 환경에서 사용된다면 encoding을 복잡하게 두지 말라. Python의 기본 encoding은 UTF-8이다. 심지어 ASCII 로만으로도 어느 상황에서든 잘 작성할 수 있다. 웬만하면 ASCII 문자로만 코드를 작성해라. 다른 언어의 개발자가 읽거나 유지 보수를 해야할 수도 있다.
Reference
[1] Python Software Foundation. "4.8. Special Parameters." The Python Tutorial, version 3.13, Python Software Foundation, 2024, docs.python.org/3.13/tutorial/controlflow.html#special-parameters. Accessed 10 July 2025.
