谈谈Python的内建方法

本文详细探讨了Python中的内建方法,包括__class__、__delattr__/__getattribute__/__setattr__、__dict__、__doc__/__format__/__str__/__repr__/__module__、__hash__、__new__/__init__、__reduce__/__reduce_ex__、__sizeof__和__subclasshook__。这些方法在Python类和对象中扮演着关键角色,涉及类型检查、属性操作、内存大小、哈希计算等方面。文章通过实例解析了它们的用途和潜在影响,提醒开发者在生产环境中谨慎使用某些方法,如__hash__,并预告了后续将单独讲解__new__和__subclasshook__。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

查看类包含的方法

# coding=utf-8
from pprint import pprint as pp


class A(object):
    pass


pp(dir(A))

下面是打印内容,也就是后面探讨的内容,下面让我们一起来看看Python类对象的内建方法都有哪些作用:

['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

1.__class__方法

一句话总结:__class__方法等同于Python的内建方法type

# coding=utf-8
from pprint import pprint as pp


class A(object):
    pass

print A.__class__, type

打印结果:

<type 'type'> <type 'type'>

这意味着A.__class__和Python的内建方法type是同一个内建方法,所以让我们看看type的这个注释:

    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass
  1. type(object) ,返回A的类型,这种用法大家都经常使用;
  2. type(name, bases, dict),生成新的类型,用于代码中动态生成新的类型,参数name用于指定新生成的类名,参数bases,用于指定继承父类的元组,dict可以动态的为新生成的类指定属性,下面来个例子,用于证明__class__方法与type等效:
# coding=utf-8
from pprint import pprint as pp


class A(object):
    def __init__(self):
        self.PropertyA = 1


class B(object):
    def __init__(self):
        self.PropertyB = 2

    def B_method(self):
        pass


NewA = A.__class__('NewA', (A, B,), {'NewProperty': 'xxxxx'})
new_a = NewA()
print(dir(new_a)[:3])  # 节约篇幅,就不完全打印了
print new_a.__class__.__name__

打印结果:

['B_method', 'NewProperty', 'PropertyA']
NewA

可以看到,新生成的实例new_a继承了父类A的属性PropertyA,父类B的方法B_method,以及自身的新增属性B_method,至于为什么没有继承父类B的属性PropertyB ,有兴趣可以去看看Python新式类关于继承深度优先的说明。

2.__delattr__方法、 __getattribute__方法、__setattr__方法

一句话总结:这三兄弟没什么好说的,就是给Python类对象,增加属性,访问属性,删除属性

3.__dict__方法

一句话总结:以字典的形式返回类实例,类对象的属性,方法,需要注意类实例和类对象的区别
比较简单的方法,直接上代码:

# coding=utf-8
from pprint import pprint as pp


class A(object):
    def __init__(self):
        self.name = 'A'
        self.scope = None

    @property
    def property1(self):
        return 0

    @staticmethod
    def method_1():
        print 'this is m1'


a = A()
A.this_a = 'a'
pp(a.__dict__)
pp(A.__dict__.items())
pp(type(A.__dict__))

打印如下:

{'name': 'A', 'scope': None}

[('property1', <property object at 0x00000000030195E8>),
 ('__module__', '__main__'),
 ('method_1', <staticmethod object at 0x000000000301B378>),
 ('this_a', 'a'),
 ('__dict__', <attribute '__dict__' of 'A' objects>),
 ('__weakref__', <attribute '__weakref__' of 'A' objects>),
 ('__doc__', None),
 ('__init__', <function __init__ at 0x0000000003022208>)]
 
<type 'dictproxy'>

看起来这个方法会返回类实例的属性,类对象的静态&动态属性和一些内建方法
有趣的是,以cls.__dict__形式调用时,返回值是一个dictproxy对象。我在内建类type的代码里面只找到一句注释,太长了懒得看:

    __dict__ = None # (!) real value is "dict_proxy({'__module__': <attribute '__module__' of 'type' objects>, '__abstractmethods__': <attribute '__abstractmethods__' of 'type' objects>, '__getattribute__': <slot wrapper '__getattribute__' of 'type' objects>, '__weakrefoffset__': <member '__weakrefoffset__' of 'type' objects>, '__dict__': <attribute '__dict__' of 'type' objects>, '__lt__': <slot wrapper '__lt__' of 'type' objects>, '__init__': <slot wrapper '__init__' of 'type' objects>, '__setattr__': <slot wrapper '__setattr__' of 'type' objects>, '__subclasses__': <method '__subclasses__' of 'type' objects>, '__new__': <built-in method __new__ of type object at 0x0000000062F771E0>, '__base__': <member '__base__' of 'type' objects>, '__mro__': <member '__mro__' of 'type' objects>, 'mro': <method 'mro' of 'type' objects>, '__dictoffset__': <member '__dictoffset__' of 'type' objects>, '__call__': <slot wrapper '__call__' of 'type' objects>, '__itemsize__': <member '__itemsize__' of 'type' objects>, '__ne__': <slot wrapper '__ne__' of 'type' objects>, '__instancecheck__': <method '__instancecheck__' of 'type' objects>, '__subclasscheck__': <method '__subclasscheck__' of 'type' objects>, '__gt__': <slot wrapper '__gt__' of 'type' objects>, '__name__': <attribute '__name__' of 'type' objects>, '__eq__': <slot wrapper '__eq__' of 'type' objects>, '__basicsize__': <member '__basicsize__' of 'type' objects>, '__bases__': <attribute '__bases__' of 'type' objects>, '__flags__': <member '__flags__' of 'type' objects>, '__doc__': <attribute '__doc__' of 'type' objects>, '__delattr__': <slot wrapper '__delattr__' of 'type' objects>, '__le__': <slot wrapper '__le__' of 'type' objects>, '__repr__': <slot wrapper '__repr__' of 'type' objects>, '__hash__': <slot wrapper '__hash__' of 'type' objects>, '__ge__': <slot wrapper '__ge__' of 'type' objects>})"

在官方文档里面也找到了它:

PyObject* PyDictProxy_New(PyObject *dict)
Return value: New reference.
Return a proxy object for a mapping which enforces read-only behavior. This is normally used to create a proxy to prevent modification of the dictionary for non-dynamic class types.

New in version 2.2.

也就是说以cls.__dict__这种方式调用时,返回了一个只读属性的DictProxy对象,然后我又仔细想了想,这个只读属性有什么作用
emmmmm,貌似没有什么作用,在上面代码里面可以看见,可以通过cls.new_attr = 1,这种形式给cls增加新的属性,再次调用__dict__时,返回值也就变化了.

4.__doc__方法,__format__方法,__str__方法,__repr__方法,__module__方法

一句话总结:冷门5兄弟,没什么用,但是很好玩

__doc__方法解锁姿势:

# coding=utf-8
from pprint import pprint as pp


class A(object):
    __doc__ = 'this is for help, but no help~~\n'

    def __init__(self):
        self.name = 'A'
        self.scope = None


a = A()
print a.__doc__, A.__doc__

打印结果,类和实例都可以调用这个方法:

this is for help, but no help~~
this is for help, but no help~~

至于__format__方法,__str__方法,__repr__方法,就要提到Python对应的三个内建方法:format,str,repr。简单来说,当你使用format(instance\cls)时,会调用cls.__format__或者instance.__format__方法,另外俩也一样。

__module__方法更直接,可以用cls.__module__的形式或者instance.__module__的形式调用,返回类所在的module

当然,具体的每个方法肯定有它应该使用的场景和方法,但是总体来说属于辅助系的功能,对实际生产环境的编码,或者功能实现贡献不大,我贴个官方文档链接,大家有兴趣可以玩一下: docs.python.org

5.__hash__方法

一句话总结:基本用不到,生产环境千万小心使用,否则很快就可以去财务室结算薪水走人

官方文档贴在这里“Called by built-in function hash() and for operations on members of hashed collections including set, frozenset, and dict.”
也就是说,有4种情况会调用这个__hash__方法:

  1. 由Python的内建函数函数hash()调用
  2. 操作set类型的成员时;
  3. 操作frozenset类型的成员时;
  4. 操作dict类型的成员时;

下面分两类来说一说:
首先是,由Python的内建函数函数hash()调用,这个不多说,这很容易理解。
第二类是操作哈希集合类型的成员,我已dict为例,举个栗子:

# 定义一个dict,一个class A,calss B,以A和B的实例为Key
class A(object):pass
class B(object):pass

a = A()
b = B()

d = dict()
d[a] = 1
d[b] = 2
print d

# 打印结果:
{<__main__.A object at 0x10f360510>: 1, <__main__.B object at 0x10f3604d0>: 2}

现在我们进行第二步,重写A和B的hash方法,让他们返回相同的值,再把它塞到字典d里面,按照我们的预期来说,字典d应该只有一对key-value:

class A(object):

    def __hash__(self):
        print 'hash a'
        return 1


class B(object):

    def __hash__(self):
        print 'hash b'
        return 1


a = A()
b = B()
d = dict()
d[a] = 1
d[b] = 2
print d

# 打印结果
hash a
hash b
{<__main__.A object at 0x108f26f10>: 1, <__main__.B object at 0x108f26f50>: 2}

我们可以看到,在修改dict的成员对象时,对Key值,调用了hash(Key),从而调用了A和B的hash函数,打印出了hash a 和hash b,但是hash值相同的a和b还是成为了字典d的两个键,很明显不符合预期。
猜测一下,操作字典成员时,会判断成员的hash值是否已经在字典中,应该会用到__eq__方法,尝试重写A或者B的eq方法:

class A(object):

    def __hash__(self):
        print 'hash a'
        return 1

    def __eq__(self, other):
        print 'call eqa'
        return True


class B(object):

    def __hash__(self):
        print 'hash b'
        return 1

    def __eq__(self, other):
        print 'call eqb'
        return True


a = A()
b = B()
d = dict()
d[a] = 1
d[b] = 2
print d

# 打印结果

hash a
hash b
call eqb
{<__main__.A object at 0x10d5b2f10>: 2}

OK,看来是符合我们预期结果的,由于hash值相同,所以a和b无法同时作为字典d的key.

6.__new__方法,__init__方法

这个内容有点多,后面单独写一篇来讲

7.__reduce__方法,__reduce_ex__方法

一句话总结:生僻的两个方法,跟序列化与反序列化有关

8.__sizeof__方法

一句话总结:简单易懂:size of object in memory, in bytes
值得一提的是:

sys模块提供的方法getsizeof(),也会返回对象的大小“Return the size of object in bytes.”,但实际使用中,使用__sizeof__()取得的值,一定会比getsizeof()要小,原因是,使用getsizeof()拿到的内存占用大小,还包含了gc时所需要的容量开销。

9.__subclasshook__方法

这个方法也单开一篇来讲

好了第一篇博客写完了,真是累啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值