Python面試基礎點錦集

For else

在 python 中,for … else 表示這樣的意思,for 中的語句和普通的沒有區別,else 中的語句會在循環正常執行完(即 for 不是通過 break 跳出而中斷的)的情況下執行,while … else 也是一樣。

原文:for loops also have an else clause which most of us are unfamiliar with. The else clause executes after the loop completes normally. This means that the loop did not encounter a break statement. They are really useful once you understand where to use them. I, myself, came to know about them a lot later.

for  i in range(3):
    if i == 2:
        print(i)
        break

else:
    print(0)

for i in range(3):
    if i == 2:
        print(i)
else:
    print(0)


for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print(n, 'equals', x, '*', n/x)
            break

for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print( n, 'equals', x, '*', n/x)
            break
    else:
        # loop fell through without finding a factor
        print(n, 'is a prime number')      
2
2
0
4 equals 2 * 2.0
6 equals 2 * 3.0
8 equals 2 * 4.0
9 equals 3 * 3.0
2 is a prime number
3 is a prime number
4 equals 2 * 2.0
5 is a prime number
6 equals 2 * 3.0
7 is a prime number
8 equals 2 * 4.0
9 equals 3 * 3.0

Try except else

```
try:
<語句>        #運行別的代碼
except <名字>:
<語句>        #如果在try部份引發了'name'異常
except <名字>,<數據>:
<語句>        #如果引發了'name'異常,獲得附加的數據
else:
<語句>        #如果沒有異常發生
```
* try的工作原理是,當開始一個try語句後,python就在當前程序的上下文中作標記,這樣當異常出現時就可以回到這裏,try子句先執行,接下來會發生什麼依賴於執行時是否出現異常。
    + 如果當try後的語句執行時發生異常,python就跳回到try並執行第一個匹配該異常的except子句,異常處理完畢,控制流就通過整個try語句(除非在處理異常時又引發新的異常)。
    + 如果在try後的語句裏發生了異常,卻沒有匹配的except子句,異常將被遞交到上層的try,或者到程序的最上層(這樣將結束程序,並打印默認的出錯信息)。
    + 如果在try子句執行時沒有發生異常,python將執行else語句後的語句(如果有else的話),然後控制流通過整個try語句。
try的工作原理是,當開始一個try語句後,python就在當前程序的上下文中作標記,這樣當異常出現時就可以回到這裏,try子句先執行,接下來會發生什麼依賴於執行時是否出現異常。
    + 如果當try後的語句執行時發生異常,python就跳回到try並執行第一個匹配該異常的except子句,異常處理完畢,控制流就通過整個try語句(除非在處理異常時又引發新的異常)。
    如果在try後的語句裏發生了異常,卻沒有匹配的except子句,異常將被遞交到上層的try,或者到程序的最上層(這樣將結束程序,並打印默認的出錯信息)。
    + 如果在try子句執行時沒有發生異常,python將執行else語句後的語句(如果有else的話),然後控制流通過整個try語句。
def A():
    try:
        print(1)
        a=3//0
    except:
        print(2)
        return
    finally:
        print(3)
    print(4)
    return

def B():   
    try:
        fh = open("testfile", "w")
        fh.write("這是一個測試文件,用於測試異常!!")
    except IOError:
        print ("Error: 沒有找到文件或讀取文件失敗")
    else:
        print ("內容寫入文件成功")
        fh.close()
    finally:
        print("Always to be excuted!")

A()
B()

try:
    age=int(input('Enter your age: '))
except:
    print ('You have entered an invalid value.')
else:
    if age <= 21:
        print('You are not allowed to enter, you are too young.')
    else:
        print('Welcome, you are old enough.')
1
2
3
內容寫入文件成功
Always to be excuted!
Enter your age: 25
Welcome, you are old enough.

Try finally return

原文:
A finally clause is always executed before leaving the try statement, whether an exception has occurred or not. When an exception has occurred in the try clause and has not been handled by an except clause (or it has occurred in a except or else clause), it is re-raised after the finally clause has been executed. The finally clause is also executed “on the way out” when any other clause of the try statement is left via a break, continue or return statement. A more complicated example (having except and finally clauses in the same try statement works as of Python 2.5):
So once the try/except block is left using return, which would set the return value to given - finally blocks will always execute, and should be used to free resources etc. while using there another return - overwrites the original one.
def func1():
    try:
        return 1
    finally:
        return 2

def func2():
    try:
        raise ValueError()
    except:
        return 1
    finally:
        return 3

print(func1())
print(func2())
2
3
  • 上面 except、except X、else是可選項,但是:

    在上面展示的完整語句中try/ except/ else/ finally所出現的順序是try–>except X–>except–>else–>finally。else和finally如果存在的話,else必須在finally之前,finally必須在整個程序的最後。

    else的存在必須以except或except X存在爲前提,如果沒有except而在tryblock中使用esle的話,會出現語法錯誤。

裝飾器

A decorator is the name used for a software design pattern. Decorators dynamically alter the functionality of a function, method, or class without having to directly use subclasses or change the source code of the function being decorated. 
參考:https://wiki.python.org/moin/PythonDecorators
    https://realpython.com/primer-on-python-decorators/
    https://www.cnblogs.com/serpent/p/9445592.html
    https://www.runoob.com/w3cnote/python-func-decorators.html

內置裝飾器

參考: https://docs.python.org/3/library/functions.html
      https://blog.csdn.net/felix_yujing/article/details/79749944
@classmethod 類方法的第一個參數是一個類,是將類本身作爲操作的方法。類方法被哪個類調用,就傳入哪個類作爲第一個參數進行操作。
@property 使調用類中的方法像引用類中的字段屬性一樣。被修飾的特性方法,內部可以實現處理邏輯,但對外提供統一的調用方式。
@staticmethod 將類中的方法裝飾爲靜態方法,即類不需要創建實例的情況下,可以通過類名直接引用。到達將函數功能與實例解綁的效果。
# @property 使調用類中的方法像引用類中的字段屬性一樣。被修飾的特性方法,內部可以實現處理邏輯,但對外提供統一的調用方式。
# coding: utf-8
class TestClass:
    name = "test"

    def __init__(self, name):
        self.name = name

    @property
    def sayHello(self):
        print ("hello", self.name)

cls = TestClass("felix")
print ("通過實例引用屬性")
print (cls.name)
print ("像引用屬性一樣調用@property修飾的方法")
cls.sayHello
通過實例引用屬性
felix
像引用屬性一樣調用@property修飾的方法
hello felix
# @staticmethod 將類中的方法裝飾爲靜態方法,即類不需要創建實例的情況下,可以通過類名直接引用。到達將函數功能與實例解綁的效果。
# coding: utf-8
class TestClass:
    name = "test"

    def __init__(self, name):
        self.name = name

    @staticmethod
    def fun(self, x, y):
        return  x + y

