Python Programming

Lecture 7 Variable Scope, Advanced Functions

7.1 Variable Scope

Variable Scope (变量的作用域)

In Python, module, class, def, lambda can introduce new variable scope, if/elif/else/, try/except, for/while will not introduce new variable scope.


if True:
    msg = 'I am from Shanghai'
print(msg) # We can use msg.

def test():
    msg = 'I am from Shanghai'
print(msg) # error

Global and Local(全局和局部)


msg_loc = "Shanghai" # Global
def test():
    msg = 'I am from Shanghai' # Local

New Assignment


msg = 'I am from Shanghai'
def test():
    msg = 'I am from Beijing'
    print(msg) 
test()
print(msg)

I am from Beijing
I am from Shanghai



Reference(引用)


msg = 'I am from Shanghai'
def test():
    print(msg) 
test()

I am from Shanghai


Referenced before assignment


msg = 'I am from Shanghai'
def test():
    print(msg) 
    msg = 'I am from Beijing' 
test() 

UnboundLocalError: local variable 'msg' 
referenced before assignment


  • How to modify the variable outside? The global keyword


num = 1
def fun():
    global num
    num = 123
    print(num)
fun()
print(num)

123
123


 


num = 1
def fun():
    print(num)
    global num  
    num = 123
    print(num)
fun()

SyntaxError: name 'num' is used 
prior to global declaration





a = 10
def test():
    a = a + 1
    print(a)
test()

UnboundLocalError: local variable 'a' 
referenced before assignment



a = 10
def test():
    global a 
    a = a + 1
    print(a)
test()

11





a = 10
def test():
    a = 10
    a = a + 1
    print(a)
test() 
print(a) 

11
10




Mutable objects


a = [1,2,3]
def test():
    print(a)
    a = [1,2,3,4]
test() 

UnboundLocalError: local variable 'a' 
referenced before assignment



a = [1,2,3]
def test():
    print(a)
    a.append(4)
test() 
print(a)

[1, 2, 3]
[1, 2, 3, 4]




a = {"color": "green"}
def test():
    print(a)
    a["color"] = "red"
    a["position"] = "left"
test() 
print(a)

{'color': 'green'}
{'color': 'red', 'position': 'left'}




Parameter and Argument

  • For mutable objects (like lists and dictionaries), assigning a new value to a variable inside a function will not change the original variable outside the function. (the same with immutable objects!)


def changeme(mylist):
   mylist = [1,2]
   print("inside: ", mylist)
 
x = [10,20]
changeme(x)
print("outside: ", x)

inside: [1, 2]
outside: [10, 20]




  • However, modifying the contents of the mutable object (e.g., adding elements to a list or updating a dictionary) can change the value of the variable outside the function.


def changeme(mylist):
   mylist.extend([1,2])
   print ("inside: ", mylist)
 
x = [10,20]
changeme(x)
print ("outside: ", x)

inside: [10, 20, 1, 2]
outside: [10, 20, 1, 2]




7.2 Example: Pomodoro Timer (番茄钟)

番茄钟

"番茄钟"在英文中通常称为 Pomodoro Technique,是一种经典的时间管理方法,由 Francesco Cirillo 在 1980 年代提出。 其名称源于番茄形状厨房定时器 (意大利语"pomodoro"意为番茄)。该方法将工作时间分割为短间隔(25分钟为一个"番茄钟"),辅以短暂休息,帮助提高专注力并减少疲劳。

  • 番茄钟 / Pomodoro: 一个完整的工作周期,通常为 25 分钟专注时间。
  • 短休息 / Short Break: 每个番茄之后的短暂休息(通常 3–5 分钟)。
  • 长休息 / Long Break: 每完成 4 个番茄后的较长休息(通常 15–30 分钟)。
  • 番茄计数 / Pomodoro Count: 已完成的番茄数量,用于衡量进度。

Step 1: Countdown Timer,倒计时


import time

def countdown(seconds):
    while seconds >= 0:
        print(f"\r剩余:{seconds:02d} 秒", end="", flush=True)
        time.sleep(1) 
        seconds -= 1

    print("\n时间到!")

