Python面試題總結(9)--高級特性

1. 函數裝飾器有什麼作用?請列舉說明?

答: 裝飾器就是一個函數,它可以在不需要做任何代碼變動的前提下給一個函數增加額外功能,啓動裝飾的效果。 它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、權限校驗等場景。 舉例說明一個日誌功能的裝飾器:

1).首先定義一個log文件

# -*- coding: utf-8 -*-
import os
import time
import logging
import sys
log_dir1=os.path.join(os.path.dirname(os.path.dirname(__file__)),"logs")
today = time.strftime('%Y%m%d', time.localtime(time.time()))
full_path=os.path.join(log_dir1,today)
if not os.path.exists(full_path):
    os.makedirs(full_path)
log_path=os.path.join(full_path,"facebook.log")
def get_logger():
     # 獲取logger實例,如果參數爲空則返回root logger
     logger = logging.getLogger("facebook")
     if not logger.handlers:
            # 指定logger輸出格式
            formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s')
 
            # 文件日誌
            file_handler = logging.FileHandler(log_path,encoding="utf8")
            file_handler.setFormatter(formatter)  # 可以通過setFormatter指定輸出格式
 
            # 控制檯日誌
            console_handler = logging.StreamHandler(sys.stdout)
            console_handler.formatter = formatter  # 也可以直接給formatter賦值
 
            # 爲logger添加的日誌處理器
            logger.addHandler(file_handler)
            logger.addHandler(console_handler)
 
            # 指定日誌的最低輸出級別,默認爲WARN級別
            logger.setLevel(logging.INFO)
     #  添加下面一句,在記錄日誌之後移除句柄
     return  logger

2).然後定義一個裝飾器文件
在這裏引用wraps,一個裝飾器的裝飾器,目的爲了保持引用進來的函數名字不發生變化

#!/usr/bin/env python 
# encoding: utf-8
from functools import wraps
from logger.log import get_logger
import traceback
def decoratore(func):
    @wraps(func)
    def log(*args,**kwargs):
        try:
            print("當前運行方法",func.__name__)
            return func(*args,**kwargs)
        except Exception as e:
            get_logger().error(f"{func.__name__} is error,here are details:{traceback.format_exc()}")
    return log

3)在使用的時候直接在函數上面引用即可

@decorator
def start():
   print("666")

代碼參考來源

2. Python 垃圾回收機制?

答:Python 不像 C++,Java 等語言一樣,他們可以不用事先聲明變量類型而直接對變量進行賦值。對 Python 語言來講,對象的類型和內存都是在運行時確定的。這也是爲什麼我們稱 Python 語言爲動態類型的原因。

主要體現在下面三個方法:

1.引用計數機制 2.標記-清除 3.分代回收
請參考:Python中垃圾回收機制

3. 魔法函數 _call_怎麼使用?

答: call 可以把類實例當做函數調用。 使用示例如下
添加鏈接描述

4. 如何判斷一個對象是函數還是方法?

答:看代碼已經結果就懂了

from types import MethodType, FunctionType

class Bar:
def foo(self):
pass

def foo2():
pass

def run():
print(“foo 是函數”, isinstance(Bar().foo, FunctionType))
print(“foo 是方法”, isinstance(Bar().foo, MethodType))
print(“foo2 是函數”, isinstance(foo2, FunctionType))
print(“foo2 是方法”, isinstance(foo2, MethodType))

if name == ‘main’:
run()
輸出

foo 是函數 False
foo 是方法 True
foo2 是函數 True
foo2 是方法 False

5. @classmethod 和 @staticmethod 用法和區別

答: 相同之處:@staticmethod 和@classmethod 都可以直接類名.方法名()來調用,不用在示例化一個類。 @classmethod 我們要寫一個只在類中運行而不在實例中運行的方法。如果我們想讓方法不在實例中運行,可以這麼做:

def iget_no_of_instance(ins_obj):
return ins_obj.class.no_inst

class Kls(object):
no_inst = 0

def __init__(self):
    Kls.no_inst = Kls.no_inst + 1