cls = TestClass("felix")
print ("通過實例引用方法")
print (cls.fun(None, 2, 3))  # 參數個數必須與定義中的個數保持一致,否則報錯

print ("類名直接引用靜態方法")
print (TestClass.fun(None, 2, 3)) # 參數個數必須與定義中的個數保持一致,否則報錯
print(TestClass.fun(5,6,9))
通過實例引用方法
5
類名直接引用靜態方法
5
15
# @classmethod 類方法的第一個參數是一個類,是將類本身作爲操作的方法。類方法被哪個類調用,就傳入哪個類作爲第一個參數進行操作。
# coding: utf-8
class Car(object):
    car = "audi"

    @classmethod
    def value(self, category): # 可定義多個參數,但第一個參數爲類本身
        print ("%s car of %s" % (category, self.car))

class BMW(Car):
    car = "BMW"

class Benz(Car):
    car = "Benz"

print ("通過實例調用")
baoma = BMW()
baoma.value("Normal") # 由於第一個參數爲類本身,調用時傳入的參數對應的時category

print ("通過類名直接調用")
Benz.value("SUV")

Car.value("audi")
通過實例調用
Normal car of BMW
通過類名直接調用
SUV car of Benz
audi car of audi

裝飾器基本用法

裝飾器參數傳遞
@裝飾器後有參數時
兩個裝飾器同時修飾一個函數(重點看執行順序)
裝飾器類
#  簡單裝飾器
def log_time(func):  # 此函數的作用時接受被修飾的函數的引用test,然後被內部函數使用
    def make_decorater():
        print('現在開始裝飾')
        func()
        print('現在結束裝飾')
    return make_decorater  # log_time()被調用後,運行此函數返回make_decorater()函數的引用make_decorater
 
@log_time  # 此行代碼等同於,test=log_time(test)=make_decorater
def test():
    print('我是被裝飾的函數')
test()  # test()=make_decorater()
現在開始裝飾
我是被裝飾的函數
現在結束裝飾
# 被裝飾的函數有形參時
def log_time(func):
    def make_decorater(*argvs,**kargvs):  # 接受調用語句的實參,在下面傳遞給被裝飾函數(原函數)
        print('Now decorater')
        tmp = func(argvs[0]) # 如果在這裏return,則下面的代碼無法執行,所以引用並在下面返回
        print('Fininshed decorater') 
        return tmp
    return make_decorater # 因爲被裝飾函數裏有return,所以需要給調用語句(test(2))一個返回

@log_time
def test(num):
    print('I am here .')
    return num + 1

test(9)
Now decorater
I am here .
Fininshed decorater





10
# @裝飾器後有參數時
def get_parameter(*args,**kwargs):  # 工廠函數,用來接受@get_parameter('index.html/')的'index.html/'
    def log_time(func):
        def make_decorater():
            print(args,kwargs)
            print('現在開始裝飾')
            func()
            print('現在結束裝飾')
        return make_decorater
    return log_time
 
@get_parameter('index.html/')
def test():
    print('我是被裝飾的函數')
    # return num+1
 
test()  # test()=make_decorater()
('index.html/',) {}
現在開始裝飾
我是被裝飾的函數
現在結束裝飾
#  兩個裝飾器同時修飾一個函數(重點看執行順序)
def log_time1(func):
    def make_decorater(*args,**kwargs): 
        print('1現在開始裝飾')
        test_func = func(args[0],**kwargs) 
        print('1現在結束裝飾') 
        return test_func 
    return make_decorater
 
def log_time2(func):
    def make_decorater(*args,**kwargs):  # 接受調用語句的實參,在下面傳遞給被裝飾函數(原函數)
        print('2現在開始裝飾')
        test_func = func(*args,**kwargs)  # 如果在這裏return,則下面的代碼無法執行,所以引用並在下面返回
        print('2現在結束裝飾')
        return test_func  # 因爲被裝飾函數裏有return,所以需要給調用語句(test(2))一個返回,又因爲test_func = func(*args,**kwargs)已經調用了被裝飾函數,這裏就不用帶()調用了,區別在於運行順序的不同。
    return make_decorater
 
@log_time1
@log_time2
def test(num):
    print('我是被裝飾的函數')
    return num+1
 
a = test(2)  # test(2)=make_decorater(2)
print(a)
1現在開始裝飾
2現在開始裝飾
我是被裝飾的函數
2現在結束裝飾
1現在結束裝飾
3
# 重寫了我們函數的名字和註釋文檔(docstring)。幸運的是Python提供給我們一個簡單的函數來解決這個問題,那就是functools.wraps。我
# @wraps接受一個函數來進行裝飾,並加入了複製函數名稱、註釋文檔、參數列表等等的功能。這可以讓我們在裝飾器裏面訪問在裝飾之前的函數的屬性。

from functools import wraps
def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated
 
@decorator_name
def func():
    return("Function is running")
 
can_run = True
print(func())
print(func.__name__)
# Output: Function is running
 
can_run = False
print(func())
# Output: Function will not run

帶參數的裝飾器——在函數中嵌入裝飾器

來想想這個問題,難道@wraps不也是個裝飾器嗎?但是,它接收一個參數,就像任何普通的函數能做的那樣。那麼,爲什麼我們不也那樣做呢? 這是因爲,當你使用@my_decorator語法時,你是在應用一個以單個函數作爲參數的一個包裹函數。Python裏每個東西都是一個對象,而且包括函數!我們可以編寫一下能返回一個包裹函數的函數。在函數中嵌入裝飾器

我們回到日誌的例子,並創建一個包裹函數,能讓我們指定一個用於輸出的日誌文件。
from functools import wraps
 
def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打開logfile,並寫入內容
            with open(logfile, 'a') as opened_file:
                # 現在將日誌打到指定的logfile
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator
 
@logit()
def myfunc1():
    pass
 
myfunc1()
# Output: myfunc1 was called
# 現在一個叫做 out.log 的文件出現了,裏面的內容就是上面的字符串
 
@logit(logfile='func2.log')
def myfunc2():
    pass
 
myfunc2()
# Output: myfunc2 was called
# 現在一個叫做 func2.log 的文件出現了,裏面的內容就是上面的字符串

裝飾器類 ,定製__call__方法

現在我們有了能用於正式環境的logit裝飾器,但當我們的應用的某些部分還比較脆弱時,異常也許是需要更緊急關注的事情。比方說有時你只想打日誌到一個文件。而有時你想把引起你注意的問題發送到一個email,同時也保留日誌,留個記錄。這是一個使用繼承的場景,但目前爲止我們只看到過用來構建裝飾器的函數。

幸運的是,類也可以用來構建裝飾器。那我們現在以一個類而不是一個函數的方式,來重新構建logit。
# 裝飾器類,
from functools import wraps
 
