個人python面試準備的一些題型

TsayDust發表於2024-07-06

Python類方法vs靜態方法

類方法(Class Methods)

類方法使用@classmethod裝飾器定義,它們的第一個引數通常命名為cls,代表類本身。

特點:

  1. 可以訪問和修改類的狀態
  2. 不能訪問例項的狀態
  3. 可以用來定義替代構造器

示例:

class MyClass:
    class_variable = 0

    @classmethod
    def increment_class_variable(cls):
        cls.class_variable += 1

    @classmethod
    def from_string(cls, string_param):
        # 替代構造器
        return cls(int(string_param))

# 使用類方法
MyClass.increment_class_variable()
obj = MyClass.from_string("10")

靜態方法(Static Methods)

靜態方法使用@staticmethod裝飾器定義,它們不接收任何特殊的第一個引數。

特點:

  1. 不能訪問或修改類的狀態
  2. 不能訪問例項的狀態
  3. 主要用於將功能邏輯組織到類中

示例:

class MathOperations:
    @staticmethod
    def add(x, y):
        return x + y

    @staticmethod
    def multiply(x, y):
        return x * y

# 使用靜態方法
result = MathOperations.add(5, 3)

主要區別

  1. 引數:類方法接收類作為隱式第一個引數,靜態方法不接收特殊引數。

  2. 訪問類屬性:類方法可以訪問和修改類屬性,靜態方法不能。

  3. 使用場景

    • 類方法通常用於需要訪問類狀態的操作,如替代構造器。
    • 靜態方法用於與類相關但不需要訪問類狀態的操作。
  4. 繼承行為:子類繼承類方法時,cls引數會指向子類。靜態方法的行為在繼承時不變。

選擇使用哪種方法

  • 如果方法需要訪問類屬性或者修改類狀態,使用類方法。
  • 如果方法不需要訪問類或例項狀態,只是提供一些相關功能,使用靜態方法。
  • 如果方法既不需要訪問類狀態也不需要訪問例項狀態,但從邏輯上屬於類,使用靜態方法。

Python中的深複製與淺複製

在Python中,當我們複製物件時,有兩種主要的方式:深複製(Deep Copy)和淺複製(Shallow Copy)。理解這兩者的區別對於正確處理複雜資料結構非常重要。

淺複製(Shallow Copy)

淺複製建立一個新物件,但是它包含的元素是原始物件中元素的引用。

特點:

  1. 建立一個新物件
  2. 新物件中的元素是原始物件元素的引用
  3. 只複製物件的第一層

實現方式:

  • 使用切片操作 [:]
  • 使用 copy() 方法
  • 使用 copy 模組的 copy() 函式

示例:

import copy

original = [1, [2, 3], 4]
shallow = copy.copy(original)

# 修改淺複製中的巢狀列表
shallow[1][0] = 'X'

print(original)  # 輸出: [1, ['X', 3], 4]
print(shallow)   # 輸出: [1, ['X', 3], 4]

在這個例子中,修改淺複製中的巢狀列表也會影響原始列表。

深複製(Deep Copy)

深複製建立一個新物件,並遞迴地複製原始物件中的所有巢狀物件。

特點:

  1. 建立一個全新的物件
  2. 遞迴地複製所有巢狀的物件
  3. 原始物件和複製物件完全獨立

實現方式:

  • 使用 copy 模組的 deepcopy() 函式

示例:

import copy

original = [1, [2, 3], 4]
deep = copy.deepcopy(original)

# 修改深複製中的巢狀列表
deep[1][0] = 'X'

print(original)  # 輸出: [1, [2, 3], 4]
print(deep)      # 輸出: [1, ['X', 3], 4]

在這個例子中,修改深複製中的巢狀列表不會影響原始列表。

主要區別

  1. 複製深度:淺複製只複製物件的第一層,而深複製遞迴地複製所有層。
  2. 記憶體使用:深複製通常比淺複製使用更多的記憶體,因為它建立了所有巢狀物件的副本。
  3. 效能:深複製通常比淺複製慢,特別是對於大型或複雜的資料結構。
  4. 獨立性:深複製建立的物件與原始物件完全獨立,而淺複製建立的物件與原始物件共享部分資料。

