python learning notes

本文详细介绍了Python中类的新旧风格区别,包括元类、属性查找、类型检查、方法解析顺序(MRO)、槽机制、属性和方法的区别。讨论了__slots__如何节省空间,以及__getattr__、__setattr__、__getattribute__的作用。还探讨了静态方法、类方法和实例方法的使用场景。
摘要由CSDN通过智能技术生成

PART I
in new-style class, implicit attribute fetch starts at class instead of instance.
X[I] is equivalent to X.__getitem__(I) in old-style class.
X[I] is equivalent to type(X).__getitem__(X, I) in new-style class.

Rationale for this:
metaclasses are the type for classes and classes are instances of metaclasses.
metaclass A
class B is an instance of A, if B wants to call a method, this skip of instance attribute will directly call the method from metaclass B. Because the methods defined in B aim for the use from its own instance. So the introduction of metaclass needs this change.

this impacts proxy class, which embeds other objects which implement operator overloading.

>>> class A:
...     data = 'spam'
...     def __getattr__(self, name):
...             print(name)
...             return getattr(self.data, name)
... 
>>> x = A()
>>> x[0]
__getitem__
's'
>>> print(x)
__str__
spam
>>> class B(object):
...     data = 'spam'
...     def __getattr__(self, name):
...             print(name)
...             return getattr(self.data, name)
... 
>>> y = B()
>>> y[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'B' object does not support indexing
>>> print(y)
<__main__.B object at 0x7f061c41b950>
>>> y.__getitem__(0)
__getitem__
's'

__getattr__(self, item) is used when fetching attribute item while the instance does’t have this one, this method will be called. So for class level attribute fetch, this method will never be called.
thus y[0]: the implicit call to __getitem__ will skip the instance level and start to find __getitem__ in class B.
Notice that the explicit class succeeded.
In other words, only built-in operation in new style class starts at class level while explicit fetches run correctly.

for delegation class, needs to redefine all names accessed by built-in operations.

>>> class C(object):
...     data = 'spam'
...     def __getattr__(self, name):
...             print('getattr: ' + name)
...             return getattr(self.data, name)
...     def __getitem__(self, i):
...             print('getitem: ' + str(i))
...             return self.data[i]
...     def __add__(self, other):
...             print('add: ' + other)
...             return self.data + other
...     def __str__(self):
...             return self.data

PART II
classes and types
please refer to http://www.cs.utexas.edu/~cannata/cs345/Class%20Notes/15%20Python%20Types%20and%20Objects.pdf
the type of an instance is its class, and the type of a user-defined class is the same as the type of a built-in object type.
classes are types
types are classes

how to determine if two objects are of the same type in both python2.x and python 3.x?

# python2
>>> class A(): pass
... 
>>> class B(): pass
... 
>>> a = A()
>>> b = B()
>>> type(a)
<type 'instance'>
>>> type(b)
<type 'instance'>
>>> type(a) == type(b)
True
>>> a.__class__ == b.__class__
False
# python3
>>> class A(): pass
... 
>>> class B(): pass
... 
>>> a = A()
>>> b = B()
>>> type(a)
<class '__main__.A'>
>>> type(b)
<class '__main__.B'>
>>> type(a) == type(b)
False

in python3.x, type(instance) is the same as instance.__class__.

>>> isinstance(object, type)
True
>>> isinstance(type, object)
True

PART III
DFLR vs MRO
refer to http://python.jobbole.com/85685/ and http://hanjianwei.com/2013/07/25/python-mro/ for more details of the history and two methods.

PART IV*
__slots__ are used for saving space, which limits the the attributes that an object’s instance can create.

Let’s get several things straight.
1. __dict__ is a dictionary or mapping object used to store an object’s writable attributes.
2. difference between __dict__ in class and instance?

>>> class A: pass
... 
>>> a = A()
>>> 
>>> a.__dict__
{}
>>> A.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>})

mappingproxy ensures keys for class-level attributes are only strings, which accelerates attribute lookup in method resolution order.
refer to:https://stackoverflow.com/questions/32720492/why-is-a-class-dict-a-mappingproxy

>>> class A: pass
... 
>>> a = A()
>>> a.__dict__
{}
>>> A.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>})
>>> class B: 
...     __slots__ = 'x',
... 
>>> b = B()
>>> b.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute '__dict__'
>>> B.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__slots__': ('x',), 'x': <member 'x' of 'B' objects>})
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> dir(B)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'x']
>>> 

the declaration of __slots__ only deny the creation of __dict__ when create instance from this object(not on this object).

but why for this to happen?

 B.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__slots__': ('x',), 'x': <member 'x' of 'B' objects>})

so how to list all instance-level attribute?

>>> class D:
...     __slots__ = ['a', 'b', '__dict__']
...     c = 3
...     def __init__(self):
...             self.d = 4
... 
>>> x = D()
>>> x.a = 1
>>> x.b = 2
>>> for attr in list(getattr(x, '__dict__', [])) + getattr(x, '__slots__', []):
...     print(attr, '=>', getattr(x, attr))
... 
d => 4
a => 1
b => 2
__dict__ => {'d': 4}

slot names becomes class-level attributes, instances acquire the union of all slot names anywhere in the tree, by the inheritance order.