class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
 
    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打開logfile並寫入
            with open(self.logfile, 'a') as opened_file:
                # 現在將日誌打到指定的文件
                opened_file.write(log_string + '\n')
            # 現在,發送一個通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function
 
    def notify(self):
        # logit只打日誌,不做別的
        print("I just print the log !")
        pass

#  類繼承
# 我們給 logit 創建子類,來添加 email 的功能(雖然 email 這個話題不會在這裏展開)。
# 

class email_logit(logit):
    '''
    一個logit的實現版本,可以在函數調用時發送email給管理員
    '''
    def __init__(self, email='[email protected]', *args, **kwargs):
        self.email = email
        super(email_logit, self).__init__(*args, **kwargs) # 使用super 調用父類的方法
 
    def notify(self):
        # 發送一封email到self.email
        # 這裏就不做實現了
        print("發送 log 到指定郵箱")
        pass
    
    
# if __name__ == "__main__":
@logit()
def myfunc1():
    print("使用裝飾器類,打印LOG")
    pass

myfunc1()

# @email_logit 將會和 @logit 產生同樣的效果,但是在打日誌的基礎上,還會多發送一封郵件給管理員。
@email_logit()
def myfunc2():
    print("通過類繼承擴展功能")
    
myfunc2()

裝飾器基礎使用場景

授權(Authorization)
日誌(Logging)
#  授權(Authorization)
# 裝飾器能有助於檢查某個人是否被授權去使用一個web應用的端點(endpoint)。它們被大量使用於Flask和Django web框架中:

from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

#  日誌(Logging)
# 日誌是裝飾器運用的另一個亮點。這是個例子:

from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
 
result = addition_func(4)
# Output: addition_func was called

super() 函數

super() 函數是用於調用父類(超類)的一個方法。
super 是用來解決多重繼承問題的,直接用類名調用父類方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查找順序(MRO)、重複調用(鑽石繼承)等種種問題。
MRO 就是類的方法解析順序表, 其實也就是繼承父類方法時的順序表。

語法

super(type[, object-or-type])

參數

type -- 類。
object-or-type -- 類,一般是 self

Python3.x 和 Python2.x 的一個區別是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :

# python3 示例
class A:
     def add(self, x):
         y = x+1
         print(y)
class B(A):
    def add(self, x):
        super().add(x)
b = B()
b.add(2)  # 3
# python2示例
#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class A(object):   # Python2.x 記得繼承 object
    def add(self, x):
         y = x+1
         print(y)
class B(A):
    def add(self, x):
        super(B, self).add(x)
b = B()
b.add(2)  # 3
# 解釋示例
#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class FooParent(object):
    def __init__(self):
        self.parent = 'I\'m the parent.'
        print ('Parent')
    
    def bar(self,message):
        print ("%s from Parent" % message)
 
class FooChild(FooParent):
    def __init__(self):
        # super(FooChild,self) 首先找到 FooChild 的父類(就是類 FooParent),然後把類 FooChild 的對象轉換爲類 FooParent 的對象
        super(FooChild,self).__init__()    
        print ('Child')
        
    def bar(self,message):
        super(FooChild, self).bar(message)
        print ('Child bar fuction')
        print (self.parent)
 
if __name__ == '__main__':
    fooChild = FooChild()
    fooChild.bar('HelloWorld')

sys 參數

from sys import argv
print(f"argv[0]:{argv[0]},argv[1]:{argv[1]},argv[2]:{argv[2]}")
argv[0]:/usr/lib/python3.6/site-packages/ipykernel_launcher.py,argv[1]:-f,argv[2]:/root/.local/share/jupyter/runtime/kernel-721d4cdc-bb76-441a-a047-c98daee2fa77.json

泛化參數組 *args 和 **kwargs

args 是 arguments 的縮寫,表示位置參數;kwargs 是 keyword arguments 的縮寫,表示關鍵字參數。這其實就是 Python 中可變參數的兩種形式,並且 *args 必須放在 **kwargs 的前面,因爲位置參數在關鍵字參數的前面。args負責的是位置參數,並放到了一個tuple中。而kwargs負責的是關鍵字參數,放到了一個dict中,通過key-value的方式存儲。

# *args就是就是傳遞一個可變參數列表給函數實參,這個參數列表的數目未知,甚至長度可以爲0。
def test_args(first, *args):
    print('Required argument: ', first)
    print(type(args))
    for v in args:
        print ('Optional argument: ', v)

test_args(1, 2, 3, 4)

def f(a,*other):
    print (type(other))
    print (other)
    
f(1,2,3)
a=[1,2]
f(3,*a)
Required argument:  1
<class 'tuple'>
Optional argument:  2
Optional argument:  3
Optional argument:  4
<class 'tuple'>
(2, 3)
<class 'tuple'>
(1, 2)
# **kwargs則是將一個可變的關鍵字參數的字典傳給函數實參,同樣參數列表長度可以爲0或爲其他值。
def test_kwargs(first, *args, **kwargs):
   print('Required argument: ', first)
   print(type(kwargs))
   for v in args:
      print ('Optional argument (args): ', v)
   for k, v in kwargs.items():
      print ('Optional argument %s (kwargs): %s' % (k, v))

test_kwargs(1, 2, 3, 4, k1=5, k2=6)
Required argument:  1
<class 'dict'>
Optional argument (args):  2
Optional argument (args):  3
Optional argument (args):  4
Optional argument k1 (kwargs): 5
Optional argument k2 (kwargs): 6
## __slots__
    告訴Python不要使用字典,而且只給一個固定集合的屬性分配空間。
# 在Python中,每個類都有實例屬性。默認情況下Python用一個字典來保存一個對象的實例屬性。這個字典浪費了很多內存。
# Python不能在對象創建時直接分配一個固定量的內存來保存所有的屬性。
# 如果你創建許多對象(我指的是成千上萬個),它會消耗掉很多內存。

# 不實用 __slots__
class MyClass(object):
    def __init__(self, name, identifier):
        self.name = name
        self.identifier = identifier
        self.set_up()
        
# 使用 __slots__        
class MyClass(object):
    __slots__ = ['name', 'identifier']
    def __init__(self, name, identifier):
      self.name = name
      self.identifier = identifier
      self.set_up()
    
# 第二段代碼會爲你的內存減輕負擔。通過這個技巧,有些人已經看到內存佔用率幾乎40%~50%的減少。
# 使用ipython_memory_usage 查看內存佔用
import ipython_memory_usage.ipython_memory_usage as imu
imu.start_watching_memory()
%cat slots.py # 將之上代碼段保存爲獨立.py並查看

全局,局部作用域 global,nonlocal

gcount = 0

# def global_test():
#     gcount+=1
#     print (gcount)
# global_test()

def global_test():
    global gcount
    gcount +=1
    print(gcount)
global_test()

1

特殊的賦值,表單時判斷和輸入輸出