ik1 = Kls()
ik2 = Kls()
print(iget_no_of_instance(ik1))
@staticmethod 經常有一些跟類有關係的功能但在運行時又不需要實例和類參與的情況下需要用到靜態方法

IND = ‘ON’

class Kls(object):
def init(self, data):
self.data = data

@staticmethod
def check_ind():
    return (IND == 'ON')

def do_reset(self):
    if self.check_ind():
        print('Reset done for:', self.data)

def set_db(self):
    if self.check_ind():
        self.db = 'New db connection'
    print('DB connection made for: ', self.data)

ik1 = Kls(12)
ik1.do_reset()
ik1.set_db()

6. Python 中的接口如何實現?

答: 接口提取了一羣類共同的函數,可以把接口當做一個函數的集合,然後讓子類去實現接口中的函數。但是在 Python 中根本就沒有一個叫做 interface 的關鍵字,如果非要去模仿接口的概念,可以使用抽象類來實現。抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化。使用 abc 模塊來實現抽象類。

7. Python 中的反射了解麼?

答:Python 的反射機制設定較爲簡單,一共有四個關鍵函數分別是 getattr、hasattr、setattr、delattr。

8. metaclass 作用?以及應用場景?

答: metaclass 即元類,metaclass 是類似創建類的模板,所有的類都是通過他來 create 的(調用new),這使得你可以自由的控制創建類的那個過程,實現你所需要的功能。 我們可以使用元類創建單例模式和實現 ORM 模式。

9. hasattr()、getattr()、setattr() 的用法

答:這三個方法屬於 Python 的反射機制裏面的,hasattr 可以判斷一個對象是否含有某個屬性,getattr 可以充當 get 獲取對象屬性的作用。而 setattr 可以充當 person.name = "liming"的賦值操作。代碼示例如下:

class Person():
def init(self):
self.name = “liming”
self.age = 12

def show(self):
    print(self.name)
    print(self.age)

def set_name(self):
    setattr(Person, "sex", "男")

def get_name(self):
    print(getattr(self, "name"))
    print(getattr(self, "age"))
    print(getattr(self, "sex"))

def run():
if hasattr(Person, “show”):
print(“判斷 Person 類是否含有 show 方法”)

Person().set_name()
Person().get_name()

if name == ‘main’:
run()

10. 請列舉你知道的 Python 的魔法方法及用途。

答:

1 init
類的初始化方法。它獲取任何傳給構造器的參數(比如我們調用 x = SomeClass(10, ‘foo’) , __init__就會接到參數 10 和 ‘foo’ 。 __init__在 Python 的類定義中用的最多。

2 new
__new__是對象實例化時第一個調用的方法,它只取下 cls 參數,並把其他參數傳給 init 。 __new__很少使用,但是也有它適合的場景,尤其是當類繼承自一個像元組或者字符串這樣不經常改變的類型的時候.

3 del
__new__和 __init__是對象的構造器, __del__是對象的銷燬器。它並非實現了語句 del x (因此該語句不等同於 x.del())。而是定義了當對象被垃圾回收時的行爲。 當對象需要在銷燬時做一些處理的時候這個方法很有用,比如 socket 對象、文件對象。但是需要注意的是,當 Python 解釋器退出但對象仍然存活的時候,__del__並不會 執行。 所以養成一個手工清理的好習慣是很重要的,比如及時關閉連接。

11. 如何知道一個 Python 對象的類型?

答:可以通過 type 方法

12. Python 的傳參是傳值還是傳址?

答:Python 中的傳參即不是傳值也不是傳地址,傳的是對象的引用。

13. Python 中的元類 (metaclass) 使用舉例

答:可以使用元類實現一個單例模式,代碼如下

class Singleton(type):
def init(self, *args, **kwargs):
print(“in init”)
self.__instance = None
super(Singleton, self).init(*args, **kwargs)

def __call__(self, *args, **kwargs):
    print("in __call__")
    if self.__instance is None:
        self.__instance = super(Singleton, self).__call__(*args, **kwargs)
    return self.__instance

class Foo(metaclass=Singleton):
pass # 在代碼執行到這裏的時候,元類中的__new__方法和__init__方法其實已經被執行了,而不是在 Foo 實例化的時候執行。且僅會執行一次。

foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)