countdown(3)

  • \r 的作用是把光标移回当前行的开头,所以每次输出都会覆盖前面的内容。
  • flush=True, 让 print 立即输出, 和 \r 组合起来实现原地刷新的效果
  • {seconds:02d} 将一个整数以两位数的形式输出,并且不足两位时用0进行填充
  • time.sleep(1) 表示暂停1秒

Step 2: Add Music 添加音乐放在相同目录,可能需要从终端运行。


# win 电脑用,先下载 playsound3 或者 playsound 包
import time
from playsound3 import playsound

def countdown(seconds):
    while seconds >= 0:
        print(f"\r剩余:{seconds:02d} 秒", end="", flush=True)
        time.sleep(1)
        seconds -= 1
    print("\n时间到!")
    playsound("alarm.mp3") #加上这行

countdown(3)

# mac 电脑用,不用下载任何包
import time
import os

def countdown(seconds):
    while seconds >= 0:
        print(f"\r剩余:{seconds:02d} 秒", end="", flush=True)
        time.sleep(1)
        seconds -= 1
    print("\n时间到!")
    os.system("afplay alarm.mp3") #加上这行

countdown(3)

Exercise: 加入轮数控制,一轮为工作3秒,休息1秒


import time

def countdown(seconds):
    while seconds >= 0:
        print(f"\r剩余:{seconds:02d} 秒", end="", flush=True)
        time.sleep(1)
        seconds -= 1
    print("\n时间到!")

count = 1
def pomodoro():
    countdown(3)
    count += 1 

pomodoro()
  1. 上面的代码想用count来统计轮数,但是代码有错误,先把它修改正确
  2. 让 pomodoro 接收轮数作为实参,例如:pomodoro(1) 会自动倒计时 3 秒,接着倒计时 1 秒。
  3. 继续修改,如果轮数到达 4 轮,休息 2 秒

7.3 Advanced Functions

Arbitrary Arguments(任意数量的实参)

  • To pass multiple arguments, we have two ways:


x = ['mushrooms', 'green peppers', 'extra cheese']
def make_pizza(toppings):
    for y in toppings:
        print(y, end=', ')

make_pizza(x)

def make_pizza(topping_1, topping_2, topping_3):
    print(topping_1, topping_2, topping_3)

make_pizza('mushrooms', 'green peppers', 'extra cheese')
  • However, sometimes we are not sure about the exact number of the arguments.

  • We can pass multiple arguments at once in the following way.


def make_pizza(*toppings):
    print(toppings)

make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

('mushrooms', 'green peppers', 'extra cheese')
  • Note that Python packs (打包) the arguments into a tuple, even if the function receives only one value. Using * with a container means "unpacking" (解包) it.


def make_pizza(*toppings):
    print(toppings)


t = ['mushrooms', 'green peppers', 'extra cheese']
make_pizza(*t) 

('mushrooms', 'green peppers', 'extra cheese')
  • Mixing Positional and Arbitrary Arguments

  • If you want a function to accept several different kinds of arguments, the parameter that accepts an arbitrary number of arguments must be placed last in the function definition.


def make_pizza(size, *toppings):
    print(str(size) + "-inch pizza:")
    for topping in toppings:
        print("- " + topping)

make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

16-inch pizza:
- pepperoni
12-inch pizza:
- mushrooms
- green peppers
- extra cheese
  • Using Arbitrary Keyword Arguments


def build_profile(**user_info):
    print(user_info)

build_profile(location='Shanghai',field='Management')

{'location': 'Shanghai', 'field': 'Management'}
  • Using ** with a dictionary means unpacking it into keyword arguments.


def build_profile(**user_info):
    print(user_info)

d = {'location': 'princeton', 'field': 'physics'}

build_profile(**d)

{'location': 'princeton', 'field': 'physics'}

def build_profile(*name, **user_info):
    print(name)
    print(user_info)

build_profile('albert', 'einstein',
               location='princeton',
               field='physics')