# x,y,z = 0
# (x,y,z) = 0
# x = (y = z = 4)
# x = (y=z) = 3
x,y,z =1,2,3
x,y = y,x 
x = y = z = 2
print(x > y or z)
2
print(any([1,2,9,4,5,0]))
print(any([0,0,False,]))
print(all([0,0,0,None]))
print(all({14,1,2,3,3,4}))
True
False
False
True

Copy 淺拷貝,深拷貝

參考:https://docs.python.org/3.6/library/copy.html
The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):

A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

* Two problems often exist with deep copy operations that don’t exist with shallow copy operations:

Recursive objects (compound objects that, directly or indirectly, contain a reference to themselves) may cause a recursive loop.
Because deep copy copies everything it may copy too much, such as data which is intended to be shared between copies.

* The deepcopy() function avoids these problems by:

keeping a memo dictionary of objects already copied during the current copying pass; and
letting user-defined classes override the copying operation or the set of components copied.

This module does not copy types like module, method, stack trace, stack frame, file, socket, window, array, or any similar types. It does “copy” functions and classes (shallow and deeply), by returning the original object unchanged; this is compatible with the way these are treated by the pickle module.

Shallow copies of dictionaries can be made using dict.copy(), and of lists by assigning a slice of the entire list, for example, copied_list = original_list[:].

Classes can use the same interfaces to control copying that they use to control pickling. See the description of module pickle for information on these methods. In fact, the copy module uses the registered pickle functions from the copyreg module.

In order for a class to define its own copy implementation, it can define special methods __copy__() and __deepcopy__(). The former is called to implement the shallow copy operation; no additional arguments are passed. The latter is called to implement the deep copy operation; it is passed one argument, the memo dictionary. If the __deepcopy__() implementation needs to make a deep copy of a component, it should call the deepcopy() function with the component as first argument and the memo dictionary as second argument.
# 淺拷貝

#     1、對於 不可 變類型 Number String Tuple,淺複製僅僅是地址指向,不會開闢新空間。
#     2、對於 可 變類型 List、Dictionary、Set,淺複製會開闢新的空間地址(僅僅是最頂層開闢了新的空間,裏層的元素地址還是一樣的),進行淺拷貝
#     3、淺拷貝後,改變原始對象中爲可變類型的元素的值,會同時影響拷貝對象的;改變原始對象中爲不可變類型的元素的值,只有原始類型受影響。 (操作拷貝對象對原始對象的也是同理)

import copy

# 不可變類型 Number String Tuple
print("對於不可 變類型 Number String Tuple,淺複製僅僅是地址指向,不會開闢新空間拷貝值")
num1 = 17
num2 = copy.copy(num1)
print("num1:" + str(id(num1)))
print("num2:" + str(id(num1)))
# num1和num2的地址都相同

print("="*20)
print("對於可變類型 List、Dictionary、Set,淺複製會開闢新的空間地址(僅僅是最頂層開闢了新的空間),進行淺拷貝")

list1 = [11,12]
list2 = copy.copy(list1)
list1.append(33)
print("list2:" + str(id(list1)),list1)
print("list2:" + str(id(list2)),list2)
# list1和list2的地址不相同

set1 = [{"AA","BB"},1,2,3]
set2 = copy.copy(set1)
set1.append()
print("set1:" + str(id(set1)))
print("set2:" + str(id(set2)))
# set1和set2的地址不相同
對於不可 變類型 Number String Tuple,淺複製僅僅是地址指向,不會開闢新空間拷貝值
num1:139789555205824
num2:139789555205824
====================
對於可變類型 List、Dictionary、Set,淺複製會開闢新的空間地址(僅僅是最頂層開闢了新的空間),進行淺拷貝
list2:139789186523848 [11, 12, 33]
list2:139789186184136 [11, 12]
set1:139789195853384
set2:139789195852936
# 深拷貝

#     1、淺拷貝,除了頂層拷貝,還對子元素也進行了拷貝(本質上遞歸淺拷貝)
#     2、經過深拷貝後,原始對象和拷貝對象所有的子元素地址都是獨立的了
#     3、可以用分片表達式進行深拷貝
#     4、字典的copy方法可以拷貝一個字典

help()和dir()函數

# Help()函數是一個內置函數,用於查看函數或模塊用途的詳細說明:
import copy
help(copy.copy)
Help on function copy in module copy:

copy(x)
    Shallow copy operation on arbitrary Python objects.
    
    See the module's __doc__ string for more info.
# Dir()函數也是Python內置函數,dir() 函數不帶參數時,返回當前範圍內的變量、方法和定義的類型列表;帶參數時,返回參數的屬性、方法列表。
# import copy
dir(copy.copy)

__new__; __init__

# __init__方法負責對象的初始化,系統執行該方法前,其實該對象已經存在了
# 只能返回 None 值,否則報錯
class A:
    def __init__(self):
        print("__init__ ")
        super(A, self).__init__()

    def __new__(cls):
        print("__new__ ")
        return super(A, cls).__new__(cls)

    def __call__(self):  # 可以定義任意參數
        print('__call__ ')

A()
# 一般我們不會去重寫該方法,除非你確切知道怎麼做,
# 它作爲構造函數用於創建對象,是一個工廠函數,專用於生產實例對象。
# 著名的設計模式之一,單例模式,就可以通過此方法來實現。
class BaseController(object):
    _singleton = None
    def __new__(cls, *a, **k):
        if not cls._singleton:
            cls._singleton = object.__new__(cls, *a, **k)
        return cls._singleton
# 段代碼出自 https://github.com/bottlepy/bottle/blob/release-0.6/bottle.py
# 這就是通過 __new__ 方法是實現單例模式的的一種方式,如果實例對象存在了就直接返回該實例即可,如果還沒有,那麼就先創建一個實例,再返回。