使用場景

  • 使用淺複製:當您只需要複製物件的頂層,而且巢狀物件可以共享時。
  • 使用深複製:當您需要建立一個完全獨立的副本,包括所有巢狀物件時。

注意事項

  1. 對於不可變物件(如元組),淺複製和深複製的行為是相同的。
  2. 迴圈引用可能會導致深複製出現問題,deepcopy() 函式有處理這種情況的機制。
  3. 自定義類可以透過實現 __copy__()__deepcopy__() 方法來控制複製行為。

Python裝飾器詳解

裝飾器是Python中的一種高階功能,允許您修改或增強函式或類的行為,而無需直接修改其原始碼。

基本概念

裝飾器本質上是一個函式,它接受一個函式作為引數,並返回一個新的函式。

基本語法

@decorator_function
def target_function():
    pass

這等同於:

def target_function():
    pass
target_function = decorator_function(target_function)

簡單裝飾器示例

1. 函式裝飾器

def uppercase_decorator(func):
    def wrapper():
        result = func()
        return result.upper()
    return wrapper

@uppercase_decorator
def greet():
    return "hello, world!"

print(greet())  # 輸出:HELLO, WORLD!

2. 帶引數的裝飾器

def repeat_decorator(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat_decorator(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")  # 將列印 3 次 "Hello, Alice!"

裝飾器的高階用法

1. 類作為裝飾器

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0
    
    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__!r}")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

say_hello()
say_hello()

2. 保留原函式的後設資料

使用 functools.wraps 裝飾器來保留被裝飾函式的後設資料:

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """Wrapper function"""
        print('Before call')
        result = func(*args, **kwargs)
        print('After call')
        return result
    return wrapper

@my_decorator
def greet(name):
    """Greet someone"""
    print(f"Hello, {name}!")

print(greet.__name__)  # 輸出:greet
print(greet.__doc__)   # 輸出:Greet someone

裝飾器的常見應用

  1. 日誌記錄
  2. 效能測量
  3. 訪問控制和認證
  4. 快取
  5. 錯誤處理和重試邏輯

注意事項

  1. 裝飾器在函式定義時就會執行,而不是在函式呼叫時。
  2. 多個裝飾器可以堆疊使用,執行順序是從下到上。
  3. 裝飾器可能會影響函式的效能,特別是在頻繁呼叫的情況下。
  4. 使用 functools.wraps 可以保留被裝飾函式的後設資料。

裝飾器的實現原理

裝飾器的實現原理涉及到Python的幾個重要概念:函式是一等公民、閉包、以及Python的語法糖。讓我們逐步分解裝飾器的實現過程:

1. 函式作為一等公民

在Python中,函式是一等公民,這意味著函式可以:

  • 賦值給變數
  • 作為引數傳遞給其他函式
  • 作為其他函式的返回值

這是裝飾器實現的基礎。

2. 閉包

閉包是一個函式,它記住了建立它時的環境。在Python中,內部函式可以訪問外部函式的變數,這就建立了一個閉包。

3. 裝飾器的基本實現

讓我們透過一個簡單的例子來說明裝飾器的實現:

def simple_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def say_hello():
    print("Hello!")

say_hello = simple_decorator(say_hello)

在這個例子中:

  1. simple_decorator 是一個函式,它接受一個函式作為引數。
  2. simple_decorator 內部,我們定義了一個新的函式 wrapper
  3. wrapper 函式在呼叫原始函式前後新增了一些行為。
  4. simple_decorator 返回 wrapper 函式。
  5. 最後,我們用 simple_decorator 返回的新函式替換了原始的 say_hello 函式。

4. 語法糖

Python提供了一個語法糖(@符號)來簡化裝飾器的使用:

@simple_decorator
def say_hello():
    print("Hello!")

這等同於前面的例子,但更加簡潔和易讀。

5. 帶引數的裝飾器

帶引數的裝飾器實際上是一個返回裝飾器的函式:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello(name):
    print(f"Hello, {name}!")

這裡,repeat 函式返回一個裝飾器,該裝飾器然後被應用到 say_hello 函式上。

6. 類裝飾器

類裝飾器利用了Python的 __call__ 方法,使得類的例項可以像函式一樣被呼叫:

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0
    
    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

7. 裝飾器的執行時機

重要的是要理解,裝飾器在函式定義時就會執行,而不是在函式呼叫時。這意味著裝飾器可以在模組匯入時就改變函式的行為。

8. 多個裝飾器

當多個裝飾器應用到一個函式上時,它們的執行順序是從下到上的:

@decorator1
@decorator2
def func():
    pass

這等同於:

func = decorator1(decorator2(func))

透過理解這些原理,我們可以看到裝飾器如何利用Python的函式特性和語法來實現強大而靈活的程式碼修改和增強功能。

Python中變數在記憶體中的儲存方式

Python的記憶體管理是一個複雜的主題,但瞭解它可以幫助我們寫出更高效的程式碼。讓我們逐步探討Python中變數的儲存方式。

1. 變數和物件的關係

在Python中,變數本質上是對物件的引用。當我們建立一個變數時,我們實際上是在記憶體中建立了一個物件,然後將變數名與該物件的記憶體地址關聯起來。

x = 5

在這個例子中,Python在記憶體中建立了一個整數物件5,然後將變數名x與這個物件的地址關聯起來。

2. 物件的記憶體表示

Python中的每個物件至少包含三個部分:

  • 型別識別符號(告訴Python這個物件是什麼型別)
  • 引用計數(用於垃圾回收)

3. 不同型別物件的儲存

小整數

Python對小整數(通常是-5到256)進行了最佳化。這些整數被預先建立並快取,所有對這些值的引用都指向同一個物件。

a = 5
b = 5
print(a is b)  # 輸出:True

大整數

對於大整數,每次賦值都會建立一個新的物件。

a = 1000
b = 1000
print(a is b)  # 輸出:False

字串

Python也對字串進行了最佳化。相同內容的字串通常會指向同一個物件(這被稱為字串駐留)。

a = "hello"
b = "hello"
print(a is b)  # 輸出:True

可變物件(如列表)

可變物件每次建立時都會在記憶體中分配新的空間。

a = [1, 2, 3]
b = [1, 2, 3]
print(a is b)  # 輸出:False

4. 變數賦值

當我們進行變數賦值時,我們實際上是改變變數引用的物件。

x = 5  # x 引用整數物件 5
x = 10  # x 現在引用整數物件 10,而不是修改原來的 5

5. 引用計數和垃圾回收

Python使用引用計數來進行記憶體管理。每個物件都有一個引用計數,表示有多少個變數引用了這個物件。當引用計數降為0時,物件就會被垃圾回收器回收。

x = 5  # 建立整數物件 5,引用計數為 1
y = x  # y 也引用同一個物件,引用計數增加到 2
del x  # 刪除 x,引用計數減少到 1
# y 仍然引用這個物件

6. 記憶體檢視

我們可以使用id()函式來檢視物件的記憶體地址:

x = 5
print(id(x))  # 輸出物件的記憶體地址

7. 可變物件vs不可變物件

  • 不可變物件(如整數、字串、元組):當這些物件的"值"改變時,實際上是建立了一個新物件。
  • 可變物件(如列表、字典):這些物件可以在原地修改,不需要建立新物件。
# 不可變物件
x = 5
print(id(x))
x += 1
print(id(x))  # 地址會改變

# 可變物件
lst = [1, 2, 3]
print(id(lst))
lst.append(4)
print(id(lst))  # 地址不會改變

理解Python的記憶體管理和變數儲存方式可以幫助我們寫出更高效的程式碼,並避免一些常見的陷阱。

相關文章