代码改变世界

python面试题之我见(1)

2017-05-26 10:40  核桃不是桃  阅读(183)  评论(0)    收藏  举报

原面试题链接:很全的python面试题

Python语言特性

1 python的函数参数传递:

两个例子:

1 a = 1
2 def fun(a):
3     a = 2
4 fun(a)
5 print a  # 1
1 a = []
2 def fun(a):
3     a.append(1)
4 fun(a)
5 print a  # [1]

理解这个问题,首先要清楚在python中所有变量都是对象的“引用”。对象分为“可变”(mutable)和“不可变”(immutable)。

因此将一个可变对象传给方法时,该方法将获得对同一个对象的引用。但如果将一个不可变对象传递给方法,那么就无法获得对同一个对象的引用。

这里举一个例子,列表是可变对象:

 1 def try_to_change_list_contents(the_list):
 2     print('got', the_list)
 3     the_list.append('four')
 4     print('changed to', the_list)
 5 
 6 outer_list = ['one', 'two', 'three']
 7 
 8 print('before, outer_list =', outer_list)
 9 try_to_change_list_contents(outer_list)
10 print('after, outer_list =', outer_list)

这时得到的输出是:

1 before, outer_list = ['one', 'two', 'three']
2 got ['one', 'two', 'three']
3 changed to ['one', 'two', 'three', 'four']
4 after, outer_list = ['one', 'two', 'three', 'four']

可见传入的参数是对其的引用outer_list,而不是它的副本。

假如我们尝试改变作为参数传递的引用时会发生什么?

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

输出:

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

由于the_list参数是通过☞传递的,所以给它分配一个新的列表并不影响方法外的代码。

字符串是不可变对象:

我们尝试改变参数:

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

输出:

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

可以看出,这种通过值传递参数的方式,和上文中的列表类似,都是没有办法改变原变量所指向的引用,而是在方法内部创建了一个新的引用。

那么如何解决这个问题?

第一种方式是使用返回值:

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

如果想避免使用返回值,另一种方式是,创建一个类来保存值,并将其传递到函数中或者使用现有的类。比如一个列表:

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

但这种方式似乎有点儿麻烦。

2 python中的元类metaclass

简而言之,就是类也是对象,元类就是“类的类”

比如代码片:

 class ObjectCreator(object):
         pass    

将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,这就是为什么它是一个类的原因。但其本质上依然是一个对象,因此可以对其进行如下操作:

  • 将其赋值给一个变量
  • 拷贝
  • 增加属性
  • 可以将其作为函数参数进行传递
    >>> print ObjectCreator     # 你可以打印一个类,因为它其实也是一个对象
    <class '__main__.ObjectCreator'>
    >>> def echo(o):
    …       print o
    …
    >>> echo(ObjectCreator)                 # 你可以将类做为参数传给函数
    <class '__main__.ObjectCreator'>
    >>> print hasattr(ObjectCreator, 'new_attribute')
    Fasle
    >>> ObjectCreator.new_attribute = 'foo' #  你可以为类增加属性
    >>> print hasattr(ObjectCreator, 'new_attribute')
    True
    >>> print ObjectCreator.new_attribute
    foo
    >>> ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量
    >>> print ObjectCreatorMirror()
    <__main__.ObjectCreator object at 0x8997b4c>

深刻理解python中的元类

3 @staticmethod和@classmethod

python中包含三种方法,分别是静态方法(staticmethod)类方法(classmethod)和实例方法,如下:

def foo(x):
    print "executing foo(%s)"%(x)
 
class A(object):
    def foo(self,x):
        print "executing foo(%s,%s)"%(self,x)
 
    @classmethod
    def class_foo(cls,x):
        print "executing class_foo(%s,%s)"%(cls,x)
 
    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)"%x
 
a=A()

实例方法和类方法类似,实例方法传入参数(self),是对实例的绑定,类方法传入参数(cls)是对类的绑定。

调用实例方法时,就是调用了实例的方法,类方法,就是调用了类的方法。

注意这里的参数self和cls可以替换称其他参数,但是python约定最好不要改。

静态方法和普通方法一样,只是不需要跟谁绑定,并且在调用时需要使用a.static_foo(x)或者A.static_foo(x)来调用.

实例方法 类方法 静态方法
a = A() a.foo(x) a.class_foo(x) a.static_foo(x)
A 不可用 A.class_foo(x) A.static_foo(x)

 

 

 

 

4 类变量和实例变量

class Person:
    name="aaa"
 
p1=Person()
p2=Person()
p1.name="bbb"
print p1.name  # bbb
print p2.name  # aaa
print Person.name  # aaa

类变量就是供类使用的变量,实例变量就是供实例使用的.

这个问题其实又回到第一个问题,实际上就是一个参数传递的问题。

所以如果是一个可变对象的话,比如一个列表。那么:

class Person:
    name=[]
 
p1=Person()
p2=Person()
p1.name.append(1)
print p1.name  # [1]
print p2.name  # [1]
print Person.name  # [1]

这样看起来问题就简单多了。

5 Python自省

简而言之就是,运行时能够获得对象的类型,比如type(),dir(),getattr(),hasattr(),isinstance()

6 字典推导式(字典生成式)

字典可以如同列表一样使用生成式:

d = {key: value for (key, value) in iterable}

7 Python中单下划线和双下划线:

  • __foo__:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突。
  • _foo:一种约定,用来表示变量私有(但实际上并不是真正的私有)
  • __foo:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名.

8字符串格式化:%和.format

.format在许多方面看起来更便利,%最烦人的地方在于无法同时传递一个变量和元祖。

9 迭代器与生成器

据说是stackoverflow中python排名第一的问题,链接可以看这里