在動態檢查對象是否包含某些屬性(包括方法〉相關的函數

1. hasattr(obj, name):檢查 obj 對象是否包含名爲 name 的屬性或方法。
2. getattr(object, name[, default]):獲取 object 對象中名爲 name 的屬性的屬性值。
3. setattr(obj, name, value,/):將obj 對象的 name 屬性設爲 value。
class Comment:
    def __init__ (self, detail, view_times):
        self.detail = detail
        self.view_times = view_times
    def info ():
        print("一條簡單的評論,內容是%s" % self.detail)
       
c = Comment('瘋狂Python講義很不錯', 20)
# 判斷是否包含指定的屬性或方法
print(hasattr(c, 'detail')) # True
print(hasattr(c, 'view_times')) # True
print(hasattr(c, 'info')) # True
# 獲取指定屬性的屬性值
print(getattr(c, 'detail')) # '瘋狂Python講義很不錯'
print(getattr(c, 'view_times')) # 20
# 由於info是方法,故下面代碼會提示:name 'info' is not defined
#print(getattr(c, info, '默認值'))
# 爲指定屬性設置屬性值
setattr(c, 'detail', '天氣不錯')
setattr(c, 'view_times', 32)
# 輸出重新設置後的屬性值
print(c.detail)
print(c.view_times)

True
True
True
瘋狂Python講義很不錯
20
天氣不錯
32

負索引

從右側開始索引
# 從右邊開始檢索,能用於列表中的切片
mylist[-3]
mylist[-6:-1]

如何以就地操作方式打亂一個列表的元素

mylist=[0,1,2,3,4,5,6,7,8]
from random import shuffle
shuffle(mylist)
mylist
[2, 4, 7, 8, 6, 0, 3, 1, 5]

Closure (閉包)

當一個嵌套函數在其外部區域引用了一個值時,該嵌套函數就是一個閉包。其意義就是會記錄這個值。
參考:https://zhuanlan.zhihu.com/p/57874441
它是一種高階函數,並且外層函數(例子中的add_num)將其內部定義的函數(add)作爲返回值返回,同時由於返回的內層函數擴展了外層函數的環境(environment),也就是對其產生了一個引用,那麼在調用返回的內部函數(add5)的時候,能夠引用到其(add)定義時的外部環境(在例子中,即 a 的值)。
def add_num(a):
    def add(b): # 嵌套定義
        return a + b 
    return add # 作爲函數的返回值

addNum = add_num(5)
addNum(4)
9
>>> def A(x):
    def B(y):
        print(y)
    return B
# >>> A(7)()
>>> A(7)
>>> A(4)(5)
5
# for循環中的lambda與閉包
# https://www.jianshu.com/p/84f3e0f4d218
def funx():
    return [lambda x : i*x for i in range(0,4)]
[0,1,2,3]
# ss = [fun(2) for fun in funx()]
for fun in funx():
    print(fun)
    print(fun(2)) 

有多少種運算符?解釋一下算數運算符。

在Python中,我們有7種運算符:算術運算符、關係運算符、賦值運算符、邏輯運算符、位運算符、成員運算符、身份運算符。

有7個算術運算符,能讓我們對數值進行算術運算: +,-,*,/,%,//,/;

邏輯運算符: and,or,not

身份運算符: is’和‘is not’,我們可以確認兩個值是否相同。

位運算符: 與(&),按位與運算符:參與運算的兩個值,如果兩個相應位都爲1,則該位的結果爲1,否則爲0
        或(|),按位或運算符:只要對應的二個二進位有一個爲1時,結果位就爲1。
        異或(^),按位異或運算符:當兩對應的二進位相異時,結果爲1
        取反(~),按位取反運算符:對數據的每個二進制位取反,即把1變爲0,把0變爲1
        左位移(<<),運算數的各二進位全部左移若干位,由 << 右邊的數字指定了移動的位數,高位丟棄,低位補0
        右位移(>>),把">>"左邊的運算數的各二進位全部右移若干位,>> 右邊的數字指定了移動的位數

//,%和 ** 運算符

//運算符執行地板除法(向下取整除),它會返回整除結果的整數部分。
** 執行取冪運算。a**b會返回a的b次方。
% 執行取模運算,返回除法的餘數。

集合(set)運算

子集⊆、真子集⊂: set的運算<對應於真子集⊂,<=對應於子集⊆,對應set類型的內置函數issubset()
超集/包含關係⊇、 ⊃:set的運算>對應於真包含⊃,>=對應於包含⊇,對應的內置函數是issuperset()
不相交集: 一個集合中的任何一個元素都不屬於另一個集合,可以說這兩個集合是不相交集(Disjoint sets),也就是說,交集爲空 。判斷函數是isdisjoint()
兩集合的交集: set 的交集 的運算符號是&,採用這個符號是顯然的,因爲交集運算與位與(bit-wise AND)運算相似。對應的內置函數是intersection()
兩集合的並集: set 的並集的運算符號是|,採用這個符號也是顯然的,因爲並集運算與位或(bit-wise OR)運算相似。對應的內置函數是union()
差集(減法)運算: set的差集運算,也就是從一個集合裏減去另一個集合的所有元素,很直接的用減號表示,內置函數是difference()
對稱差集(異或)運算: 數學上,兩個集合的對稱差(Symmetric difference)是隻屬於其中一個集合,而不被兩個集合同時包含。 例如:集合{1,2,3}和{3,4}的對稱差爲{1,2,4}。集合論中的這個運算相當於布爾邏輯中的異或運算。所以在Python裏使用了異或的符號(^)表示,內置函數爲symmetric_difference()

集合內置函數的幾個特點

集合內置函數裏,有三個判斷函數(is開頭的函數)和四個運算函數(intersection, union, difference和symmetric_difference),表示運算的函數有下面幾個特點:
1. 可以傳遞多個參數,表示連續運算
2. 可以傳遞除集合外的其他可遞歸類型(iterable) 
>>> A = {1, 2, 3}
>>> B = {1, 2, 3, 4, 5}
>>> A <= B  # 判斷是否子集
>>> A < B  # 判斷是否真子集
>>> A <= A
>>> B < A
>>> A.issubset(B)  # set類型的內置函數issubset()同樣可以判斷是否子集
True
>>> A = {1, 2, 3}
>>> B = {1, 2, 3, 4, 5}
>>> B >= A
>>> B > A
>>> A >= A
>>> A > B # A 包含 B ?
>>> A.issuperset(B) # A 包含 B ?
>>> B.issuperset(A) 

True
>>> A = {1, 2, 3}
>>> B = {1, 2, 3, 4, 5}
>>> C = {9,8,7}
>>> A.isdisjoint(B) # 判斷是否不相交
False
>>> B.isdisjoint(C)
True
>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}
>>> A & B  # 獲得交集
set([4, 5])
>>> A.intersection(B)
{4, 5}
>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}
>>> A | B # 直接或
set([1, 2, 3, 4, 5, 6, 7, 8])
>>> A.union(B)
{1, 2, 3, 4, 5, 6, 7, 8}
>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}
>>> A - B  # 差集,直接做減法
set([1, 2, 3])
>>> A.difference(B)

{1, 2, 3}
>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}
>>> A ^ B
set([1, 2, 3, 6, 7, 8])
>>> A.symmetric_difference(B) # 兩個集合的對稱差

{1, 2, 3, 6, 7, 8}
>>> A = {1, 2, 3, 4, 5}
>>> B = {4, 5, 6, 7, 8}
>>> C = {4, 5, 9, 0}
>>> A.intersection(B, C)    #連續交集運算
set([4, 5])
>>> A & B & C    #連續交集運算
set([4, 5])
>>> 
>>> A = [1, 2, 3, 4, 5]
>>> B = [4, 5, 6, 7, 8]
>>> set(A).union(B)    #和list作並集
set([1, 2, 3, 4, 5, 6, 7, 8])
>>> set('abc').symmetric_difference('cdef')    #字符串也是sequence的一種

