Package: objprint
objprintA library that can print Python objects in human readable formatInstallpip install objprintUsageopUseop()(orobjprint()) to print objects.fromobjprintimportopclassPosition:def__init__(self,x,y):self.x=xself.y=yclassPlayer:def__init__(self):self.name="Alice"self.age=18self.items=["axe","armor"]self.coins={"gold":1,"silver":33,"bronze":57}self.position=Position(3,5)op(Player())<Player 0x7fe44e1e3070
  .age = 18,
  .coins = {'bronze': 57, 'gold': 1, 'silver': 33},
  .items = ['axe', 'armor'],
  .name = 'Alice',
  .position = <Position
    .x = 3,
    .y = 5
  >
>You can print multiple objects just like print, exceptopwill print them in separate linesop([1,2],{'a':1})[1, 2]
{'a': 1}opwill return the same object it prints, so you can do something like thisa=MyObject()# print the args inline with minumum changefunction_using_object(op(a))# the difference is more significant with complex expressions# original: function_using_object(a.f() + a.g())function_using_object(op(a.f()+a.g()))It works on multiple objects as well, as it returns a tuple, you need to unpack it for functionsa=MyObject()function_using_object(*op(a.f(),a.g()))add_objprintIf you want to useprint()to print your object, you can also use the class decoratoradd_objprintto add__str__method for your class.fromobjprintimportadd_objprintclassPosition:def__init__(self,x,y):self.x=xself.y=y@add_objprintclassPlayer:def__init__(self):self.name="Alice"self.age=18self.items=["axe","armor"]self.coins={"gold":1,"silver":33,"bronze":57}self.position=Position(3,5)# This will print the same thing as aboveprint(Player())objstrIf you want thestrrepresentation of the object, instead of printing it on the screen,
you can useobjstrfunctionfromobjprintimportobjstrs=objstr(my_object)print moreThere are some optional information you can print withconfig."Public" MethodsThere are no REAL public methods in python, here I simply meant you can print methods that do not start with__as there will be
a lot of default magic methods and you don't want that.classPlayer:defattack(self,opponent):passop(Player(),print_methods=True)<Player 0x7fe44e1e3070
  def attack(opponent)
>As you can see, it will also print the method signature(withoutselfargument).Line numbersYou can print execution info, including the function it's in, the file and the line number of the printing line.
This is helpful for you to locate where this object is printed.deff():op(Player(),line_number=True)f()f (my_script.py:29)
<Player 0x7f30e8cb1ac0
  ...
>Argument namesYou can print the expression of the argument witharg_nameop(Player(),arg_name=True)Player():
<Player 0x7f495850a8d0
  ...
>objjsonobjprintsupports print objects to json to make it easier to serialize an object.objjsonreturns a jsonifiable object that can be dumped withjson.dumpsfromobjprintimportobjjsonjson_obj=objjson(Player())print(json.dumps(json_obj,indent=2)){
  ".type": "Player",
  "name": "Alice",
  "age": 18,
  "items": [
    "axe",
    "armor"
  ],
  "coins": {
    "gold": 1,
    "silver": 33,
    "bronze": 57
  },
  "position": {
    ".type": "Position",
    "x": 3,
    "y": 5
  }
}You can useopto print in json format directly withformat="json". You can pass in argument forjson.dumpsop(Player(),format="json",indent=2)add_objprintalso works withformat="json"@add_objprint(format="json",indent=2)classPlayer:passEnable/Disable the printYou can disable prints from all theop()calls globally withenableconfig.fromobjprintimportopop.disable()op([1,2,3])# This won't print anythingop.enable()# This could fix it!Or you can use it forop()functions individually with some conditionsop(obj,enable=check_do_print())attribute selectionYou can customize which attribute to print with name filters.objprintwill try to match the attribute name withattr_patternregex. The defaultattr_patternisr"(!_).*", which means anything that does NOT start with an_.You can customizeattr_patternto select the attributes you want to print:# This will print all the attributes that do not start with __op(Player(),attr_pattern=r"(!__).*")You can also useincludeandexcludeto specify attributes to print with regular expression
soobjprintwill only print out the attributes you are interested in.op(Player(),include=["name"])<Player
  .name = 'Alice'
