Python - 100天从新手到大师
项目地址:https://github.com/jackfrued/Python-100-Days
Day 06 函数和模块的使用
在Python中可以使用def关键字来定义函数,函数名命名规则和变量的命名规则一致,在函数后面的圆括号中可以放置传递给函数的参数,函数体的最后通过return关键字来返回一个值。
例如:构建一个阶乘函数
def factorial(num):
"""
求阶乘
:param num: 非负整数
:return: num的阶乘
"""
result = 1
for n in range(1, num + 1):
result *= n
return result
m = int(input('m = '))
n = int(input('n = '))
# 当需要计算阶乘的时候不用再写循环求阶乘而是直接调用已经定义好的函数
print(factorial(m) // factorial(n) // factorial(m - n))
在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持函数的重载
例如:一个简单的加和,可见可变参数是如何定义的,而且它也是可迭代对象
# 设定参数默认值
def add(a=0, b=0, c=0):
return a + b + c
# 在参数名前面的*表示args是一个可变参数
# 即在调用add函数时可以传入0个或多个参数
def add(*args):
total = 0
for val in args:
total += val
return total
还是由于Python没有函数重载的概念,所以如果重复定义函数,后面的函数会对前面的进行覆盖。但是如果项目是由多人协作进行团队开发,团队中可能有多个人定义了同名的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过import关键字导入指定的模块就可以区分到底要使用的是哪个模块中的函数
module1.py
def foo():
print('hello, world!')
module2.py
def foo():
print('goodbye, world!')
test.py
from module1 import foo
# 输出hello, world!
foo()
from module2 import foo
# 输出goodbye, world!
foo()
也可以按照如下所示的方式来区分到底要使用哪一个foo函数。
test.py
import module1 as m1
import module2 as m2
m1.foo()
m2.foo()
笔者认为第二种方式较好✔,别名总是比较方便区分的。
需要说明的是,如果我们导入的模块除了定义函数之外还中有可以执行代码,那么Python解释器在导入这个模块时就会执行这些代码,事实上我们可能并不希望如此,因此如果我们在模块中编写了执行代码,最好是将这些执行代码放入如下所示的条件中,这样的话除非直接运行该模块,if条件下的这些代码是不会执行的,因为只有直接执行的模块的名字才是“__main__”。
def foo():
pass
def bar():
pass
# __name__是Python中一个隐含的变量它代表了模块的名字
# 只有被Python解释器直接执行的模块的名字才是__main__
if __name__ == '__main__':
print('call foo()')
foo()
print('call bar()')
bar()
Python中的变量作用域
def foo():
b = 'hello'
def bar(): # Python中可以在函数内部再定义函数
c = True
print(a)
print(b)
print(c)
bar()
# print(c) # NameError: name 'c' is not defined
if __name__ == '__main__':
a = 100
# print(b) # NameError: name 'b' is not defined
foo()
上面的代码能够顺利的执行并且打印出100和“hello”,但我们注意到了,在bar函数的内部并没有定义a和b两个变量,那么a和b是从哪里来的。我们在上面代码的if分支中定义了一个变量a,这是一个全局变量(global variable),属于全局作用域,因为它没有定义在任何一个函数中。在上面的foo函数中我们定义了变量b,这是一个定义在函数中的局部变量(local variable),属于局部作用域,在foo函数的外部并不能访问到它;但对于foo函数内部的bar函数来说,变量b属于嵌套作用域,在bar函数中我们是可以访问到它的。bar函数中的变量c属于局部作用域,在bar函数之外是无法访问的。事实上,Python查找一个变量时会按照“局部作用域”、“嵌套作用域”、“全局作用域”和“内置作用域”的顺序进行搜索,前三者我们在上面的代码中已经看到了,所谓的“内置作用域”就是Python内置的那些隐含标识符min、len等都属于内置作用域)。
简单的说,就是内层的可以访问外层的,但是反之则不行。
def foo():
a = 200
print(a) # 200
if __name__ == '__main__':
a = 100
foo()
print(a) # 100
这里在foo里写a=200相当于是重新定义了一个名为a的变量并赋值,而且属于局部作用域,因此foo函数就不会理会全局作用域中的a了。如果我们希望在foo函数中修改全局作用域中的a,代码如下所示。
def foo():
global a
a = 200
print(a) # 200
if __name__ == '__main__':
a = 100
foo()
print(a) # 200
我们可以使用global关键字来指示foo函数中的变量a来自于全局作用域,如果全局作用域中没有a,那么下面一行的代码就会定义变量a并将其置于全局作用域。同理,如果我们希望函数内部的函数能够修改嵌套作用域中的变量,可以使用nonlocal关键字来指示变量来自于嵌套作用域。
在实际开发中,我们应该尽量减少对全局变量的使用,因为全局变量的作用域和影响过于广泛,可能会发生意料之外的修改和使用,除此之外全局变量比局部变量拥有更长的生命周期,可能导致对象占用的内存长时间无法被垃圾回收。事实上,减少对全局变量的使用,也是降低代码之间耦合度的一个重要举措,同时也是对迪米特法则(一个对象应当对其他对象有尽可能少的了解)的践行。减少全局变量的使用就意味着我们应该尽量让变量的作用域在函数的内部,但是如果我们希望将一个局部变量的生命周期延长,使其在函数调用结束后依然可以访问,这时候就需要使用闭包。
练习1:实现计算最大公约数和最小公倍数的函数
# -*- coding: utf-8 -*-
# 辗转相除法求最大公约数
def gcd(a: int, b: int):
if a < b:
a, b = b, a
if b == 0:
return a
else:
return gcd(b, a % b)
# 求最小公倍数
# 定理:a、b 两个数的最小公倍数乘以它们的最大公约数等于 a 和 b 本身的乘积。
def lcm(a: int, b: int):
return a * b // gcd(a, b)
if __name__ == '__main__':
print(gcd(123456, 7890)) # 6
print(lcm(24, 21)) # 168
练习2:实现判断一个数是不是回文数的函数
# -*- coding: utf-8 -*-
# 两种方法判断数字是否为回文数
# 使用遍历的方式比对头尾
def palid_1(a: int):
str_a = str(a)
for i in range(0, int(len(str_a) / 2)):
if str_a[i] != str_a[-i - 1]:
return False
return True
# 使用字符串切片的方式,将字符串倒序比较
def palid_2(a: int):
str_a = str(a)
return str_a == str_a[::-1]
练习3:实现判断一个数是不是素数的函数
# -*- coding: utf-8 -*-
import math
# 判断素数
def prime_1(a: int):
if a == 2:
return 'Yes'
else:
for i in (2, int(math.sqrt(a))):
if a % i == 0:
return 'No'
return 'Yes'
练习4:写一个程序判断输入的正整数是不是回文素数
# -*- coding: utf-8 -*-
from PalidNum import palid_1
from primeNum import prime_1
if __name__ == '__main__':
x = int(input('x='))
if palid_1(x) is True and prime_1(x) == 'Yes':
print('Yes')
ps:在import的时候死活认不出来同目录下的module,这个时候就需要把目录改成Source类型的,在Pycharm里为蓝色文件夹图标,这样就能导了。
Day 07 字符串和常用数据结构
使用字符串
def main():
str1 = 'hello, world!'
# 通过len函数计算字符串的长度
print(len(str1)) # 13
# 获得字符串首字母大写的拷贝
print(str1.capitalize()) # Hello, world!
# 获得字符串变大写后的拷贝
print(str1.upper()) # HELLO, WORLD!
# 从字符串中查找子串所在位置
print(str1.find('or')) # 8
print(str1.find('shit')) # -1
# 与find类似但找不到子串时会引发异常
print(str1.index('or'))
print(str1.index('shit'))
# 检查字符串是否以指定的字符串开头
print(str1.startswith('He')) # False
print(str1.startswith('hel')) # True
# 检查字符串是否以指定的字符串结尾
print(str1.endswith('!')) # True
# 将字符串以指定的宽度居中并在两侧填充指定的字符
print(str1.center(50, '*'))
# 将字符串以指定的宽度靠右放置左侧填充指定的字符
print(str1.rjust(50, ' '))
str2 = 'abc123456'
# 从字符串中取出指定位置的字符(下标运算)
print(str2[2]) # c
# 字符串切片(从指定的开始索引到指定的结束索引)
print(str2[2:5]) # c12
print(str2[2:]) # c123456
print(str2[2::2]) # c246
print(str2[::2]) # ac246
print(str2[::-1]) # 654321cba
print(str2[-3:-1]) # 45
# 检查字符串是否由数字构成
print(str2.isdigit()) # False
#