{'a', 'b', 'd', 'e', 'f'}
print( 5//2)
print(5**2)
print(5%2)
2
25
1

元組的解封裝

>>> mytuple=3,4,5
>>> mytuple
(3, 4, 5)
# 現在我們將這些值解封裝到變量 x,y,z 中:
>>> x,y,z=mytuple
>>> x+y+z


12
# 三元運算
y = 1
x = 'a' if y >1 else 2
print(x)
2
# 匿名函數: lambda
x = [lambda x: x +2  for x in [1,2,3,4]]
print(x)
[<function <listcomp>.<lambda> at 0x7f9fd0605e18>, <function <listcomp>.<lambda> at 0x7f9fd3684268>, <function <listcomp>.<lambda> at 0x7f9fd05a89d8>, <function <listcomp>.<lambda> at 0x7f9fd05a8730>]

管理內存

Python 中垃圾回收機制: 引用計數(主要), 標記清除, 分代收集(輔助)

Python中,主要依靠gc(garbage collector)模塊的引用計數技術來進行垃圾回收。所謂引用計數,就是考慮到Python中變量的本質不是內存中一塊存儲數據的區域,而是對一塊內存數據區域的引用。所以python可以給所有的對象(內存中的區域)維護一個引用計數的屬性,在一個引用被創建或複製的時候,讓python,把相關對象的引用計數+1;相反當引用被銷燬的時候就把相關對象的引用計數-1。當對象的引用計數減到0時,自然就可以認爲整個python中不會再有變量引用這個對象,所以就可以把這個對象所佔據的內存空間釋放出來了。

引用計數技術在每次引用創建和銷燬時都要多做一些操作,這可能是一個小缺點,當創建和銷燬很頻繁的時候難免帶來一些效率上的不足。但是其最大的好處就是實時性,其他語言當中,垃圾回收可能只能在一些固定的時間點上進行,比如當內存分配失敗的時候進行垃圾回收,而引用計數技術可以動態地進行內存的管理。

一、變量與對象

1、變量,通過變量指針引用對象

變量指針指向具體對象的內存空間,取對象的值。

2、對象,類型已知,每個對象都包含一個頭部信息(頭部信息:類型標識符和引用計數器)

注意:變量名沒有類型,類型屬於對象(因爲變量引用對象,所以類型隨對象),變量引用什麼類型的對象,變量就是什麼類型的。

3、引用所指判斷

通過is進行引用所指判斷,is是用來判斷兩個引用所指的對象是否相同。

    1、Python緩存了整數和短字符串,因此每個對象在內存中只存有一份,引用所指對象就是相同的,即使使用賦值語句,也只是創造新的引用,而不是對象本身;
    2、Python沒有緩存長字符串、列表及其他對象,可以由多個相同的對象,可以使用賦值語句創建出新的對象。

引用計數法有很明顯的優點:
+ 高效
+ 運行期沒有停頓 可以類比一下Ruby的垃圾回收機制,也就是 實時性:一旦沒有引用,內存就直接釋放了。不用像其他機制等到特定時機。實時性還帶來一個好處:處理回收內存的時間分攤到了平時。
+ 對象有確定的生命週期
+ 易於實現

原始的引用計數法也有明顯的缺點:

+ 維護引用計數消耗資源,維護引用計數的次數和引用賦值成正比,而不像mark and sweep等基本與回收的內存數量有關。
無法解決循環引用的問題。A和B相互引用而再沒有外部引用A與B中的任何一個,它們的引用計數都爲1,但顯然應該被回收。
  • 循環引用問題:

    當Python中的對象越來越多,佔據越來越大的內存,啓動垃圾回收(garbage collection),將沒用的對象清除。
    爲了解決這兩個致命弱點,Python又引入了以下兩種GC機制。

標記-清除的回收機制

針對循環引用這個問題,比如有兩個對象互相引用了對方,當外界沒有對他們有任何引用,也就是說他們各自的引用計數都只有1的時候,如果可以識別出這個循環引用,把它們屬於循環的計數減掉的話,就可以看到他們的真實引用計數了。基於這樣一種考慮,有一種方法,比如從對象A出發,沿着引用尋找到對象B,把對象B的引用計數減去1;然後沿着B對A的引用回到A,把A的引用計數減1,這樣就可以把這層循環引用關係給去掉了。

不過這麼做還有一個考慮不周的地方。假如A對B的引用是單向的, 在到達B之前我不知道B是否也引用了A,這樣子先給B減1的話就會使得B稱爲不可達的對象了。爲了解決這個問題,python中常常把內存塊一分爲二,將一部分用於保存真的引用計數,另一部分拿來做爲一個引用計數的副本,在這個副本上做一些實驗。比如在副本中維護兩張鏈表,一張裏面放不可被回收的對象合集,另一張裏面放被標記爲可以被回收(計數經過上面所說的操作減爲0)的對象,然後再到後者中找一些被前者表中一些對象直接或間接單向引用的對象,把這些移動到前面的表裏面。這樣就可以讓不應該被回收的對象不會被回收,應該被回收的對象都被回收了。

分代回收

分代回收策略着眼於提升垃圾回收的效率。研究表明,任何語言,任何環境的編程中,對於變量在內存中的創建/銷燬,總有頻繁和不那麼頻繁的。比如任何程序中總有生命週期是全局的、部分的變量。
Python將所有的對象分爲0,1,2三代;
所有的新建對象都是0代對象;
當某一代對象經歷過垃圾回收,依然存活,就被歸入下一代對象。

gc模塊提供一個接口給開發者設置垃圾回收的選項。上面說到,採用引用計數的方法管理內存的一個缺陷是循環引用,而gc模塊的一個主要功能就是解決循環引用的問題。

  • 參考:Python內存管理和垃圾回收:https://zhuanlan.zhihu.com/p/55601173
    Python 內存管理: https://wxnacy.com/2019/06/16/python-memory-management/

call, import

# 關於 __call__ 方法,不得不先提到一個概念,就是可調用對象(callable),我們平時自定義的函數、內置函數和類都屬於可調用對象,
# 但凡是可以把一對括號()應用到某個對象身上都可稱之爲可調用對象,判斷對象是否爲可調用對象可以用函數 callable
# 如果在類中實現了 __call__ 方法,那麼實例對象也將成爲一個可調用對象,我們回到最開始的那個例子:
class A:
    def __init__(self):
        print("__init__ ")
        super(A, self).__init__()

    def __new__(cls):
        print("__new__ ")
        return super(A, cls).__new__(cls)

    def __call__(self):  # 可以定義任意參數
        print('__call__ ')
        
a = A()
print(callable(a))  # True

# a是實例對象,同時還是可調用對象,那麼我就可以像函數一樣調用它。試試:
print("a是實例對象,同時還是可調用對象,那麼我就可以像函數一樣調用它。")
a()
__new__ 
__init__ 
True
a是實例對象,同時還是可調用對象,那麼我就可以像函數一樣調用它。
__call__ 
# 實例對象也可以像函數一樣作爲可調用對象來用,那麼,這個特點在什麼場景用得上呢?這個要結合類的特性來說,
# 類可以記錄數據(屬性),而函數不行(閉包某種意義上也可行),利用這種特性可以實現基於類的裝飾器,在類裏面記錄狀態,比如,下面這個例子用於記錄函數被調用的次數:
class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

@Counter
def foo():
    pass

for i in range(10):
    foo()

print(foo.count)  # 10
10
# __import__
#   1. 函數功能用於動態的導入模塊,主要用於反射或者延遲加載模塊。
#   2. __import__(module)相當於import module
#mian.py
print ('main')

index = __import__('index')
dir(index)
index.sayHello()
index.sayHelloZhCn()

# 執行main.py,可以證實動態加載了index.py,__import__返回的模塊也是index模塊
#index.py

print ('index')

def sayHello():
    print('hello index')

def sayHelloZhCn():
    print('你好 index')

對象變動 Mutation

Python中可變(mutable)與不可變(immutable)的數據類型讓新手很是頭痛。簡單的說,可變(mutable)意味着"可以被改動",而不可變(immutable)的意思是“常量(constant)”。
foo = ['hi']
print(foo)
# Output: ['hi']

bar = foo
bar += ['bye']

print(foo)
# Output: ['hi']

print(bar)
# Output: ['hi', 'bye']

# 這不是一個bug。這是對象可變性(mutability)在作怪。
# 每當你將一個變量賦值爲另一個可變類型的變量時,對這個數據的任意改動會同時反映到這兩個變量上去。
# 新變量只不過是老變量的一個別名而已。這個情況只是針對可變數據類型。
['hi']
['hi', 'bye']
['hi', 'bye']
# 賦值與地址引用
def fun(x=0,y=[]):
    y.append(x)
    return y

print(fun())
print(fun(1,[1]))
print(fun(2))
[0]
[1, 1]
[0, 2]

猴子補丁 (Monkey Patch)

屬性在運行時的動態替換,叫做猴子補丁(Monkey Patch)。

其實這根本的原因在於Python語法的靈活性,方法可以像普通對象那樣使用。

  • 名稱由來:

    1. 這個詞原來爲Guerrilla Patch,雜牌軍、游擊隊,說明這部分不是原裝的,在英文裏guerilla發音和gorllia(猩猩)相似,再後來就寫了monkey(猴子)。
    2. 還有一種解釋是說由於這種方式將原來的代碼弄亂了(messing with it),在英文裏叫monkeying about(頑皮的),所以叫做Monkey Patch。
參考:[StackOverflow](https://stackoverflow.com/questions/5626193/what-is-monkey-patching)

No, it's not like any of those things. It's simply the dynamic replacement of attributes at runtime.

For instance, consider a class that has a method get_data. This method does an external lookup (on a database or web API, for example), and various other methods in the class call it. However, in a unit test, you don't want to depend on the external data source - so you dynamically replace the get_data method with a stub that returns some fixed data.

Because Python classes are mutable, and methods are just attributes of the class, you can do this as much as you like - and, in fact, you can even replace classes and functions in a module in exactly the same way.

But, as a commenter pointed out, use caution when monkeypatching:

    If anything else besides your test logic calls get_data as well, it will also call your monkey-patched replacement rather than the original -- which can be good or bad. Just beware.

    If some variable or attribute exists that also points to the get_data function by the time you replace it, this alias will not change its meaning and will continue to point to the original get_data. (Why? Python just rebinds the name get_data in your class to some other function object; other name bindings are not impacted at all.)


class A:
    def func(self):
        print("Hi")
    def monkey(self):
        print("Hi, monkey")

a = A()
a.func()

class A:
    def func(self):
        print("Hi")
    def monkey(self):
        print("Hi, monkey")
a = A()
A.func=A.monkey   #在運行的時候,才改變了func
a.func()

Hi
Hi, monkey

monkey patch的應用場景

這裏有一個比較實用的例子,很多代碼用到 import json,後來發現ujson性能更高,如果覺得把每個文件的import json 改成 import ujson as json成本較高,或者說想測試一下用ujson替換json是否符合預期:(也可以考慮類繼承)

import json  
import ujson  

def monkey_patch_json():  
    json.__name__ = 'ujson'  
    json.dumps = ujson.dumps  
    json.loads = ujson.loads  

monkey_patch_json()

lambda表達式

lambda表達式是一行函數。
它們在其他語言中也被稱爲匿名函數。如果你不想在程序中對一個函數使用兩次,你也許會想用lambda表達式,它們和普通的函數完全一樣。

原型
lambda 參數:操作數(參數)

add = lambda x,y: y+x
print(add(2,3))

# 排序
a = [(1, 2), (4, 1), (9, 10), (13, -3)]
a.sort(key=lambda x: x[1])

print(a)
# Output: [(13, -3), (4, 1), (1, 2), (9, 10)]


# 列表並行排序
list1 = [1,2,3,4,7,8,23]
list2 = [34,2,67,7,5,9,87]
data = zip(list1, list2)
data = sorted(data)
list1, list2 = map(lambda t: list(t), zip(*data))
print(list1,list2)

簡單問題示例

# 農場裏有雞和兔子,總共有 35個腦袋和 94條腿,計算一下兔子和雞分別有多少隻?
for i in range(1,35+1):
    if 2*i + 4*(35-i) == 94:
        print(f"雞:{i} 兔:{35-i}")
雞:23 兔:12
# 合併兩個有序列表
x = [6,56,67,89,90]
y = [1,2,3,45,90,120]
def merge_order_list(l1,l2):
    if l1[-1] <= l2[0]:
        rst = l1 + l2
        return rst
    elif l2[-1] <= l1[0]:
        rst = l2 + l1 
        return rst
    else:
        pass

    maxList, littleList  = [], []
    
    if len(l1) >= len(l2):
        maxList  = l1 
        littleList = l2
    else:
        maxList  = l1 
        littleList = l2   
    for i in range(0, len(maxList)):
        for j in range(0,len(littleList)-1):
            if littleList[j] <= maxList[i]:
                maxList = maxList[0:i] + [littleList[j]] + maxList[i:]
                littleList.remove(littleList[j])
    if littleList:
        maxList.extend(littleList)
    return maxList
print(merge_order_list(x,y))
    
[1, 2, 3, 6, 45, 56, 67, 89, 90, 90, 120]
# 檢查給定數字n是否爲2或0的冪
# 0 的任何次冪都爲0;其實0沒有次冪的問題
import math
def check_log(n):
    if n == 0 :
        return (True, f"{n} is 0 `s powers" )
    if n < 0 :
        return None
    power = math.log(n,2)
    if power == 0:
        return (True, f"{n} is 2 `s powers  0" )
    elif power > 1:
        return (True, f"{n} is 2 `s power {power}")
                
print(check_log(1))
        
(True, '1 is 2 `s powers  0')
# 枚舉 (enumerate)
my_list = ['apple', 'banana', 'grapes', 'pear']
for index, value in enumerate(my_list,1): # index從 1 開始
    print(index, value)
1 apple
2 banana
3 grapes
4 pear
# 可以直接引入enmu 模塊
from enum import Enum
# 定義Season枚舉類
Season = Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))