>>> class E:
...     __slots__ = ['c', 'd']
... 
>>> 
>>> class D(E):
...     __slots__ = ['a', '__dict__']
... 
>>> x = D()
>>> 
>>> 
>>> x.a = 1
>>> x.b = 2
>>> x.c = 3
>>> x.d = 4
>>> x.__dict__
{'b': 2}
>>> x.__slots__
['a', '__dict__']

slot names, property and descriptors are the names that similarly reside in class level but provide attribute values for instance on request.

>>> class A: __slots__ = 'a'
... 
>>> class B: __slots__ = 'b'
... 
>>> class C(A, B): __slots__ = ()
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: multiple bases have instance lay-out conflict

this error means those two class could not be nested together.

>>> class A: __slots__ = 'a'
... 
>>> class B: __slots__ = 'b',
... 
>>> class C(A, B): __slots__ = 'a', 'b'
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: multiple bases have instance lay-out conflict

it seems not able to do multiple inheritance of classes with __code__

>>> class A:
...     __slots__ = ('a')
... 
>>> A.a = 11
>>> x = A()
>>> x.a = 11
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object attribute 'a' is read-only 

if class attribute shares same name as slot name, the attribute will become read-only, also this will overwrites the slot descriptor(a class attribute finally).

several rules:

>>> class A: pass
... 
>>> class B(A): __slots__ = 'a'
... 
>>> x = B()
>>> x.c = 100

slots in subs are pointless if absent in supers.

>>> class A: __slots__ = 'a'
... 
>>> class B(A): pass
... 
>>> x = B()
>>> x.c = 100

slots in supers are pointless if absent in subs.
redefinition in class renders slot name pointless and this class attribute is readonly.
no class-level defaults for slot names.
redefinition renders super slots pointless.

to get attribute source: from which super => mapping dir results to objects in the MRO
to get attribute from this object => retrieve names from __dict__

if attr in getattr(obj, '__dict__', {}):
if hasattr(obj, '__dict__') and attr in obj.__dict__:

remember that slots and property are not physically stored in instance namespace dictionary __dict__

PART V
property are slots are both virtual attribute locates not in instance’s __dict__.

refer to:
https://stackoverflow.com/questions/38682318/why-favor-object-setattr-self-name-value-only-in-new-style-classes
If __setattr__() wants to assign to an instance attribute, it should not simply execute self.name = value — this would cause a recursive call to itself. Instead, it should insert the value in the dictionary of instance attributes, e.g., self.__dict__[name] = value. For new-style classes, rather than accessing the instance dictionary, it should call the base class method with the same name, for example, object.__setattr__(self, name, value)

PART V
python attributes: instance attribute, class attribute and static attribute.

class Methods:
    def imeth(self, x):
        print([self, x])

    def smeth(x):
        print([x])

    def cmeth(cls, x):
        print([cls, x])

    smeth = staticmethod(smeth)
    cmeth = classmethod(cmeth)
# python3
Python 3.4.5 (default, May 29 2017, 15:17:55) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from bothmethods import Methods
>>> m = Methods()
>>> m.imeth(100)
[<bothmethods.Methods object at 0x7f7748d782b0>, 100]
>>> m.cmeth(100)
[<class 'bothmethods.Methods'>, 100]
>>> m.smeth(100)
[100]
>>> Methods.cmeth(100)
[<class 'bothmethods.Methods'>, 100]
>>> Methods.smeth(100)
[100]
# python3
Python 3.4.5 (default, May 29 2017, 15:17:55) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Sample:
...     def printf(x):
...             return x
... 
>>> s = Sample()
>>> s.printf(100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: printf() takes 1 positional argument but 2 were given
>>> Sample.printf(100)
100

class attribute could be accessed from both class or an instance.
python3.x allows simple functions in a class to serve as static methods without extra protocol, when called through a class object only.

difference between static method and class method: https://www.geeksforgeeks.org/class-method-vs-static-method-python/

class Spam:
    num = 0
    @classmethod
    def count(cls):
        cls.num += 1
    def __init__(self):
        self.count()

class Sub(Spam):
    num = 0
    def __init__(self):
        super(Sub, self).__init__()

class Other(Spam):
    num = 0

if __name__ == '__main__':
    s1, s2 , s3 = Spam(), Spam(), Spam()
    s4, s5 = Sub(), Sub()
    s6 = Other()
    print(Spam.num) --> 3
    print(Sub.num) --> 2
    print(Other.num) --> 1

class method holds status for each layer of class, because using class methods will receive the lowest class of the call’s subject.
static method may be a better solution for processing data local to a class.
class method may be a better solution for processing data that may differ for each class in hierarchy.
Another example:
since python doesn’t support function overloading, class method could be used to define different factory methods.

from datetime import date

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def fromBirthYear(cls, name, year):
        return cls(name, date.today().year-year)

    @staticmethod
    def isAdult(age):
        return age > 18

class Employee(Person):
    pass

if __name__ == '__main__':
    p1 = Person('alice', 21)
    p2 = Person('bob', 22)
    p3 = Employee.fromBirthYear('cathy', 2001)
    print(p1.age) --> 21
    print(p2.age) --> 22
    print(p3.age) --> 2018 - 2001 = 17
    print(p3.__class__) --> <class '__main__.Employee'>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值