Article catalog
learn from fluent python
1. Use dynamic attributes to transform data
- In Python, attributes of data and methods of processing data are collectively referred to as attributes. In fact, methods are just callable properties
- We can also create properties
from urllib.request import urlopen import warnings import os import json URL = 'http://www.oreilly.com/pub/sc/osconfeed' JSON = './osconfeed.json' def load(): if not os.path.exists(JSON): msg = 'downloading {} to {}'.format(URL, JSON) warnings.warn(msg) # Send a reminder with urlopen(URL) as remote, open(JSON, 'wb') as local: # Use two context managers local.write(remote.read()) # Read and save remote files with open(JSON) as fp: return json.load(fp) feed = load() print(feed) print(sorted(feed['Schedule'].keys())) for key, value in sorted(feed['Schedule'].items()): print('{:3} {}'.format(len(value), key)) print(feed['Schedule']['speakers'][-1]['serial']) # This syntax is too long... How to improve
from collections import abc class FrozenJSON: # A read-only interface that uses attribute notation to access JSON class objects def __init__(self, mapping): self.__data = dict(mapping) def __getattr__(self, name): if hasattr(self.__data, name): # If there are properties, get return getattr(self.__data, name) # Calling methods such as keys is handled in this way else: # No, build frozen JSON return FrozenJSON.build(self.__data[name]) @classmethod # As an alternative construction method, this is often used by the @ classmethod decorator def build(cls, obj): if isinstance(obj, abc.Mapping): return cls(obj) # Building frozen JSON elif isinstance(obj, abc.MutableSequence): # Is a sequence that build s each element return [cls.build(item) for item in obj] else: return obj raw_feed = load() feed = FrozenJSON(raw_feed) print(len(feed.Schedule.speakers)) print(sorted(feed.Schedule.keys())) # ['conferences', 'events', 'speakers', 'venues'] print(feed.Schedule.events[-1].name) # Why Schools Don't Use Open Source to Teach Programming p = feed.Schedule.events[-1] print(type(p)) # <class '__main__.FrozenJSON'> print(p.name) # Why Schools Don't Use Open Source to Teach Programming print(p.speakers) # [157509] print(p.age) # KeyError: 'age'
- Handle invalid attribute names, such as built-in keywords, keyword iskeyword
grad = FrozenJSON({'name': 'Jim Bo', 'class': 1982}) # print(grad.class) # invalid syntax print(getattr(grad, 'class')) # 1982
Modify the constructor of the class:
def __init__(self, mapping): self.__data = {} for k,v in mapping.items(): if keyword.iskeyword(k): # If it is a keyword, add an underscore suffix k += "_" self.__data[k] = v
- Invalid naming, str.isidentifier()
grad = FrozenJSON({'2name': 'Jim Bo', 'class': 1982}) print(grad.2name) # SyntaxError: invalid syntax
Rename
def __init__(self, mapping): self.__data = {} for k,v in mapping.items(): if keyword.iskeyword(k): k += "_" if not k.isidentifier(): # It's not a legal name. Change it k = "_" + k self.__data[k] = v
print(grad._2name) # Jim Bo
- __ init__ Method is actually "initialization method". The real construction method is__ new__ (but you hardly need to write it yourself)
# Pseudo code for building objects def object_maker(the_class, some_arg): new_object = the_class.__new__(some_arg) # The new method can also return other class objects if isinstance(new_object, the_class): the_class.__init__(new_object, some_arg) return new_object
class FrozenJSON: # A read-only interface that uses attribute notation to access JSON class objects def __new__(cls, arg): # The first parameter is the class itself if isinstance(arg, abc.Mapping): return super().__new__(cls) elif isinstance(arg, abc.MutableSequence): return [cls(item) for item in arg] else: return arg def __init__(self, mapping): self.__data = {} for k, v in mapping.items(): if keyword.iskeyword(k): k += "_" if not k.isidentifier(): k = "_" + k self.__data[k] = v def __getattr__(self, name): if hasattr(self.__data, name): # If there are properties, get return getattr(self.__data, name) # Calling methods such as keys is handled in this way else: # No, build frozen JSON return FrozenJSON(self.__data[name])
2. @property
https://www.liaoxuefeng.com/wiki/1016959663602400/1017502538658208 Please use @ property to add width and height attributes and a read-only attribute resolution to a Screen object
class Screen(object): def __init__(self): self._w = 0 self._h = 0 self._r = 786432 @property # Turn the method into an attribute, and do not add () to the call def width(self): return self._w @property def height(self): return self._h @width.setter # You can set the property value. No method is a read-only property def width(self, v): self._w = v @height.setter def height(self, v): self._h = v @property def resolution(self): return self._r
s = Screen() s.width = 1024 s.height = 768 print('resolution =', s.resolution) if s.resolution == 786432: print('Test passed!') else: print('Test failed!')
- Features are class properties, but they manage the access of instance properties
class Class: data = "class data attr" @property def prop(self): return "prop value" obj = Class() print(vars(obj)) # {}, the vars function returns the value of obj__ dict__ attribute print(obj.data) # class data attr obj.data = "changed" print(vars(obj)) # {'data': 'changed'} print(Class.data) # class data attr # The instance modified data, but the class attribute was not modified print(Class.prop) # <property object at 0x0000021A91E4A680> print(obj.prop) # prop value # obj.prop = "changed prop" # Error can't set attribute obj.__dict__["prop"] = "changed prop1" print(vars(obj)) # {'data': 'changed', 'prop': 'changed prop1'} print(obj.prop) # prop value # # Read obj Prop will still run the read value method of the property. Attributes are not masked by instance properties Class.prop = "haha" # Override class Prop properties, destroying property objects print(obj.prop) # changed prop1 # Now, obj Prop gets instance properties. # Class.prop is not a feature anymore, so obj will no longer be overwritten prop.
print(obj.data) # changed print(Class.data) # class data attr Class.data = property(lambda self : "data prop value") # Override class with new features data print(obj.data) # data prop value # obj.data is class Data characteristics cover up del Class.data # Delete property print(obj.data) # changed # Restore as is, obj Data gets the instance property data
obj. Expressions such as attr do not start looking for attr from obj, but from obj__ class__ At first, and only if there is no attribute named attr in the class, Python will look for it in the obj instance.
This rule applies not only to features, but also to an entire class of descriptors - overriding descriptor s
2.1 help() document
When you create a property object using the decorator, the document string of the value reading method (the method with the @ property decorator) as a whole becomes the document of the property
>>> class Foo: @property def bar(self): '''Documentation''' return self.__dict__['bar'] @bar.setter def bar(self, val): self.__dict__['bar'] = val >>> help(Foo) Help on class Foo in module __main__: class Foo(builtins.object) | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | bar | Documentation >>> help(Foo.bar) Help on property: Documentation
- Classic writing method, pass in doc parameter
weight = property(get_weight, set_weight, doc='weight in kilograms')
3. Characteristic factory function
To reduce writing getter s and setter s, you can use feature factory functions
def quantity(storage_name): def qty_getter(instance): return instance.__dict__[storage_name] def qty_setter(instance, value): if value > 0: instance.__dict__[storage_name] = value else: raise ValueError("value must be > 0") return property(qty_getter, qty_setter)
class LineItem: weight = quantity('weight') # Use the attribute factory function to define the weight class attribute price = quantity('price') # price attribute def __init__(self, description, weight, price): self.description = description self.weight = weight # Activate the property to ensure that it is not negative and 0 self.price = price def subtotal(self): return self.weight * self.price # Use values stored in properties line1 = LineItem("name1", 8, 13.5) print(line1.weight, line1.price) # 8 13.5 print(sorted(vars(line1).items())) # [('description', 'name1'), ('price', 13.5), ('weight', 8)]
The weight attribute overrides the weight instance attribute, so for self Weight or obj Each reference to weight is handled by the attribute function, with only direct access__ dict__ Property to skip the processing logic of the attribute
4. Delete attribute
del operation, deleting attributes is rare, but python supports this operation
class BlackKnight: def __init__(self): self.members = ['an arm', 'another arm', 'a leg', 'another leg'] self.phrases = ["'Tis but a scratch.", "It's just a flesh wound.", "I'm invincible!", "All right, we'll call it a draw."] @property def member(self): print("next member is:") return self.members[0] @member.deleter def member(self): text = 'BLACK KNIGHT (loses {})\n-- {}' print(text.format(self.members.pop(0), self.phrases.pop(0))) knight = BlackKnight() print(knight.member) # next member is: # an arm del knight.member # BLACK KNIGHT (loses an arm) # -- 'Tis but a scratch. del knight.member # BLACK KNIGHT (loses another arm) # -- It's just a flesh wound. del knight.member # BLACK KNIGHT (loses a leg) # -- I'm invincible! del knight.member # BLACK KNIGHT (loses another leg) # -- All right, we'll call it a draw. del knight.member # IndexError: pop from empty list
- Classic writing, fdel parameter setting, delete function
member = property(member_getter, fdel=member_deleter)
- If you do not use features, you can also implement low-level special features__ delattr__ Method handles deleting attributes
5. Important attributes and functions of processing attributes
- __ class__ Reference to the class to which the object belongs (i.e. obj. _class_ has the same function as type(obj)) Some special methods of Python, such as__ getattr__, Look only in the class of the object, not in the instance
- __ dict__ A mapping that stores the writable properties of an object or class. Yes__ dict__ Property, you can set new properties at any time If the class has__ slots__ Property, its instance may not have__ dict__ attribute
- __slots__ Class can define this property to limit which properties an instance can have __ slots__ The value of an attribute is a tuple of strings indicating the allowed attributes If__ slots__ 'not in__ dict__', Then there is no instance of this class__ dict__ Property. An instance can only have a property with the specified name
5.1 built in functions for processing attributes
- dir([object]) Most of the properties of the object are listed, and the dir function does not list several special properties of the class, such as__ mro__,__ bases__ And__ name__
>>> dir(Foo) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar']
- getattr(object, name[, default]) Get the attribute corresponding to the name string from the object object The property obtained may come from the class or superclass to which the object belongs If there is no specified attribute, the getattr function throws an AttributeError exception or returns the value of the default parameter (if this parameter is set)
- hasattr(object, name), call the above function to see if it returns an exception
- setattr(object, name, value) may create a new attribute or overwrite the existing attribute
- vars([object]) returns the name of the object__ dict__ attribute If the class to which the instance belongs is defined__ slots__ Property, no instance__ dict__ Property, then the vars function cannot handle that instance
5.2 special methods for handling attributes
- Using the dot or the built-in getattr, hasattr, and setattr functions to access properties will trigger the corresponding special methods in the following list
- However, directly through the instance__ dict__ Property reading and writing properties do not trigger these special methods, and special methods are usually skipped in this way
- Special methods are not masked by instance properties with the same name