# 定義枚舉類
# 定義枚舉時,成員名不允許重複
# 成員值允許相同,第二個成員的名稱被視作第一個成員的別名 
class Color(Enum):
    red = 1
    green = 2
    blue = 3
    
# 枚舉成員有值(默認可重複),枚舉成員具有友好的字符串表示:  

print(Color.red)              # Color.red
print(Color.blue)             # Color.red
print(Color.red is Color.blue)# True
print(Color(1))               # Color.red  在通過值獲取枚舉成員時,只能獲取到第一個成員
# 若要不能定義相同的成員值,可以通過 unique 裝飾
from enum import Enum, unique
@unique
class Color(Enum):
    red   = 1
    green = 2
    blue  = 1  # ValueError: duplicate values found in <enum 'Color'>: blue -> red

"""枚舉取值
# 可以通過成員名來獲取成員也可以通過成員值來獲取成員: """
print(Color['red'])  # Color.red  通過成員名來獲取成員
print(Color(1))      # Color.red  通過成員值來獲取成員

"""每個成員都有名稱屬性和值屬性"""
member = Color.red
print(member.name)   # red
print(member.value)  # 1

# 支持迭代的方式遍歷成員,按定義的順序,如果有值重複的成員,只獲取重複的第一個成員:
for color in Color:
    print(color)