14. 簡述 any() 和 all() 方法

答:
any()與all()函數的區別:any是任意,而all是全部。

any(x):判斷 x 對象是否爲空對象,如果都爲空、0、false,則返回 false,如果不都爲空、0、false,則返回 true。

all(x):如果 all(x) 參數 x 對象的所有元素不爲 0、’’、False 或者 x 爲空對象,則返回 True,否則返回 False。

15. filter 方法求出列表所有奇數並構造新列表,a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

答:

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b = filter(lambda x: x % 2 == 1, a)
print(b)
print("-------------------------")
print(list(b))

運行結果:

<filter object at 0x000000000267F048>
-------------------------
[1, 3, 5, 7, 9]

其實現在不推薦使用 filter,map 等方法了,一般列表生成式就可以搞定了。

16. 什麼是猴子補丁?

答: 猴子補丁(monkey patching):在運行時動態修改模塊、類或函數,通常是添加功能或修正缺陷。猴子補丁在代碼運行時內存中發揮作用,不會修改源碼,因此只對當前運行的程序實例有效。因爲猴子補丁破壞了封裝,而且容易導致程序與補丁代碼的實現細節緊密耦合,所以被視爲臨時的變通方案,不是集成代碼的推薦方式。大概是下面這樣的一個效果

def post():
print(“this is post”)
print(“想不到吧”)

class Http():
@classmethod
def get(self):
print(“this is get”)

def main():
Http.get=post #動態的修改了 get 原因的功能,

if name == ‘main’:
main()
Http.get()

17. 在 Python 中是如何管理內存的?

答: 垃圾回收:Python 不像 C++,Java 等語言一樣,他們可以不用事先聲明變量類型而直接對變量進行賦值。對 Python 語言來講,對象的類型和內存都是在運行時確定的。這也是爲什麼我們稱 Python 語言爲動態類型的原因(這裏我們把動態類型可以簡單的歸結爲對變量內存地址的分配是在運行時自動判斷變量類型並對變量進行賦值)。

引用計數:Python 採用了類似 Windows 內核對象一樣的方式來對內存進行管理。每一個對象,都維護這一個對指向該對對象的引用的計數。當變量被綁定在一個對象上的時候,該變量的引用計數就是 1,(還有另外一些情況也會導致變量引用計數的增加),系統會自動維護這些標籤,並定時掃描,當某標籤的引用計數變爲 0 的時候,該對就會被回收。

內存池機制 Python 的內存機制以金字塔行,1、2 層主要有操作系統進行操作

第 0 層是 C 中的 malloc,free 等內存分配和釋放函數進行操作

第 1 層和第 2 層是內存池,有 Python 的接口函數 PyMem_Malloc 函數實現,當對象小於 256K 時有該層直接分配內存

第 3 層是最上層,也就是我們對 Python 對象的直接操作

在 C 中如果頻繁的調用 malloc 與 free 時,是會產生性能問題的.再加上頻繁的分配與釋放小塊的內存會產生內存碎片。Python 在這裏主要乾的工作有:

如果請求分配的內存在 1~256 字節之間就使用自己的內存管理系統,否則直接使用 malloc。

這裏還是會調用 malloc 分配內存,但每次會分配一塊大小爲 256k 的大塊內存。

經由內存池登記的內存到最後還是會回收到內存池,並不會調用 C 的 free 釋放掉以便下次使用。對於簡單的 Python 對象,例如數值、字符串,元組(tuple 不允許被更改)採用的是複製的方式(深拷貝?),也就是說當將另一個變量 B 賦值給變量 A 時,雖然 A 和 B 的內存空間仍然相同,但當 A 的值發生變化時,會重新給 A 分配空間,A 和 B 的地址變得不再相同。

18. 當退出 Python 時是否釋放所有內存分配?

答:不是的,循環引用其他對象或引用自全局命名空間的對象的模塊,在 Python 退出時並非完全釋放。

另外,也不會釋放 c 庫保留的內存部分

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章