('albert', 'einstein')
{'location': 'princeton', 'field': 'physics'}
  • As a tradition, we often use *args and **kw

  • Example


# y=1 means y has a default value of 1
# An argument overrides the default,
# otherwise, y remains 1

def test(x,y=1,*a,**b):
    print(x,y,a,b)

test(1)
test(1,2)
test(1,2,3)
test(1,2,3,4)
test(x=1,y=2)
test(1,a=2)
test(1,2,3,a=4)
test(1,2,3,y=4)

  • Result








1 1 () {}
1 2 () {}
1 2 (3,) {}
1 2 (3, 4) {}
1 2 () {}
1 1 () {'a': 2}
1 2 (3,) {'a': 4}
TypeError: test() got multiple values

Map(映射函数)


def f(x):
    return x * x

y = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])    
print(list(y))

[1, 4, 9, 16, 25, 36, 49, 64, 81]

print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))


['1', '2', '3', '4', '5', '6', '7', '8', '9']

filter(过滤函数)


def k(x):
    return x%2 == 0

y = list(filter(k, [1, 2, 3, 4, 5, 6, 7, 8, 9]))   
print(y)

[2, 4, 6, 8]

Anonymous function: lambda(匿名函数)


def add( x, y ):
    return x + y
 
lambda x, y: x + y
 
lambda x, y = 2: x + y
lambda *z: z
  • Sometimes the anonymous function is convenient.

  • It has only one expression. (You do not have to use return)


print(list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))

print(list(filter(lambda x: x%2==0, [1, 2, 3, 4, 5, 6, 7, 8, 9])))

List Comprehension(列表生成式)

  • [expression for value in iterable if condition ]


>>> [x*x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

>>> [x*x for x in range(1, 11) if x%2==0]
[4, 16, 36, 64, 100]

#if-else和只有if的顺序不同
>>> [x*x if x%2==0 else x**3 for x in range(1, 11)] 
[1, 4, 27, 16, 125, 36, 343, 64, 729, 100]

>>> [m+n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

Example: 德扑2.0

  1. 先给玩家 2 张牌(hole cards)
  2. 再出公共牌 5 张(community cards)
  3. 判断有没有"顺子"

import random

# 简化处理,没有包含J, Q, K, A
ranks = list(map(str, [value for value in range(2, 15)]))
suits = ["\u2660", "\u2665", "\u2663", "\u2666"]
deck = [y + x for x in ranks for y in suits]

random.shuffle(deck)

hand = [deck.pop() for x in range(2)]
board = [deck.pop() for x in range(5)]

print("手牌:", hand)
print("公共牌:", board)

all_numbers = [int(c[1:]) for c in hand + board] # 列表合并可以用 + 加法
print(all_numbers)

判断有没有"顺子"


def has_straight(cards):
    # set 去重 + sorted 排序并返回有序列表
    values = sorted(set(cards))   

    # A 既可以当 14,也可以当 1
    if 14 in values:
        values = [1] + values

    # 检查是否存在连续5个数
    for i in range(len(values) - 4):
        if values[i+4] - values[i] == 4:
            return True

    return False

if has_straight(all_numbers):
    print("结果:有顺子")
else:
    print("结果:没有顺子")

Exercise: 续写下面的代码,判断牌型是否包含"同花",用上刚刚学的列表生成式


import random

ranks = list(map(str, [value for value in range(2, 15)]))
suits = ["\u2660", "\u2665", "\u2663", "\u2666"]
deck = [y + x for x in ranks for y in suits]

random.shuffle(deck)

# 简化处理,一次性发了7张
hand = [deck.pop() for x in range(7)]
  • 提示: 可以用(也可以不用)列表的方法.count() 用来数个数,这个方法返回值,下面是示例

x = [4, 5, 6, 7, 7, 7, 7, 8]
print(x.count(7)) # 结果是 4 

Summary

  • Functions
    • Reading: Python for Everybody, Chapter 4
    • Reading: Python Crash Course, Chapter 8