>op(Player(),exclude=[".*s"])<Player 0x7fe44e1e3070
  .name = 'Alice',
  .age = 18,
  .position = <Position
    .x = 3,
    .y = 5
  >
>If you specify bothincludeandexclude, it will do a inclusive check first, then filter out the attributes
that match exclusive check.attr_pattern,includeandexcludearguments work onobjprint,objstrand@add_objprint.Register Custom Type FormatterYou can also customize how certain types of objects are displayed by registering a custom formatter function to transform an object of a specific type into a string.For example, you can print all integers in hexadecimal format by registering thehex()function for theintdata type, or registering a customlambdafunction.fromobjprintimportopop.register_formatter(int,hex)op.register_formatter(float,lambdax:f"{round(x,3)}")op(10)# prints 0xaop(3.14159)# prints 3.142Alternatively, you can also register a custom formatter function using a decorator:@op.register_formatter(str)defcustom_formatter(obj:str):returnf"custom_print:{obj}"op("hi")# prints custom_print: hiDuring registration,objprintwill examine the specified object type, and raise aTypeErrorif an invalid object type is provided.When you finish using the custom formatters, you can unregister them withunregister_formatter().op.unregister_formatter(int,float,str)op(10)# prints 10op(3.14159)# prints 3.14159op("hi")# prints hiOr you can unregister everything by passing no argument to it.op.unregister_formatter()Theregister_formatter()function also accepts aninheritargument (defaultTrue) to dictate if the registered formatter should also apply to any derived classes of the object type.classBaseClass:name='A'classDerivedClass(BaseClass):name='B'Withinherit=True, derived class will share the same formatter registered under base class.defbase_formatter(obj:BaseClass)->str:returnf'Print{obj.name}with Base Class Formatter'op.register_formatter(BaseClass,base_formatter,inherit=True)op(DerivedClass())Print B with Base Class FormatterWithinherit=False, derived class will use the default formatter provided byobjprint.@op.register_formatter(BaseClass,inherit=False)defbase_formatter(obj:BaseClass)->str:returnf'Print{obj.name}with Base Class Formatter'op(DerivedClass())<DerivedClass 0x7fb42e8216a0
  .name = 'B'
>If a derived class inherits from multiple base classes, each with a registered formatter, the chosen formatter adheres to the Method Resolution Order (MRO) of the derived class.To check all the registered functions and their inheritance status, you can use theget_formatter()method. It returns a dictionary-like object that you can print for easy inspection.fmts=op.get_formatter()print(fmts){<class '__main__.BaseClass'>: FormatterInfo(formatter=<function base_formatter at 0x7feaf33d1f70>, inherit=False)}Please note that registering a formatter function withopwill affect the output ofobjprintandobjstrmethods in the same way.configobjprintformats the output based on some configsconfig_name(default_value)- this config's explanationenable(True)- whether to print, it's like a switchdepth(100)- how deepobjprintgoes into nested data structuresindent(2)- the indentationwidth(80)- the maximum width a data structure will be presented as a single lineelements(-1)- the maximum number of elements that will be displayed,-1means no restrictioncolor(True)- whether to use colored schemeline_number(False)- whether to print thefunction (filename:line_number)before printing the objectarg_name(False)- whether to print the argument expression before the argument valueskip_recursion(True)- whether skip printing recursive data, which would cause infinite recursion withoutdepthconstrainthonor_existing(True)- whether to use the existing user defined__repr__or__str__methodattr_pattern(r"(!_).*")- the regex pattern for attribute selectioninclude([])- the list of attribute regex to do an inclusive filterexclude([])- the list of attribute regex to do an exclusive filterYou can set the configs globally usingconfigfunctionfromobjprintimportconfigconfig(indent=4)Or if you don't want to mess up your name spacefromobjprintimportopop.config(indent=4)Or you can do a one time config by passing the arguments intoobjprintfunctionfromobjprintimportopop(var,indent=4)installMaybe you don't want to importopin every single file that you want to use. You can
useinstallto make it globally accessiblefromobjprintimportop,install# Now you can use op() in any fileinstall()# This is the sameop.install()# You can specify a name for objprint()install("my_print")my_print(my_object)Bugs/RequestsPlease send bug reports and feature requests throughgithub issue tracker.LicenseCopyright 2020-2023 Tian Gao.Distributed under the terms of theApache 2.0 license.