Best practices of class "reopening" a-la Ruby?
Hello, Ruby has following feature. Suppose the existing class "Cls" is scope (either defined before or imported from some module), then the code like: class Cls def mixin_method(args) ... end end Will "reopen" (Ruby term) that class and will add a new method "mixin_method" to it. Syntactically, that's complete madness - the same syntax is used to both define the class initially and augment it afterwards, and that's expectedly leads to user confusion, e.g. https://stackoverflow.com/questions/4464629/ruby-rails-reopening-vs-overwrit... Semantically, Python can achieve the same with "imperative" syntax like: def mixin_method(self, args): ... Cls.mixin_method = mixin_method The question then: what are the best practices in *declarative* syntax to achieve the same effect in Python? (but of course, unlike Ruby, there should be explicit syntactic marker that we augment existing class, not redefine it). One of usecases should be clear from the example above - it's an easy way to add a mixin to the class, without relying on multiple inheritance (which has many its own problems). It's also emerging language-flexibility/expressivity best practice to be able to define interfaces on classes (types) post-factum. Such interfaces are called different buzzwords in different languages, e.g. Haskell calls them "typeclasses" and Rust calls them "traits". The question is again how do we syntactically encode it in Python (with a hint that the matter is rather similar to post-factum mixing-in). -- Best regards, Paul mailto:pmiscml@gmail.com
On Thu, Jan 14, 2021 at 10:07 PM Paul Sokolovsky <pmiscml@gmail.com> wrote:
The question then: what are the best practices in *declarative* syntax to achieve the same effect in Python? (but of course, unlike Ruby, there should be explicit syntactic marker that we augment existing class, not redefine it).
Easy. @monkeypatch class Cls: ... And then you define monkeypatch as a function that locates the prior class, augments it, and returns it. If you actually want this, it's only about a dozen lines of code. The trouble is, it's nearly impossible to generalize which things should be lifted in and which shouldn't, so it basically has to be written for each use-case. ChrisA
Hello, On Thu, 14 Jan 2021 22:14:17 +1100 Chris Angelico <rosuav@gmail.com> wrote:
On Thu, Jan 14, 2021 at 10:07 PM Paul Sokolovsky <pmiscml@gmail.com> wrote:
The question then: what are the best practices in *declarative* syntax to achieve the same effect in Python? (but of course, unlike Ruby, there should be explicit syntactic marker that we augment existing class, not redefine it).
Easy.
@monkeypatch class Cls: ...
And then you define monkeypatch as a function that locates the prior class, augments it, and returns it.
Yeah, except "monkeypatch" name is unlikely the "best practice" for naming an act of adding a mixin to class, and the question was exactly about "best practices".
If you actually want this, it's only about a dozen lines of code. The trouble is, it's nearly impossible to generalize which things should be lifted in and which shouldn't, so it basically has to be written for each use-case.
Were these complications with the generalization discussed somewhere, so we don't repeat them? I specifically made an angle of particular usecases, like adding mixins or adding typeclasses. While I have a hunch that syntax would be rather similar, it's clear that implementation details of individual decorators will vary.
ChrisA
Thanks! -- Best regards, Paul mailto:pmiscml@gmail.com
On Thu, Jan 14, 2021 at 02:05:50PM +0300, Paul Sokolovsky wrote: [...]
Semantically, Python can achieve the same with "imperative" syntax like:
def mixin_method(self, args): ... Cls.mixin_method = mixin_method
The question then: what are the best practices in *declarative* syntax to achieve the same effect in Python? (but of course, unlike Ruby, there should be explicit syntactic marker that we augment existing class, not redefine it).
def Cls.mixin_method(self, args): ... has been suggested as syntax for adding new methods to an existing class. I would use that occasionally. Even more so, the generalisation: def obj.method(self, args): ... to add a method to any instance, not just to a class. (Assuming that the instance has a writable `__dict__` of course -- you can't add attributes to a float or tuple, for example.) The advantage is that there is no new keyword required. -- Steve
Hello, On Thu, 14 Jan 2021 22:19:06 +1100 Steven D'Aprano <steve@pearwood.info> wrote:
On Thu, Jan 14, 2021 at 02:05:50PM +0300, Paul Sokolovsky wrote:
[...]
Semantically, Python can achieve the same with "imperative" syntax like:
def mixin_method(self, args): ... Cls.mixin_method = mixin_method
The question then: what are the best practices in *declarative* syntax to achieve the same effect in Python? (but of course, unlike Ruby, there should be explicit syntactic marker that we augment existing class, not redefine it).
def Cls.mixin_method(self, args): ...
has been suggested as syntax for adding new methods to an existing class. I would use that occasionally.
Thanks for the info! But I'd say that I like syntax suggested by Chris Angelico (which parallels Ruby's syntax) better. It reminds more of the original class definition, and it's normal to add more than one var/method via mixin/interface, so grouping them together using "class ...:" makes sense. But "scoping" problems pops up nonetheless, e.g. one would like, but really can't, do following: import mod @mixin class mod.Cls: ... So, here's the syntax I came to: @mixin class Cls(mod.Cls): ... It literally reads like "let me add a mixin to class Cls (specifically, mod.Cls)". IMHO, it's pretty neat. What do you think?
Even more so, the generalisation:
def obj.method(self, args): ...
to add a method to any instance, not just to a class.
Nnnnooooo ;-). Instances can't have methods, only classes can. Instances can only have instance variable storing a reference to a method, which would need to be called (obj.method)(args), remember? ;-). [] -- Best regards, Paul mailto:pmiscml@gmail.com
You could also do this using just the __init_subclass__ method. Create some class AddMixin, and use it like: class _(AddMixin, using=mod.Cls): ... The AddMixin class is something like: class AddMixin: def __init_subclass_(cls, *, using, **kwargs): super().__init_subclass__(**kwargs) using.__dict__.update(cls.__dict__) return None This is probably buggy, but that's the general idea. Not sure how people would feel about how this reads, but I think it's not too bad. --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler On Thu, Jan 14, 2021 at 7:28 AM Paul Sokolovsky <pmiscml@gmail.com> wrote:
Hello,
On Thu, 14 Jan 2021 22:19:06 +1100 Steven D'Aprano <steve@pearwood.info> wrote:
On Thu, Jan 14, 2021 at 02:05:50PM +0300, Paul Sokolovsky wrote:
[...]
Semantically, Python can achieve the same with "imperative" syntax like:
def mixin_method(self, args): ... Cls.mixin_method = mixin_method
The question then: what are the best practices in *declarative* syntax to achieve the same effect in Python? (but of course, unlike Ruby, there should be explicit syntactic marker that we augment existing class, not redefine it).
def Cls.mixin_method(self, args): ...
has been suggested as syntax for adding new methods to an existing class. I would use that occasionally.
Thanks for the info! But I'd say that I like syntax suggested by Chris Angelico (which parallels Ruby's syntax) better. It reminds more of the original class definition, and it's normal to add more than one var/method via mixin/interface, so grouping them together using "class ...:" makes sense. But "scoping" problems pops up nonetheless, e.g. one would like, but really can't, do following:
import mod
@mixin class mod.Cls: ...
So, here's the syntax I came to:
@mixin class Cls(mod.Cls): ...
It literally reads like "let me add a mixin to class Cls (specifically, mod.Cls)". IMHO, it's pretty neat. What do you think?
Even more so, the generalisation:
def obj.method(self, args): ...
to add a method to any instance, not just to a class.
Nnnnooooo ;-). Instances can't have methods, only classes can. Instances can only have instance variable storing a reference to a method, which would need to be called (obj.method)(args), remember? ;-).
[]
-- Best regards, Paul mailto:pmiscml@gmail.com _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/3C7R6D... Code of Conduct: http://python.org/psf/codeofconduct/
2021-01-14 Paul Sokolovsky <pmiscml@gmail.com> dixit:
Ruby has following feature. Suppose the existing class "Cls" is scope (either defined before or imported from some module), then the code like:
class Cls def mixin_method(args) ... end end
Will "reopen" (Ruby term) that class and will add a new method "mixin_method" to it. [...] The question then: what are the best practices in *declarative* syntax to achieve the same effect in Python? (but of course, unlike Ruby, there should be explicit syntactic marker that we augment existing class, not redefine it). [...]
I suppose it could be something along the lines (warning: not tested): REOPEN_DEFAULT_IGNORED_ATTRS = frozenset({ '__dict__', '__doc__', '__module__', '__weakref__', }) def reopen(cls, ignored_attrs=REOPEN_DEFAULT_IGNORED_ATTRS): def decorator(mixin): for name, obj in vars(mixin).items(): if name not in ignored_attrs: setattr(cls, name, obj) return decorator Then it could be used, for example, in the following way: @reopen(MyClass) class _: def my_additional_method(self, foo, bar): ... Cheers. *j
participants (5)
-
Chris Angelico
-
Jan Kaliszewski
-
Paul Sokolovsky
-
Ricky Teachey
-
Steven D'Aprano