# 特殊屬性 __members__ 是一個將名稱映射到成員的有序字典,也可以通過它來完成遍歷:
for color in Color.__members__.items():
    print(color)          # ('red', <Color.red: 1>)
    
"""枚舉比較
枚舉的成員可以通過 is 同一性比較或通過 == 等值比較:"""

Color.red is Color.red
Color.red is not Color.blue

Color.blue == Color.red
Color.blue != Color.red

# 枚舉成員不能進行大小比較:
Color.red < Color.blue # TypeError: unorderable types: Color() < Color()

"""擴展枚舉 IntEnum
# IntEnum 是 Enum 的擴展,不同類型的整數枚舉也可以相互比較:"""

from enum import IntEnum
class Shape(IntEnum):
    circle = 1
    square = 2

class Request(IntEnum):
    post = 1
    get = 2

print(Shape.circle == 1)            # True
print(Shape.circle < 3)             # True
print(Shape.circle == Request.post) # True
print(Shape.circle >= Request.post) # True

# 遞歸 斐波那契
# 走樓梯問題:
# 樓梯有n階臺階,上樓可以一步上1階,2階,編程序計算共有多少種不同的走法?
def up_stairs(n):
    if n <= 1:
        return 1
    elif n == 2:
        return 2
    else:
        return up_stairs(n-1) + up_stairs(n-2)
for n in range(2,10):
    print(up_stairs(n), end=" ")
    
# # 使用生成器 yield
def fab(max):
    if max <= 2:
        return 1
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1

for ni in fab(20):
    print (ni, end=" ")
# 尾遞歸 階乘

def fabs(n, a):
    if n <= 1 :
        return a 
    else:
        return n*fabs(n-1,a)
    
print(fabs(4,1))

# 尾遞歸 斐波那契 數列
def fibs(n,a,b):
    if n <= 2:
        return b
    else:
        return fibs(n-1,b, a+b)
for i in range(1,21):    
    print(fibs(i,1,1),end=" ")
# 快速排序
def quick_sort(listA):
    length = len(listA)
    if length <= 1:
        return listA
    else:
        pivot = listA[0]
        # import random
        # pivot = random.choice(listA)
        greater = [element for element in listA[1:] if element > pivot]
        lesser = [element for element in listA[1:] if element <= pivot]
        return quick_sort(lesser) + [pivot] + quick_sort(greater)

if __name__ == "__main__":
    listA = [0, 5, 3, 2, 2]
    sortedList = quick_sort(listA)
    print(sortedList)
# 歸併排序
def merge_sort_fast(listA):
    start = []
    end = []
    while len(listA) > 1:
        a = min(listA)
        b = max(listA)
        start.append(a)
        end.append(b)
        listA.remove(a)
        listA.remove(b)
    if listA:
        start.append(listA[0])
    end.reverse()
    return start + end

if __name__ == "__main__":
    listA = [0, 5, 3, 2, 2]
    sortedList = merge_sort_fast(listA)
    print(sortedList)
[0, 2, 2, 3, 5]
# 二分查找,基於有序列表
""" 二分查找 """

def binary_search(listA, target):
    left = 0
    right = len(listA) - 1 
    while left <= right:
        midpoint = (left + right) // 2
        current_item = listA[midpoint]
        # if listA[left] == target:
        #     return left
        # elif listA[right] == target:
        #     return right
        if current_item == target:
            return midpoint
        else:
            if target < current_item:
                right = midpoint - 1
            else:
                left = midpoint + 1
    return None


if __name__ == "__main__":
    listA = [1,4,8,9,12,45,78]
    target = 12
    rst = binary_search(listA, target)
    if rst:
        print("OK: ",rst)
    else:
        print("Not found !")

yield send, next

* 帶有yield關鍵字的函數自動變成生成器
* 生成器被調用時不會立即執行
使用next函數獲取生成器的生成的值

1、對於生成器,當調用函數next(generator)時,將獲得生成器yield後面表達式的值;
2、當生成器已經執行完畢時,再次調用next函數,生成器會拋出StopIteration異常

擴展:
1、當生成器內部執行到return語句時,自動拋出StopIteration異常,return的值將作爲異常的解釋
2、外部可以通過generator.close()函數手動關閉生成器,此後調用next或者send方法將拋出異常

Error:不能將一個非None的值傳給初始的生成器
    在調用帶非空參數的send函數之前,我們應該使用next(generator)或者send(None)使得生成器執行到yield語句並暫停。
import time
def func(n):
    for i in range(0, n):
        print('func: ', i)
        yield i
 
f = func(10)
while True:
    print(next(f))
    time.sleep(1)
# next與send函數 的區別
import time
def func(n):
    for i in range(0, n):
        arg = yield i
        print('func:', arg)
 
f = func(10)
while True:
    print('main:', next(f))
    print('main:', f.send(100))
    time.sleep(1)
# yield from 生成器嵌套
# Python3.3之後引入的新語法:
# 參考:https://www.jianshu.com/p/87da832730f5;http://flupy.org/resources/yield-from.pdf
# 暫無打開雙向通道案例,把最外層的調用方與最內層的子生成器連接起來,這樣二者可以直接發送和產出值,還可以直接傳入異常,而不用在位於中間的協程中添加大量處理異常的樣板代碼。

>>> def chain(*iterables):
...     for i in iterables:
...         yield from i
...
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章